Skip to content

feat: DeepSeek-V4-Flash integration + Blackwell (sm120) bring-up - #153

Draft
drunkcoding wants to merge 96 commits into
mainfrom
feature/deepseek-v4-kernel-integration
Draft

feat: DeepSeek-V4-Flash integration + Blackwell (sm120) bring-up#153
drunkcoding wants to merge 96 commits into
mainfrom
feature/deepseek-v4-kernel-integration

Conversation

@drunkcoding

@drunkcoding drunkcoding commented May 12, 2026

Copy link
Copy Markdown
Collaborator

Summary

End-to-end integration of DeepSeek-V4-Flash (284B) into BatchGen, from kernel consolidation through full production model wiring and Blackwell (sm120) bring-up. The branch now runs V4-Flash end-to-end on 4× RTX PRO 6000 Blackwell: stable under sustained load, 72–75% MMLU-Pro, single-token character-exact vs the official reference, and dozens-to-hundreds of characters exact on long greedy generations.

129 files, ~50k insertions. Three broad phases:


Phase 1 — Kernels (original scope)

  • Fused per-head QK RMSNorm for V4 MLA (Triton + native CUDA Path A, ~2× geomean vs Triton on H20).
  • Kernel consolidation into batchgen_kernels/{triton,attention,common,moe,src}/ (cache_utils, compress_quant, indexer_q, inv_rope_fp8, hyper_connections, fp4_dequant, silu_mul_quant, hash_routing, sqrtsoftplus_topk, …); @triton.autotune on cache_utils (5.6× on k7).
  • DSA attention kernel improvements (fast_topk, fused_indexer_score, fused_kv_norm_rope_cache).
  • sm120 Triton kernels for grouped MXFP4 MoE and sparse MLA decode; sm120 JIT/AOT build + Docker base update.

Phase 2 — V4-Flash model wiring + Blackwell bring-up

  • Full V4-Flash decoder: gate routing (hash + sqrtsoftplus), MoE activation, sparse MLA decode, HC pre/post wiring.
  • Sparse prefill attention ported from the official reference (+cosine>0.999 parity test).
  • DeepSeek-V4 single KV pool + decode KV coordinator (4 pools: swa/c4/c128/indexer); sparse MLA decode adapter + torch reference + prefill populate.
  • Checkpoint metadata loader, rank-shard loading, tokenizer discovery, parameter-server V4 weight serving.
  • Compression-aware RoPE cache (per-layer YaRN theta), configurable NCCL timeout, prepack/memory tuning flags.

Correctness fixes found during bring-up (all verified)

  • Vocab-parallel prefill deadlock — 0-seq ranks must join the embedding/lm_head TP collectives (microbatch-count-synced) → fixes bs=1 hang.
  • KV page exhaustion — the 4 V4 KV pools were charged in raw token space; the c128 pool (128× compression) was over-allocated 128×. Now charged in compressed token space + per-pool admission preflight.
  • Grouped-MoE prefill persistenceconfigure_decoding mutated the shared expert-streaming task, leaking into the next prefill ("expert weights not loaded"); restore pristine task in configure_prefill.
  • Watermark on-hold crash chain — (a) ON_HOLD eviction used mgr._sequences which the V4 coordinator lacks (AttributeError→SIGSEGV); (b) coordinator not re-initialized on watermark re-prefill (destroyed-but-not-None). Both fixed; verified through a 500-prompt run that exercises the full on-hold→re-prefill→resume cycle.

Phase 3 — Character-exact decode (QAT numerics)

The model is QAT-trained (activations quantized to fp8/fp4 before each GEMM). The default paths dequantized weights to bf16 instead, causing greedy decode to drift from the reference. Fixes (all env-gated, default off; defaults preserve the fast throughput path):

  • BATCHGEN_V4_QAT_LINEAR=1 — QAT-faithful dense/attention linears (act_quant + fp8/fp4 GEMM). Bit-exact vs official (cos=1.0).
  • BATCHGEN_V4_QAT_MOE=1 — new QAT-faithful grouped MoE decode kernel (v4_grouped_mxfp4_moe_forward_qat), per-expert act_quant + fp4_gemm. Bit-exact vs per-expert reference; the prior bf16-weight-dequant grouped kernel (cos~0.9988) is annotated non-QAT and kept as the fast path.
  • Sparse indexer relu + fp4 — the lightning indexer omitted the per-head index_score.relu_() before the gate-weighted sum, changing ~30% of the topk KV selection; added relu (3 score sites) + fp4-quant on indexer q/k to match official.

