Решения

GNN на GPU: GraphSAGE/GAT и выборка

Задача страницы. Инженерный гид по построению графовых моделей (GNN) на GPU: GraphSAGE и GAT, мини‑батч‑обучение с neighbor sampling, офлайн расчёт эмбеддингов и онлайн‑сервинг для рекомендаций/поиска/аналитики. Разберём пайплайны single‑/multi‑GPU, кэш‑план (HBM↔UVA↔NVMe), метрики p50/p95, экономику, конфиги и траблшутинг. Смежные темы: производительность и тюнинг, распределёнка, хранилища/ETL, сервинг (ссылки в конце).

TL;DR

  • Два режима:
    Interruptible (train/offline‑embed) — обучение/перерасчёт эмбеддингов узлов/рёбер по расписанию.
    On‑Demand (serving) — быстрая выдача по готовым эмбеддингам (retrieval/ANN), опционально «до‑вычисление» локальным k‑hop.
  • Mini‑batch + sampling: слои GraphSAGE/GAT с fanout на каждый слой (напр. [15, 10]), выборка без/с возвращением, importance/random‑walk; фьюзинг выборки и копирования в GPU, pinned/UVA.
  • GraphSAGE vs GAT: GraphSAGE — базовый для крупных графов (стабилен и лёгок), GAT — лучше локального внимания, но тяжелее по памяти/латентности (число «голов» и размер скрытого слоя — главный рычаг).
  • Кэш/память: горячие фичи/узлы — в HBM; тёплые — pinned host (UVA/zero‑copy); холодные — NVMe mmap. Контролируйте fanout и «cap» для high‑degree узлов.
  • Инференс: слой‑за‑слоем по всему графу (layer‑wise full‑graph) для офлайна, или батч‑инференс по партиям; выгрузка эмбеддингов → ANN‑индекс и ранжирование. См. https://cloudcompute.ru/solutions/recsys/
  • Метрики: step_time, loader_time, edges_per_sec, gpu_util/HBM, dup_ratio (дубли узлов при выборке), качество (ROC‑AUC/NDCG@K), и пиковая VRAM.
  • Тюнинг: AMP (FP16/BF16), pinned/zero‑copy, предвыборка, микробатчинг, фиксированный cap степеней, шардирование по GPU/NCCL. См. https://cloudcompute.ru/solutions/performance-tuning/, https://cloudcompute.ru/solutions/fp8-bf16/

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

  • Рекомендательные системы: user‑item граф, link prediction для кандидатов, офлайн эмбеддинги узлов → ANN retrieval → ранкер DLRM. См. https://cloudcompute.ru/solutions/recsys/
  • Антифрод/транзакции: граф платежей/сессий; классификация узлов/рёбер, детект аномалий по окрестности.
  • Социальные/контентные графы: друзья‑друзей, co‑view/co‑buy; персональная навигация/поиск.
  • Знания/каталоги: enrichment каталога связями (brand↔category↔item), маршрутизация контента по близости.

Архитектуры и пайплайны

1) Обучение (mini‑batch GraphSAGE/GAT с neighbor sampling)

				
					Parquet (nodes, edges, features) on NVMe/Object
  └─> Graph Store (CSR/CSC, degree caps, feature mmap)
       └─> Neighbor Sampler (fanout per layer, UVA/pinned)
            └─> GPU: GNN Forward (L layers, AMP)
                 └─> Loss (node/edge classification or link prediction)
                      └─> Backward + Optimizer
                           └─> Checkpoint + Metrics
				
			

Особенности: fanout=[f1,f2,…], cap high‑degree узлов, предвыборка «горячих» фичей в HBM‑кэш, перекрытие samplerforward (prefetch, 2 очереди).

2) Офлайн‑эмбеддинги → ANN для сервинга

				
					Trained GNN ──> Layer-wise Inference over full graph
                 └─> Node Embeddings (FP16/FP32)
                      └─> Write Parquet (NVMe)
                           └─> ANN Index Build (GPU)
                                └─> Serving: Retrieval → Ranker
				
			

