Привет! Проводил тестирование на рабочих стендах и хочу поделиться своими результатами с Habr.

В работе исследуется влияние транспорта KV‑кэша на производительность и работоспособность disaggregated inference, в котором стадии prefill и decode выполняются на разных GPU‑узлах. На кластере из двух узлов с восемью NVIDIA B200 на каждом сравниваются RDMA/InfiniBand с GPUDirect и TCP/Ethernet в инференсе MiniMax‑M3 через sglang. Эксперимент включает четыре уровня наблюдения: потолок фабрики (L0), изолированный microbenchmark transfer engine (L0b), фактический маршрут трафика по счётчикам портов (L1) и end‑to‑end метрики движка и запросов (L2/L3). Всего проанализировано 268 прогонов.

TL;DR

В основной матрице M1 RDMA/InfiniBand уменьшил медианный TTFT в 5,4–11,5 раза и увеличил output throughput в 2,8–3,8 раза относительно TCP на валидных прогонах. TPOT оставался сопоставимым, что указывает на локализацию влияния транспорта в переходе prefill -> decode, а не в самом цикле decode. TCP завершился отказом в 31 из 75 прогонов (41%); все 75 прогонов IB были валидны. По логам отказ начинается с ошибки копирования из CUDA memory (out of memory), продолжается таймаутом KV‑transfer, после чего prefill завершается, а router возвращает HTTP 503. Изолированный microbenchmark воспроизвёл отказ TCP и одновременно показал преимущество GPUDirect над host staging на 6,3–6,5%.

End‑to‑end поток не насыщал ни одну из сетевых фабрик: TCP достигал максимум 8,7 Гбит/с из 200 Гбит/с, InfiniBand в свою очередь 52,4–61,7 Гбит/с из приблизительно 2933 Гбит/с суммарного измеренного L0-потолка восьми NDR‑портов. Следовательно, наблюдаемая разница объясняется прежде всего архитектурой переноса — host staging против zero‑copy GPUDirect, — а не нехваткой полосы. Дополнительная верификация attention backend показала снижение TTFT на 33,5–44,4% у triton относительно fa4, однако эти пути используют разные layouts KV и требуют отдельной проверки на production‑нагрузках.

Вывод: в исследованной конфигурации выбор KV‑транспорта является архитектурным ограничением PD‑инференса. TCP нельзя рассматривать как функционально эквивалентный fallback для тяжёлых профилей MiniMax‑M3.

1. Введение

Увеличение размеров языковых моделей делает полезным разделение инференса LLM на две стадии. На стадии prefill модель обрабатывает входной контекст и формирует KV‑кэш attention. На стадии decode этот кэш используется для последовательной генерации выходных токенов. При PD‑disaggregation стадии размещаются на разных GPU‑узлах, поэтому KV‑кэш пересекает границу машины.

Такое размещение меняет статус сетевого транспорта. В монолитном inference KV остаётся внутри памяти одного процесса или узла; в PD‑топологии транспорт KV входит в критический путь получения первого токена. При этом пропускная способность интерфейса не описывает всю стоимость переноса: реализация может потребовать копирования данных из VRAM в host memory, дополнительных буферов и синхронизации.

Цель исследования — разделить влияние сетевой фабрики и программного пути переноса, а также проверить, сохраняет ли TCP функциональную работоспособность на профилях с большим KV‑кэшем.

1.1. Исследовательские вопросы

  1. Как транспорт KV влияет на TTFT, TPOT, E2E latency и throughput?

  2. При каких профилях длины входа/выхода и конкурентности TCP перестаёт быть работоспособным?

  3. Является ли причиной деградации насыщение сетевой фабрики или программный staging path?

  4. Даёт ли устойчивый выигрыш выбор backend'а переноса, микро‑тюнинг mooncake или смена attention backend?

1.2. Проверяемые гипотезы

Обозначение

Гипотеза

Критерий проверки

H1

Транспорт влияет главным образом на TTFT, а не на TPOT

Сопоставление TTFT и TPOT

H2

Деградация TCP растёт с размером KV‑переноса

Профили ISL/OSL и concurrency

H3

GPUDirect быстрее host staging

VRAM против DRAM в L0b

H4

TCP ограничен LACP‑хэшем или полосой Ethernet

End‑to‑end rate против L0/L1

