Skip to content

[None][feat] Page the DSpark drafter context through the draft KV cache manager - #18343

Draft
dc3671 wants to merge 1 commit into
NVIDIA:mainfrom
dc3671:user/zhenhuanc/dspark-kvmgr
Draft

[None][feat] Page the DSpark drafter context through the draft KV cache manager#18343
dc3671 wants to merge 1 commit into
NVIDIA:mainfrom
dc3671:user/zhenhuanc/dspark-kvmgr

Conversation

@dc3671

@dc3671 dc3671 commented Aug 28, 2026

Copy link
Copy Markdown
Collaborator

Follow-up to #18043 (merged); rebased onto main at 891b483.

What

The standalone DSpark/DFlash drafter keeps its context KV in a private
arena
. That arena is dense in max_seq_len, allocated after the KV pool has
been carved (so free_gpu_memory_fraction never bounds it), and sized from the
drafter's advertised max_position_embeddings rather than the runtime limit.
For Kimi K3 that is 1048576, hundreds of GiB on its own; at max_seq_len
997696 with max_batch_size 8 the arena wants 21.4 GiB/rank and the worker
cannot start.

This reads the already-funded draft pool through the manager's per-request
block tables, so the drafter's footprint follows the sequences actually served.

before after
drafter KV private arena, dense in max_seq_len paged through the draft KV manager
sized from max_position_embeddings runtime max_seq_len
K3 @ 997696, batch 8 21.4 GiB/rank, cannot start inside the pool budget

Note the pool index space differs between the V1 and V2 managers — V1 returns a
per-layer strided slice, V2 a dense view over the layer-interleaved page space —
so the block-table divisor is derived from stride(0) rather than assumed.
Getting this wrong is silent: AL drops ~20% with no error and no accuracy
change.

Three gates, each written for a mode it does not describe

  • attention DP — the bail suits MTP, whose draft layers are target-shaped
    and appendable to the target pool. An external drafter has its own
    architecture and layer count, so nothing gets appended and it stayed on the
    arena — which under attention DP is sized with KV heads unsharded
    (20480 vs 2560 B/token for K3).
  • disaggregation — nvbugs/5807902 reported an Eagle3 RMSNorm failure in
    disagg and was worked around by disabling the separate draft KV cache for
    every speculative mode. The root cause was never found and the bug closed
    will-not-fix, so the workaround stays where it was reported.
  • disagg slot allocation_store_prefill_context was the only place
    assigning a drafter slot, so a generation worker (receives prompt KV, never
    prefills) collapsed every concurrent request onto the single dummy slot. A
    context-only worker also releases the target's IndexMapper slot once
    prefill finishes; the draft mirror never got that call and saturated until
    copy_batch_block_offsets() asserted.

Why the test asserts on acceptance length

Speculative decoding is lossless, so a drafter producing garbage scores the
same on GSM8K and merely runs slower. Accuracy cannot detect a broken
drafter.
This is not hypothetical — it is how the slot collapse above was
found:

K3 DSpark disagg, GSM8K 1319 samples, TEP16 AL decode steps
before 1.087 123860
after 3.441 39072

Output length matched to within 0.1% (134575 vs 134463 tokens), so the 3.2×
step reduction is pure acceptance. AL 1.087 means 1.2% of 7 draft tokens were
accepted — speculative decoding was effectively dead on that path, and every
accuracy gate still passed.

Validation

GB300, K3 DSpark unless noted.

arm parallel max_seq_len AL
baseline (arena) TEP8 8192 5.25 / 5.50
this change TEP8 8192 5.33 / 5.43 / 5.53
this change DEP16 8192 5.33
this change DEP16 997696 5.22
baseline (arena) TEP8 997696 cannot start
this change, disagg TEP16 8192 3.44, 96.13% strict-match

Also exercised against the embedded DSpark path (DeepSeek-V4-Pro, DEP4
cutedsl, AL 4.436) — a different drafter shape over the same shared
kv_cache_manager_v2 / py_executor code this touches.

The added test itself was run on this branch's base, 8x GB300, all 1,319
samples: GSM8K 96.361 (threshold 93.297) and AL 4.355, in 25m45s.