Особенности: батч‑инференс без выборки, слой‑за‑слоем; результат — эмбеддинги на узел/тип узла.

3) Онлайн‑сценарий (добавление новых узлов/рёбер)

				
					Stream/Kafka ─> Micro-batch ETL ─> Update Graph Store ─┬─> On-demand local k-hop encode (fast path)
                                                       └─> Nightly full refresh embeddings (slow path)
				
			

Баланс fast/slow‑path: холодный старт действует через локальную окрестность с ограниченным fanout и меньшим числом слоёв.

Профили GPU и ориентиры

Инженерные ориентиры для обучения GraphSAGE/GAT при U≈0.7, L=2–3, fanout≈[15,10], dim=128–256, AMP включён. Фактическая скорость зависит от распределения степеней, коллизий и I/O.

Профиль GPU

Память

Типичный стек

Обучение (edges/sec)*

Инференс (nodes/sec)**

Комментарии

24 ГБ (Compact)

24 ГБ

GraphSAGE 2‑слоя, dim=128

2–6 млн

0.5–1.5 млн

Базовые графы, аккуратный fanout/cap.

48 ГБ (Balanced)

48 ГБ

GraphSAGE/GAT (4‑8 голов), dim=256

6–12 млн

1.5–3 млн

Баланс качества/скорости.

80 ГБ (HQ)

80 ГБ

GAT большой/высокий fanout, dim=256–384

10–20+ млн

3–6 млн

Тяжёлые графы, аггр. фичи, многослойные сети.

* «edges/sec» — посещённые рёбра за шаги (с учётом выборки).
** «nodes/sec» — офлайн‑инференс узлов (layer‑wise).
Тюнинг и распределёнка: https://cloudcompute.ru/solutions/multi-gpu/, https://cloudcompute.ru/solutions/performance-tuning/

Конфиги и скелеты кода

Docker Compose (train + offline‑infer + ANN‑build)

				
					version: "3.9"

x-env: &env
  DATA_DIR: /data/graph
  CHECKPOINT_DIR: /checkpoints
  PRECISION: "fp16"             # bf16|fp16|fp32
  MODEL: "graphsage"            # graphsage|gat
  LAYERS: "2"
  HIDDEN_DIM: "256"
  FANOUT: "15,10"
  BATCH_SIZE: "2048"
  NEGATIVE_SAMPLING: "uniform"  # для link prediction: uniform|hard
  UVA_ENABLE: "true"
  PINNED_POOL_MB: "4096"

services:
  gnn-train:
    image: cloudcompute/gnn-train:latest
    environment: *env
    deploy:
      resources:
        reservations:
          devices: [{ capabilities: ["gpu"] }]
    volumes:
      - /nvme/graph:/data/graph
      - /nvme/ckpt:/checkpoints
    command: ["python","train.py","--data","/data/graph","--ckpt","/checkpoints"]

  gnn-infer:
    image: cloudcompute/gnn-infer:latest
    environment: *env
    deploy:
      resources:
        reservations:
          devices: [{ capabilities: ["gpu"] }]
    volumes:
      - /nvme/graph:/data/graph
      - /nvme/emb:/emb
      - /nvme/ckpt:/checkpoints
    command: ["python","infer_full_graph.py","--data","/data/graph","--out","/emb"]

  ann-build:
    image: cloudcompute/vector-index:latest
    volumes:
      - /nvme/emb:/emb
      - /nvme/index:/index
    command: ["python","build_index.py","--emb","/emb","--out","/index/main"]


				
			

K8s (онлайн‑сервинг эмбеддингов + ANN)

				
					apiVersion: apps/v1
