Skip to content

moe(b12x): zero-pad w4a8_mx expert shards to the QMMA 128-column layout - #80

Closed
voipmonitor wants to merge 6 commits into
dev/eldritch-enlightenmentfrom
fable/w4a8mx-qmma-pad-20260707
Closed

voipmonitor wants to merge 6 commits into
dev/eldritch-enlightenmentfrom
fable/w4a8mx-qmma-pad-20260707

Conversation

@voipmonitor

Copy link
Copy Markdown

Problem

The W4A8-MX QMMA repack tiles the expert intermediate dimension in 128-column blocks, so TP shards that are not multiples of 128 fail weight preparation:

W4A8-MX QMMA layout requires hidden_size % 256 == 0 and intermediate_size % 128 == 0

GLM-5.2 has moe_intermediate_size=2048, so TP6 (2048/6 → virtual-TP padded to 352/rank, a multiple of 32 but not 128) could not boot with B12X_MOE_FORCE_A8 at all — for any DCP value (the failure is TP6 itself, it dies at weight prep before KV sizing). TP8 (256/rank) and TP4 (512/rank) are unaffected.

Fix

Zero-pad the shard to the next 128 multiple at the vLLM/B12X weight handoff in b12x_moe.py, before the preparation plan is built:

  • gate/up halves of w13 and their e8m0 scale rows are extended per half,
  • w2 columns (packed FP4 nibbles) and its scale columns are extended to match,
  • the plan, the QMMA repack, and the runtime moe_problem_size all derive dimensions from the padded tensors, so the whole pipeline stays consistent (prepared.intermediate_size = padded).

Exactness: padded gate/up rows produce silu(0) * 0 = 0 activations, and the padded w2 columns only ever multiply those zeros — MoE outputs are bit-identical to an unpadded ideal. Cost is proportional to the padding: 352 → 384 is ~9% extra expert GEMM work on the padded rank, logged once as a warning. Source parameters are released after the repack as before, so the padded copy is the only surviving allocation (no double-resident weights).

Guarded narrowly: only quant_mode == "w4a8_mx", only when intermediate % 128 != 0 and % 32 == 0, and only when all four tensor shapes match the expected logical grid — anything else falls through to the existing error.

Verification

GLM-5.2-BF16-AMDMXFP4experts (AMD MXFP4 experts, BF16 dense), TP6/DCP1, MOE_MODE=force-a8-experimental + online MXFP8 dense overlay, v13 memory shape (GMU 0.957, mml 128000, mnbt 2048), image v4-vllmbbce67f-b12xe44cb77 + this file mounted:

before after
boot dies at weight prep (QMMA ValueError) Application startup complete, KV cache 335,168 tokens
coding smoke c0 72.2 tok/s, 0 CJK
coding smoke c3000 71.2 tok/s, 0 CJK

Boot log: B12X w4a8_mx: expert intermediate size 352 is not a multiple of 128; zero-padding the shard to 384 for the QMMA layout (exact results, ~9% extra expert GEMM work).

An alternative would be teaching the QMMA repack/kernels a 32-aligned tail — that is real kernel work for a niche shard shape; this padding unblocks the config with bounded, transparent cost and can be deleted if the kernel ever grows native tail support.

🤖 Generated with Claude Code

voipmonitor and others added 5 commits July 6, 2026 13:58
…d-MXFP8 dense support

- store_dtype=nvfp4 now returns ModelOptNvFp4FusedMoE (registers
  w13_weight/w2_weight names the DeepSeek/GLM fused-experts loader
  expects; the earlier CompressedTensors method registers *_weight_packed
  and KeyErrors at load).
- dense_format=mxfp8: FP8 checkpoints whose dense weights are serialized
  as MXFP8 (e4m3 values + per-32 ue8m0 uint8 scales, e.g. requantized
  offline from BF16 via mxfp8_e4m3_quantize) route LinearBase to a thin
  adapter over the compressed-tensors W8A8 MXFP8 scheme. ignored_layers
  keeps enumerated BF16 linears (routers, indexer heads, lm_head) on the
  unquantized path.
Enables GLM-5.2-FP8-NVFP4experts and GLM-5.2-MXFP8dense-NVFP4experts.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
(cherry picked from commit 4b55617)
Align the MXFP4 online-overlay hook with the ModelOpt overlay semantics:
the `linear` spec covers dense linears only, and shared-expert
projections are quantized only when a `shared_experts` spec is given
explicitly. Before this, the same
--quantization-config '{"linear":{"weight":"mxfp8"}}' meant two
different things: shared experts stayed BF16 on ModelOpt/NVFP4
checkpoints but were silently quantized on MXFP4 checkpoints (the
OnlineQuantizationConfig fallback picks the linear spec for every
LinearBase). Quantized shared experts measured both lower quality
(mean|dlogprob| 0.156 vs 0.152) and lower decode throughput
(90.1 vs 92.5 tok/s) on GLM-5.2, so the divergence was strictly harmful.