On AL references: the 5.2-5.5 figures above are --apply_chat_template runs,
which is worth ~40% acceptance on GSM8K (3.81 plain vs 5.52-5.88 chat, same
drafter, same 1,319 samples). Sample count is not the variable — 200 and 1,319
agree within 5% at fixed prompt form. The stock lm-eval workload this test uses
measured 4.16 on the same path before this change, so min_al is set at 4.0:
a return to the old behaviour still passes, an acceptance collapse (1.09, and
every accuracy gate green) does not.

CI placement

TestKimiK3::test_gsm8k_tep8_dspark needs 8 GPUs and GB300-class memory (TP8
leaves ~182 GiB of weights per GPU before any KV cache, past what a 184 GiB
B200/GB200 part holds), so it is registered as a 2x4 GB300 post-merge stage
following #18363: a new l0_gb300_multi_nodes_node2_gpu8.yml plus a
GB300-8_GPUs-2_Nodes-1_NVLink_Domain-PyTorch-Post-Merge-1 entry in
multiNodesSBSAConfigs. The 1_NVLink_Domain name is load-bearing — it is
what forces --segment=2, and a K3 instance straddling two NVL domains dies in
symm_mem.rendezvous with CUDA driver error: invalid resource handle.

Overlaps #18363, which is not merged yet: the singleNvlinkDomainMode
plumbing in L0_Test.groovy and the l0_gb300_multi_nodes_node2_gpu8.yml
header are the same hunks. Whichever lands second should drop the duplicate and
keep only its own test line / stage entry. Carried here rather than stacked so
this PR is correct standalone — without --segment the stage can silently
allocate across racks.

The drafter checkpoint is on the CI model share as Kimi-K3-DSpark
(llm-models!595, merged; RadixArk/Kimi-K3-DSpark @ 3c5bac3).

Not covered

The VANILLA drafter attention backend still uses the contiguous
flash_attn_with_kvcache and is untouched; only the TRTLLM backend reads the
paged pool.

…he manager

The standalone drafter kept a private context arena, dense in max_seq_len and
allocated after the KV pool had been carved, so free_gpu_memory_fraction never
bounded it: at max_seq_len 997696 with max_batch 8 it wants 21.4 GiB/rank and
the worker cannot start. Read the already-funded draft pool through the
manager's per-request block tables instead, so the footprint follows the
sequences served, and size what remains from max_seq_len rather than the
drafter's advertised max_position_embeddings (1048576 for K3, hundreds of GiB
on its own). The pool index space differs between the V1 and V2 managers, so
the block-table divisor is derived from stride(0) rather than assumed.

Three gates kept an external drafter off that path, each written for a mode it
does not describe:

- attention DP: the bail suits MTP, whose draft layers are target-shaped and
  appendable to the target pool. An external drafter has its own architecture,
  so nothing is appended and it stayed on the arena -- which under attention DP
  is sized with KV heads unsharded (20480 vs 2560 B/token for K3).
- disaggregation: nvbugs/5807902 reported an Eagle3 RMSNorm failure and was
  worked around by disabling the separate draft KV cache for every speculative
  mode. Keep the workaround where it was reported.
- disagg slot allocation: _store_prefill_context was the only place that
  assigned a drafter slot, so a generation worker -- which receives prompt KV
  instead of prefilling -- collapsed every concurrent request onto the single
  dummy slot. A context-only worker also releases the target's IndexMapper slot
  after prefill; the draft mirror never got that call and saturated.

Accuracy cannot detect a broken drafter: speculative decoding is lossless, so
one producing garbage scores the same and only runs slower. The added test
therefore asserts on acceptance length. That is not hypothetical -- the slot
collapse showed up as AL 1.087 vs 3.441 (decode steps 123860 -> 39072 for the
same output length) while every accuracy gate passed.

Signed-off-by: Zhenhuan Chen <zhenhuanc@nvidia.com>
@dc3671
dc3671 force-pushed the user/zhenhuanc/dspark-kvmgr branch from 6f5df55 to a0ef415 Compare August 31, 2026 07:37
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant