Skip to content

[Spec] Support DSPARK in disaggregation decode mode - #31513

Open
yz-tang wants to merge 3 commits into
sgl-project:mainfrom
yz-tang:spec/dspark-disaggregation-decode
Open

yz-tang wants to merge 3 commits into
sgl-project:mainfrom
yz-tang:spec/dspark-disaggregation-decode

Conversation

@yz-tang

@yz-tang yz-tang commented Jul 17, 2026

Copy link
Copy Markdown

Motivation

DSPARK/DFLASH crashed when run with --disaggregation-mode decode (PD-disaggregation). The disagg decode path was only wired up for EAGLE (build_disagg_draft_input had an is_eagle() branch only); the dflash family hit three independent failures on a freshly transferred prebuilt batch, whose first decode runs prepare_for_decode before any local forward.

Three failure points

  1. spec_info was None at prepare_for_decode. On a disagg decode node the first decode runs prepare_for_decode before any forward, so the worker's lazy spec_info init (_forward_decode) never fires. build_disagg_draft_input returned None for dflash_family, leaving batch.spec_info None and crashing spec_prepare_for_decode (batch.spec_info.prepare_for_decode(batch) -> AttributeError: 'NoneType').

  2. max(None) in can_run_graph for the IDLE draft participation batch. run_idle_participation builds the ForwardBatch by hand (not via init_new), so original_global_num_tokens_cpu stayed None. Under --enable-dp-attention + --moe-a2a-backend none, require_mlp_tp_gather is true, so can_run_graph read max(forward_batch.original_global_num_tokens_cpu) and crashed (TypeError: 'NoneType' object is not iterable). The normal TARGET_VERIFY draft batch didn't hit this because can_run_graph early-returns for target_verify.

  3. future_indices was None in _resolve_spec_extras. The initial DFlashDraftInputV2 lacked future_indices, so the overlap relay that DFLASH uses to relay bonus_tokens (_resolve_spec_extras -> output_tokens_buf[indices]) hit indices.shape on None (AttributeError). The normal path sets future_indices every forward (scheduler.py overlap path) and stashes the bonus, so it never went None there.