H5

RDMA даёт кратный выигрыш на длинных prefill

Валидные M1-прогоны

H6

fake даёт лучший TPOT, но худший TTFT (decode пересчитывает префилл)

Запуск fake backend в PD‑режиме

H7

Микро‑тюнинг даёт не более порядка 10%

OFAT по sglang и mooncake

H8

nixl/UCX сопоставим с mooncake на RDMA

M2 на одинаковых профилях

2. Материалы и методы

2.1. Топология эксперимента

Две машины использовались следующим образом: node-a выполняла prefill и router, node-b — decode. Каждая роль модели работала с tensor parallelism TP=8 на восьми B200. Имена узлов, адреса и имена сетевых устройств обезличены.

PD‑disaggregation (prefill/decode disaggregation) разделяет две фазы инференса по разным GPU‑узлам: prefill считает префилл промпта и владеет его KV‑кэшем, decode генерирует токены, читая этот кэш. Между фазами KV‑кэш (единицы‑сотни МиБ на запрос) должен физически переехать с узла prefill на узел decode — этот перенос и есть предмет работы. Поскольку decode не может начать работу до переноса, латентность транспорта лежит в критическом пути TTFT, но не в цикле декодирования (TPOT), где KV читается локально из памяти своей ноды.

Схема PD-disaggregation: клиент, router, prefill и decode
Топология PD‑disaggregation: prefill и decode на разных TP8-узлах; после prefill KV‑кэш передаётся на decode.

После завершения prefill KV‑кэш передавался на decode. Сравнивались RDMA/InfiniBand через восемь NDR‑портов 400G с GPUDirect и TCP/Ethernet через bond0, LACP 2×100G, MTU 9000.

2.2. Аппаратная и программная конфигурация

Компонент

Значение

GPU

2 узла × 8 NVIDIA B200

Модель

MiniMax‑M3 MXFP8, 31 shard, 443 771 536 556 байт

Engine

sglang 0.0.0.dev1+g56e290315

MoE backend

deep_gemm

Attention backend

fa4, page size 128

KV backend M1

mooncake

RDMA

NDR 400G, HCA mlx5_4,7,8,9,10,13,14,15

Ethernet

bond0, LACP 802.3ad, 2 × 100G

CUDA / OFED

CUDA 13.0.1 / MLNX‑OFED 24.10

Базовый runtime

mem-fraction-static=0.85, chunked prefill 8192, max running requests 20, lpm

Для исключения расхождения артефактов контейнер был пересобран с подавлением записи pycache и очисткой TorchInductor cache. Content‑addressed diffID всех 64 слоёв совпадали на обеих нодах.

2.3. Матрица нагрузок

Основная матрица M1 содержала пять профилей ISL/OSL, три уровня concurrency и пять повторов для каждого из двух транспортов.

Профиль ISL/OSL

Назначение

128/128

короткий вход и короткий выход

2048/256

базовый прикладной профиль

8192/256

длинный вход

32768/256

очень длинный вход

2048/2048

длинный вход и длинный выход

Всего M1: 5 × 3 × 5 × 2 = 150 прогонов. Дополнительно проведены M2, ось D (18 конфигураций sglang), ось C (13 конфигураций mooncake), отдельная верификация fa4/triton и L0/L0b. В summary.json — 268 записей.

2.4. Процедура и метрики

Перед стартом выполнялась жёсткая очистка контейнеров и orphan‑процессов. Стек запускался в порядке prefill → decode → router. Перед измерениями выполнялись прогрев MSA JIT, HF processor и warmup‑запрос. Использовались фиксированные seed=1234, num-prompts=32 и пять повторов на ячейку.

В аналитических графиках M1 валидным считался прогон с положительным TTFT выше артефактного порога и положительным output throughput. Отказы с ttft=0 не включались в медианы производительности, но учитывались отдельно в failure‑rate и heatmap. Для агрегатов использовались медианы, графики показывают межквартильный разброс.

