Skip to content

WIP: PD-DFlash Task 6 Step 5 — SpecSession seam + engine byte-demand VERIFY - #163

Merged
drunkcoding merged 13 commits into
devfrom
feat/pd-dflash-task6-step5-seam
Aug 15, 2026
Merged

WIP: PD-DFlash Task 6 Step 5 — SpecSession seam + engine byte-demand VERIFY#163
drunkcoding merged 13 commits into
devfrom
feat/pd-dflash-task6-step5-seam

Conversation

@drunkcoding

Copy link
Copy Markdown
Contributor

What & why

Completes Task 6 Step 5 of the PD-DFlash serving plan: the serving engine now drives per-round DRAFT→VERIFY→DRAFT and registers each pending verify's EXACT token/byte demand so the 2-D verify scheduler governs when VERIFY runs under concurrency. This unblocks the seam reported in docs/superpowers/notes/2026-08-14-pd-dflash-task6-step5-seam.md.

Stacks on #162 (feat/pd-dflash-task6-engine) which already contains #156 (DRAFT/VERIFY 2-D scheduler) + #157 (expert_nbytes instrumentation). Target base: dev.

The SpecSession seam (spec_decode/dflash.py)

Externalizes the _generate_single loop as a public single-round control surface:

  • begin_session(...) -> SpecSession — prefill/anchor forward; seeds per-round state (target_kv, draft_kv, context_feature, anchor, start, emitted, step_trace). Handles the immediate-stop anchor case.
  • draft_round(session) -> DraftResult — one _run_drafter pass builds the block; returns the pending verify's projected demand (tokens=block_size, expert_union, expert_bytes).
  • verify_round(session) -> VerifyResult — one _verify_target_block under route_ahead_context, exact accept/commit/rollback + step-trace, then updates the projection for the next round.

Read-only route projection (no second router)

