[3/3] perf(dsv4): add hardware-aware adaptive verification - #71
[3/3] perf(dsv4): add hardware-aware adaptive verification#71calvarado2004 wants to merge 13 commits into
Conversation
Shard MLA heads, vocabulary, shared experts, and routed FP4 expert banks across TP ranks. Give every shard independent storage, size collectives for skewed expert loading, partition CPU workers, and avoid redundant checkpoint reads. This is the foundation that makes DeepSeek-V4-Flash fit and start reliably on 4x RTX A4000.
Load the checkpoint DSpark blocks and heads, capture target features, maintain draft KV, run non-causal block drafting, verify candidate prefixes, and integrate multi-token request advancement. Use standard p/q rejection sampling for temperature-1 requests and preserve DSV4 compressor state across rejected tails.
Correct target-feature addressing, per-row rotary context writes, target-layer taps, GPU affinity and NUMA placement, speculative page release, and drafter residency. Route short verification blocks through FreeToken hybrid CPU/GPU MoE execution, and harden the TP service lifecycle and startup diagnostics.
Fix TP>1 Lightning Indexer row addressing, capture all legal verification widths, share compressor carry storage across graphs, and select widths from cumulative survival probabilities plus the measured draft/verify cost curve. Keep sampled verification exact and device-resident, preserve request and cache state through rejection, and cover the production path with focused DSV4 tests.
|
Reviewed this stack against my open PRs. There's real overlap in the CPU-MoE / host-memory layer: one filename collision worth settling before either lands, and one calibration point that I think bears directly on the adaptive width model here. 1.
|
| bs | reuse | speedup |
|---|---|---|
| 4 | 1.08x | 1.06x |
| 8 | 1.19x | 1.16x |
| 64 | 3.16x | 2.96x |
At DSpark's width that's a modest ~1.05–1.1x, and on a 2-socket Xeon with more bandwidth per core it will be smaller still — the traffic saved is hardware-independent, but how much time it buys is not. Not a headline. But it's free, and it moves the right way.
3. It shifts the cost curve this PR profiles
The more interesting consequence. choose_adaptive_draft_width maximizes expected tokens/ms against a profiled verification cost curve. Expert dedup makes the MoE component of that curve sublinear in width, so the width that maximizes the objective moves up. Because you profile at runtime instead of hard-coding the curve, this stack should find the better operating point on its own once #46/#47/#49 land — nothing to change here, but the achievable ceiling rises.
Merge-order note
#36 and this stack both touch moe/cpu_executor.py, but in disjoint regions (mine: _make_table / _resolve_banks; yours: threading/affinity and _tp_core_share). Should merge cleanly in either order.
All my numbers above are from a single-socket consumer box, so treat them as indicative for your 2× Xeon / 4× A4000 target rather than predictive.
|
Reviewed and benchmarked as part of the #70 → #69 → #71 stack on 2× RTX 6000 Ada, I could not isolate this PR's contribution, and I want to be straight about why rather than imply I validated it: with the whole stack applied, speculation is a net ~8% slowdown on this box (35.9 vs 39.3 tok/s at 42% acceptance, details in my comment on #69). Adaptive verification tunes something that is losing here for a reason upstream of it — with offloaded experts, more tokens per step means more expert traffic per step, and expert traffic is the bottleneck. So the adaptive machinery had nothing to recover. That is a statement about the regime, not about the code. On a resident-expert or latency-bound machine this is presumably where the acceptance-rate gains get realised, and I have no measurement contradicting that. One thing that would help a reviewer in my position: the PR is +6332/−204 across 53 files, stacked on a +5138 PR, and the end-to-end effect on my hardware is negative for reasons neither PR controls. A short "expected regime" note — resident vs offloaded experts, what acceptance rate is needed to break even, what hardware it was tuned on — would let people decide whether to enable it without running the full stack first. Right now the only way to find out is to build all three and measure. Not blocking, and no defects found. |
|
Thank you for testing the complete stack and for separating correctness from performance. Your result is consistent with ours rather than contradictory: exact DSpark can lose when the target is dominated by offloaded-expert traffic and prefix survival is low. The selector does not assume linear verification cost. It measures There is a concrete mismatch in the current calibration that reinforces your point: I also prototyped grouped DSFP4 route processing locally while investigating this. It preserved exact output bits in the CPU/GPU/graph-replay tests, but the production-shaped H=4096, I=512, top-k=6, rows=6 cold-working-set case (34 valid routes, 19 unique experts) improved only 1.03x, and a fixed hot loop regressed about 5%. That agrees with your observation that the gain at DSpark widths is modest and workload/cache dependent. Since #81 and its prerequisite stack already own this work, I do not want to land a competing dedup implementation here; composing the adaptive profiler with that stack is cleaner. On the expected regime: there is no universal acceptance threshold because break-even depends on The workload measurements show the same boundary clearly:
So I agree the documentation should say this explicitly: the current win is for a hybrid/resident-enough target path with high prefix survival; pure offload plus low acceptance can be a net loss. Hardware tuning should be enabled and calibrated on the deployment geometry, not treated as a portable constant. The NUMA work is complementary as well. The current intent is to bind each rank before expert-bank allocation so loader first-touch follows rank affinity, but explicit Thanks again for the full-stack review and for #81. These measurements give us a much sharper boundary for what this PR does and does not accelerate. |
|
Follow-up correctness fix: we missed one multi-token output-path issue in the original stack. Sorry about that — the final raw-API coherence gate caught it. When a speculative verify accepted more than one token, the scheduler placed multiple Commit Validation on the TP=4 hybrid A4000 workstation:
The PR head is now |
``hc_pre`` and ``hc_head`` compute the inverse RMS as
xf = x.flatten(2).float()
rsqrt = torch.rsqrt(xf.square().mean(-1, keepdim=True) + eps)
``square()`` materialises a second full fp32 copy of the hidden state purely to
reduce it away. At DSV4-Flash shapes (hc_dim 16384, T=8192) that is a 537 MB
write plus a 537 MB read on top of the 537 MB read -- to produce [T, 1].
``inv_rms`` does it in one pass over the bf16 source (the fp32 upcast is exact,
so the per-element squares are unchanged, and reading the source halves the
bytes again). RTX 6000 Ada, hc_dim 16384:
T=8192 1.944 ms -> 0.304 ms (6.4x, ~880 GB/s of a 960 GB/s peak)
T=4096 0.972 ms -> 0.153 ms (6.4x)
T= 64 0.020 ms -> 0.013 ms (1.5x -- decode benefits too)
In an nsys trace of 16-concurrent long-prompt serving, the four kernels this
block emits are ~13.7% of GPU kernel time.
Not bit-identical, and it cannot be. ATen's reduction order for
``mean(x**2, -1)`` is chosen per shape: a tree/2 fold reproduces it exactly at
M=4 and nothing reproduces it at M=64, because the block/grid split changes with
M. There is no fixed order to target, so any fused form changes the last ULP --
and on a deterministic server that changes greedy continuations. Two baseline
runs produced byte-identical output, so this was verified, not assumed.
What the fused form must not do is get *worse*, and this one does not. Against
an fp64 reference the mean relative error is 3.65e-08 vs ATen's 3.60e-08 at
M=8192, and 3.60e-08 vs 3.63e-08 at M=4096 -- ahead at some shapes, behind at
others, always within 8% of ATen's own distance from the truth. A test pins
that. Two faster-looking alternatives were rejected on this basis:
``linalg.vector_norm`` is the same speed but consistently ~23% worse (4.47e-08)
because sqrt-then-square rounds twice, and ``einsum`` over bf16 accumulates in
bf16 (1.9e-03). Bit-identical alternatives exist but none is faster:
``vecdot(xf, xf)`` lowers to the same path at 1.944 ms, and vecdot with fp32
accumulate over bf16 is 4.557 ms.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01GpGe2fQ5pDShGrnSuksfun
Extend Gabriel Devenyi's fused inv_rms path to the DSpark drafter, keep HC combines on the bf16 source, and honor the configured prefill chunk limit so one long OpenWebUI history cannot materialize an unbounded activation. Retain opt-in one-shot verification profiling for deployment cost measurements; it remains disabled during normal serving. Based-on: FlashML-org#101
|
Promoted the validated community HC-normalization improvement into this PR.
Thank you Gabriel for the measured optimization and numerical-error analysis. We are deliberately building on the community result rather than duplicating it. The original recovery branch remains protected; this enhanced state is also preserved at |
|
Exact promoted-head validation (
This confirms the PR head itself—not only the larger experiment branch—preserves the established coding baseline and quality gate. |
Avoid the symmetric-window staging buffer and its two device copies for every tensor-parallel reduction. NCCL can reduce the contiguous input directly in place, which also avoids the symmetric collective path that dominated the TP4 verify profile on PCIe RTX A4000 GPUs.
Gather the post-capture serving footprint from every TP rank immediately before the scheduler ready acknowledgement. The summary maps ranks to NUMA nodes and GPUs, showing actual VRAM use, host expert-bank bytes, CPU-MoE workers and coordinator cores, per-node RAM pressure, workstation totals, and the counted CPU/GPU placement split. Keep the formatter and bank accounting independently testable without loading a model.
Spend the paper-derived hybrid fetch budget on missing experts used by the most rows in the current verify block. Retain expert recency and id as deterministic tie-breakers so the existing steady-state cache policy remains intact.
|
Follow-up after composing the community feedback on the 4x RTX A4000 / dual-NUMA TP4 workstation. What we adopted:
Measured promoted PR head df6c340:
Isolated milestone 2ce0070 (milestone/dsv4-dspark-33tps-routes):
The re-profile is more important than the peak number. The original trace had symmetric NCCL all-reduce at 878.0 ms / 92.1% CUDA time across 87 calls. The improved trace has standard NCCL at 27.2 ms / 15.8%. The new leader is fast_index_copy_multi at 65.9 ms / 38.3%, so Gabriel's batch-geometry calibration concern is now the measured next bottleneck: speculative q* must balance PCIe expert fetch against CPU execution at rows=2..6, not inherit a bs=1 constant. Final pre-ready accounting reports 146.62 GiB CPU host experts plus 55.24 GiB GPU footprint, or counted placement CPU 72.6% / GPU 27.4%, with ranks 0/1 on NUMA0 and 2/3 on NUMA1. This is ownership/footprint accounting, not proof of page residency; I still agree explicit mbind/place_banks is stronger than first touch and should compose additively. Decision: keep exact probabilistic verification plus device-resident adaptive width selection as the integration spine, while composing measured community improvements underneath it. The selector automatically consumed the changed non-linear V(k) curve; no portable constant or static-width replacement was needed. This is not a universal DSpark claim. Open-ended reasoning remains acceptance-limited, and the reported pure-offload TP2 result at 42% acceptance is a valid losing regime. Our favorable regime is hybrid/resident-enough execution with structured-code survival above 80%. Also, the earlier repeated-prefix issue was our own multi-token detokenization contract bug, fixed in 986823e; it was not caused by the community changes. The milestone remains separate while q* is re-calibrated and stability gates continue; we are not replacing the protected/PROD branch on the strength of a peak checkpoint alone. |
|
Thanks for picking up the fused I merged #69, #70 and #71 at their current heads into a deployment branch with the other open PRs and ran it on 2× RTX 6000 Ada (sm_89), DeepSeek-V4-Flash. Two results worth passing back.
|
|
PR3 head has now been promoted from Newly included in this PR:
Promotion gates on the TP=4 RTX A4000 workstation:
The request-level A newer experiment implementing the FreeToken paper's “at least one cache fill” rule remains isolated on the experiment branch and is intentionally not included here until its A/B proves a net gain. |
|
I feel that my setup should be able to reach 60 tok/s on coding tasks or so. I won't stop until proven wrong on that idea haha @gdevenyi reasoning could be lower, around. 35 tok/s but that would be amazing for a 75% on CPU model. |
|
Thank you — both results are extremely useful, and the independent 115.5 -> 119.5 tok/s result is especially valuable validation of the fused norm + bf16 combine + chunk-limit follow-on. I traced the capture stack and found a likely common cause for the two failure modes. At the widest DSpark verify span, The current fix is here:
It does two things:
Lenovo validation on 4x RTX A4000 Ampere (sm_86):
Could you please try the branch on RTX 6000 Ada in this matrix?
If default overlap still throws capture isolation, the next useful detail is the first event/stream line after Thanks again for testing this on hardware we do not have locally. |
|
Promoted the validated request-local low-acceptance fallback into PR3 at What changedThis is an opt-in circuit breaker around the existing exact DSpark path; it does not classify prompts or replace the paper's measured-width selector.
The feature is disabled by default ( Exact PR-head validationThe pushed PR head was fetched into an isolated Lenovo Git worktree and passed: Suite: Hardware remains the 4x RTX A4000 TP4 hybrid workstation, with the counted placement reported by the runtime as 72.6% CPU / 27.4% GPU. Structured-code controlThe standard 600-token pet-store landing-page request did not enter fallback:
So the established approximately 30 tok/s coding baseline is preserved. Low-acceptance creative-prose controlIdentical temperature-1 prompt:
The fallback run repeatedly measured local windows in the 30.3%-59.4% range. Target-only phases sustained 17.86-19.59 tok/s. The response stopped naturally, was coherent end to end, and contained no repetition. This is about 10% lower wall time on this matched sample; it is evidence for this hardware/workload, not a universal claim. Long high-reasoning quality/stability gatePrompt: the 1/2/7/10-minute bridge-and-torch problem, temperature 1.0, The intermediate 8K test did not pass the output gate:
With a 20K ceiling, the identical test did complete naturally:
This passes the requested long-reasoning correctness/stability gate, while also showing that DeepSeek-V4-Flash high effort may legitimately require a very large reasoning budget for an explicit optimality proof. The fallback improves low-acceptance execution; it does not attempt to change the model's reasoning termination policy. ScopeThe result sharpens the expected regime already discussed above:
|



Outcome
This PR turns the exact DSpark path from PR 2 into a hardware-aware implementation: it profiles the real TP target-verification cost curve, chooses the prefix width that maximizes expected emitted tokens per millisecond, safely replays the matching CUDA graph, and performs probabilistic acceptance on GPU.
On structured code generation, the complete stack improves steady single-request generation from approximately 18 tok/s target-only to 27.7–29.7 tok/s at temperature 1.0 (1.54–1.65×). Open-ended Spanish reasoning/chat reaches 17.7–18.6 tok/s at lower acceptance; that workload-sensitive result is reported deliberately rather than hidden.
Stack and review boundary
This is PR 3 of 3 and depends on PR 1: the TP foundation followed by PR 2: exact DSpark:
13d456038d7b2fDelta from PR 2: 33 files, 2,251 additions, 1,095 deletions. The deletion count includes replacement of the first fixed-width rollback/loop scaffolding with graph-stable per-row state journals and the production scheduler path.
Design references
This PR prices the algorithm from the actual FreeToken engine. It does not substitute a confidence threshold or a guessed linear cost model for the paper's hardware-aware capacity decision.
Adaptive objective
For proposal confidence values
c₁ … cγ, the probability that proposal positionksurvives is the cumulative prefix product:Verifying width
kis expected to emit the target bonus plus the surviving proposal prefix:The engine measures draft cost
Dand the target CUDA-graph costV(k)for every legal width. It selects:Important details:
argmaxbehavior.Dis common to all candidate widths.Measuring the real engine
1..6rows).A representative hot target-verification curve on the validation system was:
The measured draft pass was approximately 23.6 ms. These values explain why a fixed “always verify five” policy is not universally optimal and why the selector must account for hardware cost cliffs.
Stale-confidence scheduling
The implementation uses two pinned host buffers and a dedicated CUDA copy stream:
This preserves the causal/non-blocking deployment shape described by DSpark instead of synchronizing the current confidence tensor onto the CPU in the same critical path.
The full five-position draft is still produced before admission. Only target inputs and metadata are compacted to
anchor + k; allocation retains the gamma-wide frontier until acceptance, so releasing a rejected or unverified tail remains correct.TP-safe variable-width CUDA graphs
Variable physical widths exposed a TP4-only correctness bug in the Lightning Indexer path. Captured graphs could read row indices from a stale six-row buffer while replaying a shorter graph, producing an out-of-bounds access and CUDA Xid 13.
This PR:
Every TP rank selects and replays the same span, preserving collective ordering.
Exact GPU-resident probabilistic verification
The earlier exact implementation copied full-vocabulary target probabilities to CPU for
p/qacceptance. This PR keeps the distribution-correct algorithm on GPU:p(x)andq(x)for proposed tokens;max(p - q, 0)for the rejected row;Probabilistic requests no longer compute or copy an unused target argmax. Greedy requests reduce logits to argmax on GPU before crossing PCIe. Temperature 1.0 skips a mathematically redundant divide-by-one kernel while producing bit-identical probabilities to direct FP32 softmax.
These changes remove transfers and launches; they do not approximate, truncate, or otherwise alter the
p/qrejection rule.Suggested review order
deepseek_v4/dspark.pyengine/graph.py,engine/engine.pyengine.py,scheduler.pydeepseek_v4/dspark.py, generation/OpenAI pathsp/q, first rejection, residual/bonus draw, two-int transferValidation
The suite covers:
Final result on the validation server:
End-to-end measurements
These are steady decode measurements from serving logs, not prefill throughput or a first interval diluted by startup/queueing. Both workloads are included because the DSpark paper predicts workload-dependent prefix survival: structured code is more predictable and amortizes target verification better than open-ended multilingual reasoning.
The current bottleneck at wider prefixes remains target verification through the hybrid MoE path. The measured curve makes that cost visible to the scheduler; it does not claim the remaining CPU/GPU expert service is solved.
Scope and follow-ups
gammaremains the checkpoint-trained value of five.