Термины адаптеров. HCA (Host Channel Adapter) — сетевой адаптер InfiniBand, ядро видит его как устройство mlx5_N. NDR = 400 Гбит/с на порт, HDR = 100 Гбит/с — поколения скорости IB. PIX‑local означает, что адаптер и GPU висят под общим PCIe‑switch: копия VRAM→HCA не пересекает межпроцессорную шину, что важно для GPUDirect. bond0 — агрегированный Ethernet‑интерфейс узла (Linux bonding): два порта объединены по LACP 802.3ad в один логический канал 2 × 100G с MTU 9000. Счётчики bond0 и IB‑портов mlx5_* независимы, поэтому по их дельтам однозначно определяется фактический маршрут KV (раздел 3.3).

Уровень

Инструмент

Наблюдаемые величины

L0

ib_write_bw, ib_write_lat

потолок фабрики, latency

L0b

transfer_engine_bench

GDR/host staging, read/write, status

L1

IB counters, bond0

дельта переданных байтов

L2

/metrics sglang

alloc/bootstrap KV‑transfer

L3

bench_serving

TTFT, TPOT, ITL (inter‑token latency), E2E, tok/s

L0 и L0b измеряют железо и переносчик в изоляции (L0 — полосу IB утилитой ib_write_bw независимо от sglang; L0b — transfer_engine_bench без модели). L1-L3 — end‑to‑end: L1 по счётчикам портов доказывает, куда пошёл байт, L2 берёт телеметрию движка, L3 — пользовательские метрики. IB‑счётчики PortXmitData/PortRcvData отдаются в 4-байтных словах (×4 для байт); величины ib_tx/bond_tx — приращение за прогон (after - before), а не абсолютное значение счётчика.

3. Результаты

3.1. End‑to‑end сравнение транспорта

При concurrency=8 и только на валидных прогонах получены следующие медианы. Здесь TTFT — время до первого токена, TPOT — время на последующие токены.

Профиль

IB TTFT, мс

TCP TTFT, мс

Выигрыш IB

IB tok/s

TCP tok/s

Выигрыш IB

128/128

451.8

2427.8

5.4×

414.7

128.4

3.2×

2048/256

398.2

4575.5

11.5×

519.2

182.2

2.8×

8192/256

554.0

3215.5

5.8×

495.7

130.4

3.8×

Транспорт входит в TTFT через перенос KV и bootstrap, но не участвует в основном decode‑цикле. Поэтому TPOT составил 10.8–11.7 мс для IB и 10.0–10.5 мс для TCP; небольшое преимущество TCP не является причинным эффектом и связано со смещением валидной выборки после исключения отказов.

G_{\mathrm{TTFT}} = \frac{\mathrm{TTFT}_{\mathrm{TCP}}}{\mathrm{TTFT}_{\mathrm{IB}}}; \qquad G_{\mathrm{thr}} = \frac{\mathrm{Throughput}_{\mathrm{IB}}}{\mathrm{Throughput}_{\mathrm{TCP}}}

Рисунок 1. TTFT против длины входа (isl/osl). Медиана по повторам при conc=8, усы - IQR. Серии: RDMA / IB - RDMA/InfiniBand NDR 400G; TCP / Ethernet - Ethernet bond0, LACP 2 × 100G.
Рисунок 1. TTFT против длины входа (isl/osl). Медиана по повторам при conc=8, усы — IQR. Серии: RDMA / IB — RDMA/InfiniBand NDR 400G; TCP / Ethernet — Ethernet bond0, LACP 2 × 100G.
Рисунок 2. TPOT против длины входа (isl/osl). Медиана по повторам при conc=8. Проверка H1: транспорт не влияет на TPOT; серии обозначены как на Рисунке 1.
Рисунок 2. TPOT против длины входа (isl/osl). Медиана по повторам при conc=8. Проверка H1: транспорт не влияет на TPOT; серии обозначены как на Рисунке 1.
Рисунок 3. Пропускная способность против конкурентности: output и total tok/s для профиля 2048/256. TCP-отказы исключены из агрегатов, а не заменены нулями.
Рисунок 3. Пропускная способность против конкурентности: output и total tok/s для профиля 2048/256. TCP‑отказы исключены из агрегатов, а не заменены нулями.
Рисунок 4. Метрики KV-transfer engine (M1, валидные прогоны, дельта за прогон): prefill - GB/s и мс/оп, decode - bootstrap, мс/оп. speed/latency/total_mb отдаёт передающая (prefill) сторона; принимающая (decode) - только bootstrap/alloc.
Рисунок 4. Метрики KV‑transfer engine (M1, валидные прогоны, дельта за прогон): prefill — GB/s и мс/оп, decode — bootstrap, мс/оп. speed/latency/total_mb отдаёт передающая (prefill) сторона; принимающая (decode) — только bootstrap/alloc.
Рисунок 5. Выигрыш RDMA/IB относительно TCP, %. Положительное значение означает преимущество IB; conc=8.
Рисунок 5. Выигрыш RDMA/IB относительно TCP,%. Положительное значение означает преимущество IB; conc=8.