kind: Deployment
metadata: { name: gnn-serving }
spec:
  replicas: 3
  selector: { matchLabels: { app: gnn-serving } }
  template:
    metadata: { labels: { app: gnn-serving } }
    spec:
      containers:
        - name: serving
          image: cloudcompute/gnn-serving:latest
          ports: [{ containerPort: 9100 }]
          env:
            - { name: PRECISION, value: "fp16" }
            - { name: ANN_INDEX_PATH, value: "/index/main" }
          volumeMounts:
            - { name: index, mountPath: /index }
            - { name: emb,   mountPath: /emb }
          resources:
            limits: { nvidia.com/gpu: 1, cpu: "4", memory: "24Gi" }
      volumes:
        - name: index ; hostPath: { path: /nvme/index }
        - name: emb   ; hostPath: { path: /nvme/emb }


				
			

Конфиг пайплайна (YAML)

				
					graph:
  nodes: "/data/graph/nodes.parquet"     # id, type, features[*]
  edges: "/data/graph/edges.parquet"     # src, dst, type, ts
  features:
    dim: 256
    dtype: "fp16"
  sampling:
    fanout: [15, 10]
    with_replacement: false
    degree_cap: 50
    prefetch_batches: 2
    uva: true
train:
  task: "link_prediction"                # node_classification|link_prediction
  batch_size: 2048
  epochs: 5
  lr: 0.001
  negative_sampling: "uniform"
  fp_precision: "fp16"
model:
  type: "graphsage"                      # graphsage|gat
  layers: 2
  hidden_dim: 256
  heads: 4                               # для GAT
  dropout: 0.1
infer:
  layerwise_full_graph: true
  shard: 8
  output_embeddings_path: "/emb"
serving:
  ann_topk: 200
  min_score: 0.05

				
			

Python (скелет): GraphSAGE/GAT + neighbor sampling (PyTorch)

				
					import torch, torch.nn as nn, torch.nn.functional as F
from torch.cuda.amp import autocast, GradScaler
# Предполагаем наличие loader, который выдаёт батчи k-hop подграфов:
# for seeds, blocks, labels in loader: blocks = [ (x, edge_index), ... ]

class SAGE(nn.Module):
    def __init__(self, in_dim, hidden, out_dim, layers=2):
        super().__init__()
        self.lins = nn.ModuleList()
        dims = [in_dim] + [hidden]*(layers-1) + [out_dim]
        for a, b in zip(dims[:-1], dims[1:]):
            self.lins.append(nn.Linear(a, b))
    def forward(self, x, blocks):
        h = x
        for i, (x_src, edge_index) in enumerate(blocks):
            h_dst = h[:x_src.shape[0]]  # convention: dst first
            h = self.lins[i](h)
            h = F.relu(h)
            # mean aggregation (псевдокод)
            # h = aggregate_mean(h, edge_index)
        return h

class GAT(nn.Module):
    def __init__(self, in_dim, hidden, out_dim, layers=2, heads=4):
        super().__init__()
        self.layers = nn.ModuleList()
        for i in range(layers):
            h_in = in_dim if i==0 else hidden*heads
            h_out = out_dim if i==layers-1 else hidden
            self.layers.append(nn.MultiheadAttention(embed_dim=h_out*heads, num_heads=heads, batch_first=True))
    def forward(self, x, blocks):
        h = x
        for attn, (x_src, edge_index) in zip(self.layers, blocks):
            # attention over neighborhood (псевдокод: соберите соседей по edge_index)
            h, _ = attn(h, h, h, need_weights=False)
            h = F.elu(h)
        return h

def train(model, loader, feats, optimizer, epochs=5, device="cuda"):
    scaler = GradScaler()
    model.to(device).train()
    for ep in range(epochs):
        for seeds, blocks, labels in loader:
            x = gather_features(feats, blocks).to(device, non_blocking=True)  # UVA/pinned
            labels = labels.to(device, non_blocking=True)
            optimizer.zero_grad(set_to_none=True)
            with autocast(enabled=True):
                out = model(x, blocks)
                loss = F.binary_cross_entropy_with_logits(out, labels.float())  # для link prediction
            scaler.scale(loss).backward()
            scaler.step(optimizer); scaler.update()


				
			