Char-exact progress (greedy A/B vs golden)

identity first divergence: ~char 10 (no QAT) → char 122; haiku: char 2 → char 71; tiny-math: exact. The remaining residual is inherent FP rounding between batchgen's optimized sm120 kernels (log2-domain softmax, different GEMM tiling) and the reference's natural-log kernels — i.e. near-tie token flips, not a discrete bug (verified: fp8 KV is bit-exact, router is float32-faithful, attn_sink is algebraically equivalent).


Tests

  • 22 kernel test files + 13 integration/parity tests (QAT linear/expert/grouped-MoE parity, sparse-prefill parity, KV coordinator, compressor, decode integration).
  • E2E MMLU-Pro batch harness + cross-framework comparison; divtrace + sanity tooling under tools/.

Notes / follow-ups

  • All QAT/char-exact flags default off — production defaults run the fast throughput path (72–75% MMLU-Pro).
  • Full character-exact parity is achieved-in-practice (single-token exact, long-gen exact for dozens–hundreds of chars); closing the final near-tie gap would require reimplementing the optimized kernels to match the reference's exact reduction order (diminishing returns; documented in HANDOFF.md).
  • See HANDOFF.md for the complete bring-up log, root-cause analyses, and the working launch configuration.

@drunkcoding
drunkcoding force-pushed the feature/deepseek-v4-kernel-integration branch from 948efa6 to 1a584bc Compare May 12, 2026 17:11
leyang.xue and others added 4 commits May 12, 2026 20:39
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Fixes two bugs in the original v4_fused_qk_rmsnorm: (1) variance was
computed over the entire flattened row instead of per-head, and (2) only 2
CTAs per token regardless of n_heads. New kernel uses grid (T, n_heads+1)
with 3D Q input [T, n_heads, head_dim] for correct per-head normalization.

37 tests including per-head independence gate, parametrized n_heads, and
non-contiguous stride coverage.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Handwritten CUDA kernel replacing Triton dispatch overhead for the fused
per-head Q + global KV RMSNorm. Key design: 4 warps/CTA, 16-byte vectorized
loads, warp-shuffle reduction, __launch_bounds__(128,16) for 32 regs/thread.

Microbench results vs Triton: geomean 1.52x on Blackwell, 2.00x on H20.
Vs sglang: wins 24/32 cells (12/16 per arch), losses only at large T where
sglang's tile::Memory abstraction extracts 4% more DRAM bandwidth.

NCU verified: 86-92% DRAM peak utilization, 32 regs/thread.

Also includes minor formatting cleanup in setup.py and _jit_registry.py.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@drunkcoding drunkcoding reopened this May 21, 2026
leyang.xue and others added 8 commits May 24, 2026 15:09
Move all V4 kernel modules from batchgen/attention/mla/ to batchgen_kernels/{triton,attention,common,moe,src}/. Includes @triton.autotune for cache_utils (5.6x speedup on k7) and CUDA fused_silu_mul_quant port.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- fast_topk_cuda: add valid-m WGMMA variant for variable-length batches
- fused_indexer_score: support 4D aux_blocked_k, expand topk configs
- fused_kv_norm_rope_cache: extend CUDA kernel for V4 compress pipeline

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
20 test files covering all V4 kernel families: cache_utils, compress_quant, compressor, fp4_dequant, fp4_kv, fused_silu_quant, hash_routing, hyper_connections, indexer, inv_rope_fp8, mxfp4_marlin, qnorm_rope_kv, routing, topk, tilelang_score, c128_online, and integration/attn_backend.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- DeepSeekV4Flash model: decoder layer, attention, MoE, compressor modules
- Wire hc_pre/hc_post from batchgen_kernels.common.v4_hyper_connections
  (eliminates inline _hc_split/_hc_pre/_hc_post duplicates)
