Skip to content

Commit 21bb7b1

Browse files
Fangzhou-Aicodex
andcommitted
[Perf] Tune DeepSeek V4 fused MoE for C32/C64 decode
Enable 16-row FP8-activation/FP4-weight FlyDSL kernels for the gfx950 DeepSeek V4 7168/512/E385/top-k7 sparse-decode shape, selecting them only for the C32 and C64 production rows. Preserve the shuffled activation-scale buffer physical allocation by rounding descriptor bounds to 32 rows. This adds no serving environment knob, dependency, or public API. The commit is based directly on AITER main 45ef6f3 with FlyDSL 0.2.4. C4, C16, and C128+ rows are unchanged. The unrelated mixed-MoE XCD remap fix is intentionally excluded because both selected BM16 kernels use XCD0. Kernel efficiency was remeasured after rebasing. Both sides use the same main-based source, compiler, full production routes, live non-unit activation scales, and full-size weights; only the upstream-main BM32 versus proposed BM16 configuration rows differ. Per-stage HIP event timing excludes sorting and quantization and uses 200 warmups, 1,000 iterations, and three repeats. At C32, stage 1 improves from 93.5605 to 54.3600 us (-41.90%), stage 2 from 48.6800 to 27.7200 us (-43.06%), and combined stages from 142.2405 to 82.0800 us (-42.29%, 1.73x). At C64, stage 1 improves from 161.8010 to 86.8410 us (-46.33%), stage 2 from 87.9210 to 42.5210 us (-51.64%), and combined stages from 249.7220 to 129.3620 us (-48.20%, 1.93x). Every per-stage repeat range is below 0.3%. Repeated stage outputs are bitwise stable and explicit two-stage output is bitwise identical to fused output. Exact route replay is bitwise identical to the upstream-main BM32 row at C32 and to the safe BM32/XCD0 control at C64. The upstream C64 row uses an unsafe non-divisible XCD4 mapping, while the selected BM16 route uses XCD0. Historical serving measurements used AITER release/v0.1.16.post4 plus the preceding XCD correctness change, FlyDSL 0.2.2, and vLLM 0aa67bb9b on eight gfx950 GPUs with DeepSeek-V4-Pro TP8, shared-expert fusion, FP8 KV cache, and a random 8K-input/1K-output workload. C32 output throughput improved from 754.678 to 816.933 tok/s (+8.249%), mean TPOT from 40.0946 to 36.8640 ms (-8.057%), and p99 TPOT from 46.6609 to 43.2632 ms (-7.282%). C64 output throughput improved from 988.157 to 1074.459 tok/s (+8.734%), mean TPOT from 61.578 to 56.418 ms (-8.380%), and p99 TPOT from 72.778 to 67.272 ms (-7.566%). All 320 and 640 requests completed. These are post4 results, not an A/B claim against current main; C64 was the accepted-row selection run rather than a rerun of the combined final configuration. Full GSM8K validation of the same BM16 implementation completed all 1,319 samples: strict match 1258/1319 (95.375%) and flexible extraction 1257/1319 (95.299%), both above the required 94% threshold. AI assistance was used to implement, test, benchmark, and document this change. Co-authored-by: OpenAI Codex <codex@openai.com> Signed-off-by: Fangzhou Ai <fangzhouai@gmail.com>
1 parent 45ef6f3 commit 21bb7b1

4 files changed

Lines changed: 105 additions & 12 deletions

File tree