Changes

  • speculative/dflash_disaggregation.py (new): build_dflash_disagg_draft_input, mirroring the EAGLE path — seed the initial DFlashDraftInputV2 via make_draft_input_v2 (empty topk/hidden + bonus + seq_lens, same as the prefill node's _forward_extend) and wire up the overlap relay (future_indices / publish / stash) so the first decode resolves seq_lens and bonus_tokens correctly. DFLASH/DSPARK only relay bonus_tokens through the FutureMap (topk/hidden are regenerated each step), so the RelayPayload carries nothing else.
  • speculative/spec_info.py: build_disagg_draft_input gains an is_dflash_family() branch that calls the new function (previously returned None).
  • speculative/dspark_components/dspark_draft.py: _fill_dp_moe_sync_metadata now sets original_global_num_tokens_cpu to match ForwardBatch.init_new's assignment, fixing the IDLE draft batch (and any other hand-built DFLASH draft ForwardBatch).

Test

  • Prefill
sglang serve \
  --trust-remote-code \
  --model-path /data2/DeepSeek-V4-Flash-DSpark \
  --tp 8 \
  --dp 8 \
  --mem-fraction-static 0.8 \
  --chunked-prefill-size 65536 \
  --max-running-requests 256 \
  --enable-dp-attention \
  --enable-dp-lm-head \
  --tool-call-parser deepseekv4 \
  --reasoning-parser deepseek-v4 \
  --speculative-algorithm DSPARK \
  --disaggregation-mode prefill \
  --disable-flashinfer-autotune \
  --moe-a2a-backend none \
  --moe-runner-backend flashinfer_mxfp4 \
  --disaggregation-transfer-backend mooncake \
  --disaggregation-ib-device "mlx5_0,mlx5_1,mlx5_2,mlx5_3,mlx5_4,mlx5_5,mlx5_6,mlx5_7" \
  --log-requests \
  --log-requests-level 0 \
  --enable-metrics \
  --enable-cache-report \
  --host 0.0.0.0 \
  --port 30000
  • Decode
sglang serve \
  --trust-remote-code \
  --model-path /data2/DeepSeek-V4-Flash-DSpark \
  --tp 8 \
  --dp 8 \
  --mem-fraction-static 0.8 \
  --max-running-requests 256 \
  --enable-dp-attention \
  --enable-dp-lm-head \
  --cuda-graph-bs-decode 1 2 3 4 5 6 7 8 9 10 12 16 \
  --tool-call-parser deepseekv4 \
  --reasoning-parser deepseek-v4 \
  --speculative-algorithm DSPARK \
  --disaggregation-mode decode \
  --disable-flashinfer-autotune \
  --moe-a2a-backend none \
  --moe-runner-backend flashinfer_mxfp4 \
  --disaggregation-transfer-backend mooncake \
  --disaggregation-ib-device "mlx5_0,mlx5_1,mlx5_2,mlx5_3,mlx5_4,mlx5_5,mlx5_6,mlx5_7" \
  --log-requests \
  --log-requests-level 0 \
  --enable-metrics \
  --enable-cache-report \
  --host 0.0.0.0 \
  --port 30020

mmlu: 91.4%
gsm8k: 96.88%


CI States

Latest PR Test (Base): ❌ Run #29559761224
Latest PR Test (Extra): ❌ Run #29559761129

DSPARK/DFLASH crashed when run with `--disaggregation-mode decode` because
the disagg decode path was never wired up for the dflash family (only EAGLE
had a `build_disagg_draft_input` branch). Three independent failure points
on a freshly transferred prebuilt batch:

1. `spec_info` was None at `prepare_for_decode`. On a disagg decode node the
   first decode runs `prepare_for_decode` *before* any forward, so the
   worker's lazy `spec_info` init (`_forward_decode`) never fires.
   `build_disagg_draft_input` returned None for dflash_family, leaving
   `batch.spec_info` None and crashing `spec_prepare_for_decode`.

2. `max(None)` in `can_run_graph` for the IDLE draft participation batch.
   `run_idle_participation` builds the ForwardBatch by hand (not via
   `init_new`), so `original_global_num_tokens_cpu` stayed None. Under
   `--enable-dp-attention` + `--moe-a2a-backend none` this makes
   `require_mlp_tp_gather` true, so `can_run_graph` read
   `max(forward_batch.original_global_num_tokens_cpu)` and crashed.

3. `future_indices` was None in `_resolve_spec_extras`. The initial
   `DFlashDraftInputV2` lacked `future_indices`, so the overlap relay that
   DFLASH uses to relay `bonus_tokens` (`_resolve_spec_extras` ->
   `output_tokens_buf[indices]`) hit `indices.shape` on None.

Fixes:
- Add `build_dflash_disagg_draft_input` (mirrors the EAGLE path): seed the
  initial `DFlashDraftInputV2` via `make_draft_input_v2` and wire up the
  overlap relay (`future_indices` / `publish` / `stash`) so the first decode
  resolves seq_lens and bonus_tokens correctly. DFLASH/DSPARK only relay
  `bonus_tokens` through the FutureMap (topk/hidden are regenerated each
  step), so the RelayPayload carries nothing else.
- Set `original_global_num_tokens_cpu` in `_fill_dp_moe_sync_metadata` to
  match `ForwardBatch.init_new`'s assignment, fixing the IDLE draft batch
  and any other hand-built DFLASH draft ForwardBatch.

Co-Authored-By: Claude <noreply@anthropic.com>
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@yz-tang
yz-tang marked this pull request as draft July 17, 2026 03:33
Co-Authored-By: Claude <noreply@anthropic.com>
@yz-tang
yz-tang marked this pull request as ready for review July 17, 2026 06:17
@gemini-code-assist

Copy link
Copy Markdown
Contributor

Warning

You have reached your daily quota limit. Please wait up to 24 hours and I will start processing your requests again!

@yz-tang

yz-tang commented Jul 17, 2026

Copy link
Copy Markdown
Author

/rerun-failed-ci

@yz-tang yz-tang changed the title [Spec] Support DSPARK/DFLASH in disaggregation decode mode [Spec] Support DSPARK in disaggregation decode mode Jul 17, 2026
@wxsms

wxsms commented Aug 5, 2026

Copy link
Copy Markdown
Contributor

verified works also when prefill node not enable spec (prefill CP). Thank you.

Copy link
Copy Markdown

Independent B300 validation of this PR's disaggregation-decode path.

I hit the exact batch.spec_info is None failure described here on plain v0.5.16:
spec_prepare_for_decode -> batch.spec_info.prepare_for_decode(batch) -> AttributeError.

After a minimal source-equivalent backport of #31513 on top of v0.5.16, the following configuration came up clean:

  • 8x NVIDIA B30Z
  • TP8 / DP8
  • DP attention + DP LM head
  • DSPARK
  • moe_a2a_backend=none
  • disaggregation mode = decode
  • disaggregation transfer backend = fake
  • fake-bootstrap client
  • full CUDA Graph decode path

The spec_info initialization issue disappeared, /health reached 200, and sustained decode load completed successfully.

Across the subsequent D-only sweep:

  • 18,560 / 18,560 requests succeeded
  • 0 scheduler exceptions
  • 0 CUDA illegal-address / device-assert faults
  • post-run health remained 200
  • CUDA Graph replay was observed through the per-rank bs=128 tier

This was tested together with the already-merged #33098 metadata fix, because plain v0.5.16 otherwise fails earlier in DSpark + DP attention on original_global_num_tokens_cpu.

So from my side this provides additional evidence that the disaggregation-decode initialization/relay changes in #31513 work on B300 with the fake-transfer D-only path as well.

Copy link
Copy Markdown

Follow-up regression coverage is now in #34410.

Current main already has the DSpark PD-decode builder path, so #34410 does not re-add production support. It pins the first-decode handoff contract that this PR originally exposed: non-None SpecInput before prepare_for_decode, plus overlap future_indices / FutureMap relay initialization.

The PR also records independent B300 TP8/DP8 runtime evidence from the v0.5.16 backport investigation, with the historical stack and SPS limitations explicitly disclosed.

Copy link
Copy Markdown

2026-08-11 follow-up on the historical DSpark disaggregation-decode path:

I used the #31513-style handoff as the v0.5.16 backport prerequisite in an independent B300/B30Z TP8/DP8 DSpark decode-only runtime regression, together with merged #33098 and the final reviewer-selected no-init #32467 producer fix.

That disclosed stack completed C64/C256/C512/C1024 for 18560/18560 successful requests, with zero observed CUDA illegal-address/device-assert/scheduler failures and /health=200. A forced compact-ragged window also remained clean.

Current main has since evolved a dedicated DSpark disaggregation implementation, so I am not treating this historical runtime validation as evidence that #31513 should land unchanged. Instead, #34410 now pins the current-main handoff contract as a CPU regression: DSpark PD decode must receive a non-None draft input before first decode, and overlap must seed/publish/stash the relay state.

The distinction between this historical implementation/failure analysis, current-main behavior, and the separate CUDA-Graph producer/capture issues is documented in #34297.

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.

3 participants