Рекомендательные системы на GPU: DLRM и двухбашенные модели
Задача страницы. Практический гид по построению рекомендательных систем на GPU: DLRM (Dense+Sparse, большие эмбеддинги), двухбашенные модели (two‑tower) для быстрого кандидатообразования, негатив‑семплинг, оффлайн‑батчи и онлайн‑сервинг с низкой латентностью. Дадим пайплайны (retrieval→ranking→rerank/diversity), sizing по профилям 24/48/80 ГБ, конфиги (Docker/K8s/YAML), метрики (CTR/AUC/NDCG/latency), экономику и траблшутинг. Предполагается локальный NVMe‑кэш, режимы On‑Demand (онлайн‑рекомендации) и Interruptible (оффлайн‑тренинг/ингест), контейнеризация и наблюдаемость.
TL;DR
- Архитектура в проде: Two‑tower retrieval (ANN на GPU) → DLRM‑ранкер → [опц.] Rerank/диверсификация/правила.
- Тренинг: оффлайн батчи, чекпоинт‑рестарт, негатив‑семплинг (in‑batch/hard negatives), FSDP/ZeRO/шардирование эмбеддингов.
- Данные/фичи: разреженные категориальные (ID/хэши), числовые (нормировка), взаимодействия (feature crossing). Хранилище фичей на NVMe. См. https://cloudcompute.ru/solutions/storage-data/
- Наблюдаемость: CTR/AUC/NDCG@K, latency p50/p95, QPS, калибровка (ECE), дрифт фичей/лейблов. См. https://cloudcompute.ru/solutions/monitoring-logging/, https://cloudcompute.ru/solutions/llm-inference/observability/
- Экономика: Cost_per_1M_recs ≈ (c_gpu × 1e6 × t_infer) / (3600 × U); размер эмбеддингов: Mem = Σ |V_i| × d_i × bytes × multipliers. См. https://cloudcompute.ru/solutions/cost-planner/, https://cloudcompute.ru/solutions/throughput-vs-latency/
- Пулы: On‑Demand для онлайн‑скара (низкая латентность), Interruptible для тренинга/переиндексации. См. https://cloudcompute.ru/solutions/interruptible-patterns/
- Мульти‑GPU/мульти‑нод: DP/FSDP/шардирование таблиц (row/table/column), NCCL‑топологии. См. https://cloudcompute.ru/solutions/multi-gpu/
Сценарии (когда это нужно)
- E‑commerce/маркетплейсы: персонифицированные ленты, блок «Похожие», up‑/cross‑sell.
- Контент/медиа: рекомендации видео/музыки/статей; гибридные сигналы (контент+коллаборатив).
- Финтех/реклама: next‑best‑offer, bid‑рекомендации, ограничение частоты.
- B2B/SaaS: рекомендации функций/тем/документации, персональные дайджесты.
- Поиск по каталогу: двухбашенный retrieval (ANN) для быстрых кандидатов, ранжирование DLRM.
Архитектуры и пайплайны 1) Онлайн‑сервинг (retrieval → ranking → rerank)
Request(user_id, ctx)
└─> Feature Builder (real-time)
├─> User tower embed(u) # two-tower
├─> ANN Search on Item Index (GPU) # top-N candidates
└─> Feature Join (candidates + ctx)
└─> DLRM Ranker (dense + sparse + crosses)
└─> Rerank (diversity/business rules)
└─> Top-K + Response (JSON)
Особенности: кэш user‑эмбеддингов в HBM, шардированный ANN‑индекс на GPU, быстрый join фичей, p95 latency контролируется размером кандидатов/фичей/MLP.
2) Оффлайн‑тренинг (батчи, негатив‑семплинг)
Logs/Events ─┬─> ETL → Feature Store (NVMe)
├─> Negative Sampling (in-batch/hard)
└─> Trainer (FSDP/ZeRO, AMP)
├─> Two-tower (user/item)
└─> DLRM (sparse tables + MLP)
└─> Checkpoint/Export
├─> Item Tower → ANN Index Build (GPU)
└─> Ranker Weights → Serving
3) Обновление каталога/индекса (репликация без простоя)
New Item Embeddings ─> Shadow ANN Index ─> Warmup/QA ─> Swap alias ─> Traffic shift
Профили GPU и ориентиры (онлайн‑сервинг)
Оценка по количеству одновременных онлайн‑рекомендаций при U≈0.7, ANN на GPU, K=200 кандидатов, ранкер DLRM среднего размера. Для оффлайна используйте формулы в «Экономике».
| **Профиль GPU** | **Память** | **Типовой стек** | **Real‑time RPS\*** | **Комментарии** |
| 24 ГБ (Compact) | 24 ГБ | Two‑tower retrieval + лёгкий DLRM | 100–300 | Малая глубина фичей, компактные эмбеддинги (32–64). |
| 48 ГБ (Balanced) | 48 ГБ | Two‑tower + средний DLRM | 300–700 | Баланс latency/качества, стабильный p95. |
| 80 ГБ (HQ) | 80 ГБ | Крупный рангер + расширенные фичи | 700–1500 | Глубокий MLP, много фичей, аггрегаты по сессии. |
* Диапазон зависит от числа кандидатов, размера эмбеддингов/фичей, глубины MLP и I/O.
Вместимость on‑device кэша эмбеддингов (инференс, FP16, бюджет 75% HBM)
| **Профиль** | **dim=64** | **dim=128** |
| 24 ГБ | ≈ 151 млн | ≈ 75 млн |
| 48 ГБ | ≈ 302 млн | ≈ 151 млн |
| 80 ГБ | ≈ 503 млн | ≈ 252 млн |
Для тренинга учитывайте умножители памяти (оптимизатор/градиенты/активации), см. «Экономика».
Конфиги и скелеты кода
Docker Compose (онлайн‑retrieval + DLRM‑ranker)
version: "3.9"
x-env: &env
MODELS_DIR: /models
CACHE_DIR: /var/cache/recsys
PRECISION: "fp16" # bf16|fp16
TOPK: "200"
RERANK_K: "50"
ANN_INDEX_PATH: "/index/items"
USER_CACHE_HOT: "true"
services:
recsys-serving:
image: cloudcompute/recsys-serving:latest
environment: *env
deploy:
resources:
reservations:
devices: [{ capabilities: ["gpu"] }]
ports: ["9000:9000"]
volumes:
- /nvme/models:/models
- /nvme/recsys-cache:/var/cache/recsys
- /nvme/index:/index
command: ["python", "serve.py", "--host=0.0.0.0", "--port=9000"]
Docker Compose (оффлайн‑тренинг two‑tower + DLRM)
version: "3.9"
services:
recsys-train:
image: cloudcompute/recsys-train:latest
environment:
DATA_DIR: /data
CHECKPOINT_DIR: /checkpoints
PRECISION: fp16
NEGATIVE_SAMPLING: "in_batch" # in_batch|hard|mixed
BATCH_SIZE: "65536"
FSDP: "true"
LR: "3e-4"
deploy:
resources:
reservations:
devices: [{ capabilities: ["gpu"] }]
volumes:
- /mnt/logs:/data
- /nvme/ckpt:/checkpoints
command: ["python", "train.py"]
K8s (онлайн‑сервинг, 1 GPU/под)
apiVersion: apps/v1
kind: Deployment
metadata:
name: recsys-serving
spec:
replicas: 3
selector: { matchLabels: { app: recsys-serving } }
template:
metadata: { labels: { app: recsys-serving } }
spec:
containers:
- name: serving
image: cloudcompute/recsys-serving:latest
ports: [{ containerPort: 9000 }]
env:
- { name: PRECISION, value: "fp16" }
- { name: TOPK, value: "200" }
- { name: RERANK_K, value: "50" }
volumeMounts:
- { name: models, mountPath: /models }
- { name: cache, mountPath: /var/cache/recsys }
- { name: index, mountPath: /index }
resources:
limits: { nvidia.com/gpu: 1, memory: "24Gi", cpu: "4" }
volumes:
- name: models ; hostPath: { path: /nvme/models }
- name: cache ; hostPath: { path: /nvme/recsys-cache }
- name: index ; hostPath: { path: /nvme/index }
Конфиг пайплайна (YAML)
schema:
dense: ["price", "discount", "session_time", "age_norm"]
sparse: ["user_id", "item_id", "brand", "category", "geo", "device"]
crosses:
- ["brand","category"]
- ["geo","device"]
two_tower:
user_tower:
emb_dims: { user_id: 64, geo: 16, device: 8 }
mlp: [256, 128, 64]
item_tower:
emb_dims: { item_id: 64, brand: 16, category: 16 }
mlp: [256, 128, 64]
loss: "bpr" # bpr|softmax|sampled_softmax
neg_sampling: "in_batch" # in_batch|hard|mixed
dlrm_ranker:
embedding_dims: { user_id: 64, item_id: 64, brand: 16, category: 16, geo: 16, device: 8 }
bottom_mlp: [256, 128]
top_mlp: [256, 128, 1]
interactions: "dot" # dot|concat|cross
loss: "bce"
serving:
ann:
type: "gpu_hnsw" # gpu_flat|gpu_ivf|gpu_hnsw
topk: 200
rerank:
k: 50
diversity_lambda: 0.2
thresholds:
min_score: 0.05
Python (скелет): двухбашенный тренинг с in‑batch негативами
import torch, torch.nn as nn, torch.nn.functional as F
class Tower(nn.Module):
def __init__(self, emb_dims, mlp_layers):
super().__init__()
self.embs = nn.ModuleDict({k: nn.Embedding(num_embeddings=card, embedding_dim=dim)
for k, (card, dim) in emb_dims.items()})
dims = [sum(dim for _, dim in emb_dims.values())] + mlp_layers
self.mlp = nn.Sequential(*[nn.Linear(dims[i], dims[i+1]) for i in range(len(dims)-1)])
def forward(self, x_sparse):
e = [self.embs[k](x_sparse[k]) for k in self.embs] # [B, dim]
h = torch.cat(e, dim=-1)
return F.normalize(self.mlp(h), dim=-1)
class TwoTower(nn.Module):
def __init__(self, user_cfg, item_cfg):
super().__init__()
self.user = Tower(user_cfg["emb_dims"], user_cfg["mlp"])
self.item = Tower(item_cfg["emb_dims"], item_cfg["mlp"])
def forward(self, user_sparse, item_sparse):
u = self.user(user_sparse) # [B, D]
v = self.item(item_sparse) # [B, D]
return u, v
def bpr_loss(u, v):
# in-batch negatives: scores = u @ v.T
s = u @ v.T # [B, B]
pos = s.diag()
neg = s - torch.diag_embed(pos)
loss = -torch.log(torch.sigmoid(pos.unsqueeze(1) - neg)).mean()
return loss
# Тренинг-цикл (AMP + FSDP/ZeRO по необходимости)
Python (скелет): онлайн‑сервинг retrieval→ranking
def retrieve_candidates(user_vec, ann_index, topk=200):
# ann_index.search(user_vec) -> ids, distances
ids, d = ann_index.search(user_vec, topk)
return ids
def rank_candidates(features_batch, dlrm_model):
# features_batch: объединённые dense+sparse фичи для user×items
with torch.cuda.amp.autocast():
scores = dlrm_model(features_batch) # [B, 1]
return scores
def recommend(user_features):
user_vec = user_tower(user_features["sparse"])
cand_ids = retrieve_candidates(user_vec, ann_index, topk=cfg.topk)
feats = feature_join(user_features, cand_ids) # dense+sparse join
scores = rank_candidates(feats, dlrm_model)
reranked = diversify(cand_ids, scores, lambda_=0.2)
return topk(reranked, k=cfg.rerank_k)
``` **Наблюдаемость/метрики/алерты**
**Offline (качество):**
- recsys\_auc, recsys\_ndcg@k, recsys\_map@k, recsys\_mrr@k, recsys\_ece (калибровка).
- recsys\_hardneg\_share — доля «жёстких» негативов; recsys\_label\_leak\_check.
**Online (прод):**
- **Latency**: recsys\_latency\_seconds{stage=feature|ann|rank|rerank} (p50/p95).
- **Throughput**: recsys\_qps, recsys\_batch\_size\_effective.
- **CTR/CVR/Revenue‑per‑Mille**: агрегаты по трафику/сегментам.
- **Drift**: recsys\_feature\_drift\_{mean,std}, recsys\_score\_drift.
- **GPU**: recsys\_gpu\_utilization, recsys\_gpu\_mem\_peak\_bytes, recsys\_nvme\_{read,write}\_mb\_s.
**Алерты (примеры):**
- recsys\_latency\_p95 > SLA — уменьшить TOP‑K, упростить MLP, включить FP16/BF16/фьюзинг, добавить GPU.
- recsys\_auc\_drop > threshold — регресс качества; откат модели/индекса.
- gpu\_mem\_peak/HBM > 0.9 — уменьшить размеры эмбеддингов/батч, разделить сервисы (ANN/ранкер) по пулам.
- recsys\_feature\_drift > threshold — пересобрать нормировку/калибровку, обновить фичи.
Подробнее: <https://cloudcompute.ru/solutions/monitoring-logging/> • <https://cloudcompute.ru/solutions/llm-inference/observability/>
## **Экономика и формулы**
Обозначения: c\_gpu — цена GPU/час, U — целевая загрузка, t\_infer — среднее время инференса одной рекомендации (сек).
- **Стоимость 1 млн рекомендаций (онлайн):
Cost\_per\_1M\_recs ≈ (c\_gpu × 1e6 × t\_infer) / (3600 × U).
- **Сколько GPU (онлайн):
GPU\_count = ceil( (QPS × t\_infer) / U ).
- **Время тренинга:
T\_epoch ≈ N\_samples / (throughput\_samples\_per\_sec × GPU\_count × U).
- **Память эмбеддингов (тренинг):
Mem\_params = Σ\_i |V\_i| × d\_i × bytes
Mem\_opt ≈ multipliers × Mem\_params (Adam: ~2× для m,v; +градиенты).
Итог: Mem\_total ≈ (1 + multipliers + градиенты + активации) × Mem\_params.
Для инференса можно держать **on‑device кэш hot‑ID**, cold‑ID — на CPU/NVMe (UVA/pinned), см. <https://cloudcompute.ru/solutions/performance-tuning/>
- **Задержка retrival→rank:
Latency\_total ≈ t\_ann(topk) + t\_join + t\_rank(k) + t\_rules. Балансируйте topk/rerank\_k.
Подробно про компромисс throughput↔latency: <https://cloudcompute.ru/solutions/throughput-vs-latency/> • Планирование бюджета: <https://cloudcompute.ru/solutions/cost-planner/>
**Безопасность/политики**
- **PII/персонализация:** хэшируйте пользовательские идентификаторы; разделяйте пользовательские/товарные фичи по бакетам; шифрование «в канале/на диске».
- **Ретеншн:** события/логи — TTL; обучающие выборки — версионирование/аудит; без хранения исходных PII в фичах.
- **Доступ:** разграничение по тенантам/региону; отдельные пулы GPU; журналирование экспорта моделей/индексов.
- **Защита от утечки сигналов:** фильтрация лейблов/фичей, анти‑label‑leakage проверки.
Подробнее: <https://cloudcompute.ru/solutions/security/> • <https://cloudcompute.ru/solutions/storage-data/>
**Траблшутинг**
<table><tbody><tr><td>**Симптом**
</td><td>**Возможная причина**
</td><td>**Решение**
</td></tr><tr><td>Latency p95 ↑
</td><td>Большой TOP‑K/тяжёлый ранкер/медленный join
</td><td>Снизить TOP‑K, упростить MLP, предварительный фильтр, ускорить фичестор (NVMe)
</td></tr><tr><td>CTR не растёт при AUC↑
</td><td>Модель некалибрована/не учтён бизнес‑контекст
</td><td>Калибровка (Platt/Isotonic), бизнес‑правила/диверсификация, онлайн‑A/B
</td></tr><tr><td>Память HBM OOM
</td><td>Крупные эмбеддинги/батчи
</td><td>FP16/BF16, уменьшить d, шардировать таблицы, вынос cold‑ID на CPU/NVMe
</td></tr><tr><td>Обучение «взрывается»
</td><td>Слишком агрессивные hard‑negatives/LR
</td><td>Ослабить hard‑negatives, reduce LR, gradient clipping
</td></tr><tr><td>Фичи «разъехались»
</td><td>Скью между тренингом и продом
</td><td>Единый фичестор, версионирование трансформов, тест «тренинг=прод»
</td></tr><tr><td>Перенасыщение популярных ID
</td><td>Частотные дисбалансы
</td><td>Частотное семплирование, регуляризация/декорреляция фичей
</td></tr><tr><td>Падение recall@K в retrieval
</td><td>Недостаточный ANN/квантование
</td><td>Поднять efSearch/nprobe, уменьшить PQ, увеличить размер индекса
</td></tr><tr><td>Дрифт показателей
</td><td>Смена сезона/каталога
</td><td>Регулярные переобучения, онлайн‑апдейты эмбеддингов, детект дрифта
</td></tr></tbody></table>
См. также: <https://cloudcompute.ru/solutions/performance-tuning/> • <https://cloudcompute.ru/solutions/interruptible-patterns/>
**Как запустить в cloudcompute.ru**
1. Откройте **Шаблоны запусков**: <https://cloudcompute.ru/solutions/templates/> — выберите **RecSys (Serving)** и/или **RecSys (Training + Index Build)**.
2. Выберите профиль GPU: **24/48/80 ГБ** — по глубине модели/объёму эмбеддингов и SLA.
3. Смонтируйте диски: /nvme/models, /nvme/recsys-cache, /nvme/index, для тренинга — /mnt/logs и /nvme/ckpt.
4. Заполните переменные окружения из docker-compose.yml (ANN, TOP‑K, precision, batching).
5. Для продакшна: раздельные пулы **On‑Demand**/**Interruptible**, автоскейл по U/latency/QPS, дашборды и алерты, канареечный деплой.
Дополнительно:
- Сервинг/оптимизация: <https://cloudcompute.ru/solutions/triton-inference-server/>
- CI/CD контейнеров: <https://cloudcompute.ru/solutions/containers-ci-cd/>
- Хранилища и данные: <https://cloudcompute.ru/solutions/storage-data/>
- Мульти‑GPU/распределёнка: <https://cloudcompute.ru/solutions/multi-gpu/>
**Чек‑лист перед продом**
- Согласованы целевые метрики: **AUC/NDCG@K/CTR uplift**.
- Стабильный p95 latency для feature→ANN→rank→rerank; запас по U ≥ 0.2.
- Индекс кандидатов прогрет; стратегия **shadow‑index → alias‑swap** задеплоена.
- Единый фичестор и идентичные трансформы в тренинге/проде.
- Настроены алерты: latency/QPS/HBM/recall@K/дрифт.
- Политики PII/ретеншна/доступов внедрены.
- Чекпоинты/откат модели и индекса готовы.
- Нагрузочный прогон ≥ 30 мин на целевом профиле/трафике.
**Навигация**
- Хаб «Решения»: <https://cloudcompute.ru/solutions/>
- Шаблоны запусков: <https://cloudcompute.ru/solutions/templates/>
- Планирование стоимости: <https://cloudcompute.ru/solutions/cost-planner/>
- Throughput vs Latency: <https://cloudcompute.ru/solutions/throughput-vs-latency/>
- Производительность и тюнинг: <https://cloudcompute.ru/solutions/performance-tuning/>
- Multi‑GPU: <https://cloudcompute.ru/solutions/multi-gpu/>
- Хранилища и данные: <https://cloudcompute.ru/solutions/storage-data/>
- Безопасность: <https://cloudcompute.ru/solutions/security/>
- Мониторинг и логи: <https://cloudcompute.ru/solutions/monitoring-logging/>
- Наблюдаемость инференса: <https://cloudcompute.ru/solutions/llm-inference/observability/>
- Interruptible‑паттерны: <https://cloudcompute.ru/solutions/interruptible-patterns/>
- Triton Inference Server: <https://cloudcompute.ru/solutions/triton-inference-server/>
- CI/CD контейнеров: <https://cloudcompute.ru/solutions/containers-ci-cd/>
Готовы запустить?
Запустить GPU-сервер