aiter/configs/model_configs/dsv4_fp8fp4_tuned_fmoe.csv

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -44,8 +44,8 @@ gfx950,256,2,7168,512,385,7,ActivationType.Silu,torch.bfloat16,torch.float8_e4m3
4444
gfx950,256,4,7168,512,385,7,ActivationType.Silu,torch.bfloat16,torch.float8_e4m3fn,torch.float4_e2m1fn_x2,QuantType.per_1x32,1,0,32,0,21.9888,flydsl_moe1_afp8_wfp4_bf16_t32x128x256_gui_fp8,0.0%,17.2479,flydsl_moe2_afp8_wfp4_bf16_t32x256x256_atomic_bnt2_xcd4,0.0%,39.2367,0,0,0,15.71,108035.45,
4545
gfx950,256,8,7168,512,385,7,ActivationType.Silu,torch.bfloat16,torch.float8_e4m3fn,torch.float4_e2m1fn_x2,QuantType.per_1x32,1,0,32,0,37.0281,flydsl_moe1_afp8_wfp4_bf16_t32x128x256_w2_gui_fp8,0.0%,25.8309,flydsl_moe2_afp8_wfp4_bf16_t32x128x256_atomic_bnt2_persist,0.0%,62.859,0,0,0,19.62,67437.29,
4646
gfx950,256,16,7168,512,385,7,ActivationType.Silu,torch.bfloat16,torch.float8_e4m3fn,torch.float4_e2m1fn_x2,QuantType.per_1x32,1,0,32,0,71.2231,flydsl_moe1_afp8_wfp4_bf16_t32x128x256_w2_gui_fp8,0.0%,41.8664,flydsl_moe2_afp8_wfp4_bf16_t32x128x256_atomic_bnt2,0.0%,113.0895,0,0,0,21.81,37485.47,
47-
gfx950,256,32,7168,512,385,7,ActivationType.Silu,torch.bfloat16,torch.float8_e4m3fn,torch.float4_e2m1fn_x2,QuantType.per_1x32,1,0,32,0,134.8852,flydsl_moe1_afp8_wfp4_bf16_t32x128x256_w3_gui_xcd4_fp8,0.0%,71.8893,flydsl_moe2_afp8_wfp4_bf16_t32x256x256_atomic_bnt2_xcd4_persist,0.0%,206.7745,0,0,0,23.85,20503.29,
48-
gfx950,256,64,7168,512,385,7,ActivationType.Silu,torch.bfloat16,torch.float8_e4m3fn,torch.float4_e2m1fn_x2,QuantType.per_1x32,1,0,32,0,232.2826,flydsl_moe1_afp8_wfp4_bf16_t32x128x256_w3_gui_xcd4_fp8,0.0%,119.2028,flydsl_moe2_afp8_wfp4_bf16_t32x256x256_atomic_bnt2_xcd4,0.0%,351.4854,0,0,0,28.07,12063.79,
47+
gfx950,256,32,7168,512,385,7,ActivationType.Silu,torch.bfloat16,torch.float8_e4m3fn,torch.float4_e2m1fn_x2,QuantType.per_1x32,1,0,16,0,54.3600,flydsl_moe1_afp8_wfp4_bf16_t16x128x256_w3_gui_fp8,0.0%,27.7200,flydsl_moe2_afp8_wfp4_bf16_t16x256x256_atomic,0.0%,82.0800,0,0,0,60.09,51651.52,
48+
gfx950,256,64,7168,512,385,7,ActivationType.Silu,torch.bfloat16,torch.float8_e4m3fn,torch.float4_e2m1fn_x2,QuantType.per_1x32,1,0,16,0,86.8410,flydsl_moe1_afp8_wfp4_bf16_t16x128x256_w3_gui_fp8,0.0%,42.5210,flydsl_moe2_afp8_wfp4_bf16_t16x256x256_atomic,0.0%,129.3620,0,0,0,76.26,32778.13,
4949
gfx950,256,128,7168,512,385,7,ActivationType.Silu,torch.bfloat16,torch.float8_e4m3fn,torch.float4_e2m1fn_x2,QuantType.per_1x32,1,0,32,0,233.1225,flydsl_moe1_afp8_wfp4_bf16_t32x128x256_w3_gui_fp8,0.0%,121.6478,flydsl_moe2_afp8_wfp4_bf16_t32x256x256_atomic_bnt2_xcd4,0.0%,354.7703,0,0,0,55.61,11955.96,
5050
gfx950,256,256,7168,512,385,7,ActivationType.Silu,torch.bfloat16,torch.float8_e4m3fn,torch.float4_e2m1fn_x2,QuantType.per_1x32,1,0,32,0,233.9255,flydsl_moe1_afp8_wfp4_bf16_t32x128x256_w4_gui_fp8,0.0%,127.8476,flydsl_moe2_afp8_wfp4_bf16_t32x256x256_atomic_bnt2_xcd4,0.0%,361.7731,0,0,0,109.07,11732.14,
5151
gfx950,256,512,7168,512,385,7,ActivationType.Silu,torch.bfloat16,torch.float8_e4m3fn,torch.float4_e2m1fn_x2,QuantType.per_1x32,1,0,32,0,236.6982,flydsl_moe1_afp8_wfp4_bf16_t32x128x256_w3_gui_fp8,0.0%,136.2029,flydsl_moe2_afp8_wfp4_bf16_t32x256x256_atomic_bnt2_persist,0.0%,372.9011,0,0,0,211.64,11396.8,

