Рекомендательные системы на 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

Сценарии (когда это нужно)

  • 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-сервер