The projection reuses the existing route_ahead_context(..., stats=…) recorder slot: a RouteUnionCollector (duck-typing begin_step/observe_layer/commit_step) captures the verify's ACTUAL per-layer routed union via the same union_experts_from_mask gate/top-k primitive the verify's dispatch_local uses — read-only (no pin, no prefetch dispatch, no expert exec, no routing/output change). Bytes = project_expert_bytes(union, ExpertPrefetcher.expert_nbytes_map) — the exact summed FP4 payload, never an expert-count estimate. This needs zero changes to expert_executor.py/_route_ahead_ctx.py (a smaller blast radius than the spec's sketch, same contract).

Because exact same-round routing is only knowable during the verify forward (each MoE block computes router_mask inline per layer), the projection is round-to-round: the prior verify's union projects the next round's demand; the first round is a byte-free warm-up (0 bytes).

generate() is byte-identical

generate() / _generate_single / _generate_batched are untouched; the session runs alongside. Proven by: (a) all pre-existing spec-decode tests stay green, and (b) a new test asserting the session loop reproduces generate() token-for-token (greedy and lossless-sampled).

Engine wiring (serving/engine.py) — opt-in, default unchanged

_step_speculative_session runs only when verify budgets are configured and the speculator exposes the seam. Per round: draft_roundset_verify_demand(seq_id, tokens=B, expert_bytes=Σ)schedule() → run verify only for admitted verify_seq_idsclear_verify_demand; sequence advances PREFILL→DRAFT→(VERIFY→DRAFT)*→FINISHED. Unadmitted rounds stay DRAFT and their 2-D deficit carries. Reuses #156's admit_verify_demands/set_verify_demand and #157's expert_nbytes_map; adds Scheduler.verify_scheduling_enabled.

Default serving is byte-for-byte unchanged: without budgets, or with a non-session speculator, the delegated request keeps the whole-request generate() path (_step_speculative).

Tests / verification

  • tests/python/dflash/test_spec_session.py (10) — session==generate() identity (greedy+sampled), round-API order, immediate-stop anchor, exact-bytes projection (count ≠ bytes), collector read-only.
  • tests/python/serving/test_dflash_engine_step5.py (5) — exact-byte demand registration, carried-deficit liveness, misconfig raise, default/fallback preservation.
  • No spec-decode regression: 89 core dflash tests + 152 route-ahead/prefetch/sampled/batched/engine-wire tests + 49 serving-core tests all green.
  • gpt-oss offload topology test green (5/5). (The test_gpt_oss_mxfp4_dispatch failures are pre-existing/environmental — unbuilt moe_infinity._v4_fp4 native ext — identical on the base branch; this PR is pure Python and touches no FP4/gpt-oss code.)
  • ruff check + ruff format clean; LSP diagnostics clean on all changed files.

Scope guardrails

Pure Python (no _store/C++ rebuild). Does not merge, does not touch GLM / feat/dflash-spec-decode / #158#161/#119, and adds no second router/prefetch path.

Draft — do not merge.

drunkcoding added 13 commits August 14, 2026 21:24
Record exact stored FP4/FP8 expert payload bytes through the route-ahead
observer so the measure-first gate can report wasted_prefetch_bytes as bytes,
not an expert count. RouteAheadStats.observe_layer gains an optional
expert_nbytes map and RouteAheadStepSummary/as_dict expose predicted/kept/
wasted byte fields (None when unavailable -- never a fabricated average).
ExpertPrefetcher.expert_nbytes_map is populated at registration time in
model_offload from live params (before offload placeholders erase shape);
the executor seam forwards it None-safely (isinstance-dict guard keeps mocks
and resident runs at None). Strictly additive and off by default.
Opt-in RTX PRO 6000 B0-B3 route-ahead serving runner. The CLI module is
import-safe (torch/moe_infinity imported only inside the GPU path), emits one
JSON row per (model,baseline,B,concurrency,repeat) matching REQUIRED_METRICS,
validates the device is an RTX PRO 6000 (12,0), refuses resident B0/B1/B2, and
blocks B2 as BLOCKED_UNTIL_2D_SCHEDULER until the 2-D scheduler lands. Wraps
draft/router/issue/verify/H2D in the frozen NVTX ranges the BM4 parser keys on.
GPU test is gated on MOE_DFLASH_SERVING_GPU (1 skipped, side-effect free
collection); CPU contract test locks the pure matrix/schema/writer logic.
Add BM1 summarise_row (pass iff t_router < t_verify, ratio + raw terms
retained) and a CPU-safe aggregation CLI: group raw rows into §8 matrices
keyed by (model,block,concurrency), permit blocked B2 via --allow-blocked,
attach BM1, and emit result_matrix.json/CSV/Markdown for validate_result_matrix.
Refactors the per-baseline metric check out of validate_result_matrix
(behavior-preserving).
run_phase_a.sh drives the full §8 matrix for both required MoE targets on one
RTX PRO 6000 with the documented env (HF_HOME, MOE_ENABLE_SM120, device-memory-
ratio<0.9 to force offload) and aggregates into result_matrix.json. All inputs
are documented env vars in the script header.
…(DRAFT/VERIFY scheduler)

Stacks the two independent additive PD-DFlash features from the same plan:
- origin/feat/pd-dflash-phaseA-runner (#157): expert_nbytes / ExpertPrefetcher.expert_nbytes_map
- origin/feat/pd-dflash-serving-scheduler (#156): Scheduler DRAFT/VERIFY + set/clear/admit_verify_demands

Conflicts in benchmarks/dflash/report.py and tests/python/dflash/test_pd_dflash_report.py
resolved KEEP-BOTH: #157's versions are strict supersets (Task-1 contract identical,
plus BM1/aggregation/CLI), preserving 100% of both sides' behavior.
…lator seam

Honest gate: Steps 1-4 of Task 6 are complete via the #156+#157 stack this
branch is based on. Step 5 (engine DRAFT->VERIFY->DRAFT with exact per-round
expert-byte demand) genuinely requires a public single-round control seam on
DFlashSpeculator (begin_session/draft_round/verify_round + a read-only route
projection) so the engine can register the pending verify's EXACT summed
expert_nbytes BEFORE the verify runs.

That seam does not exist (no draft_round/verify_round anywhere), and Task 6's
authorized file list scopes only serving/{batch,scheduler,engine}.py -- not
spec_decode/dflash.py or distributed/expert_executor.py. Post-hoc RouteAheadStats
can't gate admission, fabricating bytes from expert count is forbidden, and
re-implementing the draft/verify loop in the engine would be a forbidden second
router. Reporting the exact seam instead of hacking one in.
Externalize the _generate_single loop behind begin_session / draft_round /
verify_round (Task 6 Step 5). draft_round returns the pending verify's projected
demand (block width + a read-only routed-expert union captured from the SAME
union_experts_from_mask gate/top-k path the verify uses, via RouteUnionCollector)
whose bytes are the EXACT sum of ExpertPrefetcher.expert_nbytes_map -- never a
fabricated expert-count estimate. verify_round runs one _verify_target_block
under route_ahead_context and reproduces the accept/commit/rollback exactly.

generate() / _generate_single / _generate_batched are left byte-identical; the
session runs alongside them. Adds project_expert_bytes + the SpecSession /
DraftResult / VerifyResult / RouteUnionCollector public surface. CPU tests pin
session==generate() token identity (greedy + lossless-sampled), round-API order,
immediate-stop anchor, and the exact-bytes projection.
…Step 5)

Wire the engine per-round DRAFT->VERIFY->DRAFT lifecycle behind an opt-in gate:
only when verify budgets are configured AND the speculator exposes the
SpecSession seam. Per round the engine registers the drafter-projected EXACT
token/byte demand (set_verify_demand), runs the verify only for the scheduler's
admitted verify_seq_ids, advances the sequence PREFILL->DRAFT->(VERIFY->DRAFT)*
->FINISHED, and clears the demand; unadmitted rounds stay DRAFT and their 2-D
deficit carries. Reuses #156 admit_verify_demands/set_verify_demand and #157
expert_nbytes_map; adds Scheduler.verify_scheduling_enabled.

Default serving is byte-for-byte unchanged: without budgets or with a
non-session speculator the delegated request keeps the whole-request generate()
path. CPU tests cover exact-byte registration, carried-deficit liveness, the
misconfig raise, and default/fallback preservation.
@drunkcoding
drunkcoding marked this pull request as ready for review August 15, 2026 18:22
@drunkcoding
drunkcoding merged commit e22ff2b into dev Aug 15, 2026
9 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.

1 participant