Skip to content

Commit b2f6744

Browse files
yindingclaude
andcommitted
Raise SM90 FP4 MegaMoE decode->prefill boundary to e=80
The decode config (BLOCK_M=64) and prefill config (BLOCK_M=128) flipped at a hardcoded e>=64. Measured on 8xH20 both modes across the boundary: decode wins for e in [64, 80) (its first m-block is exactly full while prefill's 128-row block runs half empty, -13% at GLM5.2 b256); prefill wins from e=80 up (decode's second m-block is mostly empty, +2~9%). Parameterize the four boundary checks (block config auto_split_mn, split-N band, 2-WG thread band, API prefill band) through a single DG_SM90_FP4_PREFILL_E knob (default 80) plus a PREFILL_E mirror constant in the test. GLM5.2 b256: 1277 -> 1123 us; vs FP8 low-latency 0.98x -> 1.11x (cliff fixed, b245->b320 now monotone). DSV4-Flash e=72 also improves -1.8%. Accuracy: forced-decode reference checks at e=64/96/128 pass (diff ~ 6e-4, tol 0.1). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 56b6af2 commit b2f6744

3 files changed

Lines changed: 40 additions & 16 deletions

File tree

csrc/apis/sm90_mega.hpp

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -68,7 +68,11 @@ static FP4SM90APIDefaults get_fp4_sm90_api_defaults(
6868
(void)intermediate_hidden;
6969
const float expected_tokens_per_expert =
7070
static_cast<float>(num_tokens) * num_topk / num_experts_per_rank;
71-
const bool prefill_band = expected_tokens_per_expert >= 64.0f;
71+
// Decode -> prefill boundary; keep in sync with the JIT heuristics'
72+
// get_fp4_sm90_prefill_threshold (DG_SM90_FP4_PREFILL_E, default 80).
73+
const float prefill_threshold =
74+
static_cast<float>(get_env<int>("DG_SM90_FP4_PREFILL_E", 80));
75+
const bool prefill_band = expected_tokens_per_expert >= prefill_threshold;
7276
const bool decode_band =
7377
expected_tokens_per_expert > 0.0f and !prefill_band;
7478
// swapAB on/off kill-switch (default ON). Set DG_SM90_FP4_SWAP_AB=0 to force

csrc/jit_kernels/heuristics/sm90_mega_moe.hpp

Lines changed: 17 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -119,6 +119,17 @@ static std::pair<int, int> get_pipeline_config_for_mega_moe_sm90(
119119
return {num_stages, smem_size};
120120
}
121121

122+
// Decode -> prefill boundary for the FP4 MegaMoE path, in expected tokens per
123+
// expert. At the boundary the kernel flips from the decode config (BLOCK_M=64,
124+
// split-N epilogue) to the prefill config (BLOCK_M=128, 2-WG decode offload).
125+
// 80 = measured on H20: decode wins for e in [64, 80) (its first m-block is
126+
// exactly full while prefill's 128-row block runs half empty); prefill wins
127+
// from e=80 up (decode's second m-block is mostly empty). Overridable via
128+
// DG_SM90_FP4_PREFILL_E for boundary A/B tuning.
129+
static float get_fp4_sm90_prefill_threshold() {
130+
return static_cast<float>(get_env<int>("DG_SM90_FP4_PREFILL_E", 80));
131+
}
132+
122133
static std::tuple<int, int> get_block_config_for_mega_moe_sm90_fp4(
123134
const int& num_ranks, const int& num_experts,
124135
const int& num_max_tokens_per_rank, const int& num_topk,
@@ -127,7 +138,8 @@ static std::tuple<int, int> get_block_config_for_mega_moe_sm90_fp4(
127138

128139
const float expected_tokens_per_expert =
129140
static_cast<float>(num_tokens) * num_ranks * num_topk / num_experts;
130-
const bool auto_split_mn = expected_tokens_per_expert >= 64.0f;
141+
const bool auto_split_mn =
142+
expected_tokens_per_expert >= get_fp4_sm90_prefill_threshold();
131143
const bool ultra_small_split_n =
132144
expected_tokens_per_expert > 0.0f and expected_tokens_per_expert < 0.375f;
133145
int block_m = auto_split_mn ? 128 : 64;
@@ -257,7 +269,8 @@ static MegaMoESM90Config get_mega_moe_config_sm90_fp4(
257269
block_m == 64 and block_n % 128 == 0;
258270
const bool fp4_split_n_shape_band =
259271
fp4_flash_or_pro_shape and
260-
expected_tokens_per_expert > 0.0f and expected_tokens_per_expert < 64.0f;
272+
expected_tokens_per_expert > 0.0f and
273+
expected_tokens_per_expert < get_fp4_sm90_prefill_threshold();
261274
if (fp4_split_n_eligible and fp4_split_n_shape_band) {
262275
fp4_num_epilogue_warpgroups = 2;
263276
}
@@ -285,7 +298,8 @@ static MegaMoESM90Config get_mega_moe_config_sm90_fp4(
285298
fp4_small_block_n_kernel and fp4_split_n_shape_band;
286299
const bool fp4_2wg_decode_offload_kernel_band =
287300
block_m == 128 and block_n == 128 and
288-
fp4_num_epilogue_threads == 256 and expected_tokens_per_expert >= 64.0f;
301+
fp4_num_epilogue_threads == 256 and
302+
expected_tokens_per_expert >= get_fp4_sm90_prefill_threshold();
289303
const bool fp4_decode_assist_thread_kernel_band =
290304
fp4_2wg_decode_offload_kernel_band or
291305
(fp4_small_block_n_kernel and

sgl_deep_gemm/tests/test_mega_moe_fp4_hopper.py

Lines changed: 18 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -33,6 +33,12 @@
3333

3434
GENERIC_FALLBACK = "generic"
3535

36+
# Decode -> prefill boundary (expected tokens per expert). Mirrors the C++
37+
# default of DG_SM90_FP4_PREFILL_E (the CPU mirror assumes the env is unset).
38+
# 80 = measured: BLOCK_M=64 decode wins for e in [64, 80) (full first m-block),
39+
# BLOCK_M=128 prefill wins from e=80 up (decode's second m-block mostly empty).
40+
PREFILL_E = 80.0
41+
3642

3743
def table_wave(
3844
num_experts_per_rank: int,
@@ -65,11 +71,11 @@ def legacy_threads(
6571
small_block_n = block_m == 64 and block_n == 128
6672
e = expected_tokens_per_expert
6773
decode_heavy_small_batch = small_block_n and 0.0 < e <= 24.0
68-
pro_large_decode_assist_batch = small_block_n and intermediate_hidden >= 3072 and 24.0 <= e < 64.0
69-
pro_split_n_decode_threads = small_block_n and intermediate_hidden >= 3072 and 0.0 < e < 64.0
70-
flash_split_n_decode_threads = small_block_n and intermediate_hidden <= 2048 and 0.0 < e < 64.0
74+
pro_large_decode_assist_batch = small_block_n and intermediate_hidden >= 3072 and 24.0 <= e < PREFILL_E
75+
pro_split_n_decode_threads = small_block_n and intermediate_hidden >= 3072 and 0.0 < e < PREFILL_E
76+
flash_split_n_decode_threads = small_block_n and intermediate_hidden <= 2048 and 0.0 < e < PREFILL_E
7177
two_wg_decode_offload = (
72-
block_m == 128 and block_n == 128 and num_epilogue_threads == 256 and e >= 64.0
78+
block_m == 128 and block_n == 128 and num_epilogue_threads == 256 and e >= PREFILL_E
7379
)
7480
dispatch = (
7581
64
@@ -101,9 +107,9 @@ def legacy_epilogue_threads(
101107
e = expected_tokens_per_expert
102108
epilogue_warpgroups = num_epilogue_threads // 128
103109
split_n_eligible = block_m == 64 and block_n % 128 == 0
104-
split_n_band = 32.0 <= e < 64.0
105-
pro_split_n_band = intermediate_hidden >= 3072 and 0.0 < e < 64.0
106-
flash_split_n_band = intermediate_hidden <= 2048 and 0.0 < e < 64.0
110+
split_n_band = 32.0 <= e < PREFILL_E
111+
pro_split_n_band = intermediate_hidden >= 3072 and 0.0 < e < PREFILL_E
112+
flash_split_n_band = intermediate_hidden <= 2048 and 0.0 < e < PREFILL_E
107113
small_split_n_band = flash_split_n_band or pro_split_n_band
108114
default_split_n = (
109115
split_n_eligible
@@ -126,7 +132,7 @@ def table_epilogue_threads(
126132
epilogue_warpgroups = num_epilogue_threads // 128
127133
split_n_eligible = block_m == 64 and block_n % 128 == 0
128134
split_n_shape_band = (
129-
(intermediate_hidden <= 2048 or intermediate_hidden >= 3072) and 0.0 < e < 64.0
135+
(intermediate_hidden <= 2048 or intermediate_hidden >= 3072) and 0.0 < e < PREFILL_E
130136
)
131137
if split_n_eligible and split_n_shape_band:
132138
epilogue_warpgroups = 2
@@ -143,11 +149,11 @@ def table_threads(
143149
small_block_n_kernel = block_m == 64 and block_n == 128
144150
e = expected_tokens_per_expert
145151
split_n_shape_band = (
146-
(intermediate_hidden <= 2048 or intermediate_hidden >= 3072) and 0.0 < e < 64.0
152+
(intermediate_hidden <= 2048 or intermediate_hidden >= 3072) and 0.0 < e < PREFILL_E
147153
)
148154
split_n_decode_thread_kernel_band = small_block_n_kernel and split_n_shape_band
149155
two_wg_decode_offload_kernel_band = (
150-
block_m == 128 and block_n == 128 and num_epilogue_threads == 256 and e >= 64.0
156+
block_m == 128 and block_n == 128 and num_epilogue_threads == 256 and e >= PREFILL_E
151157
)
152158
decode_assist_thread_kernel_band = two_wg_decode_offload_kernel_band or (
153159
small_block_n_kernel and 0.0 < e <= 24.0
@@ -246,8 +252,8 @@ def table_api_features(
246252
threshold, shape-agnostic (no per-(shape x e-band) table)."""
247253
del num_experts_per_rank, intermediate_hidden
248254
e = expected_tokens_per_expert
249-
prefill = e >= 64.0
250-
decode = 0.0 < e < 64.0
255+
prefill = e >= PREFILL_E
256+
decode = 0.0 < e < PREFILL_E
251257
return {
252258
"math_wg_participates": False,
253259
"first_decode_assist_warp": 2,

0 commit comments

Comments
 (0)