3.2. Граница отказа TCP

Все 75 прогонов IB были валидны. Для TCP 44 из 75 прогонов были валидны, а 31 завершились отказом.

Профиль

c=1

c=8

c=32

128/128

0/5 bad

0/5 bad

0/5 bad

2048/256

0/5 bad

0/5 bad

0/5 bad

8192/256

0/5 bad

0/5 bad

1/5 bad

2048/2048

5/5 bad

5/5 bad

5/5 bad

32768/256

5/5 bad

5/5 bad

5/5 bad

Ошибки по хронологии:

ClientSession::writeBody failed to copy from CUDA memory: out of memory
↓
KVTransferError: timeout after 300 s in KVPoll.WaitingForInput
↓
prefill: running_phase_sigquit_handler → kill_process_tree
↓
router: No available prefill workers
↓
HTTP 503

Профиль 2048/2048 ломается уже при c=1, тогда как 8192/256 выдерживает c=8. Это согласуется с зависимостью от объёма KV на запрос, а не только от concurrency. Изолированный transfer_engine_bench с TCP и VRAM‑буфером воспроизвёл тот же класс отказа.

Рисунок 6. Граница отказа TCP: доля bad-прогонов (prefill мёртв, ttft=0, KVTransferError/503) по профилям и concurrency. IB - 0% на всех профилях.
Рисунок 6. Граница отказа TCP: доля bad‑прогонов (prefill мёртв, ttft=0, KVTransferError/503) по профилям и concurrency. IB — 0% на всех профилях.

3.3. Маршрут KV и утилизация фабрики

Транспорт

ib_tx

bond_tx

IB

3 654–206 714 МиБ

примерно 0–12 МиБ

TCP

0

3 642–55 158 МиБ (валидные прогоны)

В самых тяжёлых валидных TCP‑прогонах скорость по bond0 составляла 7.4–8.7 Гбит/с, до 4.4% от 200 Гбит/с. Для IB максимум составлял 61,7 Гбит/с (c=32), не более 2,1% от суммарного измеренного L0-потолка восьми NDR‑портов — около 2933 Гбит/с. Гипотеза о насыщении LACP‑хэша не подтверждена.

Рисунок 7. Маршрут KV-трафика: дельта счётчиков портов IB (tx) и Ethernet bond (tx) за прогон, conc=8, по профилям isl/osl.
Рисунок 7. Маршрут KV‑трафика: дельта счётчиков портов IB (tx) и Ethernet bond (tx) за прогон, conc=8, по профилям isl/osl.

3.4. L0 и L0b: GPUDirect против host staging

Cross‑node ib_write_bw на восьми рабочих NDR‑портах дал среднюю скорость 366.7 Гбит/с при диапазоне 364.2–368.5 Гбит/с; latency на измеренных портах составляла 1.78–2.00 мкс.

Режим

GB/s

Гбит/с

GDR write из VRAM

48.82

390.56

host write из DRAM

45.93

367.44

GDR read из VRAM

49.00

392.00

host read из DRAM

46.01

368.08

TCP write из VRAM

FAILED

-

GDR составил 97.6% от номинальных 400 Гбит/с NDR и превысил host staging на 6.29% для write и 6.50% для read. L0 и L0b используют разные инструменты и режимы, поэтому их абсолютные значения нельзя смешивать как одно измерение.

Рисунок 8. L0: потолок NDR-фабрики по портам (ib_write_bw cross-node, 64K msg) и ориентир Ethernet bond0 (8 потоков).
Рисунок 8. L0: потолок NDR‑фабрики по портам (ib_write_bw cross‑node, 64K msg) и ориентир Ethernet bond0 (8 потоков).
Рисунок 9. L0b: mooncake transfer_engine_bench - GPUDirect (GDR) против host staging. mlx5_4, RDMA RC, block=64K, 8 каналов, cross-node node-a→node-b.
Рисунок 9. L0b: mooncake transfer_engine_bench — GPUDirect (GDR) против host staging. mlx5_4, RDMA RC, block=64K, 8 каналов, cross‑node node‑a→node‑b.

