Skip to content

perf(deepseek-v4): select two-stage fused mHC launch by token count#661

Draft
Xiangyi1996 wants to merge 3 commits into
xiangyi/v4-mhc-cliff-routingfrom
xiangyi/v4-mhc-fused-backend-select
Draft

perf(deepseek-v4): select two-stage fused mHC launch by token count#661
Xiangyi1996 wants to merge 3 commits into
xiangyi/v4-mhc-cliff-routingfrom
xiangyi/v4-mhc-fused-backend-select

Conversation

@Xiangyi1996

@Xiangyi1996 Xiangyi1996 commented Jul 13, 2026

Copy link
Copy Markdown
Collaborator

Summary

Stacked on #622 (and transitively #600). Replaces the hardcoded fused-mHC backend selection with a measured launch table, and fixes the fused-workspace accumulator sizing that the k-split backend requires.

What the sweep found

The vendored mhc_fused_hc op exposes backend/tile_n/num_k_splits launch parameters that the wrapper hardcoded (allinone-fma for M≤32, allinone-mma above). Sweeping all 768 combinations on B200 (graph-replay timed, parity-checked against the composed two-stage path):

M hardcoded best in-snapshot delta
16 16.0µs (fma) 11.9µs (b1 tn2 ks2) −26%
32 22.2µs (fma) 15.2µs (b1 tn2 ks2) −31%
48 229.9µs (mma cliff) 18.0µs (b1 tn4 ks2) −92%
64 240.8µs (mma cliff) 21.8µs −91%

backend=1 is the two-stage fused_pmap_gemm_fma_ksplit + mhcBigFuseKernel organization (profiler-verified). The M>32 cliff that #622 routes around is a bad backend choice — allinone-mma is 206-241µs at every M — not a large-M property.

The workspace contract

The k-split backend accumulates num_k_splits × M partial rows into y_acc/r_acc. The fused workspace sized them at M rows; the spill silently corrupted small-M outputs in isolation and crashed a production p8 run with cudaErrorIllegalAddress. Accumulators now reserve FUSED_HC_MAX_K_SPLITS × max_bs rows (a few KB per buffer set), with the contract documented on the exported constant.

Endpoint certification (DeepSeek-V4-Pro TP8, SWE-smith golden p8, b200-79)

Order-reversed same-day pairs against a 7-run baseline plateau (TPOT 12.48-12.88ms):

run TPOT steady tok/s accept completions
patched #1 12.270ms 588.2 66.7% 189/189
bracket 12.870ms 544.7 66.8% 189/189
bracket 12.560ms 563.9 66.9% 189/189
patched #2 11.840ms 604.1 66.7% 189/189

TPOT −46%, steady throughput +68%, acceptance unchanged. An exact decode trace confirms single-variable causality: only the mHC kernel category moves (3.43 → 2.87 ms/step), every other category flat; steady-window cadence −4.6% matches the endpoint TPOT delta.

Tests

  • Launch-table unit tests (backend never lands on the mma cliff, k-splits bounded by the workspace contract, boundary Ms)
  • sm100-gated GPU parity: selected launch vs allinone reference at M∈{13,16,24,32,36,48,64} and M∈[1,8]
  • Workspace-contract regression: k-split writes >M but ≤ks×M rows; runtime ping-pong buffers sized accordingly
  • Existing fix(deepseek-v4): route large-token fused mHC around the allinone cliff #622 8-layer chained-drift and graph capture/replay suites pass unchanged on the new selection

Remaining headroom

In-snapshot best (15.2µs @m=32) vs the TRT two-stage reference (13.4µs) leaves ~0.24ms/step — pursued separately via the atomic-GEMM backend tile tuning / vendor sync.


Update 2026-07-13: full-curve endpoint data + big-fuse block tuning

Full golden-protocol curve with this PR's launch table (b200-79, same protocol/dataset as the baselines; C = base stack + this PR, measured before the big-fuse commit):