- Add v4_backend, v4_indexer_metadata, v4_mxfp4_marlin_moe, v4_fp4_kv_cache

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Update raw_bytes_per_token from 583 to 584 for both V4-Flash and V4-Pro profiles, matching TOKEN_BYTES (NOPE_DIM=448 + ROPE_DIM*2=128 + SCALE_DIM=8) from v4_cache_utils.py.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Gate: replace inline PyTorch with hash_routing/sqrtsoftplus_topk from batchgen_kernels
- Expert: fused_silu_mul_quant_cuda for SiLU*mul+FP8 quant (PyTorch fallback)
- Wrapper: _forward_decode_optimized routes decode to DeepseekV4AttnBackend
  (Q/KV projection via module ops, attention via FlashMLA sparse/dense/compressed)

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
- Fix _ref_pre/_ref_post/_hc_split to standalone PyTorch (deleted model methods)
- New test_v4_wiring_correctness.py: gate routing (T1), expert activation (T2),
  attention decode shapes (T3), KV cache bytes (T4), decoder layer HC (T5),
  performance regression gate (T6) — 54/54 passed on H20
- New test_v4_e2e_logits.py: scaffolding for full model logit validation

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@github-actions github-actions Bot added the ci:run Trigger build + GPU regression on H20 label May 27, 2026
leyang.xue and others added 12 commits June 1, 2026 15:17
DeepSeekV4FlashMoE ran each rank's expert shard over only its own local
decode tokens, then dense all_reduce'd a [local_tokens, H] buffer. Under DP
attention (each rank owns different sequences) this both (a) silently dropped
contributions for tokens routed to experts on other ranks and (b) deadlocked
NCCL whenever ranks had different token counts.

Adopt the established EP-decode contract used by minimax/kimi/glm5: pad local
tokens to a global-max (num_tokens_per_rank), all-gather hidden states and
input_ids, run the owned expert shard over all global tokens, all_reduce(SUM),
then slice back this rank's rows. Wire padding_bsz through configure_decoding
(previously discarded) via _init_decoding_padding_bsz/set_num_tokens_per_rank,
which the worker's existing _sync_decode_moe_rank_counts already drives.

Add a 2-rank torchrun test suite covering remote-rank routing, uneven token
counts, hash routing + topk=2 + empty rank, dynamic resize, and guard errors.
…ecode

Three fixes that let DeepSeek-V4-Flash decode execute end-to-end:

1. c4 indexer DP-attention gather: the indexer wq_b/weights_proj were left
   TP-sharded (n_local_heads) while the decode wrapper reshaped to the full
   n_heads, crashing on c4 layers. Gather them across ranks alongside the main
   attention tensors (conditional on c4 layers) and use the full-head weights in
   DP mode, mirroring the main attention path.

2. Compressor bridge init: DeepSeekV4Compressor ran xavier_uniform_ at
   construction even though the runtime bridge immediately rebinds the weights;
   on degenerate freshly-created params this raised. Add init_weights=False so
   the bridge skips random init.

3. Compressor inference-tensor safety: assigning runtime-loaded inference
   tensors into nn.Linear/nn.Parameter .data made F.linear raise "Inference
   tensors do not track version counter". Convert the kernel compressor to
   functional _runtime_linear with buffer-backed ape/norm/wkv_weight/wgate_weight
   (mirroring _linear_from_weight), and have the bridge detach + assign plain
   tensors.
… IMA)