Adds selection tests for the MXFP4 overlay path: shared experts skipped
without the spec, quantized with it, and `ignore` patterns honored
(e.g. re:.*kv_b_proj).

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…kens

The B12X PCIe DCP A2A path is a one-barrier, latency-optimized exchange:
it wins small decode batches (+13% CC1 decode on GLM-5.2 DCP4) but loses
~21-27% standalone prefill throughput to AG+RS, because for sparse MLA
every batch (prefill included) goes through forward_mqa and the one-shot
staging copy is bandwidth-poor at large token counts.

Add VLLM_DCP_A2A_MAX_TOKENS (0 = uncapped, current behavior): batches with
more tokens than the cap skip the B12X path and take a pipelined NCCL
collective instead — VLLM_DCP_A2A_LARGE_BACKEND selects AG+RS (default,
the measured prefill winner) or the packed NCCL all-to-all. The cap also
bounds the B12X IPC staging pool, which scales linearly with its token
capacity (max_num_batched_tokens=8192 previously reserved ~0.9 GB/rank;
a cap of 64-128 needs ~7-14 MB).

Dispatch happens on the per-step token count, mirroring the deterministic
size crossovers already used for the PCIe allreduce (oneshot/PyNCCL/DMA).
CUDA-graph safe: capture bakes one path per padded graph size; eager
prefill re-evaluates per step; all DCP ranks see the same batch so the
choice is uniform across the group.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…d size

The B12X PCIe DCP channel exists only for world sizes 2/4/8, and the
runtime dispatchers already fall back to NCCL collectives per call. The
dedicated DCP warmup added in 'Optimize B12X DCP collectives and warmup'
turned that graceful fallback into a boot failure: with TP6 + DCP3/DCP6
(GLM head66 configs that worked on the v13 stack) warmup_b12x_dcp_a2a
raised 'B12X PCIe DCP query all-gather is unavailable for the configured
attention geometry' and EngineCore died.

- warmup_b12x_dcp_a2a: log once and skip when world size is not 2/4/8
  (still raises for genuinely broken geometries at supported sizes).
- MLAAttention: don't set dcp_b12x for unsupported DCP sizes, so neither
  warmup nor the per-step dispatch attempts the PCIe channel at all.

Verified on GLM-5.2 NVFP4 TP6/DCP6/MTP3 (v13 wiki shape: GMU 0.957,
max_model_len 128000, max_num_batched_tokens 2048): boots clean, KV cache
989k tokens, test.py c0/c3000 coherent (CJK 0), 73-75 tok/s.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jul 7, 2026

Copy link
Copy Markdown

Important

Review skipped

Auto reviews are disabled on base/target branches other than the default branch.

