Skip to content

Commit 56b6af2

Browse files
yindingclaude
andcommitted
Simplify SM90 FP4 MegaMoE heuristics to FP8 parity
The FP4 path had ~49 tuned conditions (17 shape x e-band API-default bands, 9+12 wave-table rules, 11 stage-cap rules) fitted point-by-point on benchmark batches; on real shapes most bands collapsed to constants or slivers. Retire them: - API defaults: constants + one decode/prefill split + a single swapAB threshold (measured crossover on H20: swapAB wins at e<=12, ties at e~16, loses beyond -> bound 16). wide_load_decode on for all decode. - Wave scheduling: delegate to the FP8 rule (get_num_experts_per_wave_for_mega_moe_sm90). - Stage cap: removed; always use as many pipeline stages as SMEM allows. - CPU heuristic mirrors in the test synced to the same closed forms. Verified on 8xH20 EP8: accuracy suites pass (diff <= 0.0006, tol 0.05); GLM5.2 flat-or-better (b2 -12%, b96 -9%, prefill bit-identical); DSV4-Flash within noise; DSV4-Pro avg ~+2%, worst b1 +8.6%. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent c5920e9 commit 56b6af2

3 files changed

Lines changed: 66 additions & 653 deletions

File tree

csrc/apis/sm90_mega.hpp