3.5. Backend переноса: mooncake и nixl

M2 проводился на IB и холодном стеке. Поэтому значения TTFT сопоставимы только между backend'ами внутри M2.

Backend

Профиль

c

TTFT, мс

TPOT, мс

Output tok/s

ib_tx, МиБ

mooncake

2048/256

8

4295.6

11.28

200.1

16 016

mooncake

2048/256

32

1344.8

13.45

749.6

16 016

mooncake

8192/256

8

3121.6

10.27

254.4

51 430

mooncake

8192/256

32

3901.3

13.10

567.7

51 430

nixl

2048/256

8

3775.9

11.37

194.4

15 862

nixl

2048/256

32

4104.7

13.33

595.8

15 862

nixl

8192/256

8

2065.4

10.64

268.4

50 938

nixl

8192/256

32

4407.6

12.19

589.2

50 938

nixl работоспособен и использует сопоставимый IB‑маршрут: различие ib_tx не превышало примерно 1%. fake не может использоваться как prefill backend в PD‑режиме: sglang завершается assertion в pd_disaggregation_hook.py:70.

Рисунок 10. M2: backend переноса KV - mooncake против nixl (UCX). IB, холодный стек (без прогрева), поэтому сравнение только между backend'ами внутри M2. fake недоступен для prefill (assertion pd_disaggregation_hook.py:70).
Рисунок 10. M2: backend переноса KV — mooncake против nixl (UCX). IB, холодный стек (без прогрева), поэтому сравнение только между backend'ами внутри M2. fake недоступен для prefill (assertion pd_disaggregation_hook.py:70).

3.6. OFAT и attention backend

По оси D измерены 18 конфигураций sglang (n=2-3, с прогревом) на профиле 2048/256, c=8, IB, mooncake. Единственный выигрыш дал attention-backend triton; остальные 16 измеренных параметров базу не улучшили — их TTFT составил 435–562 мс против 418 мс у base (включая варианты max-running-req 8/16/32/64, mem-fraction 0.70–0.90, schedule-policy fcfs/random, chunked-prefill off/4096/16384, cuda-graph-* и max-total-tokens 500k). Ещё две конфигурации — cuda-graph-prefill full и attention-backend fa3 — стек не подняли (таймаут health) и в число 18 не входят. По оси C проверены 13 параметров mooncake на профиле 8192/256, c=8, IB; все рабочие варианты находились в диапазоне разброса M1, а MC_NUM_QP_PER_EP=4 приводил к зависанию decode.

Профиль

fa4 TTFT

triton TTFT

Δ TTFT

Δ throughput

2048/256

536.4 мс

298.3 мс

-44.4%

+19.9%

8192/256

578.7 мс

384.7 мс

-33.5%

+17.7%

В обоих режимах завершились 32/32 запроса. При temperature=0 два повторения детерминированного semantic check дали идентичные корректные ответы. Однако fa4 использовал page_size=128, а triton — page_size=None; результат не доказывает эквивалентность на длинных контекстах или при speculative decoding.

Рисунок 11. Ось D: полный OFAT по 18 параметрам sglang, профиль 2048/256, c=8, IB, с прогревом. Зелёное - лучше base; cold-прогоны (TTFT > 1000 мс) отброшены. cuda-graph-prefill full и attention-backend fa3 стек не подняли.
Рисунок 11. Ось D: полный OFAT по 18 параметрам sglang, профиль 2048/256, c=8, IB, с прогревом. Зелёное — лучше base; cold‑прогоны (TTFT > 1000 мс) отброшены. cuda‑graph‑prefill full и attention‑backend fa3 стек не подняли.
Рисунок 12. Ось C: микро-тюнинг mooncake, профиль 8192/256, c=8, IB, с прогревом. Все варианты в пределах шума M1 (413-574 мс); MC_NUM_QP_PER_EP=4 - decode зависает.
Рисунок 12. Ось C: микро‑тюнинг mooncake, профиль 8192/256, c=8, IB, с прогревом. Все варианты в пределах шума M1 (413–574 мс); MC_NUM_QP_PER_EP=4 — decode зависает.
Рисунок 13. Отдельная верификация attention backend: fa4 против triton (n=3 warm, тот же стек). triton использует page_size=None (без paged-KV), ib_tx -4.2%, p99 TTFT 420 против 1619 мс; 32/32 запроса завершены в обоих режимах.
Рисунок 13. Отдельная верификация attention backend: fa4 против triton (n=3 warm, тот же стек). triton использует page_size=None (без paged‑KV), ib_tx -4.2%, p99 TTFT 420 против 1619 мс; 32/32 запроса завершены в обоих режимах.