aiter/ops/flydsl/kernels/mixed_moe_gemm_2stage.py

Lines changed: 13 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -112,7 +112,7 @@ def compile_mixed_moe_gemm1(
112112
is_f4_b = b_dtype == "fp4"
113113
is_f8_b = b_dtype == "fp8"
114114

115-
sort_block_m = max(32, tile_m)
115+
sort_block_m = tile_m
116116
num_waves = min(4, tile_n // 32)
117117
# accumulators are reduced in LDS before the epilogue. k_wave=1 keeps the
118118
num_n_waves = num_waves
@@ -559,7 +559,12 @@ def ptr_buffer_resource(ptr, num_records_bytes):
559559
if const_expr(not a_scale_one):
560560
c32 = arith.constant(32, index=True)
561561
kblk = k_in // c32
562-
sx_nbytes_idx = sorted_m * kblk
562+
scale_rows = (
563+
(sorted_m + arith.constant(31, index=True))
564+
// arith.constant(32, index=True)
565+
* arith.constant(32, index=True)
566+
)
567+
sx_nbytes_idx = scale_rows * kblk
563568
sx_nbytes_i32 = arith.index_cast(T.i32, sx_nbytes_idx)
564569
sx_rsrc = ptr_buffer_resource(arg_scale_x, sx_nbytes_i32)
565570

@@ -3296,7 +3301,12 @@ def check_c_k_valid_gate(base_k):
32963301
if const_expr(is_f4_a or is_f8_a):
32973302
# #3476: use 256-padded K/32 to match host scale padding.
32983303
kblk = arith.constant(scale_kblk_padded, index=True)
3299-
sx_nbytes_idx = num_valid_idx * kblk
3304+
scale_rows = (
3305+
(num_valid_idx + arith.constant(31, index=True))
3306+
// arith.constant(32, index=True)
3307+
* arith.constant(32, index=True)
3308+
)
3309+
sx_nbytes_idx = scale_rows * kblk
33003310
sx_nbytes_i32 = arith.index_cast(T.i32, sx_nbytes_idx)
33013311
sx_rsrc = ptr_buffer_resource(arg_scale_x, sx_nbytes_i32)
33023312
else:

aiter/ops/flydsl/moe_kernels.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -82,7 +82,7 @@ def get_flydsl_stage1_kernels(
8282

8383
tile_ns = [32, 64, 128] if is_fp4_b else [128]
8484
tile_ks = [256]
85-
tile_ms = [32, 64, 128]
85+
tile_ms = [16, 32, 64, 128] if a_dtype == "fp8" and is_fp4_b else [32, 64, 128]
8686

8787
waves_per_eus = [1, 2, 3, 4]
8888
k_batches = [1, 2, 4, 7, 14]

op_tests/test_moe_2stage.py

Lines changed: 89 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -480,15 +480,18 @@ def weight_per_128x128_quant(weight, quant_dtype):
480480
"us_stage2": us2_stage,
481481
}
482482

483+
test_graph = args.bm16_scale_boundary
483484
out2_ck, us2 = run_perftest(
484485
fused_moe,
485486
input,
486487
w1_qt_aiter,
487488
w2_qt_aiter,
488489
topk_weights,
489490
topk_ids,
490-
num_iters=5,
491-
num_warmup=2,
491+
num_iters=2 if test_graph else 5,
492+
num_warmup=1 if test_graph else 2,
493+
num_rotate_args=1 if test_graph else 0,
494+
testGraph=test_graph,
492495
**_fused_moe_kwargs,
493496
)
494497
# Regression guard for aiter #3117 (MXFP4 fused-MoE stage2 EP-prefill):
@@ -688,6 +691,11 @@ def calc_diff(x: torch.Tensor, y: torch.Tensor):
688691
action="store_true",
689692
help="Skip the original hardcoded shape sweep and skinny tests.",
690693
)
694+
parser.add_argument(
695+
"--bm16-scale-boundary",
696+
action="store_true",
697+
help="Run only the deterministic BM16 tiled-scale boundary regression.",
698+
)
691699
parser.add_argument(
692700
"--swiglu-limit",
693701
"-sl",
@@ -1030,14 +1038,89 @@ def _kw(
10301038
), extras
10311039

10321040

1041+
def test_bm16_tiled_scale_boundary():
1042+
"""Validate tuned BM16 dispatch and the 33-row scale boundary."""
1043+
if get_gfx() != "gfx950":
1044+
aiter.logger.info("skip BM16 tiled-scale boundary test on %s", get_gfx())
1045+
return
1046+
1047+
from aiter.ops.flydsl.moe_kernels import get_flydsl_kernel_params
1048+
1049+
expected_kernels = (
1050+
"flydsl_moe1_afp8_wfp4_bf16_t16x128x256_w3_gui_fp8",
1051+
"flydsl_moe2_afp8_wfp4_bf16_t16x256x256_atomic",
1052+
)
1053+
for token in (32, 64):
1054+
metadata = get_2stage_cfgs(
1055+
get_padded_M(token),
1056+
7168,
1057+
512,
1058+
385,
1059+
7,
1060+
torch.bfloat16,
1061+
dtypes.fp8,
1062+
dtypes.fp4x2,
1063+
aiter.QuantType.per_1x32,
1064+
True,
1065+
aiter.ActivationType.Silu,
1066+
False,
1067+
0,
1068+
0,
1069+
True,
1070+
GateMode.INTERLEAVE.value,
1071+
)
1072+
assert metadata.block_m == 16
1073+
stages = (metadata.stage1, metadata.stage2)
1074+
kernel_names = tuple(stage.keywords["kernelName"] for stage in stages)
1075+
assert kernel_names == expected_kernels
1076+
stage1_params = get_flydsl_kernel_params(kernel_names[0])
1077+
stage2_params = get_flydsl_kernel_params(kernel_names[1])
1078+
assert stage1_params is not None
1079+
assert stage2_params is not None
1080+
assert stage1_params["tile_m"] == 16
1081+
assert stage1_params["waves_per_eu"] == 3
1082+
assert stage2_params["tile_m"] == 16
1083+
assert stage2_params["tile_n"] == 256
1084+
assert stage2_params.get("xcd_swizzle", 0) == 0
1085+
1086+
old_moe_bound = os.environ.get("AITER_BF16_FP8_MOE_BOUND")
1087+
os.environ["AITER_BF16_FP8_MOE_BOUND"] = "0"
1088+
torch.manual_seed(0)
1089+
try:
1090+
test_fmoe(
1091+
dtype=torch.bfloat16,
1092+
token=33,
1093+
model_dim=7168,
1094+
inter_dim=512,
1095+
E=385,
1096+
topk=7,
1097+
actType=aiter.ActivationType.Silu,
1098+
gateMode=GateMode.INTERLEAVE.value,
1099+
qType=aiter.QuantType.per_1x32,
1100+
AQDType=dtypes.fp8,
1101+
WQDType=dtypes.fp4x2,
1102+
use_g1u1=True,
1103+
strict_accuracy=True,
1104+
check_aot_cache=False,
1105+
)
1106+
finally:
1107+
if old_moe_bound is None:
1108+
os.environ.pop("AITER_BF16_FP8_MOE_BOUND", None)
1109+
else:
1110+
os.environ["AITER_BF16_FP8_MOE_BOUND"] = old_moe_bound
1111+
1112+
10331113
# ---------------------------------------------------------------------------
10341114
# Run
10351115
# ---------------------------------------------------------------------------
10361116
_case_iters = []
1037-
if not args.no_flydsl_csv:
1038-
_case_iters.append(_iter_csv_cases())
1039-
if not args.no_legacy:
1040-
_case_iters.append(_iter_legacy_cases())
1117+
if args.bm16_scale_boundary:
1118+
test_bm16_tiled_scale_boundary()
1119+
else:
1120+
if not args.no_flydsl_csv:
1121+
_case_iters.append(_iter_csv_cases())
1122+
if not args.no_legacy:
1123+
_case_iters.append(_iter_legacy_cases())
10411124
case_iter = itertools.chain(*_case_iters)
10421125

10431126
_csv_out = os.environ.get("AITER_TUNED_OP_BENCH_CSV", "tuned_op_bench.csv")

0 commit comments

Comments
 (0)