DSpark на двух DGX Spark: порт, баг на одну строку и бенчмарки, которые пришлось мерить заново

от автора

DeepSeek выпустил DSpark — спекулятивный декодер для V4. В окне 27–30 июня 2026 рабочего публичного пути для GB10 не было — мы портировали его сами, нашли унаследованный баг, убивавший качество драфта, и сняли профиль своего стенда. Потом комьюнити выложило рецепт вдвое быстрее нашего. Мы его воспроизвели — и вместо гонки за цифрой сняли данные, которых в паблике не нашли: кривую деградации до реального 1M, механизм выигрыша NVFP4 и петли когерентности на глубине. Плюс три негативных результата.

TL;DR

  • Порт состоялся. DSpark — спекулятивный модуль DeepSeek поверх чекпойнта V4 — работает на 2× DGX Spark (GB10, SM121, TP=2 через QSFP 200G / RoCEv2) как новый метод dspark в нашем форке поверх community‑форка jasl. Порт независимый: в окне 27–30 июня 2026 в доступном нам публичном vLLM‑пути для GB10/SM121 method=dspark не был рабочей опцией — запуск не проходил дальше конфиг‑валидации.

  • Главный багфикс — одна строка: унаследованный кернел сэмплил драфт из noise‑слота вместо bonus‑слота. Сдвиг индекса сэмплинга на один слот поднял acceptance позиции 0 с 0.41 до 0.74, среднюю принятую длину с 1.75 до 2.5, throughput с 26 до 36 tok/s.

  • Single‑stream на fp8: 26.7 tok/s без спекуляции → 32.4 tok/s с DSpark = +21–24%. Это не опровержение цифр DeepSeek: их headline (+60–85% per‑user speedup и кратно большие aggregate‑выигрыши в SLA‑режимах) снят над baseline MTP-1 и на другом железе — мы сравниваем другую метрику с другим baseline. Разбор по четырём слагаемым внутри.

  • Фактический выигрыш DSpark на GB10 — конкурентность: ~99 tok/s агрегата на 8 потоках (против ~64 у прежнего MTP-2 прода, +55%), 141–146 tok/s на 12 потоках.

  • Комьюнити: tonyd2wild выложил NVFP4-KV рецепт с заявкой 67 tok/s single‑stream против наших 32. Мы воспроизвели: single 55–63, C16-static 324 против его 315.1 — ничья в пределах разброса.

  • Механизм NVFP4: в no‑spec контроле 4-битный KV не ускорил голый forward (26.6 ≈ 26.7 tok/s у fp8) — single‑stream выигрыш рецепта пришёл через acceptance 3–4 против ~2.1.

  • Глубина и когерентность: кривая деградации до реального 1M: 42.7 tok/s на 16K → 17.6 на 512K → 17.2 на 1.03M (после 512K — полка, не обвал); на глубине ≥256K билд иногда уходит в петли (а на 1M оба прогона связные — лотерея, не стена), и acceptance в петле завышается.

  • Три негатива задокументированы: YaRN re‑anchor драфта (замедление в ~15 раз, «рост» acceptance был миражом), confidence‑scheduler (−9% агрегата), сэмплер‑фикс когерентности (ни один конфиг не держит обе глубины, min_p под spec‑decode заблокирован сервером).

  • Репозиторий: botAGI/DeepSeek‑V4-Flash‑DSpark‑GB10-2x‑DGX‑Spark-1m‑fp4-fp8.

Где мы остановились

В части 5 мы подняли DeepSeek‑V4‑Flash (284B MoE, ~13B активных на токен) на двух GB10 с TP=2 и вылечили multi‑user. Заголовочные числа части 5 — 44.9 tok/s single и 144–155 tok/s агрегата — синтетика на коротких промптах. Для честного сравнения дальше используем продовую нагрузку: MTP-2 давал ~40 tok/s single‑stream и ~64 tok/s агрегата на 8 потоках. От этой точки и считаем.