Lines changed: 23 additions & 111 deletions
Original file line numberDiff line numberDiff line change
@@ -60,123 +60,35 @@ struct FP4SM90APIDefaults {
6060
static FP4SM90APIDefaults get_fp4_sm90_api_defaults(
6161
const int& num_experts_per_rank, const int& num_tokens, const int& num_topk,
6262
const int& intermediate_hidden) {
63+
// Simplified, shape-agnostic defaults, mirroring the FP8 path's style
64+
// (`should_use_swap_ab_for_mega_moe_sm90`): one decode/prefill split plus a
65+
// single swapAB threshold. The historical per-(shape x e-band) table was
66+
// tuned point-by-point on benchmark batches; on the shapes that matter it
67+
// collapsed to constants plus a few sliver bands, so it is retired.
68+
(void)intermediate_hidden;
6369
const float expected_tokens_per_expert =
6470
static_cast<float>(num_tokens) * num_topk / num_experts_per_rank;
65-
// Shape bands exclude kernel tile/thread constraints; JIT heuristics add those as kernel bands.
66-
const bool fp4_flash_shape = intermediate_hidden <= 2048;
67-
const bool fp4_pro_shape = intermediate_hidden >= 3072;
68-
const bool fp4_middle_shape = !fp4_flash_shape and !fp4_pro_shape;
69-
const bool fp4_decode_lookahead_shape_band =
70-
expected_tokens_per_expert >= 3.0f and expected_tokens_per_expert <= 6.0f;
71-
const bool fp4_bigband_lookahead_shape_band =
72-
expected_tokens_per_expert >= 12.0f and expected_tokens_per_expert <= 24.0f;
73-
const bool fp4_b4_skip_decode_shape_band =
74-
expected_tokens_per_expert >= 0.5f and expected_tokens_per_expert < 1.0f;
75-
const bool fp4_pro_single_token_per_expert_shape_band =
76-
fp4_pro_shape and
77-
expected_tokens_per_expert >= 1.0f and expected_tokens_per_expert < 1.5f and
78-
num_experts_per_rank % 8 == 0;
79-
const bool fp4_pro_split_n_mbarrier_shape_band =
80-
fp4_pro_shape and
81-
expected_tokens_per_expert > 0.0f and expected_tokens_per_expert < 64.0f;
82-
const bool fp4_pro_two_tokens_per_expert_shape_band =
83-
fp4_pro_shape and
84-
expected_tokens_per_expert >= 1.5f and expected_tokens_per_expert < 3.0f;
85-
const bool fp4_pro_mid_decode_assist_shape_band =
86-
fp4_pro_shape and
87-
expected_tokens_per_expert >= 6.0f and expected_tokens_per_expert < 12.0f;
88-
const bool fp4_pro_large_decode_assist_shape_band =
89-
fp4_pro_shape and
90-
expected_tokens_per_expert >= 24.0f and expected_tokens_per_expert < 64.0f;
91-
const bool fp4_flash_two_tokens_per_expert_shape_band =
92-
fp4_flash_shape and
93-
expected_tokens_per_expert >= 1.5f and expected_tokens_per_expert < 2.0f;
94-
const bool fp4_flash_half_token_per_expert_shape_band =
95-
fp4_flash_shape and
96-
expected_tokens_per_expert >= 0.375f and expected_tokens_per_expert < 0.5f;
97-
const bool fp4_flash_decode_lookahead_shape_band =
98-
fp4_flash_shape and
99-
expected_tokens_per_expert >= 3.0f and expected_tokens_per_expert < 6.0f;
100-
const bool fp4_flash_wide_load_decode_shape_band =
101-
fp4_flash_shape and
102-
expected_tokens_per_expert >= 6.0f and expected_tokens_per_expert < 64.0f;
103-
const bool fp4_pro_wide_load_decode_shape_band =
104-
fp4_pro_shape and
105-
expected_tokens_per_expert > 0.0f and expected_tokens_per_expert < 64.0f;
106-
const bool fp4_flash_split_n_mbarrier_shape_band =
107-
fp4_flash_shape and
108-
expected_tokens_per_expert >= 0.75f and expected_tokens_per_expert < 64.0f;
109-
const bool fp4_flash_small_mbarrier_shape_band =
110-
fp4_flash_shape and
111-
expected_tokens_per_expert > 0.0f and expected_tokens_per_expert < 0.5f;
112-
const bool fp4_2wg_decode_offload_shape_band =
113-
expected_tokens_per_expert >= 64.0f;
114-
const bool fp4_shared_decode_assist_shape_band =
115-
((expected_tokens_per_expert > 0.0f and expected_tokens_per_expert < 0.375f) or
116-
fp4_flash_half_token_per_expert_shape_band or
117-
fp4_b4_skip_decode_shape_band or fp4_decode_lookahead_shape_band or
118-
fp4_flash_split_n_mbarrier_shape_band or
119-
fp4_pro_mid_decode_assist_shape_band or fp4_pro_large_decode_assist_shape_band or
120-
fp4_bigband_lookahead_shape_band or fp4_2wg_decode_offload_shape_band);
121-
const bool default_math_wg_decode =
122-
fp4_shared_decode_assist_shape_band or
123-
(expected_tokens_per_expert >= 1.0f and expected_tokens_per_expert < 2.0f) or
124-
fp4_pro_two_tokens_per_expert_shape_band;
125-
const bool math_wg_participates_in_decode =
126-
!default_math_wg_decode;
127-
const bool default_skip_loader_decode_assist =
128-
fp4_shared_decode_assist_shape_band or
129-
fp4_pro_single_token_per_expert_shape_band or
130-
(expected_tokens_per_expert >= 1.5f and expected_tokens_per_expert < 3.0f);
131-
const bool default_wide_load_decode =
132-
fp4_pro_wide_load_decode_shape_band or
133-
fp4_flash_half_token_per_expert_shape_band or
134-
fp4_flash_two_tokens_per_expert_shape_band or
135-
fp4_flash_wide_load_decode_shape_band;
136-
const bool default_ss_early_b_decode =
137-
((expected_tokens_per_expert >= 1.5f and expected_tokens_per_expert <= 3.0f and
138-
!fp4_pro_two_tokens_per_expert_shape_band and
139-
!fp4_flash_two_tokens_per_expert_shape_band and
140-
!fp4_flash_decode_lookahead_shape_band) or
141-
fp4_2wg_decode_offload_shape_band);
142-
const bool fp4_middle_decode_lookahead_mbarrier_shape_band =
143-
fp4_middle_shape and fp4_decode_lookahead_shape_band;
144-
const bool fp4_middle_bigband_mbarrier_shape_band =
145-
fp4_middle_shape and fp4_bigband_lookahead_shape_band;
146-
const bool default_decode_done_mbarrier =
147-
fp4_pro_split_n_mbarrier_shape_band or
148-
fp4_flash_split_n_mbarrier_shape_band or
149-
fp4_flash_small_mbarrier_shape_band or
150-
fp4_middle_decode_lookahead_mbarrier_shape_band or
151-
fp4_middle_bigband_mbarrier_shape_band or
152-
fp4_2wg_decode_offload_shape_band;
153-
const bool default_l2_arrival_counter =
154-
((fp4_flash_shape and
155-
expected_tokens_per_expert >= 0.375f and expected_tokens_per_expert < 0.75f) or
156-
(fp4_pro_shape and
157-
expected_tokens_per_expert >= 0.25f and expected_tokens_per_expert < 0.375f));
71+
const bool prefill_band = expected_tokens_per_expert >= 64.0f;
72+
const bool decode_band =
73+
expected_tokens_per_expert > 0.0f and !prefill_band;
15874
// swapAB on/off kill-switch (default ON). Set DG_SM90_FP4_SWAP_AB=0 to force
15975
// the non-swap path for A/B accuracy comparison.
16076
const bool swap_ab_env_enabled = get_env<int>("DG_SM90_FP4_SWAP_AB", 1) != 0;
161-
const bool default_swap_ab =
162-
swap_ab_env_enabled and
163-
(fp4_flash_shape or fp4_pro_shape) and
164-
expected_tokens_per_expert > 0.0f and expected_tokens_per_expert <= 24.0f;
165-
const bool default_swap_ab_fast_amax =
166-
swap_ab_env_enabled and
167-
fp4_pro_shape and
168-
expected_tokens_per_expert >= 12.0f and expected_tokens_per_expert <= 24.0f;
16977
return {
170-
math_wg_participates_in_decode,
171-
math_wg_participates_in_decode ? 4 : 0,
172-
default_skip_loader_decode_assist ? 2 : 0,
173-
default_wide_load_decode,
174-
default_ss_early_b_decode,
175-
default_decode_done_mbarrier,
176-
default_l2_arrival_counter,
177-
expected_tokens_per_expert >= 64.0f,
178-
default_swap_ab,
179-
default_swap_ab_fast_amax
78+
/*math_wg_participates_in_decode=*/ false,
79+
/*num_math_wg_decode_warps=*/ 0,
80+
/*first_decode_assist_warp=*/ 2,
81+
/*wide_load_decode=*/ decode_band,
82+
/*early_b_decode=*/ prefill_band,
83+
/*decode_done_mbarrier=*/ expected_tokens_per_expert > 0.0f,
84+
/*l2_arrival_counter=*/ false,
85+
/*ss_nsplit=*/ prefill_band,
86+
// Measured crossover on H20: swapAB wins clearly at e<=12, ties at
87+
// e~16 and loses beyond, so the bound is 16 (FP8 uses 30; the FP4
88+
// kernel pays extra decode work on the swapped path).
89+
/*swap_ab=*/ swap_ab_env_enabled and decode_band and
90+
expected_tokens_per_expert < 16.0f,
91+
/*swap_ab_fast_amax=*/ false
18092
};
18193
}
18294

csrc/jit_kernels/heuristics/sm90_mega_moe.hpp

Lines changed: 9 additions & 170 deletions
Original file line numberDiff line numberDiff line change
@@ -144,169 +144,14 @@ static std::tuple<int, int> get_block_config_for_mega_moe_sm90_fp4(
144144
return {block_m, num_epilogue_warpgroups * 128};
145145
}
146146

147-
struct FP4SM90WaveRule {
148-
float min_tokens_per_expert;
149-
float max_tokens_per_expert;
150-
bool include_min;
151-
int required_expert_divisor;
152-
int num_experts_per_wave;
153-
};
154-
155-
enum class FP4SM90StageShape {
156-
Any,
157-
Flash,
158-
Pro,
159-
NotPro,
160-
};
161-
162-
struct FP4SM90StageCapRule {
163-
float min_tokens_per_expert;
164-
float max_tokens_per_expert;
165-
bool include_min;
166-
bool include_max;
167-
FP4SM90StageShape shape;
168-
int num_stages_cap;
169-
};
170-
171-
static bool try_get_num_experts_per_wave_for_sm90_fp4(
172-
const FP4SM90WaveRule* rules, const int& num_rules,
173-
const float& expected_tokens_per_expert, const int& num_experts_per_rank,
174-
int& num_experts_per_wave) {
175-
for (int i = 0; i < num_rules; ++ i) {
176-
const auto& rule = rules[i];
177-
const bool in_lower_bound = rule.include_min
178-
? expected_tokens_per_expert >= rule.min_tokens_per_expert
179-
: expected_tokens_per_expert > rule.min_tokens_per_expert;
180-
if (!in_lower_bound or expected_tokens_per_expert >= rule.max_tokens_per_expert)
181-
continue;
182-
183-
if (rule.num_experts_per_wave == 0) {
184-
if (num_experts_per_rank <= 0)
185-
continue;
186-
num_experts_per_wave = num_experts_per_rank;
187-
return true;
188-
}
189-
DG_HOST_ASSERT(rule.required_expert_divisor > 0);
190-
if (num_experts_per_rank % rule.required_expert_divisor == 0) {
191-
num_experts_per_wave = rule.num_experts_per_wave;
192-
return true;
193-
}
194-
}
195-
return false;
196-
}
197-
198-
static bool fp4_sm90_stage_shape_matches(
199-
const FP4SM90StageShape& shape, const bool& fp4_flash_shape, const bool& fp4_pro_shape) {
200-
switch (shape) {
201-
case FP4SM90StageShape::Any:
202-
return true;
203-
case FP4SM90StageShape::Flash:
204-
return fp4_flash_shape;
205-
case FP4SM90StageShape::Pro:
206-
return fp4_pro_shape;
207-
case FP4SM90StageShape::NotPro:
208-
return !fp4_pro_shape;
209-
}
210-
DG_HOST_ASSERT(false);
211-
return false;
212-
}
213-
214-
static int get_default_num_stages_cap_for_mega_moe_sm90_fp4(
215-
const int& intermediate_hidden, const int& block_m, const int& block_n,
216-
const float& expected_tokens_per_expert) {
217-
if (!(block_m == 64 and block_n == 128)) {
218-
return 0;
219-
}
220-
221-
const bool fp4_flash_shape = intermediate_hidden <= 2048;
222-
const bool fp4_pro_shape = intermediate_hidden >= 3072;
223-
// Ordered first-match rules preserve the historical stage-cap priority.
224-
static constexpr FP4SM90StageCapRule stage_cap_rules[] = {
225-
{6.0f, 12.0f, true, false, FP4SM90StageShape::Flash, 4},
226-
{3.0f, 6.0f, false, false, FP4SM90StageShape::Flash, 4},
227-
{0.0f, 0.25f, false, false, FP4SM90StageShape::Pro, 5},
228-
{0.375f, 0.75f, true, false, FP4SM90StageShape::Pro, 5},
229-
{1.5f, 3.0f, true, false, FP4SM90StageShape::Pro, 5},
230-
{1.0f, 1.5f, true, false, FP4SM90StageShape::Pro, 5},
231-
{24.0f, 64.0f, true, false, FP4SM90StageShape::Pro, 5},
232-
{0.375f, 0.75f, true, false, FP4SM90StageShape::Any, 6},
233-
{3.0f, 6.0f, false, false, FP4SM90StageShape::Flash, 6},
234-
{1.5f, 3.0f, true, false, FP4SM90StageShape::NotPro, 6},
235-
{1.5f, 24.0f, true, true, FP4SM90StageShape::Any, 5},
236-
};
237-
for (const auto& rule: stage_cap_rules) {
238-
const bool in_lower_bound = rule.include_min
239-
? expected_tokens_per_expert >= rule.min_tokens_per_expert
240-
: expected_tokens_per_expert > rule.min_tokens_per_expert;
241-
const bool in_upper_bound = rule.include_max
242-
? expected_tokens_per_expert <= rule.max_tokens_per_expert
243-
: expected_tokens_per_expert < rule.max_tokens_per_expert;
244-
if (in_lower_bound and in_upper_bound and
245-
fp4_sm90_stage_shape_matches(rule.shape, fp4_flash_shape, fp4_pro_shape)) {
246-
return rule.num_stages_cap;
247-
}
248-
}
249-
return 0;
250-
}
251-
252147
static int get_num_experts_per_wave_for_mega_moe_sm90_fp4(
253148
const int& num_experts_per_rank, const int& num_tokens, const int& num_topk,
254149
const int& intermediate_hidden, const int& block_m, const int& block_n, const int& num_sms,
255150
const int& num_ring_tokens, const int& num_max_tokens_per_rank, const int& num_ranks) {
256-
const float expected_tokens_per_expert =
257-
static_cast<float>(num_tokens) * num_topk / num_experts_per_rank;
258-
const bool fp4_small_block_n_kernel =
259-
block_m == 64 and block_n == 128;
260-
const bool fp4_flash_shape = intermediate_hidden <= 2048;
261-
const bool fp4_pro_shape = intermediate_hidden >= 3072;
262-
int fp4_num_experts_per_wave = 0;
263-
if (fp4_small_block_n_kernel and fp4_flash_shape) {
264-
static constexpr FP4SM90WaveRule flash_wave_rules[] = {
265-
{0.75f, 1.0f, true, 16, 16},
266-
{1.5f, 2.0f, true, 16, 16},
267-
{3.0f, 6.0f, true, 16, 16},
268-
{6.0f, 12.0f, true, 32, 32},
269-
{6.0f, 12.0f, true, 8, 8},
270-
{24.0f, 32.0f, true, 16, 16},
271-
{12.0f, 24.0f, true, 32, 32},
272-
{12.0f, 24.0f, true, 8, 8},
273-
{32.0f, 64.0f, true, 16, 16},
274-
};
275-
if (try_get_num_experts_per_wave_for_sm90_fp4(
276-
flash_wave_rules,
277-
static_cast<int>(sizeof(flash_wave_rules) / sizeof(flash_wave_rules[0])),
278-
expected_tokens_per_expert, num_experts_per_rank,
279-
fp4_num_experts_per_wave)) {
280-
return fp4_num_experts_per_wave;
281-
}
282-
}
283-
if (fp4_small_block_n_kernel and fp4_pro_shape) {
284-
static constexpr FP4SM90WaveRule pro_wave_rules[] = {
285-
{0.0f, 0.25f, false, 16, 16},
286-
{0.25f, 0.375f, true, 16, 16},
287-
{0.375f, 0.75f, true, 16, 16},
288-
{0.25f, 1.0f, true, 24, 24},
289-
{1.0f, 1.5f, true, 1, 0},
290-
{1.5f, 3.0f, true, 16, 16},
291-
{3.0f, 6.0f, true, 8, 8},
292-
{6.0f, 12.0f, true, 16, 16},
293-
{6.0f, 12.0f, true, 8, 8},
294-
{12.0f, 24.0f, true, 24, 24},
295-
{12.0f, 24.0f, true, 8, 8},
296-
{24.0f, 64.0f, true, 8, 8},
297-
};
298-
if (try_get_num_experts_per_wave_for_sm90_fp4(
299-
pro_wave_rules,
300-
static_cast<int>(sizeof(pro_wave_rules) / sizeof(pro_wave_rules[0])),
301-
expected_tokens_per_expert, num_experts_per_rank,
302-
fp4_num_experts_per_wave)) {
303-
return fp4_num_experts_per_wave;
304-
}
305-
}
306-
if (expected_tokens_per_expert < 1.0f or expected_tokens_per_expert > 4.0f) {
307-
return num_experts_per_rank;
308-
}
309-
return get_num_experts_per_wave_for_mega_moe(
151+
// Simplified: schedule FP4 expert waves exactly like the FP8 path. The
152+
// historical flash/pro wave tables (9 + 12 first-match rules) were tuned
153+
// point-by-point on benchmark batches and are retired.
154+
return get_num_experts_per_wave_for_mega_moe_sm90(
310155
num_experts_per_rank, num_tokens, num_topk,
311156
intermediate_hidden, block_m, block_n, num_sms,
312157
num_ring_tokens, num_max_tokens_per_rank, num_ranks);
@@ -319,7 +164,6 @@ static std::pair<int, int> get_pipeline_config_for_mega_moe_sm90_fp4(
319164
const int& num_dispatch_warps, const int& num_epilogue_warps,
320165
const bool& use_early_b_decode = false,
321166
const bool& use_decode_done_mbarrier = false,
322-
const int& default_num_stages_cap = 0,
323167
const bool& use_swap_ab = false,
324168
const bool& use_swap_ab_fast_amax = false) {
325169
constexpr int kSmemAlignment = 1024;
@@ -380,12 +224,10 @@ static std::pair<int, int> get_pipeline_config_for_mega_moe_sm90_fp4(
380224
const int smem_fixed =
381225
smem_dispatch_size + smem_cd + smem_amax_scratch + smem_barriers_fixed;
382226

383-
const int max_num_stages = (smem_capacity - smem_fixed) /
384-
(smem_per_stage + smem_barriers_per_stage);
385-
int num_stages = max_num_stages;
386-
if (default_num_stages_cap > 0) {
387-
num_stages = std::min(num_stages, default_num_stages_cap);
388-
}
227+
// No FP4 stage cap (FP8 parity): always use as many pipeline stages as
228+
// SMEM allows. The historical 11-rule cap table is retired.
229+
const int num_stages = (smem_capacity - smem_fixed) /
230+
(smem_per_stage + smem_barriers_per_stage);
389231
DG_HOST_ASSERT(num_stages >= 2);
390232
return {num_stages,
391233
smem_fixed + num_stages * (smem_per_stage + smem_barriers_per_stage)};
@@ -461,15 +303,12 @@ static MegaMoESM90Config get_mega_moe_config_sm90_fp4(
461303
num_non_epilogue_threads % 64 == 0);
462304
DG_HOST_ASSERT((num_dispatch_threads + num_non_epilogue_threads) % 128 == 0);
463305

464-
const int default_num_stages_cap = get_default_num_stages_cap_for_mega_moe_sm90_fp4(
465-
intermediate_hidden, block_m, block_n, expected_tokens_per_expert);
466-
467306
const auto [num_stages, smem_size] = get_pipeline_config_for_mega_moe_sm90_fp4(
468307
SM90ArchSpec::smem_capacity,
469308
num_experts, hidden,
470309
block_m, block_n, block_k,
471310
num_dispatch_threads / 32, fp4_num_epilogue_threads / 32,
472-
use_early_b_decode, use_decode_done_mbarrier, default_num_stages_cap,
311+
use_early_b_decode, use_decode_done_mbarrier,
473312
use_swap_ab, use_swap_ab_fast_amax);
474313

475314
const auto config = MegaMoESM90Config {

0 commit comments

Comments
 (0)