[FlyDSL] [Tune] Retune the Kimi-K3 a16w4 MoE tile geometry per token bucket - #5118
Draft
amd-wsung102 wants to merge 2 commits into
Draft
[FlyDSL] [Tune] Retune the Kimi-K3 a16w4 MoE tile geometry per token bucket#5118amd-wsung102 wants to merge 2 commits into
amd-wsung102 wants to merge 2 commits into
Conversation
Contributor
🏷️ CI GuideRuns automatically on every PR:
Extended tests (opt-in via labels):
PR title tags: |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
This PR retunes the tile geometry of the Kimi-K3 a16w4 (bf16 activation × MXFP4 weight) SiTUv2 expert GEMMs, on a different axis for each end of the token range:
token <= 512): halve the M-block, 32 → 16. With 896 experts and top-16 routing a decode batch spreads ~2k routed rows over ~800 distinct experts — about 2.5 real rows per expert.moe_sortingpads every expert up to a fullblock_m, so atblock_m=32roughly 92% of the rows the two GEMMs process are padding. Halving the M quantum halves that waste.token >= 2048): widen the N-tile. Pick the largest gemm1tile_nthat dividesinter_dimexactly, up to 4 accumulator groups — 192 forinter_dim=384, 256 for 512 — collapsingNUM_N_BLOCKS3→2 and 4→2 and cutting both the A-gather traffic and the CTA count. gemm2 moves 128 → 256.In both cases the kernel already supported the value and what was missing was the ability to select it.
get_flydsl_stage1_kernelsonly put 16 intile_msfora_dtype=="fp8", andtile_n192/256 were absent attile_m=32entirely, so no a16w4 name existed for a tuned CSV row to reference and the tuner never measured either. No kernel math is modified — the only edits inside the two kernel files are compile-time asserts.Performance improvement:
Relevant Files
aiter/configs/model_configs/kimik3_fp4_tuned_fmoe.csv—block_m32→16 on the 11inter_dim=384,token<=512rows; N-tiles widened on the 8token>=2048rows of bothinter_dimfamilies; 25 inert_w{n}and 21 inert_persistsuffixes droppedaiter/ops/flydsl/moe_kernels.py— registertile_m=16andtile_n192/256 stage-1 names for a16w4; lettile_m=16sharetile_m=32's tile-N and k-wave sets on the a16w port; forwardwaves_per_euinstead of hardcodingNone; tighten the int4 registry's wave-partition filteraiter/ops/flydsl/kernels/moe_2stage_a16wmix/gemm1.py— assert the per-wave column count is a multiple of 16 (+8 lines)aiter/ops/flydsl/kernels/moe_2stage_a16wmix/gemm2.py— assertTILE_Nis a multiple of 64 (+6 lines)4 files, +96 / −48. Scope: gfx950, a16w4 SiTUv2,
model_dim=3584, E=896, top-16.inter_dim=384(TP=8) for the M-block change; both 384 and 512 for the N-tile change. Validated on MI355X (gfx950, 256 CU), which is thegfx950,256key the CSV is written against.Optimizations Applied
Decode — M-block 32 → 16 on
token <= 512block_m=16wins for every bucket from 1 to 512 tokens (1.17–1.38× on the isolated GEMMs) and loses from 1024 up (0.85–0.98×). Becauseblock_mis a per-bucket CSV field there is no runtime coupling between the decode and prefill configurations.t16stage-1 names for a16w4.tile_msgains 16 when the weight is fp4 and the activation is bf16, matching the existing fp8 case. Strictly additive: the a16w4 stage-1 registry grows,fp4×fp4/fp8×fp8/fp8×fp4are bit-identical, and every kernel name in the shipped CSVs still resolves.tile_m=16sharestile_m=32's tile-N and k-wave sets on the a16w port. The tiny-batch decode configs use narrow N-tiles (t*x32/t*x64) andk_wave2/4; both are legal atBM=16(m_repeat <= 2either way, and the port takestile_nas given) and they are needed to name buckets 1–4. Guarded tois_a16w4.Prefill — N-tile widening on
token >= 2048tile_nthat dividesinter_dim, capped at 4 accumulator groups.pick_flydsl_stage1_tile_nonly ever returns 256 or 128, and for a non-256-alignedinter_dimit silently forces 128 while warning that 256 "is NOT tunable for such shapes" — so the values that divide exactly were never considered. Legal only on the a16w port, which takestile_nas given; the other fp4 dtypes route throughresolve_flydsl_stage1_tile_n, which would force the tile back to 128 and make the kernel name a lie. Registered for a16w4 only.tile_n=384(6 accumulator groups) and 512 (8) both lose to register pressure — 512 is 0.56× (2448.1 vs 1361.6 µs at token 8192). The occupancy loss swamps the A-traffic saving.tile_nchanges:xcd_swizzle=1beats 0 and 4 (1057.8 vs 1182.4 / 1066.8 µs),b_nt=0beats 2 (1057.8 vs 1221.8 µs), andblock_m=32remains optimal at prefill.Performance
Measured on MI355X (gfx950, 256 CU). Kimi-K3 routed-expert geometry at TP=8:
model_dim=3584,inter_dim=384, E=896, top-16, SiTUv2, bf16 × MXFP4. Uniform random top-16 routing. Baseline ismain; candidate is this branch.Isolated gemm1 + gemm2 pair
Each arm timed at its own production config (
block_m, tile_n/k,b_nt,xcd_swizzle,k_wave), one bucket per process.Whole MoE layer through the production dispatch
moe_sorting+ stage1 + stage2, routing excluded, per layer per GPU.The 1024 row is the control: it resolves
block_m=32and the unchanged N-tiles under both arms and moves within noise (1.000× isolated, 0.997× at layer level), confirming the two halves are additive across the token range rather than interacting at any bucket.At 1–8 tokens the win disappears because topk/sort/launch overhead is ~110 µs and swamps the GEMMs; the gain materialises from roughly 32 tokens up, which covers the serving range.
Kimi-K3 end-to-end benchmark:
mainvskimi_k3_tile_m_blockKimi-K3, 8x gfx950, TP8,
kv_cache_dtype=fp8,block-size=128, prefix caching off. Workload: 8192 input / 1024 output, 128 prompts @ concurrency 128,request-rate=inf,--ignore-eos. 3 runs per branch; run 1 excluded as cold start. Profiling enabled on both branches.Steady-state comparison (mean of runs 2-3)
All six runs, as reported
Accuracy
The N-tile half is bitwise identical.
tile_nonly changes how the N axis is blocked across workgroups and waves; it does not change the K-reduction order. Cross-checking every legaltile_nagainst 128 atinter_dim=384:(
tile_n=96is the latent hole this PR closes; it is now rejected at compile time.tile_n=256is likewise bitwise identical atinter_dim=512.)The M-block half cannot be bitwise identical, because changing
block_mchanges the order of the bf16 atomic scatter in stage 2. Two checks were used:block_m=16vsblock_m=32, identical activations, weights and routing: cosine 1.0000 at every token count in the sweep above.fused_moeoutput vs a savedblock_m=32reference: cosine 0.999983 – 0.999998, relative L2 2–6e-3. The control matters here: the unchanged 1024 and 8192 buckets show the same ~2e-3 relative L2 as the changed ones, so that residual is the stage-2 atomic accumulation's inherent run-to-run non-determinism, not an error introduced byblock_m.End-to-end through the production
get_2stage_cfgs→fused_moe_2stagesdispatch against a bf16 SiTUv2 torch reference, cos/logits_diff, baseline vs this branch:Identical to four significant figures at every prefill point.
op_tests/flydsl_tests/test_flydsl_moe_a16wfp4.pypass (2 shapes × 3 token counts × 2 SiTUv2 beta pairs) atcos_diff < 1e-2.kernelName1/kernelName2entries parse against the registry; no residual_w{n}or_persistnames remain.get_2stage_cfgsverified to select the intendedblock_mand tiles at every bucket, including the unchangedt32x128x128at 1024.TILE_Nguards verified to reject 96 (gemm1) and 112 (gemm2), accept 192/256.Submission Checklist