tune(sm90): fold H200 W4A8 MoE calibrations into the generic grouped-scale policy - #76
tune(sm90): fold H200 W4A8 MoE calibrations into the generic grouped-scale policy#76Leoyzen wants to merge 3 commits into
Conversation
|
Thank you. But we should not create a separate |
|
@jinzhen-lin Agree — H200 shares the 132-SM grid with H100/H800, so the tuning is really SM-count-driven rather than device-specific. Plan to rework: move the calibrated decisions (2-CTA residency cap, large-per-expert-M large-N tile with stream-K off, small-M wide tile) into the generic sm90 policy path keyed on num_sms, and drop the separate class + name dispatch. One question: the current sm90.py routes W4A8 grouped-scale shapes through select_grouped_scale candidates — do you prefer these tweaks inside sm90_policies.py candidate generation, or as a post-selection adjustment in sm90.py? |
Temporary guard for PR#76: diffs the old Sm90H200Heuristics against the new grouped-scale sm90 policy on the bench shapes (w13/w2, 288 experts, M 64-16384, INDEXED + GROUPED_MASKED) at num_sms=132. Every difference is attributed to one of the three KEEP calibrations (2-CTA cap, large per_expert_m 256-N tile, small-M wide tile), a generic policy-infra field, or flagged unattributed. The num_warps==4 K-doubling branch is identified as the one un-ported legacy behavior (documented in CONFIG_DIFF.md).
Replace the device-name-dispatched H200 heuristics with three SM-count-gated calibrations inside the generic grouped-scale policy (enabled at device.num_sms >= 128; H100 PCIe at 114 is unaffected): - K1 2-CTA residency cap: _grouped_scale_ctas_per_sm derives the cap from num_threads / resident smem / output-tile grid like the indexed-A16 policy, applied via _analyze_grouped_scale_candidate. Calibration candidates that pin residency explicitly are only clamped, never raised. - K2 large per-expert-M tile: per_expert_m >= 48 with shape_n % 256 == 0 proposes (block_m 128|64, block_n 256, block_k 128) at one resident CTA with stream-K off as the highest-priority candidate. - K3 small-M wide tile: routed W4A8 (fp8 a / int4 b) batches under ~8 rows per expert propose (block_n 512, block_k 64, warp_n 64) at two resident CTAs and three stages. Supporting change: sm90.get_tuning_decision now includes the device grid size for the grouped-scale branch, degrading gracefully when no live device is available (unlike indexed-A16). The config-diff harness now compares the old device class against the calibrated policy directly; CONFIG_DIFF.md records the final state: small and large M match the old implementation exactly, mid M differs only in the deliberately-unported num_warps==4 K-doubling branch and the threshold-table-vs-measured-argmin block_m choice.
Covers: 256-N large per-expert-M selection with stream-K off and the per_expert_m >= 96 block_m 128 branch; the 2-CTA residency cap and its gate-off behavior; byte-identical generic behavior at num_sms=114 (H100 PCIe) and with num_sms=None; and the small-M W4A8 wide tile including the w2 shape, dense and MXFP4 exclusions.
a690ce4 to
1c232d0
Compare
|
Reworked as suggested — dropped the separate class and name dispatch, folded the three calibrated decisions into the generic sm90 grouped-scale policy keyed on |
Fixes #26
Reworked per review: no separate
sm90_h200.py. H200 shares the GH100 die and the 132-SM grid with H100 SXM and H800, so the tuning is SM-count-driven, not device-name-driven. The three calibrated decisions now live in the generic sm90 grouped-scale policy, keyed onnum_sms, and H100 SXM / H800 (also 132 SMs) pick them up for free.Changes
All in
sm90_policies.py, gated onproblem.device.num_sms >= 128(so H20 at 78 SMs and H100 PCIe at 114 SMs keep byte-identical behavior with current main; verified on real H20):_indexed_a16_ctas_per_smpattern).Supporting changes: grouped-scale path now carries the device grid size (
include_grid_size=True); the vLLM downstream constraints (K-block <= 128, warp_n >= 32 when block_n % 32 == 0) are enforced by construction.sm90_h200.py, name dispatch, andHUMMING_FORCE_HEURISTICSare gone.Why SM-count keying
Dispatch by device name was already broken in the field: our production H200 node reports an OEM-rebranded name (
L20X), so the name-based dispatch in the previous revision never matched and the server ran generic SM90 heuristics. SM count survives rebranding. It is also more robust than hardcoding 132: H800 SM counts are ambiguous across public sources and H100 PCIe has 114 — readingsm_countat runtime picks correctly in all cases.Results
Kernel bench (real H200, OEM-rebranded L20X, indexed MoE GEMM, 288 experts top-8, fp8e4m3 x int4-128, triton do_bench, vs generic SM90 on the same card):
No regressions on the production packed-K path: configs are byte-identical with stock (verified end-to-end in a serving pod).
Config equivalence against the previous separate-class revision was checked shape-by-shape with
scripts/config_diff_h200.py: small-M and large-M selections match exactly; mid-M (4096-8192) differs by one tile step within the same family (threshold table vs measured argmin), expected performance-equivalent.Accuracy
GSM8K 5-shot (greedy completions), GLM-5.3-W4AFP8 served via vLLM (TP4 + EP), same checkpoint and server config, only heuristics differ:
Within noise (~1.3 sigma). Raw-completion protocol, not comparable to official thinking-mode scores.
Tests
pytest tests/ -k "sm90 or grouped"— 60 passed, 80 skipped (SM100-gated) on real H20; 66 passed, 2 skipped on CPU with a pinned device profiletests/test_sm90_grouped_scale_sm_count.py(17 tests): calibration trigger at 132 SMs, gate-off byte-identical behavior at 114/None, 2-CTA cap, wide-tile and 256-N selectionsKnown limitations
Related but non-overlapping: #64 (W4A8 indexed block-M selection, group-32 MXFP4) — this PR does not touch the generic block-M selector.
Assisted-by: opencode (GLM)