27 июня DeepSeek выложил DSpark — спекулятивный декодер следующего поколения из их стека DeepSpec (MIT‑лицензия). Спекулятивный декодинг, если в одном абзаце: маленькая быстрая «драфт»‑голова набрасывает несколько токенов вперёд, большая модель проверяет их одним широким проходом и принимает совпавшие; доля принятых (acceptance) и есть выигрыш. DeepSeek заявлял +60–85% per‑user speedup и кратно большие aggregate‑throughput выигрыши в SLA‑режимах — и то и другое над baseline MTP-1, не над голой авторегрессией; на HF уже лежал готовый чекпойнт DeepSeek-V4-Flash-DSpark — те же веса плюс пристёгнутый спекулятивный модуль, как честно пишет сама карточка.

В день релиза мы упёрлись в стену: в окне 27–30 июня 2026 в доступном нам публичном vLLM‑пути для GB10/SM121 method=dspark не был рабочей опцией — в используемом форке это закрытый pydantic‑enum, а не флаг. ValidationError прилетал раньше, чем скачались бы 165 ГБ весов. Ни кода загрузчика, ни кернелов, ни PR в upstream. Здесь важна оговорка о датах: экосистема после релиза двигалась быстро, и все утверждения про upstream/vLLM в этой статье относятся именно к тому окну — уже сейчас та самая карточка модели на HF показывает штатный vLLM‑запуск с --speculative-config '{"method":"dspark",...}'. А базовый V4 на SM121 и так жил только на community‑форке jasl (PR #41834 в upstream vLLM) — на нём и строим. Мы записали DSpark в watch‑лист — а потом решили не ждать и портировать самим.

Порт: тяжёлое переиспользуем, тонкое дебажим

Хорошая новость обнаружилась при чтении референсного инференса из чекпойнта: DSpark — это не новая модель. Драфт — это 3 MTP‑стадии под namespace mtp.*, каждая структурно равна обычному слою V4 (MLA‑attention + MoE на 256 экспертов), которые уже работают у нас на GB10 под продовой нагрузкой. Сверху немного обвязки: main_proj, принимающий конкатенацию hidden‑состояний трёх слоёв таргета ([40,41,42]), low‑rank Markov‑голова (rank 256), авторегрессивно поправляющая логиты внутри блока, и confidence‑голова. За шаг драфт набрасывает блок из 5 токенов, таргет проверяет их одним проходом шириной 6 токенов (5 драфтовых + 1 бонусный).

Порт лёг в vLLM двумя новыми модулями (голова DSparkV4MTP + proposer DSparkProposer, унаследованный от DFlashProposer — его non‑causal cross‑attention обвязка оказалась ровно схемой DSpark) и точечными правками на семь строк: enum, предикаты, диспетчер раннера. Загрузилось, поехало — и выдало мусор: acceptance позиции 0 около 5%, драфт предсказывал почти шум.

Дальше была долгая методичная охота за багом, из которой остались три фикса. Два — «нормальные»: aux hidden‑состояния таргета надо сворачивать per‑layer (hc‑fold — проекция hc_post плюс усреднение по оси вспомогательных hidden‑состояний), а не отдавать сырым тензором; и финальную норму чекпойнта (mtp.N.norm.weight) надо применять перед LM‑головой, плюс токены блока должны видеть друг друга non‑causally — унаследованный билдер делал блок каузальным.

А третий — главный. Унаследованный кернел сэмплил драфт‑токены из слотов блока 1..5, тогда как референс сэмплит все слоты начиная с нулевого. Слот 0 — это бонусный токен на естественной next‑token позиции; слот 1 — noise‑токен (служебный токен‑заглушка 128799, которым драфт заполняет ещё не предсказанные позиции блока) на позиции через одну. То есть первый драфт‑токен мы брали из запроса, живущего на два RoPE‑шага впереди реального. Фикс — буквально сдвиг индекса (patches/vllm/spec_decode/dspark.py#L251):

# proposer, выбор слотов для сэмплинга драфта:token_indices_to_sample = token_indices_to_sample - 1  # выход k читает слот k, а не k+1

Эффект на debug‑прогоне: acceptance позиции 0 — 0.41 → 0.74 (для референсной имплементации DeepSeek репортится ~0.76, то есть мы почти дошли до её уровня: 0.74 — пик, рабочий диапазон 0.62–0.74, см. ниже), средняя принятая длина — 1.75 → 2.5, throughput — 26 → 36 tok/s. В сопоставимом steady‑state профиле ниже single‑stream цифра — 32.4 tok/s: debug‑прогон короче и на другом промпте, сравнивать между собой надо числа из таблицы. Одна строка. До неё мы успели всерьёз рассматривать гипотезы «это стена точности fp8/fp4» и «это несовместимость позиционных режимов EAGLE и DSpark» — обе оказались неверными, что багфикс и показал.

Отдельно пришлось чинить стабильность под нагрузкой: в sparse‑MLA‑кернеле два билдера метаданных считали порог decode/prefill‑разбиения по‑разному (parallel drafting удваивает его у одного из них), и chunked‑prefill кусок в 7–11 токенов, совпавший с verify‑проходами, ронял движок под конкуренцией. После выравнивания порога — 8/8 параллельных потоков стабильно, ноль крашей.

Полный послойный численный дифф против референса DeepSeek снять невозможно: их инференс собран на tilelang‑кернелах, которые на SM121 не билдятся, — оракула на этом железе просто нет. Мы это оговариваем прямо, а не заявляем паритет: после багфикса acceptance вышла на референсный уровень, значит остаточное расхождение — если оно есть — небольшое.

Методика: чем и как меряем

Сначала словарик на пять строк, чтобы дальше читалось без спотыканий:

  • acceptance — доля драфт‑токенов, которые таргет‑модель приняла при проверке (считается по позициям блока: pos0 — первый драфт‑токен, и дальше вглубь);

  • средняя принятая длина — сколько токенов в среднем прибавляет один verify‑шаг (принятые драфт‑токены плюс один бонусный);

  • single‑stream — один запрос к движку, без конкуренции;

  • aggregate — суммарный throughput по всем параллельным потокам;

  • no‑spec — спекуляция выключена, голая авторегрессия (наш контроль).

Прежде чем показывать цифры — как они получены, потому что здесь легче всего наврать самому себе.

Throughput везде — это usage.completion_tokens / wall_time, non‑streaming. Считать токены по SSE‑чанкам стрима нельзя: при спекулятивном декодинге в один чанк приезжает несколько принятых токенов, и счётчик занижает результат примерно в 2.5 раза. Мы наступали на эти грабли ещё в части 5 (те самые фантомные «18 tok/s») и с тех пор SSE‑счёт не используем вообще. Acceptance берём из строки SpecDecoding metrics в логах vLLM за таймированное окно. Движок прогрет (≥12 запросов warmup, включая глубокие контексты, — холодный кэш раздувает первый глубокий замер). Один класс промптов, один движок, один и тот же двухнодовый деплой — все сравнения в одинаковых условиях.

Воспроизводимость

Параметры, по которым можно повторить замеры:

  • fp8-билд — наши патчи поверх community‑форка jasl (PR #41834); базовый образ aidendle94/sparkrun-vllm-ds4-gb10:production-ready (vLLM 0.21.1rc1.dev339), digest sha256:f869281d869b2a1d418cade7dcbabe65216cb8f54891d5e2e50718c3dc8b630f; патч‑сет — в репозитории.

  • NVFP4-билд — byte‑identical образ tonyd2wild Stage‑C, коммит 89bb82b; база ghcr.io/bjk110/vllm-spark, digest sha256:d8492e7677cf1b9aaa3344e0e6865efc468454013eee5ebabac85be90af027be; его же бенч‑харнесс (bench_concurrent.py / staggered_bench.py) без модификаций.

  • Чекпойнт — HF snapshot 913f0657a874f76844e2e91cbe706dbcaceeb6d7.

  • Сколько раз меряли: C16-static — 3 прогона (разброс 282–330); c12 — 3 прогона; кривая глубины — 1 прогон на глубину на прогретом движке; 1M‑точка — 2 таймированных прогона (greedy, 200 токенов, prompt_tokens = 1,032,953).

  • Термины конкурентности: C16-static — 16 одновременных стартов; C16-staggered — старты со сдвигом во времени; seqs=N — --max-num-seqs N, потолок батча движка.

Честный профиль fp8: куда делись «+85%»

Сводка на нашем fp8-билде (fp8_ds_mla KV, block_size=5, контекст 1M):

Замер

Результат

No‑spec авторегрессия (спекуляция выключена)

26.7 tok/s

DSpark single‑stream

32.4 tok/s (+21–24%)

DSpark, block_size 5 → 4

31.4 tok/s (без выигрыша)

DSpark, конкурентность 8 потоков (seqs=12)

~99 tok/s агрегат

DSpark, 12 потоков

141–146 tok/s (устойчиво, 3 прогона)

DSpark, 16 потоков

117 tok/s (против 75 при seqs=4, +56%; ниже, чем c12, — при --max-num-seqs 12 часть из 16 запросов ждёт в очереди)

Acceptance pos0 / средняя длина (под нагрузкой c8–c16)

0.62–0.74 / ~2.2–2.56

+21–24% — это не +85%. Разрыв грубо раскладывается на четыре слагаемых, и ни одно из них не «мы криво портировали».

Первое и главное — baseline mismatch (~40% разрыва). DeepSeek репортит «+60–85% per‑user speed при matched throughput над MTP-1» — над самой слабой спекулятивной конфигурацией. Мы же сравниваем с no‑spec и со своим MTP-2 продом (средняя принятая длина ~2.5) — куда более сильной точкой отсчёта. То есть мы сравниваем не ту же метрику (у DeepSeek — per‑user при matched throughput и aggregate, у нас — сырой single‑stream) и не тот же baseline (у них MTP-1, у нас no‑spec и MTP-2). Большая часть процента испаряется по построению.

Второе — железо (~30%). У GB10 — 128 ГБ unified LPDDR5x с 273 ГБ/с против 3.35 ТБ/с HBM3 у H100 SXM — примерно в 12 раз меньше. Verify‑проход по 43 слоям fp8-MLA с MoE на 256 экспертов занимает ~65 мс и полностью доминирует шаг. Межнодовый all‑reduce через RoCE (ConnectX-7, 200 Gbps), на который грешат первым делом, — ложный след: живой профиль показал ~1.5 мс из 65-мс шага, около 2.3%, в любом случае меньше 3.5%. Узкое место в нашем профиле — не интерконнект, а сам forward на этой памяти.

Третье — дефицит acceptance (~20%). Наш pos0 0.62–0.74 против ~0.76 у референса; вероятно, вклад fp8-весов + FP4-экспертов на SM121 против их bf16/tilelang.

Четвёртое — тяжёлый драфт (~10%). Наш драфт — это три полных V4-стадии на vLLM‑кернелах против ручного tilelang у DeepSeek. Но драфт занимает лишь ~15% шага: даже бесплатный драфт упёрся бы в ~37 tok/s single‑stream.

Самый показательный эксперимент — block_size 5 → 4: throughput практически не изменился (32.4 → 31.4). Если бы шаг лимитировался драфтом, минус один драфт‑токен дал бы прирост. Не дал — значит, шаг — это фиксированный verify‑forward, и рычага «увеличь блок и езжай быстрее» здесь нет. 40+ tok/s single‑stream в нашем fp8-профиле не получались: при таком step‑time их не взять без роста acceptance, step‑time уровня NVLink или sub-4-битного чекпойнта, которого для этой модели на момент написания нет.

Зато конкурентность — фактический выигрыш. 6-wide verify амортизируется по пользователям, и с подобранным --max-num-seqs 12 восемь потоков дают ~99 tok/s против ~64 у прежнего MTP-2 прода — +55%. Обе цифры — реальная продовая нагрузка, не синтетика (синтетический MTP-2 бенч на коротких промптах показывал и 144–155 агрегата — те самые числа из части 5 — но сравнивать синтетику с продовой нагрузкой было бы как раз той методической ошибкой, от которой мы ушли в разделе про методику). Под нагрузкой c8–c16: acceptance 2.3–2.56, ноль крашей, ноль OOM, RestartCount 0.

Неудобный факт, который мы не прячем: single‑stream 32.4 у DSpark — медленнее, чем ~40 у лёгкого MTP-2. Для одной интерактивной сессии DSpark не нужен; его место — множественные пользователи и длинный контекст.

Комьюнити выкладывает 67 tok/s: воспроизводим

Пока мы дописывали профилирование, tonyd2wild опубликовал рецепт NVFP4-KV билда той же связки — DeepSeek‑V4-Flash‑DSpark на 2× DGX Spark, 1M контекста — с заявленным single‑stream 67 tok/s (диапазон «~60–67» — из его треда на форуме NVIDIA; README репозитория цитирует 61.3/61.8 tok/s). Против наших 32.4. Вдвое.

У рецепта длинная цепочка авторов: базовый образ bjk110, nvfp4_ds_mla (4-битный NVFP4 KV‑кэш) из линии drowzeys/Keys, упакованный tonyd2wild в 584-байтный padded sparse‑MLA конверт, плюс наработки rafaelcaricio, fraserprice и MiaAI‑Lab. Мы перечисляем всех, потому что это тот случай, когда «community build» — не фигура речи, а родословная из полудюжины контрибьюторов.

Сначала мы не поверили: по нашему fp8-профилю выходило, что при этом step‑time 40+ single‑stream не взять без роста acceptance, — а тут 67. Правильная реакция — воспроизвести и понять. Если число настоящее — в нашем анализе есть ошибка, и надо найти где.

Результат воспроизведения

Взяли его билд byte‑identical (коммит 89bb82b), его же бенч‑харнесс без модификаций, наше железо: 2× GB10, TP=2 через RoCE, max_model_len=1048576, block_size=5. Важно: репозиторий быстро обновлялся — мы воспроизводим конкретный профиль коммита 89bb82b с 1M‑окном, а текущий README апстрима уже описывает более свежую 1.5M‑конфигурацию.

Метрика

tonyd2wild (опубл.)

Наше воспроизведение

C16-static агрегат (200K, seqs=16)

315.1

324 (разброс 282–330)

C16-static, контроль (экспериментальный рычаг WO_PROJECTION выключен)

319

C16-staggered

205.0

191–196

Single‑stream, код

67

55–63

Контроль — ключ ко всей таблице: с выключенным рычагом C16-static ложится на 319, почти точно воспроизводя его опубликованные 315.1. Это значит, что его бенч‑методика валидна, а наши 324 с включённым рычагом — внутри его же run‑to‑run разброса, не устойчивый выигрыш. Single‑stream мы получили 55–63 на кодовом промпте (44.4 на общем) — на уровне или чуть ниже его 67. Staggered — минус ~4%.

Вердикт: чистое независимое воспроизведение, ничья. Его рецепт на нашем железе выдаёт его числа. А наш fp8-анализ в этой части подтвердился: как выяснилось ниже, NVFP4-билд обходит потолок не там, где мы его меряли. Воспроизведение, кстати, не только наше: инженеры Classmethod на своей паре Spark получили ~55 tok/s decode (thinking OFF; с включённым thinking — ~41), правда с тремя собственными патчами поверх рецепта, включая принудительную маршрутизацию vLLM на QSFP‑линк.

Наш вклад: данные, которых мы не нашли в паблике

Соревноваться за пиковую цифру было бессмысленно, поэтому мы сняли то, чего в опубликованных бенчах не встречали.

Механизм: acceptance, а не bandwidth

Очевидная гипотеза «NVFP4 быстрее, потому что 4-битный KV‑кэш читается быстрее 8-битного» — проверяется одним контролем, которого в опубликованных бенчах не было: выключить спекуляцию и померить голую авторегрессию на обоих билдах.

Конфигурация

No‑spec single‑stream

NVFP4-KV билд

26.6 tok/s

fp8-KV билд (наш)

26.7 tok/s

Идентично. В нашем no‑spec single‑stream контроле 4-битный KV не ускорил голый forward — при single‑stream на 273 ГБ/с узкое место — чтение весов и активаций, а не KV. Раз tps = средняя_принятая_длина / время_шага и время шага одинаковое (no‑spec это показывает), single‑stream выигрыш рецепта проявился через acceptance: 3.0–4.2 против ~2.1 у нашего fp8 (single‑stream; под нагрузкой у fp8 длина выше, ~2.2–2.56). Оптимизированный базовый образ + DSpark‑опции + NVFP4-KV дают драфту заметно более длинные принятые серии. Оговорка: на других режимах — большой batch, глубокий контекст, где KV‑трафик доминирует, — KV‑bandwidth вполне может играть; мы изолировали только single‑stream случай. В паблике NVFP4 обсуждался как «быстрее», без изоляции причины — no‑spec контроль и был недостающим экспериментом.

Кривая глубины до реального 1M: короткий контекст не описывает глубокий

Заголовочные 67 tok/s измерены на почти пустом контексте — ~300 токенов из 1M‑окна. А throughput спекулятивного движка — это acceptance, делённая на время шага, и acceptance падает с глубиной: драфту всё меньше сигнала. Значит, кривая обязана проседать. Опубликованной кривой мы не нашли — на форумах было только качественное «на 1M все медленные, меньше 10 tok/s». Мы промерили операционную кривую — не статистический бенчмарк, а контрольные точки на одном классе continuation‑промптов; число прогонов — в блоке «Воспроизводимость». Промпт — неповторяющаяся художественная проза с паддингом до целевой глубины, движок прогрет. Самая глубокая точка — фактически заполненное окно: 1,032,953 токена промпта из 1,048,576 возможных:

Глубина

tok/s

Средняя принятая длина

16K

42.7

3.30

64K

28.9

2.23

256K

25.4

2.42

512K

17.6

1.98

1.03M

17.2

~2.1

Acceptance по позициям блока (pos 0..4):

16K:  0.80 / 0.56 / 0.46 / 0.30 / 0.1864K:  0.62 / 0.34 / 0.17 / 0.07 / 0.03256K: 0.70 / 0.44 / 0.16 / 0.07 / 0.05512K: 0.54 / 0.27 / 0.10 / 0.04 / 0.02

От 16K к 512K скорость падает более чем вдвое: acceptance нулевой позиции сползает с 0.80 до 0.54, а глубокие позиции драфта коллапсируют к нулю — к 512K verify‑шаг приносит в среднем около двух токенов total (1.98): бонусный плюс примерно один принятый драфт‑токен. Небольшая немонотонность принятой длины на 64K/256K (2.23 против 2.42) — в пределах шума сэмпла, тренд по скорости однозначен.

А дальше кривая делает то, чего мы не ожидали: выполаживается. Точка на реальном 1.03M — 17.2 tok/s (два прогона: 17.21 и 17.19, разброс 0.02) — всего на 2% ниже 512K. Полка, а не обвал: к этой глубине спекуляция уже почти не помогает (принятая длина ~2.1), и скорость упирается в тот же verify‑forward, что и у no‑spec. Практический вывод для сервинга: бюджетируйте глубокий хвост по ~17 tok/s — хоть на 512K, хоть на полном окне. Два операционных факта в довесок: холодный префилл 1.03M токенов занял 642 секунды (~10.7 минуты, ~1600 tok/s префилла), а повторный запрос по тому же контексту через prefix‑cache стартовал за 4 секунды — для чат‑сценариев поверх гигантского документа это разница между «практически непригодно» и «нормально». Методическая оговорка: 1M‑точка снята на другом фрагменте того же класса прозы (соседние глубины — на другом), один конфиг — greedy, 200 токенов на прогон.

Подчеркнём: это кривая немодифицированного апстрим‑билда, не артефакт наших правок. Если вы планируете глубоко‑контекстный сервинг по короткому бенчмарку — вот на сколько вы ошибётесь.

Петли на глубине: и метрика, которая при этом врёт

Апстримный README упоминает «gibberish, loops» на длинном контексте как проблему оркестрации и сэмплинга. Мы её промерили — и она глубже.

На ≥256K с детерминированным (greedy) декодом генерация время от времени сваливается в петлю — одна фраза по кругу до исчерпания бюджета токенов. На 256K greedy‑прогон дал repetition ratio 0.878 («i said i would» — 28 повторов); тот же greedy на 512K — 0.013, связное продолжение затравки (в этих пробах промпт — фрагмент Мелвилла). А на реальном 1.03M оба greedy‑прогона оказались связными (ratio 0.037 и 0.0 — модель спокойно продолжала диалог Болконского с Долгоруковым). То есть это не порог по глубине, а лотерея по decode‑пути: та же конфигурация на большей глубине не петляла — дважды. Решающий контроль: мы выключили все свои экспериментальные ручки — билд запетлял на 256K всё равно. Петля — свойство немодифицированного NVFP4-билда с детерминированным continuation‑декодом, а не наших патчей.

И самое коварное: во время петли acceptance растёт — до 3.64 на 256K‑контроле против 2.42 у связного прогона. Драфт тривиально предсказывает следующий токен собственного повтора. Если тюнить по метрике acceptance, петля выглядит как улучшение. Высокая acceptance при деградации — симптом, а не выигрыш; это ловушка для любого, кто оптимизирует спекуляцию по одной цифре.

Что не сработало: три негатива под протокол

Каждый из трёх заходов бил в правдоподобную гипотезу. Все три проиграли. Документируем, чтобы следующему не пришлось проходить их заново.

YaRN re‑anchor драфта — чистый проигрыш. Гипотеза красивая: драфт видит только 128-токенное скользящее окно основного KV, но применяет RoPE‑скейлинг полного 1M‑окна — то есть экстраполирует позиции, что и должно ронять acceptance на глубине. Фикс — перебазировать RoPE драфта на локальное окно. Acceptance действительно «выросла»: 2.42 → 4.00 на 256K, 2.23 → 3.70 на 64K. Мираж: весь «рост» — это драфт, предсказывающий петлю повтора (см. ловушку выше), выход был бессвязным на всех глубинах. Вдобавок torch.where‑путь ре‑анкора катастрофически замедлил декод: 256K — с 25.4 до 1.73 tok/s (в ~15 раз), 16K — с 42.7 до 18.6 даже с выключенным по глубине ре‑анкором: лишняя пер‑шаговая тензорная операция на GB10 не бесплатна. Мы починили модульную арифметику, добавили identity под порогом, отгейтили по глубине — всё равно проигрыш на каждой глубине. Эксперимент закрыт. Сама гипотеза окна‑против‑YaRN может быть направленно верной, но валидировать её по acceptance нельзя — метрика отравлена петлёй.

Confidence‑scheduler — регресс. В билде есть выключенный по умолчанию VLLM_DSPARK_CONFIDENCE_SCHEDULER — гейтить принятие драфта по порогу уверенности. Выглядело как бесплатный env‑only выигрыш. Включили с порогом 0.6: C16-static упал с 319 (OFF) до 290 (ON) — минус ~9%. Попутно OFF‑прогон в очередной раз воспроизвёл опубликованные 315.1: и бенч tonyd2wild честный, и его решение поставлять шедулер выключенным — правильное. Мы проверили, чтобы вам не пришлось.

Сэмплер‑фикс когерентности — мы его не нашли. Оператор сервинга спросит очевидное: раз greedy петляет, может, есть конфиг сэмплера, который надёжно держит связность на глубине? Мы прогнали пять конфигов (greedy; repetition_penalty 1.15; frequency+presence 0.4; top_p/top_k с temperature 0.7; комбо) на 256K и 512K. Ни один не связен на обеих глубинах. Лучший из одиночных на 256K — repetition_penalty=1.15 (ratio 0.009) — на 512K сам уходит в петлю (0.561, одна и та же фраза дословно пять раз). Комбо на 512K не петляет, но дегенерирует в ранний стоп из 15 токенов. Бонус‑находка: min_p под спекулятивным декодингом вообще заблокирован — сервер отвечает HTTP 400 «not yet supported with speculative decoding»; та же формулировка сервера всплывает в vllm#39295, наш прогон задокументирован в benchmarks/20260701-sampling‑coherence‑probe.md. Популярные анти‑дегенерационные рецепты на min_p к этому билду неприменимы as‑is. Вердикт: мы не нашли sampler‑only конфига, который держит обе глубины, и не публикуем «фикс», потому что публиковать нечего; похоже, лечится это не ручкой сэмплера.

Что мы не можем утверждать

Чтобы границы выводов были такими же явными, как сами выводы:

  • Паритет с референсом DeepSeek. Послойного численного оракула на SM121 нет — tilelang‑кернелы референса здесь не собираются. Совпадение acceptance с их репортом — сильный, но косвенный аргумент.

  • Что NVFP4-KV никогда не даёт bandwidth‑выигрыша. Проверен только single‑stream no‑spec; на режимах, где доминирует KV‑трафик, картина может быть другой.

  • Что петли неизбежны на любой 256K+ задаче. Баг нестабильно воспроизводится; мы показали, что он существует и отравляет метрику acceptance, а не что он ловится каждый раз.

  • Что DSpark плох для single‑user вообще. Мы показали только, что наш fp8-профиль уступил лёгкому MTP-2 на одной сессии; другой билд или рецепт может вести себя иначе.

  • Что релиз DeepSeek «соврал». Их headline снят над другим baseline (MTP-1) и на другом железе; наши цифры его не опровергают — они отвечают на другой вопрос.

Итог: что DSpark на GB10 даёт — и чего не даёт

Сводная таблица, все числа — completion_tokens / wall_time, один и тот же двухнодовый стенд:

Замер

fp8 DSpark (наш)

NVFP4 DSpark (рецепт tonyd2wild)

No‑spec авторегрессия

26.7 tok/s

26.6 tok/s

Single‑stream, короткий контекст

32.4 tok/s

55–63 tok/s

Средняя принятая длина (single‑stream)

~2.1

3.0–4.2

Конкурентность

c8 ~99, c12 141–146 tok/s

C16-static ~315–324 tok/s (200K)

Single‑stream на 512K

17.6 tok/s

Single‑stream на реальном 1M (1.03M)

17.2 tok/s

Не даёт DSpark бесплатного single‑stream выигрыша. На GB10 шаг — это фиксированный memory‑bound verify‑forward (~65 мс на чтение весов при 273 ГБ/с unified memory), в нашем профиле интерконнект не был главным узким местом (меньше 3.5% шага), и большого single‑stream процента спекуляция на этом железе структурно не даёт. Для одной интерактивной сессии лёгкий MTP‑драфт быстрее нашего fp8-DSpark. NVFP4-билд поднимает single‑stream не bandwidth’ом, а acceptance — и эта прибавка тает с глубиной контекста: 42.7 tok/s на 16K превращаются в 17.2 на реальном 1.03M (после 512K кривая выполаживается — глубокий хвост можно бюджетировать по ~17 tok/s).

Даёт он две вещи, ради которых его стоит деплоить. Первая — конкурентность: +55% агрегата над MTP-2 продом на 8 потоках, 141–146 tok/s на 12; у NVFP4-варианта — ~315–324 tok/s C16 на 200K‑контекстах. Вторая — 1M‑окно как рабочий режим: с кривой деградации, промеренной до реального 1.03M, вместо рекламной точки — и с prefix‑cache, который превращает повторное обращение к прогретому 1M‑контексту из 10-минутного префилла в 4-секундный старт.

Всё выложено: DeepSeek‑V4-Flash‑DSpark‑GB10-2x‑DGX‑Spark-1m‑fp4-fp8 — наш fp8-рецепт (патчи, архитектурная карта, разбор багов) плюс все бенчмарк‑чекпойнты, включая негативы. NVFP4-рецепт мы сознательно не вендорим — он живёт у автора, tonyd2wild/DeepSeek‑v4-Flash‑DSpark-1M‑NVFP4-KV-2x‑DGX‑Spark, у нас — ссылка, воспроизведение и CREDITS с полной родословной (tonyd2wild, bjk110, drowzeys/Keys, rafaelcaricio, fraserprice, MiaAI‑Lab).

Открытый вопрос, который мы оставляем комьюнити, — когерентность на глубине. Петли на ≥256K — не порог и не сэмплер: это нестабильно воспроизводящийся баг decode‑пути немодифицированного билда, у которого теперь есть воспроизводимая характеристика (проявляется от ~256K, зависит от decode‑пути, отравляет метрику acceptance) вместо анекдота «иногда ломается». Если у вас есть пара GB10 и идея — теперь есть и от чего оттолкнуться.


Все числа сняты с живого стенда 2× DGX Spark (GB10, SM121, TP=2 через QSFP 200G / RoCEv2) 30 июня — 4 июля 2026. NVFP4-замеры — на byte‑identical билде tonyd2wild, коммит 89bb82b, его харнессом без модификаций. Методика: usage.completion_tokens / wall_time, non‑streaming; SSE‑чанки не считаем никогда.

ссылка на оригинал статьи https://habr.com/ru/articles/1055792/