🗂️ Base branches to auto review (1)
  • dev/*

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro Plus

Run ID: 8fccc1e9-6c3c-4d67-9926-71b3379cd928

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fable/w4a8mx-qmma-pad-20260707

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.

…layout

Both B12X MoE kernel families reject expert shards whose intermediate
size is not tile-aligned, which makes GLM-5.2 (moe_intermediate 2048)
unbootable at TP6 (2048/6 -> virtual-TP padded 352 per rank) on MXFP4
checkpoints, for any DCP value:

- w4a8_mx dies at weight prep: 'W4A8-MX QMMA layout requires
  hidden_size % 256 == 0 and intermediate_size % 128 == 0'
- w4a16 (packed e8m0 path) dies at profile run: 'no valid W4A16 tile
  config for M/N/K=16384/6144/352, moe_block_size=64'

Zero-pad the shard to the next 128 multiple at the vLLM/B12X weight
handoff, before the preparation plan is built: gate/up halves and their
e8m0 scale rows are extended per half, w2 columns (packed FP4 nibbles)
plus its scale columns are extended to match. Padded gate/up rows
produce silu(0) * 0 = 0 activations and the padded w2 columns only
multiply those zeros, so MoE outputs are bit-exact; the plan, the
repack, and the runtime problem size all derive dimensions from the
padded tensors. Cost is proportional to the padding (352 -> 384 is ~9%
extra expert GEMM work); source parameters are released after the
repack as before. Guarded narrowly: e8m0 source format only, exact
logical-shape match required, otherwise the original errors remain.

Verified on GLM-5.2-BF16-AMDMXFP4experts, TP6/DCP1, online MXFP8 dense
overlay, v13 memory shape (GMU 0.957, mml 128000, mnbt 2048), coding
smoke c0/c3000 coherent (0 CJK) in both modes:

| mode | KV cache | decode c0 | decode c3000 | prefill 8k | prefill 64k |
|---|---|---|---|---|---|
| force-A8 (w4a8_mx) | 335,168 | 72.2 | 71.2 | 4,770 | 5,225 |
| A16 (w4a16)        | 338,304 | 79.2 | 77.9 | 4,643 | 4,816 |

(Previously both modes failed before ready.)

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
@voipmonitor
voipmonitor force-pushed the fable/w4a8mx-qmma-pad-20260707 branch from c28cb30 to 49bed02 Compare July 7, 2026 09:08
@voipmonitor

Copy link
Copy Markdown
Author

Amended (49bed0297c) after finding the same class of failure on the w4a16 packed-e8m0 path: TP6 dies at profile run with no valid W4A16 tile config for M/N/K=16384/6144/352, moe_block_size=64 (this is why the earlier TP6 a16 validation passed only on the NVFP4 checkpoint — modelopt_nvfp4 takes the SOURCE_NATIVE w4a16 layout, which handles K=352). The padding now covers both w4a8_mx and w4a16 on fp4_e8m0_k32 sources.

Measured on GLM-5.2-BF16-AMDMXFP4experts TP6/DCP1 (both modes previously failed before ready):

mode KV cache decode c0 decode c3000 prefill 8k prefill 64k
force-A8 (w4a8_mx, padded 384) 335,168 72.2 71.2 4,770 5,225
A16 (w4a16, padded 384) 338,304 79.2 77.9 4,643 4,816

Interesting inversion vs TP8: at TP6 A16 decodes ~9% faster while A8 prefills up to ~8% faster (64k) — the QMMA padding overhead lands on the decode side, prefill still benefits from the MXFP8×FP4 MMA. All outputs coherent (0 CJK).

@voipmonitor

Copy link
Copy Markdown
Author

Follow-up on the TP6 A8-vs-A16 decode gap (72 vs 79 tok/s): the A8 decode parity kernel (tiny_decode, ex-tiny_rp, default-on, M≤4) never engages at TP6 because _tiny_decode_supports requires n % 256 == 0 and (n // 128) % 2 == 0 — the padded shard is 384 (3×128, odd tile count), so M=1 decode falls back to the dynamic QMMA path.

Options considered:

  • Teach tiny_decode odd 128-tile counts (N=384) — the real fix, CUTE kernel work; leaving this one to you.
  • Padding to 512 instead of 384 would satisfy the tiny contract but adds +33% expert weight bytes vs the 384 A16 baseline — decode is weight-bandwidth-bound, so the roofline lands ~60 tok/s, worse than today's 72. Not viable.
  • Dual prepared copies (384 QMMA + 512 tiny) is blocked by the single-repack storage policy and costs ~1.2 GB/rank.

Until the kernel grows N=384 support, the practical TP6 guidance stands: A16 for decode-heavy, A8 for prefill-heavy (+8% @64k prefill).

@voipmonitor

Copy link
Copy Markdown
Author

Final resolution: the tiny_decode kernel now supports odd FC2 K-tile counts — lukealonso/b12x#26 (FC2 K-tiles-per-task becomes a configure()-time value, 2→1 for odd counts; support gate relaxed to n % 128; fp32-oracle tests 6/6 incl. n=384). With this PR's padding plus b12x#26, TP6 A8 goes from broken → fastest mode:

TP6/DCP1, AMD MXFP4 A8 (padding only) A8 + b12x#26 A16
decode c0 72.2 83.0 79.2
decode c3000 71.2 81.4 77.9
prefill 8k / 64k 4,770 / 5,225 4,864 / 5,219 4,643 / 4,816

The earlier per-mode guidance (A16 for decode) is obsolete once both land — A8 wins both axes at TP6.

@voipmonitor
voipmonitor changed the base branch from codex/eldritch-enlightenment-v5-dcp-hybrid-pr77-20260707 to dev/eldritch-enlightenment July 7, 2026 14:15
@lukealonso

Copy link
Copy Markdown
Collaborator

This one is no good - the whole point is to avoid as much of this padding as possible, and 128 is way too much. The other modes (A4, A16) support much smaller alignments to support this.

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.

2 participants