Инференс слой‑за‑слоем (псевдокод)

				
					def full_graph_inference(model, graph, feats, batch_nodes):
    model.eval()
    # для каждого слоя вычисляем представления узлов батчами, переиспользуя k-hop соседей
    H = feats
    for l in range(model_layers):
        for seeds in iter_batches(all_nodes, batch_nodes):
            neigh = neighbors_of(seeds, graph)           # без выборки — все соседи слоя
            x = gather_feats(H, seeds, neigh)
            H[seeds] = model_layer_forward(l, x, neigh)
    return H


				
			

Наблюдаемость/метрики/алерты

Perf/Latency:

  • gnn_step_time_seconds (p50/p95), gnn_loader_time_seconds, gnn_forward_time_seconds, gnn_backward_time_seconds.
  • gnn_edges_per_sec, gnn_nodes_per_batch, gnn_dup_ratio (доля дублей узлов в батче).
  • gpu_utilization, gpu_memory_bytes, gpu_mem_peak_bytes, pinned_host_bytes, nvme_{read,write}_mb_s.
  • sampler_queue_depth, prefetch_ahead_batches.

Quality (задача):

  • Классификация узлов/рёбер: roc_auc, f1, pr_auc.
  • Link‑prediction: roc_auc, hits@K, ndcg@K, mrr@K.

Алерты (примеры):

  • gnn_loader_time / gnn_step_time > 0.4 — CPU/I/O узкое место: увеличить prefetch, включить UVA, поднять workers и pinned‑pool.
  • gpu_mem_peak/HBM > 0.9 — уменьшить fanout, degree_cap, batch_size, перейти на FP16/BF16.
  • dup_ratio > 0.3 — много дублей узлов: снижайте fanout, применяйте sampling без возвращения/importance.
  • edges_per_sec ↓ при стабильном loader — перегрузка модели (особенно GAT): уменьшить heads/hidden, сократить слои.

Дашборды и логирование: https://cloudcompute.ru/solutions/monitoring-logging/, https://cloudcompute.ru/solutions/llm-inference/observability/

Экономика и формулы

Обозначения: f_l — fanout слоя l, B — размер батча (узлов‑семян), L — число слоёв, Eps — edges/sec, N — число узлов в графе, U — целевая загрузка GPU, c_gpu — цена/час.

  • Сколько узлов/рёбер в батче (оценка):
    Nodes_per_batch ≈ B × (1 + f₁ + f₁·f₂ + … + ∏_{i=1}^L f_i)
    Edges_per_batch ≈ B × (f₁ + f₁·f₂ + … + ∏_{i=1}^L f_i)
  • Память под фичи в батче:
    Mem_feats ≈ Nodes_per_batch × dim × bytes_per_comp (добавьте промежуточные тензоры и граф‑структуры).
  • Время эпохи:
    T_epoch ≈ (Total_edges_visited / Eps); где Total_edges_visited ≈ (|Seeds|/B) × Edges_per_batch.
  • Стоимость эпохи:
    Cost_epoch ≈ c_gpu × T_epoch / U.
  • Офлайн инференс:
    T_infer ≈ N / nodes_per_sec, Cost_infer ≈ c_gpu × T_infer / U.

Планирование бюджета и компромиссов — см. https://cloudcompute.ru/solutions/cost-planner/, https://cloudcompute.ru/solutions/throughput-vs-latency/

Безопасность/политики

  • PII/идентификаторы: храните хэш‑ID; маскируйте чувствительные атрибуты; разграничивайте доступ к узлам/рёбрам по тенантам.
  • Ретеншн: TTL для временных подграфов/эмбеддингов/чекпоинтов; аудит выгрузок.
  • Изоляция: пулы GPU для тренинга (Interruptible) и сервинга (On‑Demand); отдельные бакеты для графа и эмбеддингов.
  • Экспорт моделей/индексов: фиксируйте версии и схемы, журналируйте операции экспорта.