p TRT-LLM matched base stack (#563v2 + #621/#622) + this PR gap to TRT
1 3,012 tps/gpu / 4.70ms 2,672 / 5.10 2,688 / 5.10 −10.8%
2 4,771 / 5.78 4,207 / 6.35 4,352 / 6.20 −8.8%
4 6,695 / 7.86 6,084 / 8.40 6,174 / 8.20 −7.8%
8 9,693 / 11.08 8,666 / 12.60 8,955 / 12.05 −7.6%

p4 and p8 both benefit (decode M≥13 hits the new table); p1/p2 are unchanged as expected — their decode shapes (M=4/8) stay on the default backend, confirming the launch table only moves the paths it claims to.

Second commit (a3b42c0): bigfuse_block_size=512 over the kernel default — 15.1→14.7µs at M=32, 11.9→9.6µs at M=16, 17.9→17.2µs at M=48 (graph-replay microbench). Endpoint-neutral by magnitude (~0.1 ms/step at p8); kernel-level claim only. The same sweep ran the full backend=0 (atomic-routing GEMM) tile grid at M∈{32,64}: nothing beats the backend=1 table at production shapes, which narrows the remaining vendor-sync headroom to ~1.3µs/call at M=32 (~0.16 ms/step).

The vendored fused_hc op exposes backend/tile_n/num_k_splits launch
parameters that the wrapper previously hardcoded: allinone-fma for M<=32
and allinone-mma above. A 768-combination sweep on B200 (graph-replay
timed, parity-checked against the composed two-stage path) found:

* backend=1 (fma_ksplit + mhcBigFuseKernel), tile_n=2, num_k_splits=2 runs
  15.2us/call at M=32 vs 22.2us for the current default (-31%).
* the allinone-mma backend selected for M>32 runs 206-241us at every M;
  the routing cliff fixed by the previous commit is a bad backend choice,
  not a large-M property. backend=1 with tile_n=4 stays flat (18-24us).
* the k-split backend accumulates num_k_splits x M partial rows into the
  y_acc/r_acc workspaces. The fused workspace sized them at M rows, so
  the spill corrupted small-M outputs and crashed a production p8 run
  with cudaErrorIllegalAddress; accumulators now reserve
  FUSED_HC_MAX_K_SPLITS x max_bs rows (a few KB per buffer set).

Certified on DeepSeek-V4-Pro TP8 SWE-smith golden p8 (b200-79, order-
reversed pairs vs a 7-run baseline plateau): TPOT -4~6%, steady
throughput +6~8%, acceptance rate unchanged, 189/189 completions in all
runs. An exact decode trace shows the mHC kernel category alone moving
(3.43 -> 2.87 ms/step) with every other category flat.

Signed-off-by: Xiangyi Zhang <xiangyiz@nvidia.com>
@Xiangyi1996 Xiangyi1996 force-pushed the xiangyi/v4-mhc-fused-backend-select branch from a826592 to fad2253 Compare July 13, 2026 05:50
Sweeping bigfuse_block_size (fixed at the kernel default in the first
sweep) over the selected launch configs on B200: 512 improves the
two-stage path at every production decode shape (graph-replay
microbench): 15.1 -> 14.7us at M=32, 11.9 -> 9.6us at M=16,
17.9 -> 17.2us at M=48. Endpoint-neutral by magnitude (~0.1 ms/step at
p8); claims are kernel-level only.

The same sweep ran the full backend=0 (atomic-routing GEMM) tile grid
at M in {32, 64}: nothing beats the backend=1 table at production
shapes, so the atomic path stays unselected.

Signed-off-by: Xiangyi Zhang <xiangyiz@nvidia.com>
…ounts

With contract-sized accumulator workspaces the two-stage k-split path
wins at every measured M, not just M>=13: 7.6-9.2us vs 9.7-11.4us for
the allinone default at M in [1, 12] (graph-replay microbench, parity
within 0.5% of the composed reference at every point). The earlier
small-M gate guarded against wrong values that were actually the
workspace undersizing corrupting the k-split spill, so it can go.

Low-concurrency decode (p1/p2, M=4/8) sits on this path and also drops
the per-call fhcZeroWorkspacesKernel prologue the allinone backend
launches; estimated ~0.3-0.45 ms/step at p1/p2 (kernel-level claim,
to be verified by decode-trace kernel counts).

Signed-off-by: Xiangyi Zhang <xiangyiz@nvidia.com>
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