Skip to content

Use the FlyDSL BF16 GEMM by default for large untuned gfx1250 shapes - #6397

Open
jhinpan wants to merge 8 commits into
mainfrom
jhinpan/gfx1250-bf16-flydsl-default
Open

jhinpan wants to merge 8 commits into
mainfrom
jhinpan/gfx1250-bf16-flydsl-default

Conversation

@jhinpan

@jhinpan jhinpan commented Oct 11, 2026 •

Copy link
Copy Markdown
Contributor

Refs #6345, #6402.

On gfx1250, a BF16 GEMM without a tuned row runs on Triton (gluon). Where gluon has no config for the shape's (N, K), it runs its DEFAULT.json config. get_GEMM_A16W16_config now picks AITER's FlyDSL gfx1250 kernel with a fixed config for such a shape when all of these hold:

  • gfx1250, BF16 input, BF16 or FP32 output, no scales, no bias with FP32 output;
  • M ≥ 512, N ≥ 4096, K ≥ 512, and M ≤ 1024 or M > 4096 (1024 < M ≤ 4096 too when C is larger than 2^31 bytes);
  • no GEMM-A16W16-N={N}-K={K}.json among the gfx1250 gluon configs;
  • the last M tile of C, padded to the tile height, ends below byte 2^32.

M ≤ 768 gets 128x128x64 tiles with 4 stages and 2x2 waves, larger M gets 256x128x64 with 4 stages and 4x2 waves. Other untuned shapes without their own gluon config run on Triton's default config when C fits in 2^31 bytes, and on torch otherwise. Tuned rows still win because they are matched first. Only gfx1250 changes.