4. Обсуждение

4.1. Почему эффект транспорта виден в TTFT, но не в TPOT

В PD‑disaggregation decode не может начать полноценную генерацию до получения KV‑кэша. Поэтому задержка переноса прибавляется ко времени первого токена. После bootstrap decode выполняет вычисления локально; транспорт уже не участвует в каждом шаге генерации. Наблюдаемая разница — секунды по TTFT против менее 1.7 мс/токен по TPOT — соответствует этой структуре пути.

4.2. Почему скорость линии не объясняет результат

Если бы TCP упирался в физическую полосу или LACP‑хэш, end‑to‑end поток должен был бы приближаться к потолку bond0. Наблюдалось обратное: максимум 8.7 Гбит/с составлял лишь 4.4% от 200 Гбит/с. Наиболее согласованная с логами и L0b интерпретация — различие software path: TCP требует копирования KV из VRAM в host bounce‑buffer, а GPUDirect RDMA передаёт KV без промежуточного host staging.

Увеличение номинальной Ethernet‑полосы без устранения staging path не должно считаться решением наблюдаемого TCP failure mode.

4.3. Практическое значение результата triton

Выигрыш triton существенно превышал эффекты микро‑тюнинга mooncake, но получен сменой вычислительного пути attention, а не оптимизацией транспорта. Перед production‑переключением требуются тесты на реальных распределениях контекстов, длинных цепочках, p99, потреблении памяти, качестве ответов и speculative decoding.

5. Ограничения и угрозы валидности

  1. Ограниченный набор транспортов. Сравнивались только RDMA/InfiniBand с GPUDirect и TCP/Ethernet; иные варианты транспорта KV не исследовались.

  2. Масштаб decode. Использовался один decode‑узел; влияние числа decode workers и балансировки не исследовалось.

  3. Смещение TCP‑выборки. 31 TCP‑прогон завершился отказом и исключён из агрегатов производительности. Failure rate приводится отдельно.

  4. Холодный M2. Абсолютные TTFT M2 нельзя сравнивать с прогретым M1; M2 предназначен для относительного сравнения backend'ов.

  5. Малые выборки tuning. В осях C/D и attention verification использовалось 2–3 тёплых повтора на конфигурацию.

  6. Attention layouts. fa4 и triton неэквивалентны на уровне paged/non‑paged KV layout.

  7. Метрики движка и дельты. sglang:kv_transfer_* — монотонные счётчики Prometheus, поэтому в харнессе берётся дельта after - before за прогон (чтение after даёт lifetime‑среднее). kvtransfer_speed_gb_s/_latency_ms/_total_mb отдаёт передающая (prefill) сторона, bootstrapms/_alloc_ms — принимающая (decode). Скорость KV подтверждается двумя независимыми источниками — метриками движка и Δ портовых счётчиков, — абсолюты которых расходятся.

  8. Конкретная сборка. Вывод о TCP относится к MiniMax‑M3, данной версии sglang/mooncake и конфигурации памяти.

  9. Конфаундеры модели. Перенос результата на другую точность, модель или layout требует отдельного эксперимента.

6. Что забрать в свой кластер

Измерять end‑to‑end

  • Считать TTFT, TPOT и failure rate одновременно.

  • Проверять маршрут KV дельтой счётчиков IB и Ethernet после тестового запроса.

  • Включать длинный input, длинный output и их сочетание.

Не портить бенчмарк

  • Прогревать стек и исключать cold‑run: 4,1–4,3 с против 0,41–0,43 с TTFT.

  • Ставить health‑gate после каждого прогона и не измерять мёртвый prefill.

  • Разделять transport tuning и смену attention backend.

Cпасибо. А посмотреть интерактивные графики вы можете тут.

Комментарии (0)