Подробнее: https://cloudcompute.ru/solutions/security/, https://cloudcompute.ru/solutions/storage-data/

Траблшутинг

Симптом

Возможная причина

Решение

CUDA OOM при обучении

Завышенный fanout/batch_size, high‑degree узлы

Уменьшить fanout/батч, ограничить degree cap, включить AMP, вынести «холодные» фичи в pinned‑host

GPU простаивает

Медленный sampler/I/O

Увеличить prefetch/num_workers, UVA/zero‑copy, хранить CSR/фичи на NVMe, прогрев кэша

Взрыв памяти в GAT

Много «голов» и большой hidden

Снизить heads/hidden, уменьшить слои, перейти на GraphSAGE

Качество не растёт

Пересемплирование лёгких соседей

Importance‑sampling, баланс классов/негативов, регуляризация/Dropout

Переносимая инстанс‑утечка

Дубликаты и пересечение батчей

Dedup узлов в батче, контроль dup_ratio, sampling без возвращения

Плохая онлайн‑скорость

Тяжёлый GAT на сервинге

Предрассчитывать эмбеддинги офлайн; онлайн — локальный k‑hop GraphSAGE

Дрифт эмбеддингов

Изменился граф/каталог

Регулярный перерасчёт, канареечный индекс, мониторинг метрик качества

Нестабильная утилизация

Конкуренция задач на GPU

Ограничить конкурентность, разнести тренинг/инференс по пулам GPU

См. также: https://cloudcompute.ru/solutions/interruptible-patterns/, https://cloudcompute.ru/solutions/performance-tuning/

Как запустить в cloudcompute.ru

  1. Откройте Шаблоны запусков: https://cloudcompute.ru/solutions/templates/ и выберите GNN (Train + Infer) или GNN Serving + ANN.
  2. Выберите профиль GPU: 24/48/80 ГБ по размеру графа, dim, типу модели (GraphSAGE/GAT) и SLA.
  3. Подключите диски: /nvme/graph (узлы/рёбра/фичи), /nvme/ckpt, /nvme/emb, /nvme/index.
  4. Задайте параметры пайплайна по config.yaml (fanout, degree cap, batch, precision, задача).
  5. Для продакшна: пулы Interruptible (обучение/офлайн‑эмбеды) и On‑Demand (retrieval), автоскейл по U/queue, дашборды и алерты.

Дополнительно:
https://cloudcompute.ru/solutions/triton-inference-server/ — сервинг моделей/эмбеддингов.
https://cloudcompute.ru/solutions/recsys/ — retrieval→ranking стек.
https://cloudcompute.ru/solutions/rapids/ и https://cloudcompute.ru/solutions/spark-rapids/ — GPU‑ETL перед графом.
https://cloudcompute.ru/solutions/multi-gpu/ — распределёнка и NCCL.
https://cloudcompute.ru/solutions/performance-tuning/ — низкоуровневый тюнинг.
https://cloudcompute.ru/solutions/cost-planner/ — планирование бюджета.

Чек‑лист перед продом

  • Достигнуты целевые edges_per_sec/nodes_per_sec и step_time p95.
  • Подобраны fanout/degree_cap/batch_size под HBM и стабильный p95.
  • AMP (FP16/BF16) включён; UVA/pinned‑pool настроены; кэш горячих узлов/фичей реализован.
  • Отработан офлайн‑инференс и сборка ANN‑индекса; версионирование эмбеддингов.
  • Дашборды/алерты: loader_time, edges_per_sec, dup_ratio, HBM/pinned/NVMe, качество (ROC‑AUC/Hits@K).
  • Политики PII/ретеншна/экспорта внедрены; разнесены пулы On‑Demand/Interruptible.
  • Канареечный релиз индекса/модели и план отката готовы; нагрузочный прогон ≥ 30 мин.

Навигация