Why the limits

  • Triton's default config leaves the rows of C past byte 2^31 unwritten, and FlyDSL overwrites the first rows of C once its padded last M tile passes byte 2^32 ([gfx1250] BF16 GEMM: Triton gemm_a16w16 leaves the rows of C past byte 2^31 unwritten #6402). torch is right on both, but with BF16 output it runs at about 145 TFLOPS on such shapes, against FlyDSL's 1,600 to 1,700.
  • For 1024 < M ≤ 4096, the M_LEQ_2048 and M_LEQ_4096 buckets of the retuned DEFAULT.json ([Triton/Gluon] [Config] Tune gfx1250 Gluon a16w16 GEMMs for GPT-OSS 120B #6549) are as fast as these FlyDSL configs.
  • Below M = 512 or N = 4096 the fixed tiles leave CUs idle, and with K < 512 the 4-stage pipeline does not get enough K tiles.
  • flydsl_gemm adds a bias it cannot fuse in the bias dtype, so a BF16 bias rounded an FP32 output (max row rel-L2 0.0023, against 0.0000 on Triton).
  • Where gluon has a config for the (N, K), Triton can win, so FlyDSL only replaces the default config. The check reads the file, not get_gemm_config's is_tuned: these files cover most M through their any entry, for which is_tuned is false.

Measurements

gfx1250 B0 (heliosr-1b114-d04-1, ASIC revision 1), lmsysorg/sglang-rocm:v0.5.20-rocm10-mi45x-20260921 (ROCm 10.0.0, torch 2.11.0, Triton 3.8.0), FlyDSL 0.4.0, main c118faf. Each kernel called the way tuned_gemm calls it, same inputs in one process, median of 7 interleaved rounds, 3 repeats in separate runs. A shape counts as slower only if every repeat is above 1.0081, the noise of this GEMM on this host. Every output was checked row by row against FP32. The shapes are the 98 (N, K, bias, output dtype) combinations of the BF16 untuned CSVs with N ≥ 4096, K ≥ 512 and no gluon config, without the ones that have a tuned row.

FlyDSL / Triton's default config:

M Shapes Geomean (range) Slower in every repeat
512 92 0.748 (0.572-1.102) 3
640 92 0.686 (0.501-1.069) 1
1000 92 0.645 (0.522-0.947) 0
1536 92 1.029 (0.862-1.278) 40
2048 92 1.015 (0.806-1.270) 35
3000 92 0.964 (0.823-1.176) 19
4096 92 0.941 (0.815-1.099) 13
6000 91 0.877 (0.642-1.076) 2
8192 90 0.854 (0.615-1.002) 0
12288 87 0.861 (0.655-1.002) 0
16384 86 0.851 (0.662-0.975) 0

The M range comes from this table, so I checked it again at M values it was not chosen from:

M Shapes This PR picks FlyDSL / Triton, geomean (range) Slower in every repeat
896 92 FlyDSL 0.708 (0.532-1.111) 1
1100 92 Triton 1.084 (0.839-1.843) 57
1800 92 Triton 1.057 (0.843-1.272) 60
2600 92 Triton 1.030 (0.857-1.356) 36
3500 92 Triton 0.945 (0.790-1.082) 12
4500 91 FlyDSL 0.875 (0.596-1.050) 4
5000 91 FlyDSL 0.885 (0.613-1.095) 7
7000 91 FlyDSL 0.898 (0.657-1.092) 5
10000 89 FlyDSL 0.859 (0.647-1.021) 1

Between M = 4096 and 8192, up to 8% of the shapes stay slower on FlyDSL, by at most 1.10x. They have N ≤ 9216 and K ≥ 6144, for example 5120 x 17408 and 5120 x 25600. At M = 640, the 128x128 config took 0.84 of the time of 256x128; at M = 1000, 1.00.

Large outputs

Wrong rows against FP32, output memory filled with NaN first. Kernels on main c118faf; the last column is this PR's dispatch at 27670ca:

M x N x K C (GB) Triton default config FlyDSL 128 FlyDSL 256 torch This PR
7068 x 151936 x 4096 2.15 1, from row 7067 0 0 0 FlyDSL, 0
14080 x 151936 x 4096 4.28 7013, from row 7067 0 0 0 FlyDSL, 0
14081 x 151936 x 4096 4.28 7014, from row 7067 74, from row 0 202, from row 0 0 torch, 0
16384 x 151936 x 4096 4.98 11567, from row 0 4500, from row 0 4500, from row 0 0 torch, 0
21249 x 50016 x 6144, FP32 4.25 10516, from row 10733 0 37, from row 0 0 torch, 0
7068 x 151936 x 256 2.15 1, from row 7067 0 torch, 0
3534 x 151936 x 4096, FP32 2.15 1, from row 3533 0 FlyDSL, 0
8192 x 151936 x 4096, FP32, bias 4.98 5784 0 torch, 0

The MI450 A0 benchtop gives the same rows. torch takes 141 ms on 16384 x 151936 x 4096; moving C's buffer base per tile in both kernels (#6402) would let them write such outputs.

DeepSeek shapes on gfx1250

I replayed the dispatch on every (N, K) of the DeepSeek CSVs in aiter/configs/model_configs/ (dsv4, dsv41, dsr1, dsv3) with the gfx1250 tuned rows (cu_num 256) and the gluon configs. Four (N, K) reach the FlyDSL default; the others have a tuned row, a gluon config, or N < 4096. DeepSeek-V4-Flash on one GPU does not change: its lm_head (129280 x 4096) has a gluon config. Every row of these shapes is right through the new dispatch, and FlyDSL / Triton is:

N x K CSV M = 512 768 1024 4224 6000 8192 16384
64640 x 4096 dsv4 0.605 0.556 0.590 0.881 0.880 0.866 0.869
32320 x 4096 dsv4 0.659 0.581 0.627 0.920 0.918 0.867 0.877
32320 x 5120 dsv41 0.660 0.588 0.597 0.950 0.921 0.897 0.896
32768 x 512 dsr1 0.627 0.612 0.605 0.589

At M = 2048 these shapes stay on Triton (FlyDSL took 0.95 to 0.98 there). 32768 x 512 reaches the default above M = 4096 because its gfx1250 tuned rows stop at M = 6144 and come in steps of 512 above 4096.

Tests

TestGfx1250DefaultDispatch mocks gfx1250 and checks each branch: both FlyDSL tiles, the M range, both sides of FlyDSL's 2^32 limit (14080 / 14081 x 151936 x 4096) and of the 2^31 limit of Triton's default config (7067 / 7068 x 151936), and bias with FP32 output. It runs in the nightly Tuning Tests workflow, not in the PR checks. It passes at 27670ca on the B0 host above, and its new cases fail on the previous heads.

PR Self-Review

Reviewed revision

6b05290

Behavior and intent

On gfx1250, a BF16 GEMM without a tuned row and without a gluon config for its (N, K) ran Triton's DEFAULT.json config. get_GEMM_A16W16_config now gives such a shape AITER's FlyDSL gfx1250 kernel with a fixed 128x128x64 or 256x128x64 config inside the M, N and K limits above, when its padded last M tile of C ends below byte 2^32. A shape that FlyDSL cannot take and whose C passes byte 2^31 goes to torch, because Triton's default config leaves those rows unwritten. Tuned rows and gluon (N, K) configs still win. Other architectures do not change.

Validation and unverified scope

  • Speed and correctness: see Measurements and Large outputs above (gfx1250 B0 heliosr-1b114-d04-1, FlyDSL 0.4.0, main c118faf).
  • This revision merges main 0f6cb2f. On a CPU-only container, python3 -m pytest op_tests/tuning_tests/test_a16w16_tuned_config_lookup.py: the 5 TestGfx1250DefaultDispatch tests and the other test methods passed. 8 subtests of main's TestLegacyFlydslHgemmConfigs::test_gfx1250_legacy_wrapper_routing failed the same way on main, because they launch the TDM kernel and need a gfx1250 GPU.
  • On main, flydsl_hgemm_kernel_name still builds ..._pft_gfx1250 from this config, and get_flydsl_hgemm_kernel_params parses it. Since c118faf, the gfx1250 gluon DEFAULT.json only gained REGISTER_STORE: 0, which is the kernel's default, and gemm_a16w16_gfx1250.py only changed its split-K counter cache.
  • Unverified: the FlyDSL / Triton ratios were not measured again after main moved FlyDSL from 0.4.x to 0.5.0.

PR-specific question

No CSV or JSON file changes. The new default applies only when get_GEMM_A16W16_tuned_config misses on gfx1250 with BF16 input, BF16 or FP32 output and no scales, the (N, K) has no GEMM-A16W16-N={N}-K={K}.json among the gfx1250 gluon configs, and the shape is inside the limits above. Every other key keeps its row or its previous default: the branch sits inside the gfx1250 default path, and tuned rows are matched before it. test_other_shapes_stay_on_triton and TestA16W16TunedConfigLookup cover the unchanged selections (M = 256, 1025 to 4096, N < 4096, K < 512, an (N, K) with a gluon config, FP16, tuned rows).

Changed op tests

op_tests/tuning_tests/test_a16w16_tuned_config_lookup.py: adds TestGfx1250DefaultDispatch. It mocks gfx1250 and checks each branch of the new default: both FlyDSL tiles, the M range, both sides of FlyDSL's 2^32 limit and of the 2^31 limit of Triton's default config, and bias with FP32 output. No existing test covers the gfx1250 default dispatch, and the new cases fail on the previous heads.

@jhinpan
jhinpan requested review from a team and zufayu October 11, 2026 09:11
@github-actions

Copy link
Copy Markdown
Contributor

🏷️ CI Guide

Runs automatically on every PR:

  • ✅ Pre-checks (submodule verification, code formatting)
  • ✅ Aiter op tests (gfx942 + gfx950)
  • ✅ Triton tests on MI35X (only when aiter/ops/triton/** or related paths are changed)

Extended tests (opt-in via labels):

Label Tests
ci:gfx1250-ffm-triton Run the five-shard gfx1250 FFM Triton test suite
ci:triton-300x Run an additional Triton test job on MI300X in PRs (added automatically when gfx942 configs change); main branch always runs both MI35X and MI300X
ci:triton-355 Run the full Triton test suite on MI35X, not only the tests the change affects
ci:multi-gpu Aiter multi-GPU tests on the 8-GPU runner
ci:sglang SGLang integration tests: DeepSeek-R1-MXFP4 accuracy, Qwen 3.5 accuracy
ci:atom ATOM benchmark: DeepSeek-R1-0528, GPT-OSS-120B
ci:atom_full ATOM accuracy suite for PR and main models from ATOM models_accuracy.json
ci:vllm vLLM benchmark: DeepSeek-V4-Pro, MiniMax-M3-MXFP4
ci:all All standard extended tests (excludes ci:atom_full)

Only add ci:atom_full for FlyDSL or Triton upgrades.
Add labels via the sidebar or gh pr edit 6397 --add-label <label>

One backend per PR:
A PR changes one kernel backend: [Triton/Gluon] (Triton and Gluon count as one), [HIP], [ASM], [CK], [OPUS] or [FlyDSL]. If the title ends up with two backend tags, split the PR -- as stacked pull requests when one part cannot merge without the other.

PR title tags & labels:
Component tags ([Triton/Gluon], [HIP], [CK], [ASM], ...) are added to the PR title and as PR labels automatically from the changed files and re-synced on every push — change-type tags like [fix]/[Perf], op tags like [MLA], and human labels (ci:*) are left untouched. Add the no-auto-title label to stop the title rewrites; labels stay in sync either way.

@github-actions github-actions Bot changed the title [FlyDSL] Use the FlyDSL BF16 GEMM by default for large untuned gfx1250 shapes Use the FlyDSL BF16 GEMM by default for large untuned gfx1250 shapes Oct 11, 2026
@zufayu
zufayu requested a review from yzhou103 October 12, 2026 07:40
@zufayu

zufayu commented Oct 12, 2026

Copy link
Copy Markdown
Contributor

+Zoe for tune flow review

Comment thread aiter/tuned_gemm.py Resolved
@jhinpan
jhinpan force-pushed the jhinpan/gfx1250-bf16-flydsl-default branch from 0e7e7bc to 6444612 Compare October 13, 2026 04:58
yzhou103
yzhou103 previously approved these changes Oct 15, 2026
@yzhou103
yzhou103 dismissed their stale review October 15, 2026 07:46

need test

@zufayu
zufayu requested a review from aoli26 October 15, 2026 07:53
@zufayu

zufayu commented Oct 15, 2026

Copy link
Copy Markdown
Contributor

This is not merge-ready yet. The new heuristic routes M=16384, N=151936, K=4096 with BF16 output to FlyDSL, while the PR already confirms that this exact path is incorrect because the output exceeds 2^32 bytes and the output offsets wrap. The existing Triton bug does not make selecting another known-broken backend acceptable.

Please exclude every known-incorrect FlyDSL shape and route it to a verified-correct fallback, then add dispatch regression coverage around the 2^32 output boundary and provide exact-head gfx1250 correctness results.

On gfx1250 a BF16 GEMM without a tuned row runs on Triton. Where gluon
has no config for the (N, K), it runs DEFAULT.json, and the FlyDSL
gfx1250 kernel with a fixed config takes 0.35 to 0.68 of that time for
M >= 512, N >= 4096 and K >= 512.

Pick FlyDSL in that case (BF16 input, BF16 or FP32 output): 128x128x64
tiles up to M = 768, 256x128x64 above, 4 stages. Smaller grids, short K,
fp16 and (N, K) pairs with their own gluon config stay on Triton.

Signed-off-by: Jin Pan <jin.pan@amd.com>
The gfx1250 FlyDSL kernel writes C with 32-bit byte offsets and drops
the rows of its last M tile past M only through C's record count. Once
those rows reach byte 2^32 they wrap onto the first rows of C, so the
default now picks FlyDSL only when the padded last tile ends below
2^32. Larger outputs go to torch: Triton's default config leaves the
rows past byte 2^31 unwritten.

FP32 outputs with a bias stay on Triton, because flydsl_gemm adds an
unfused bias in the bias dtype and a BF16 bias rounds the FP32 result.
The gfx1250 gluon DEFAULT.json was retuned (#6549). Against it, the two
FlyDSL default configs are slower than Triton in every repeat on 107 of
the 368 untuned shapes with 1024 < M <= 4096 (up to 1.28x), the
M_LEQ_2048 and M_LEQ_4096 buckets of that file, and stay faster for M
from 512 to 1024 or above 4096 (0.50 to 1.10, geomean 0.78). The FlyDSL
default now skips that range.
… on gfx1250

Triton's default gfx1250 config leaves the rows of C past byte 2^31
unwritten. An untuned shape without its own gluon config now runs on it
only when C fits in 2^31 bytes, and on torch otherwise. The FlyDSL
default keeps 1024 < M <= 4096 when C is larger, because FlyDSL writes C
up to 2^32 bytes and runs about 10x faster than torch on such outputs.
The docstring now covers the validation run at M values the range was not
chosen from (896, 1100, 1800, 2600, 3500, 4500, 5000, 7000, 10000) as well
as the two sets the range came from.
@jhinpan
jhinpan force-pushed the jhinpan/gfx1250-bf16-flydsl-default branch from 6444612 to 27670ca Compare October 15, 2026 22:30
@jhinpan

jhinpan commented Oct 15, 2026

Copy link
Copy Markdown
Contributor Author

@zufayu thanks, fixed in 27670ca (rebased onto main c118faf). The description now has the gfx1250 results at that head.

  • FlyDSL is picked only when its padded last M tile of C ends below byte 2^32. A larger output goes to torch, not to Triton's default config, because that config leaves the rows past byte 2^31 unwritten. For the same reason, every untuned shape that config cannot write now goes to torch.
  • TestGfx1250DefaultDispatch checks both sides of each limit (14080 / 14081 x 151936 x 4096 for FlyDSL, 7067 / 7068 x 151936 for Triton's default config). The new cases fail on the previous heads.
  • Through the new dispatch, every row of the boundary shapes, the DeepSeek shapes and the bias + FP32 shapes is right on B0 (heliosr-1b114-d04-1) and on the A0 benchtop. See "Large outputs".

The retest changed two more things. #6549 retuned the gluon DEFAULT.json, and against it FlyDSL is no longer faster for 1024 < M ≤ 4096, so that range stays on Triton unless C passes 2^31 bytes. I checked the range again at M values it was not chosen from. FP32 output with a bias also stays on Triton, because flydsl_gemm rounded that output to BF16.

@yzhou103 the gfx1250 tests are in the description.

@zufayu

zufayu commented Oct 22, 2026

Copy link
Copy Markdown
Contributor

The updated PR still has correctness and dispatch blockers.

For large outputs, the Torch fallback converts through the input dtype before producing the requested output dtype. For BF16-to-FP16 and FP16-to-BF16 cases, that introduces an extra rounding step compared with the original Gluon path, which converts the FP32 accumulator directly to the output type. Values around rounding boundaries can therefore change when the dispatch crosses the size threshold.

Also, current main already contains #6629, which removes the Gluon 2 GiB limitation that motivated this fallback. Keeping the old size-based switch after that fix can route supported shapes to a slower path. The newly selected FlyDSL bias=True path also lacks performance evidence; the submitted measurements cover bias=False only.

Please rebase on current main including #6629, redesign the dispatch around the remaining real limitations, and verify every newly selected FlyDSL path. The validation should cover cross-dtype output, values around the dispatch threshold, both bias modes, and same-machine performance against the current default path.

@aiter-bot

aiter-bot commented Nov 5, 2026 •

Copy link
Copy Markdown

READY FOR HUMAN REVIEW

PR Self-Review

Revision 6b05290ccace · mode shadow

Author self-review accepted. Readiness report:

Author answers

  • PASS — current-SHA answers are present and structurally specific
Show the author's self-review answers

Behavior and intent

On gfx1250, a BF16 GEMM without a tuned row and without a gluon config for its (N, K) ran Triton's `DEFAULT.json` config. `get_GEMM_A16W16_config` now gives such a shape AITER's FlyDSL gfx1250 kernel with a fixed 128x128x64 or 256x128x64 config inside the M, N and K limits above, when its padded last M tile of C ends below byte 2^32. A shape that FlyDSL cannot take and whose C passes byte 2^31 goes to torch, because Triton's default config leaves those rows unwritten. Tuned rows and gluon (N, K) configs still win. Other architectures do not change.

Validation and unverified scope

- Speed and correctness: see Measurements and Large outputs above (gfx1250 B0 `heliosr-1b114-d04-1`, FlyDSL 0.4.0, main c118faf).
- This revision merges main 0f6cb2f. On a CPU-only container, `python3 -m pytest op_tests/tuning_tests/test_a16w16_tuned_config_lookup.py`: the 5 `TestGfx1250DefaultDispatch` tests and the other test methods passed. 8 subtests of main's `TestLegacyFlydslHgemmConfigs::test_gfx1250_legacy_wrapper_routing` failed the same way on main, because they launch the TDM kernel and need a gfx1250 GPU.
- On main, `flydsl_hgemm_kernel_name` still builds `..._pft_gfx1250` from this config, and `get_flydsl_hgemm_kernel_params` parses it. Since c118faf, the gfx1250 gluon `DEFAULT.json` only gained `REGISTER_STORE: 0`, which is the kernel's default, and `gemm_a16w16_gfx1250.py` only changed its split-K counter cache.
- Unverified: the FlyDSL / Triton ratios were not measured again after main moved FlyDSL from 0.4.x to 0.5.0.

PR-specific question

No CSV or JSON file changes. The new default applies only when `get_GEMM_A16W16_tuned_config` misses on gfx1250 with BF16 input, BF16 or FP32 output and no scales, the (N, K) has no `GEMM-A16W16-N={N}-K={K}.json` among the gfx1250 gluon configs, and the shape is inside the limits above. Every other key keeps its row or its previous default: the branch sits inside the gfx1250 default path, and tuned rows are matched before it. `test_other_shapes_stay_on_triton` and `TestA16W16TunedConfigLookup` cover the unchanged selections (M = 256, 1025 to 4096, N < 4096, K < 512, an (N, K) with a gluon config, FP16, tuned rows).

Changed op tests

`op_tests/tuning_tests/test_a16w16_tuned_config_lookup.py`: adds `TestGfx1250DefaultDispatch`. It mocks gfx1250 and checks each branch of the new default: both FlyDSL tiles, the M range, both sides of FlyDSL's 2^32 limit and of the 2^31 limit of Triton's default config, and bias with FP32 output. No existing test covers the gfx1250 default dispatch, and the new cases fail on the previous heads.

PR state

  • PASS — pull request is marked ready for review

Copilot findings

  • PASS — no active Copilot finding lacks a substantive author reply
  • ADVISORY — Copilot did not review the current head SHA

Code and op-test hygiene

  • PASS — no committed artifact or conflict marker detected

Required CI

  • PASS — Black: passed
  • PASS — Ruff: passed
  • PASS — Repository dependency: passed
  • PASS — Aiter Test Gate: passed

Shadow mode reports what would block but keeps the status successful. Set AITER_PR_SELF_REVIEW_MODE=enforce only after reviewing real false positives.

@yzhou103

Copy link
Copy Markdown
Contributor

Hi @jhinpan please resolve the conflicts

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants