Skip to content

DSpark speculation (K=7): drafter chain, verify, and acceptance - #223

Merged
superxf merged 5 commits into
hw-native-sys:mainfrom
ndleslx:dspark-m2-speculation
Sep 11, 2026
Merged

superxf merged 5 commits into
hw-native-sys:mainfrom
ndleslx:dspark-m2-speculation

Conversation

@ndleslx

@ndleslx ndleslx commented Sep 8, 2026

Copy link
Copy Markdown
Collaborator

Implements milestone 2 of #211: the DSpark speculative chain (K=7) behind the merged target-model serving (#213).

What this adds

Select with --speculative-config '{"method": "dspark", "num_speculative_tokens": 7}'; 0 keeps the target-only path and loads, requires, allocates, and compiles nothing drafter-related.

  • Submodule f069bc7 → 216b497: the target programs gain the rank-owned dspark_target_hidden tap (pypto-lib#1084), idle DP groups skip natively (pypto-lib#1161, replacing the M1 mirror replay), and the decode head tail right-sizes to owner rows (pypto-lib#1182): the decode ABI drops the four dead local RoPE tables (113 → 109 args, the remaining tables ride the owner-token T_DYN axis) and the LM-head/sampling windows shrink from the 512-row group stream to each owner's 128 rows. The perf batch between the pins (#1150, #1158-#1159, #1163, #1165, #1168-#1172, #1175, #1181, #1183-#1184) rides along.
  • Drafter weights: mtp.0/1/2 plus three replicated heads packed into the exact l3_dspark_drafter / l3_distributed_markov_sample bank shapes — projection transposes, FP32 gate/confidence casts, INT32 tid2eid from the hash layers, TP-sharded o-projection, EP-sharded experts. All banks upload before the KV-capacity snapshot.
  • Speculative runner: stable group-local drafter leases (64 per group, six rotated ring blocks per layer) over runner-private SWA pools; prefill-tail seeding through the worker's terminal-prefill hook; eager eight-row verify publication with per-step single-anchor fallback near the position ceiling; host linear-chain acceptance (longest matching draft prefix plus bonus, 1..8 tokens/step) with per-request counters and acceptance logging.
  • Async correctness: decode positions come from the runner's committed count, not the scheduler's optimistic reservation (the MTP _correct_mtp_seq_lens equivalent); CSA state rings are built at the window end so every written page resolves through its absolute page id across steps.
  • Guard robustness: the accuracy guard waits for per-device HBM to return to baseline between its two server boots — each boot needs nearly the whole card, and the driver reclaims a torn-down server's pooled arenas asynchronously after process exit.

Commits

  1. DSpark submodule bump to 216b497: owner-row head tail and ABI trim
  2. DSpark drafter weights: mtp.* checkpoint to stacked device banks
  3. DSpark speculation: drafter programs, verify, acceptance, and routing
  4. DSpark speculation tests, docs, and CI

Verification

  • Unit: the DSpark functional guard (tests/unit/model/deepseek_dspark/, 7 tests) covers prefill/decode/drafter staging contracts, AST ABI parity for all four programs, acceptance semantics for every m in 0..7, and the end-to-end accept-then-redraft state updates. Repo-wide unit tests pass except two known pre-existing base failures (MTP owner-outputs fixture, worker-step-protocol KeyError: 'req', reproducible on pristine main). Ruff, header, English, docs-nav, and public-docs checks clean.
  • Device (16 cards, canonical TP4/DP4/EP16, Palace 64→128): the combined guard passes at this pin — K=0 and K=7 each complete with the exact same greedy text (parity across configs), finish_reason: length, exact token accounting; K=7 acceptance runs at a mean accepted length ≈ 2.0 with zero fallback steps and the DSpark speculation progress line asserted from the server log. Measured KV capacity is unchanged by the bump (K=0: 42 slots, K=7: 37 slots per partition). Note: pypto-lib main past this pin (through #1187's attention pipelining) deadlocks the serving dispatch path — the standalone kernel harness passes, serving hangs at a ring-3 producer — so this pin is also the serving-known-good boundary.

Closes #211.

@coderabbitai

coderabbitai Bot commented Sep 8, 2026

Copy link
Copy Markdown

Important

  • 🔍 Trigger review

This repository does not receive automatic reviews because it has fewer than 10 stars.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Advanced

Run ID: 38fc1241-9c19-41a8-bee7-944ec68dc3be


Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands.

@ndleslx
ndleslx force-pushed the dspark-m2-speculation branch 4 times, most recently from 136a611 to 1ef8991 Compare September 9, 2026 08:11
@ndleslx ndleslx closed this Sep 9, 2026
@ndleslx ndleslx reopened this Sep 9, 2026
@ndleslx
ndleslx force-pushed the dspark-m2-speculation branch from 1ef8991 to f244b1b Compare September 10, 2026 03:38
Adopt pypto-lib origin/main through #1184: the drafter backbone tap,
the native idle-group skip, and the owner-row right-sizing of the
decode head tail.

- Swap prefill's hidden_workspace for the rank-owned dspark_target_hidden
  output ([rows, 3*D] BF16, layers 40/41/42 through one hc_head
  projection each) and register it on the local dynamic axis; decode gains
  the same [16-row tile, 3*D] output between pre_hc_hidden_out and x_out
  (pypto-lib#1084).
- Trim the decode ABI's four dead local RoPE tables
  (freqs_cos_local/sin_local, compressed_freqs_cos_local/sin_local,
  113 -> 109 args): the remaining freqs tables absorbed their role and now
  ride the owner-token T_DYN axis, so each rank stages its own
  local-token slice of the group stream instead of the whole stream.
- Right-size the LM-head and greedy-sampling windows from the step's 512
  group rows to each owner's MOE_TOKENS = 128 (DSPARK_MAX_LOGIT_ROWS,
  pypto-lib#1182); serving's packed logit-row indices and sampled
  readbacks were already owner-local, so only the window extents change.
- Pin the AST name/order parity of both target programs (101/109) plus
  their dynamic-axis, dtype, and Out-annotation contracts against the
  kernel sources.
- Drop the M1 mirror replay into inactive TP groups: pypto-lib#1161 skips
  their attention and sampling tails natively from a zero query_start_loc
  terminal while keeping every EP MoE wave, so idle-group staging stays
  zero-initialized (-1 logit rows and cache mappings).
- Carry the decode/HCA/MoE perf batch, the prefill compressor state
  fixes, and the prefill-to-decode token-loop validation session between
  f069bc7 and this pin (pypto-lib#1150, #1158, #1159, #1163, #1165,
  #1168-#1172, #1175, #1181, #1183, #1184, #1092).
Pack the speculative drafter's checkpoint tensors into the exact bank
shapes l3_dspark_drafter and l3_distributed_markov_sample declare.

- Flatten the mtp.0/1/2 layers along each bank's first rank-local axis
  with per-name transforms the target banks do not need: the checkpoint's
  [out, in] projections (wq_a/wq_b/wkv) transpose into the kernel's
  matmul orientation, the router gate and confidence head cast to FP32,
  and tid2eid stacks from the target's hash layers as INT32.
- TP-shard the drafter o-projection (wo_a groups / wo_b lora columns) and
  EP-shard its routed experts per rank, mirroring the target policies.
- Replicate the drafter heads verbatim: mtp.0 main_proj/main_norm, the
  mtp.2 final norm, markov tables, and hc_head constants.
- Cross-check every model dimension from the loaded tensors so a
  mismatched checkpoint fails at load time, not on device.
- Gate the whole drafter module set behind validate_drafter_startup_contract:
  the K=0 path requires, loads, and validates none of it.
Run the K=7 speculative chain behind the target programs
(l3_dspark_drafter + l3_distributed_markov_sample), with host
linear-chain acceptance and eager verify publication.

- Compile the two drafter programs only at num_speculative_tokens=7
  (DSPARK_QUERY_WIDTH; anything outside {0, 7} is rejected), pass --dp
  through the kernel import context so the markov program covers the
  full 16-rank world, and validate the checkpoint's drafter config
  (target layers, noise token, markov rank) at startup.
- Give each live request a stable group-local drafter lease (64 per
  group, six rotated ring blocks per draft layer) so drafter storage
  survives batch reshuffles; a shared read-only zeroed filler range
  backs the padded batch rows, which publish nothing.
- Stage the drafter through per-extent shared buffers (batch paddings
  4/8/12/16, context extents 32/64/96/128) because the group-context
  tensors' dynamic axis is not a per-rank storage prefix; selectors,
  hidden taps, and draft readbacks go through packed prefix views.
- Capture a rolling 128-row prompt tail from the prefill tap each chunk
  and seed the first draft chain in the worker's terminal-prefill hook
  (num_sampled=0, the first sampled token as next_prefill_tokens).
- Verify eagerly: drafts occupy rows 1..7 with all eight rows published
  (kv_seq_lens at the full window), so a truncated acceptance's stale
  rows are rewritten by the next window before any read; requests whose
  window cannot fit the position ceiling fall back per step to the
  single-anchor K=0 contract and skip their next drafter query.
- Build the CSA state rings at the window end so every written page
  resolves through its absolute page id and survives into later steps'
  rebuilt tables.
- Correct async scheduling's optimistic seq_lens from the runner's
  committed count before any metadata is built, mirroring the MTP
  runner's _correct_mtp_seq_lens.
- Accept on host (longest matching prefix plus the bonus prediction,
  1..8 tokens per step), redraft from the just-committed tap rows, and
  report acceptance progress plus a per-request summary on release.
- Materialize the drafter's persistent buffers before the KV-capacity
  snapshot, release every speculative resource in close(), and keep K=0
  loading, allocating, and compiling nothing drafter-related.
Extend the DSpark HTTP guard and its coverage for the K=7 chain.

- Parameterize the guard over the speculative depth: the same Palace
  64-token prompt / 128-token gate now runs the K=0 target-only contract
  and the K=7 chain, each on its own server with isolated logs.
- Keep the assertions contract-only (model, finish reason, exact token
  accounting) and require the runner's acceptance-progress line for the
  K=7 case as proof the drafter/markov/verify path really dispatched;
  acceptance changes speed, never text, so no exact-text comparison.
- Pin both server commands' speculative-config in the host contract
  test.
- Run both cases in the existing 16-card CI job under a wider
  task-submit budget.
- Wait for per-device HBM to return to baseline between the two server
  boots (npu-smi poll, 8 GiB threshold): each boot needs nearly the
  whole card, and the driver reclaims a torn-down server's pooled arenas
  asynchronously after process exit, so an immediate next boot dies in
  its weight upload with a device OOM.
- Document the speculative design in the developer guide: program and
  weight contracts, the lease/ring cache model, eager verify publication
  and its fallback, seeding, async position correction, and the
  acceptance metrics.
@ndleslx
ndleslx force-pushed the dspark-m2-speculation branch from f244b1b to 00da7dc Compare September 10, 2026 05:20
@superxf
superxf merged commit 8785c16 into hw-native-sys:main Sep 11, 2026
18 of 24 checks passed
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.

[Feature] Serve DeepSeek-V4-Flash DSpark: target model e2e first, DSpark speculation next

2 participants