The c4 sparse path called the fused indexer with the RAW sequence length
(meta.seq_lens_casual) while its cached_k buffer is sized for the COMPRESSED
length (seq_len // 4). The scoring kernel masks K loads with
`s_offs < cache_seqlens` over a buffer whose stride is seq_len//4, so once the
raw length exceeded the compressed buffer it read ~4x out of bounds, causing a
CUDA illegal memory access partway through decode.

Pass meta.c4_topk_lengths_clamp1 (= max(seq_len // 4, 1)) instead. Verified on
4xH20: 2 sequences decode the full 64 tokens (128 total) with zero illegal
memory errors; decode now runs end-to-end.
The continuous-decode path pops completed sequences out of
self._local_to_uuid_map / self.query_book before generate()'s result
finalize runs, so the gather loop found them empty and returned no results
("Detokenization complete: 0 sequences" -> empty HTTP response). Iterate
global_batch.get_sequences_for_rank(rank) and read each sequence's own
decoded_tokens buffer-pool view + decoded_length instead, which persist on
the SequenceEntry across the decode lifecycle.

Verified on 4xH20: HTTP /v1/inference now returns
{"status":"success","results":[...]} with 2 sequences detokenized.

NOTE: this commit also carries prior in-progress V4 worker changes that were
already uncommitted in the working tree on this WIP branch (the file could not
be split cleanly). Adds an env-gated V4_RESULT_DEBUG diagnostic.
…A decode

Pure-Triton (no wgmma/TMA) slot-based grouped FP4 MoE and sparse MLA decode kernels for Blackwell sm120, where the wgmma-based FlashMLA path cannot run.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
584-byte/token paged FP8 KV pool (FP8 nope + BF16 rope + UE8M0 scales) and the decode-time KV coordinator for V4 sparse MLA.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…efill populate

FlashMLA decode adapter dispatching across torch-ref / sm120-Triton / wgmma backends, the eager torch reference (correctness oracle), and prefill KV population.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…perts, and PyNCCL collectives

Adds decode-timing hooks, env-gated grouped FP4 MoE (BATCHGEN_V4_GROUPED_MOE) with shared-scratch weight staging, persistent local experts (GLM5-style get_tensor load), PyNCCL EP collectives (BATCHGEN_V4_PYNCCL_COMM), and the sm120 Triton MLA decode branch (BATCHGEN_V4_MLA_SM120_TRITON). All flag-gated; default paths preserved.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Metadata-driven dtype/key resolution for the V4 checkpoint converter and assets package init.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…hanges

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
…ted)

Investigated last near-tie residual: MoE router is float32-faithful to official (refuted); SM120 attn_sink logaddexp is algebraically identical to official/torch (agent's 'LSE mismatch' claim refuted by direct math). Real residual = inherent FP rounding between equivalent kernels (SM120 log2-domain softmax + different GEMM tiling vs official natural-log), flipping only genuine near-ties. Not a discrete bug; documents the diminishing-returns options.

Ultraworked with [Sisyphus](https://github.com/code-yeongyu/oh-my-openagent)

Co-authored-by: Sisyphus <clio-agent@sisyphuslabs.ai>
@drunkcoding drunkcoding changed the title feat: DeepSeek V4 kernel integration (vLLM + SGLang) feat: DeepSeek-V4-Flash integration + Blackwell (sm120) bring-up Jun 15, 2026
leyang.xue added 18 commits June 16, 2026 13:36
Add ARG GPU_ARCH (blackwell default | hopper) conditional branching to the
single Dockerfile instead of a separate file, keeping the ~90% shared base and
gating only the arch-specific steps:
- batchgen_kernels: sm90a (Hopper WGMMA) vs sm120 (Blackwell)
- FlashMLA: built from public upstream source at an arch-pinned commit —
  deepseek-ai/FlashMLA@c741387 for Hopper (the deferred-scheduling /
  DeepSeek-V3.2 sparse API with zero-arg get_mla_metadata + attn_sink that
  V4-Flash requires), 1408756a for Blackwell (never calls FlashMLA at runtime)
- tilelang + fast_hadamard_transform added for BOTH archs (V4-Flash sparse
  prefill needs them; the image previously lacked them). fht is built from
  GitHub source because its PyPI sdist is broken (missing csrc).

Everything is built from public source — no prebuilt binaries vendored.

Fix sm90a WGMMA build in batchgen_kernels/setup.py: replace bare -arch=sm_90a
with explicit -gencode arch=compute_90a,code=sm_90a (6 sites). Bare -arch=sm_90a
also emits a plain compute_90 PTX pass that ptxas rejects for wgmma.*.

Re-pin apache-tvm-ffi==0.1.5 after flashinfer install: flashinfer pulls in
0.1.12 which breaks tilelang import on torch 2.9.

Both GPU_ARCH=hopper and GPU_ARCH=blackwell images build clean from source and
import all V4 deps (verified locally; Hopper runtime validated at 82.5%
MMLU-Pro on H20).
Remove HANDOFF.md, VLLM_ENTRY_POINTS.md, and the tools/ diagnostic
scripts that are no longer referenced. Ignore agent/session docs
(AGENTS.md, HANDOFF*.md, opencode.json, etc.) and clean the dangling
HANDOFF.md reference in model.py.
Replace the per-sequence Python loops (with .item() host syncs) in the
full-prefix, SWA-window, and single-token slot builders with vectorized
page-table gathers, gated to the sm120 triton backend via
BATCHGEN_V4_FAST_PREFIX_INDICES (default on, slow-path fallback retained).

Measured on RTX PRO 6000: full-prefix index build 1.29ms->0.23ms (5.8x),
SWA-window 0.22ms->0.12ms (1.8x), resolve_swa_token_slots 104us->63us.
The shared _physicalize_tail_padded_contiguous helper is sound only for
tail-padded contiguous positions (no interior holes), so sparse c4/c128
selection paths are intentionally untouched.

Bit-exact vs the slow path: unit equivalence across page boundaries +
extension, plus full c128/dense e2e decode parity (atol=0).
…20 sparse decode

Two ~374ms Triton compilations otherwise landed on live decode steps 0
(first-call JIT) and 64 (topk_rounded 64->128 transition), inflating
attn_sm120_main mean to 1666us vs 170us p50. Add warmup_sm120_sparse_decode
covering both topk buckets (called lazily before serving / graph capture)
and pin the autotuned config (BLOCK_T=32, num_warps=8) since the topk is
SWA-capped at 128. Result: attn_sm120_main 1666us->240us, step-0 spike
377ms->25us.

Also add a geometry guard in _run_triton_sparse_decode: the kernel infers
page_size from k_cache.shape[1] and addresses via stride(0), so a flat
[num_pages, page_bytes] cache silently misreads page_size and causes OOB
scale/rope reads. Require stride(0) >= page_size*584 (allows allocator
padding) and raise a clear error instead of a CUDA illegal access.
_pack_model1_rows ran a 7-iteration Python loop (~70 tiny ops) for the
per-tile UE8M0 scale + e4m3 quantization, flat at 0.40ms from B=1 to 256
(launch-bound). Reshape nope to [N,7,64] and compute all tiles in one
batched op. Byte-exact output; pack 0.40ms->0.075ms (5.3x), cutting
attn_kv_store from 16.8% to ~8.6% of the decode step.
…arity

Update test references from the removed wkv/wgate nn.Linear submodules to
the current wkv_weight/wgate_weight buffers (F.linear), restoring the c128
eager-decode canonical-parity coverage that had silently broken.

Add eager<->fused-kernel bridge parity tests: drive the eager compressor
and the fused compress+norm+rope+quant+store kernels from identical
projections and assert the emitted compressed-KV agrees within fp8/mxfp4
tolerance (overlap=0). Closes the previously-uncovered cross-path
equivalence gap before any swap of eager for fused write.
MXFP4 cvt.e2m1x2 PTX is rejected by ptxas on sm_90a (Hopper). Make the
DeepSeek-V4 indexer quantization arch-aware so Hopper falls back to FP8:

- fused_indexer_q: default use_fp4 now derived from device capability
  (cc>=12) instead of always-False; explicit override still honored.
- _maybe_rotate (eager indexer compressor): the runtime sm90 blocker.
  Its fp4_act_quant (tilelang cvt.e2m1x2) now falls back to an fp8 e4m3
  block-32 fake-quant approximation on sm90.
- New _fused_kv_compress_norm_rope_insert_indexer_fp8_attn kernel matching
  the indexer pool's [128B fp8 | 4B fp32 scale] layout, plus a
  capability/env-gated dispatcher fused_kv_compress_norm_rope_insert_indexer
  (BATCHGEN_V4_INDEXER_QUANT=auto|mxfp4|fp8).

Validated: sm120 parity unchanged; H20/sm90 runs the fp8 paths with no
PTXASError.
The grouped MoE path uses MXFP4 WGMMA kernels (cvt.e2m1x2), rejected by
ptxas on sm_90a. Add _v4_grouped_moe_enabled() to AND the BATCHGEN_V4_GROUPED_MOE
flag with a cc>=12 check, so Hopper uses the per-expert loop fallback
(_dequant_fp4_e2m1_weight is pure-torch, correct on all archs).
…atch coverage

Add requires_mxfp4 marker (skipif cc<12) to the 6 MXFP4-only tests so they
SKIP cleanly on sm90 instead of crashing ptxas. Add coverage for the new
arch-aware paths: fp8 indexer-compress parity, indexer-quant selector, and
the fused_indexer_q capability dispatch default.

Validated on H20: MXFP4 tests skip, fp8/dispatch tests pass, no PTXASError.
…ode-loop e2e

The DeepSeekV4Compressor kernel class moved from wkv/wgate nn.Module
submodules to wkv_weight/wgate_weight buffers; update the two stale
assertions. Add world_size=1 to the SimpleNamespace module mocks the
current wrapper reads. Skip the indexer fp4_act_quant tests on sm120 when
tilelang is unavailable (sm90 exercises the fp8 fallback and runs them).

Validated: sm120 11 passed / 2 skipped; H20/sm90 13 passed, 0 PTXASError.
One-stop script (build/launch/wait/smoke/mmlu/stop) to rebuild the Hopper
image from current source and run DeepSeek-V4-Flash on 4x H20. Encodes the
launch gotchas found during sm-aware validation: 512G --shm-size (100G
host-KV + 320G weight region), stale-shm cleanup between launches, and the
sm-aware env flags (BATCHGEN_V4_INDEXER_QUANT=auto, grouped-MoE off).

Context: the sm-aware kernel switch is validated to load + serve on H20 with
zero PTXASError; this runbook closes the loop so a fresh-from-HEAD image runs
inference (fixing the stale-image FlashMLA/tilelang drift).
Building the image from inside China was failing/crawling on three upstream
sources. Parameterize them (defaults unchanged, so non-CN builds are unaffected):

- TORCH_FIND_LINKS: torch==2.9.0+cu129 via uv --find-links to a flat wheel
  mirror (download.pytorch.org was ~127KB/s in CN; Aliyun is multi-MB/s).
- UV_DEFAULT_INDEX: PyPI mirror for the remaining uv installs (uv ignores
  PIP_INDEX_URL); the flashinfer-python step otherwise crawled on
  files.pythonhosted.org.
- GitHub recursive clones (FlashMLA/DeepGEMM + cutlass): force git HTTP/1.1 +
  bigger postBuffer, and wrap DeepGEMM in a 5x retry loop to survive the
  intermittent 'HTTP2 framing layer' / GnuTLS clone failures from CN.

Verified by a full hopper rebuild on a CN H20 node (batchgen:v4flash-hopper-current,
27.2GB): sm90a kernels + FlashMLA c741387 + tilelang all build clean.
docker/README.md documents the CN build invocation.
A rank with an empty local decode_uuids returned early from
_sync_decode_uuids_tensor / _sync_completion_status_tensor BEFORE their
dist.all_reduce, skipping a decode-entry collective the other ranks ran.
The idle rank then slept on dist.barrier() (futex) while the rest blocked
forever in the skipped all_reduce / the per-layer MoE all-gather -> the
multi-GPU decode hang (7 spinning + 1 futex on MP8).

Derive the tensor size and the empty-decision from an all_reduce(MAX) of a
local max-index so every rank runs the identical collective sequence even
when its local list is empty. Add a global all_reduce(MAX) break-validation
before the decode-entry break so ranks leave the loop together, with a
fail-fast RuntimeError on residual desync.

Validated on H20 MP8: decode advances through all 43 layers with all 8
ranks in lockstep (was: hung at iteration 0).
Per-rank, immediately-flushed fd-2 markers ([DDL] pid=... rank=... ...) at
the decode-entry syncs, the decoding_continuous loop, and the per-layer MoE
collectives (states/ids all-gather, all-reduce). fd 2 is written directly so
markers survive a hung/buffered logging pipeline. Off by default; enable on
multi-GPU H20 with BATCHGEN_DECODE_DEADLOCK_TRACE=1.

Used to confirm rank lockstep during the decode-deadlock validation; paired
with docker/v4_h20_validate_decode_fix.sh diagnose.
_run_owned_experts called counts[expert_idx].item() once per owned expert
to skip empty experts -- ~32 device-to-host syncs/layer, ~1.4k/token over
43 layers, the dominant decode-step cost. Replace with a single .tolist()
of the owned-counts slice, then index the Python list. Numerically
identical; removes the per-expert sync from the decode hot path.
Mount a persistent host dir at /root/.cache/torch_extensions
(TORCH_EXT_CACHE) so core_engine + the runtime load() kernels are not
recompiled (~15-20min, 4 extensions) on every fresh container; later
launches reuse the cached .so. Add warmup / cache-status subcommands to
populate and inspect it.

Add --ulimit memlock=-1 --ulimit stack=67108864: pinning the 100GB
host-KV region via cudaHostRegister fails with the default 64KB memlock
(workers crash with 'cudaHostRegister failed: invalid argument'). Matches
the known-good sibling container's limits.

Add RUN_ENV_EXTRA hook and the v4_h20_validate_decode_fix.sh harness
(launch + decode smoke + wchan/marker capture) used to validate the
decode-deadlock fix on H20 MP8.
Patch/minor bumps clearing ~17 Dependabot alerts (no API breakage):
- filelock 3.20.0 -> 3.20.3 (TOCTOU symlink)
- idna 3.11 -> 3.15 (CVE-2024-3651 bypass)
- python-dotenv 1.2.1 -> 1.2.2 (symlink overwrite)
- python-multipart 0.0.21 -> 0.0.31 (DoS / file write)
- requests 2.32.5 -> 2.33.0 (temp file reuse)
- sentencepiece 0.2.0 -> 0.2.1 (heap overflow)
- urllib3 2.5.0 -> 2.7.0 (redirect header leak / decompression bomb)
@drunkcoding

Copy link
Copy Markdown
Collaborator Author

sm120 (consumer Blackwell) decode path — implemented & merged

The sm120 (RTX PRO 6000 / 5090, compute 12.x, no wgmma/TMA) decode path is implemented and merged via #253 (merged 2026-06-09, 0f6c393f), under this umbrella branch. Pointers for reviewers:

Kernels & code locations

  • Sparse-MLA decode (wgmma/TMA-free Triton)batchgen/attention/dsa/v4_mla_sm120_triton.py
    • introduced in 92b4fb5a (feat(v4flash): add sm120 Triton kernels for grouped MoE and sparse MLA decode)
    • hardened: 813c7916 (warmup + pinned autotune + cache-geometry guard), 2ccdfdaa (vectorized sparse-index physicalization), a579e7f9 (vectorized index builders)
    • Replaces the ~533 ms/token eager flashmla_decode_torch_reference with a fused tiled gather+dequant+flash-decode.
  • Grouped MXFP4 MoE (3D fast path)batchgen/moe/v4_slot_moe_sm120.py
    • 92b4fb5aa67fd171 (add grouped MXFP4 MoE decode path) → consolidated to the 3D path in 39e96a8f (consolidate to 3D grouped MXFP4 MoE; sm120 CUDA 12.9 build)
    • ~19 ms @ b256 / ~11 ms @ b128, vs ~92 ms slot-GEMV and ~75 ms FlashInfer native-FP4 (the two alt paths were removed).
  • Decode wiring + persistent experts + PyNCCL DP collectivese86e5841
  • Sparse-MLA decode adapter + torch reference + prefill populate5bd32500
  • Backend auto-routing (cc==12 → sm120 Triton, torch-ref fallback) — batchgen/attention/dsa/v4_flashmla_adapter.py (_v4_mla_sm120_triton_default / _select_v4_mla_backend)
  • Kernel build retarget for Blackwell9bbb0505 (build/op_builder/docker), a0812278 (retarget sm120 JIT extensions); base infra 6ee7a6fd
  • Arch gating (cc[0]==12 → "sm120"/"blackwell") across batchgen_kernels/__init__.py, batchgen_kernels/setup.py, batchgen/server/worker_manager.py, model set_basic_config.py / Parallel_Strategy_Manager.py

Arch-aware MoE gates (below sm120 fall back / skip)

  • 9c558bfd gate grouped MXFP4 MoE off below sm120
  • 4a594b39 sm-aware indexer quant (MXFP4 on sm120, FP8 on sm90)
  • cdfc8a8a skip-guard MXFP4-only tests below sm120

Status

  • Runs V4-Flash end-to-end on 4× RTX PRO 6000 Blackwell, stable under sustained load, 72–75% MMLU-Pro.
  • Known residual: bit-exact (QAT) grouped MoE exists (v4_grouped_mxfp4_moe_forward_qat) but is gated off (per-expert tilelang launches wedge the multi-process decode loop); current default uses the faster bf16-dequant 3D path (cos ~0.9988 vs per-expert). Remaining char-exact drift is attributed to inherent FP rounding (30ffb98e), not a discrete bug.

Note: this is sm120 (consumer Blackwell) only. sm100 / B200 is a separate, deferred track (#169 / #204 / #208).

leyang.xue added 8 commits July 4, 2026 13:57
…) + FP4 utils

MXFP4 grouped-expert decode/prefill kernels for RTX 6000 Pro (sm120),
mega3 as the production decode path (int64-indexed), plus JIT registry
and setup wiring. Includes microbenchmark harness.
Route V4-Flash MoE through the sm120 grouped kernels as the only path
(fallbacks raise), add BATCHGEN_V4_RESIDENT_EXPERTS env propagation,
EP offloading config plumbing, and DP-prefill/EP-decode phase strategy.
The prefill/compress RoPE caches were capped at max_position_embeddings
(default 8192; V4-Flash config only sets original_seq_len=65536), so any
position >= 8192 indexed out of bounds -> device-side assert during long
sequence prefill and decode. Floor at original_seq_len and rebuild when a
longer sequence arrives. Verified: 8192-token prefill 390 tok/s, 0 asserts.
Prefill admission only checked host-KV capacity and decode selection used
the two-page-buffer working-set estimate, while actual allocation needs
full-context KV: oversized batches raised 'Insufficient free pages'
mid-forward, silently dropping all but ~2 sequences from the response
(prefill) or failing the whole request (decode).

Add _truncate_batch_to_gpu_kv_fit: cumulative per-pool preflight via
can_allocate_pages_for_sequences, MIN-all-reduced so the SPMD batch stays
rank-identical; excess sequences stay queued for the next wave. Also
clear pending KV-append tasks on batch reset (post-OOM host leak path).

Verified e2e: 192x8192 request now returns 192/192 results with
wave-cycling ([PREFILL] 27/189..., [DECODE] 33/192...) and zero
allocation failures (was 2/192 silent).
…ch harness

Setup doc records verified launch config, tuning-lever findings (decode
best c20_f06 22.3 tok/s; recommended prefill 128x8192 ~2.4K tok/s
aggregate), bug root causes (RoPE cap, silent drop, decode
over-admission) and fix verification. Autoresearch harness (force-added
past benchmarks/ gitignore) is the reproducible experiment runner with
the full results ledger.
Expose 7 planner-internal knobs (BATCHGEN_{ATTN,MOE}_{PREFILL,DECODE}_MB,
BATCHGEN_EXPERT_{PREFILL,DECODE}_CAP, BATCHGEN_PREFILL_TOKEN_CAP) applied
after model-specific planning; unset keeps planner defaults.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

ci:run Trigger build + GPU regression on H20

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants