Skip to content

[FlyDSL] [Bugfix] gfx950 A16W16: fix int32 C-offset overflow - #6853

Open
josh-schell-amd wants to merge 12 commits into
ROCm:mainfrom
josh-schell-amd:fix/flydsl-hgemm-int64-offsets
Open

josh-schell-amd wants to merge 12 commits into
ROCm:mainfrom
josh-schell-amd:fix/flydsl-hgemm-int64-offsets

Conversation

@josh-schell-amd

@josh-schell-amd josh-schell-amd commented Oct 23, 2026 •

Copy link
Copy Markdown

Summary

Fixes a GPU memory fault in the gfx950 FlyDSL A16W16 GEMM when M·N ≥ 2^31 (int32 C offsets), which crashes vLLM amd/MiniMax-M3-MXFP4 at startup on AITER v0.1.26. It also stops the kernel's FlyDSL arguments from keeping the last call's tensors alive, rejects shapes the kernel can't address, and accepts a row-strided out on gfx950 so this kernel's own config test passes.

Motivation

Since AITER v0.1.26, vLLM amd/MiniMax-M3-MXFP4 (MI355X, TP4) crashes at engine startup with Memory access fault ... Write access to a read-only page during the encoder-cache profile run.

  • [Config] [GEMM] [gfx950] Add MiniMax-M3 master BF16 GEMM table & tuned ViT linear projections #6235 added an M=8192, N=6144, K=1280 row to minimax_m3_mxfp4_bf16_tuned_gemm.csv. getPaddedM(gl=1) maps every M > 8192 (with N > 4096) to that row, so the 768000-row ViT GEMM runs the gfx950 FlyDSL A16W16 kernel.
  • That kernel's epilogue computes each output element's offset, global_row * n + global_col, in int32, which wraps once M·N ≥ 2^31 (row 349,526 at N=6144). The wrap causes:
    • out-of-bounds stores: the wrapped offset is negative, so stores land before C's base address and hit a read-only page. That's the Memory access fault vLLM reports at M=768000.
    • silent corruption: past M·N ≥ 2^32 (row 699,051), offsets wrap back to positive values and overwrite earlier rows of C without faulting.
  • Reproduces without vLLM on v0.1.26: tgemm.mm with N=6144, K=1280 passes at M=340000 and faults at M=360000.

Fixing the crash exposed a second issue. The kernel's layout-dynamic from_dlpack arguments keep their last call's tensors alive (a FlyDSL 0.4.0 bug, fixed upstream in ROCm/FlyDSL#1321). After the ViT profile run that pins 10.6 GiB, which costs vLLM 7.4 GiB of KV cache.

AITER main at 537ac7f6d (2026-10-30) still has the int32 offsets, the from_dlpack arguments and the 8192,6144,1280 FlyDSL row.

Changes

  • C offsets are int64 in both non-split-K epilogues. Split-K's buffer atomics stay far below 2^31 (SPLIT_K_SEMAPHORE_MAX_LEN).
  • Shape limits: shapes whose A or B reaches 4 GiB are rejected, because each is read through one flat buffer descriptor with i32 offsets; this mirrors the A8W8 preshuffle GEMM's flat-buffer check. gemm_a16w16 also checks the real span of strided A/B views (rows × row stride), which can reach 4 GiB while M·K·2 does not; dense operands skip that check.
  • Dispatch: on gfx950, get_GEMM_A16W16_tuned_config skips a FlyDSL row the kernel can't run at the real M/N/K (flydsl_hgemm_supports_shape) and falls back, as it does for an unrecognized kernel. It reuses the lookup's own gfx, so the check models the architecture the row was selected for.
  • Arguments: _dynamic_tensor_arg uses from_torch_tensor, which retains nothing and is cheaper per launch. FlyDSL v0.4.1 does not include [Fix] Don't retain the last tensor in layout-dynamic DLPack fills FlyDSL#1321, so this is still needed.
  • Strided out: flydsl_hgemm already accepts a row-strided out on gfx1250 and returns it; gfx950 now does too. A strided out builds a separate variant (_ldc) that reads C's row stride from out; a contiguous out compiles to the same code as before. It's in this PR because op_tests/flydsl_tests/test_gemm_a16w16.py, this kernel's own test, fails without it, and it's its own commit (7b73ede). The row stride must be below 2^31. Strided variants aren't in the AOT cache, so the first strided call compiles at runtime.
  • Tests: see Changed op tests below.

Performance

Hardware Workload/configuration Before After Change
MI355X gfx950 A16W16 kernel, 37 shapes (A/B vs v0.1.26, 95% CI) v0.1.26 this PR 27 no difference, 2 faster, 8 slower; 2 real (+0.05% to +0.09%), 6 measurement artifacts
MI355X 360448x6144x1280 (M·N > 2^31) memory fault (torch.mm: 4729 µs) 4702 µs runs
MI355X gemm_a16w16 host time per call, 8x4096x4096 56.9 µs 50.8 µs −11%
MI355X, TP4 vLLM MiniMax-M3 perf-eval, output tok/s: 8k/1k c16, 1k/1k c1 / c64 / c128 1294 / 189 / 4035 / 6293 (v0.1.25 image) 1296 / 188 / 4048 / 6341 within 1%

Kernel perf vs v0.1.26. v0.1.26's kernel and this PR's kernel are built side by side in one process and timed with CUDA graphs over 20 rounds that alternate which one runs first. Each result is the mean time ratio with a 95% confidence interval. An A/A control (v0.1.26 against an identical copy of itself) shows how large a difference the method reports when nothing changed: up to about ±0.9% on 5 µs kernels.

Shapes: the 31 tuned nt rows in test_flydsl_hgemm.py that v0.1.26 can run (M·N < 2^31), plus nn/tn/tt at 4096³ and 98304x6144x1280, for 37 in total.

Result Shapes Explanation
No difference 27
Faster 2: 4096x4096x8192 (−0.52%), 98304x6144x1280 with A and B transposed (−0.24%) Measurement: 4096x4096x8192 also reads −0.42% in the A/A control
Slower, split-K 6: 1–16x5120x2880 with split-K 3 (+0.16% to +0.74%), 32x384x7168 with split-K 8 (+0.22%) Measurement artifact. The ISA is byte-identical to v0.1.26, the A/A control reads +0.4% to +0.9% on four of the same rows, and the sign follows run order when swapped.
Slower, non-split-K 2: 32x14336x4096 (+0.09 ± 0.07%), 16x28672x4096 (+0.05 ± 0.03%) Real. The 64-bit C offset adds one instruction per store, about 20 ns, consistent across three runs.

360448x6144x1280, which v0.1.26 can't run (M·N > 2^31), is compared with torch.mm instead: 4702 µs vs 4729 µs.

In the large interleaved kernel (256x256 tiles), the 64-bit offset adds 26 instructions to the epilogue, outside the main loop; the A/B shows no difference on those shapes (4096x4096x8192, 8192³, and the 98304-row layouts). Computing a 64-bit tile base once and 32-bit offsets per store was tried: the compiler then added more per-store address math (for example 965 → 1043 epilogue instructions on 4096x4096x8192), so it isn't used.

The later commits (strided out, the A/B span check) don't change these kernels: for a contiguous out they compile byte-identical to the build measured here (checked on all three kernel paths: standard tiles, large interleaved tiles, and split-K).

vLLM amd/MiniMax-M3-MXFP4 (MI355X, TP4, perf-eval minimax_m3-mi355x). On v0.1.26 the engine faults while profiling the encoder cache (CI build 709):

Encoder cache will be initialized with a budget of 192000 tokens, and profiled with 1 video items of the maximum feature size.
Memory access fault by GPU node-2 (Agent handle: 0x3cbfdfa0) on address 0x7e1efea01000. Reason: Write access to a read-only page.

With this PR the same step runs that GEMM on FlyDSL (AITER_LOG_TUNED_CONFIG=1), and the engine starts and completes the workload:

Encoder cache will be initialized with a budget of 192000 tokens, and profiled with 1 video items of the maximum feature size.
[aiter] shape is M:768000, N:6144, K:1280 ... found padded_M: 8192, N:6144, K:1280 is tuned on cu_num = 256 ..., libtype is flydsl
v0.1.25 (last green image) v0.1.26 + this PR
KV cache (GiB) 176.56 176.6
gsm8k flexible / strict 0.8802 / 0.8795 0.8961 / 0.8954 (±0.008)
Output tok/s: 8k/1k c16, 1k/1k c1 / c64 / c128 1294 / 189 / 4035 / 6293 1296 / 188 / 4048 / 6341
Mean TPOT (ms), same configs 10.54 / 5.22 / 15.06 / 19.48 10.55 / 5.26 / 14.98 / 19.35

MiniMax-M3's decode GEMMs run on this kernel, so serving exercises the changed store path. The columns are different vLLM images.

Testing

  • Unit tests added or updated
  • Performance benchmarks run
  • Tested on MI250X
  • Tested on MI300X

Tested on MI355X (gfx950); the kernel and the new dispatch check are gfx950-only. Setup: the vLLM image where the nightly fails (vLLM df6603f0, AITER v0.1.26), with and without this PR's changes applied, on FlyDSL 0.4.0 and again on 0.4.1 (main's pin).

Reproduction. With the image's default tuned configs (which include minimax_m3_mxfp4_bf16_tuned_gemm.csv), this faults on v0.1.26 and matches torch with this PR:

import torch
from aiter.tuned_gemm import tgemm
w = torch.randn(6144, 1280, dtype=torch.bfloat16, device="cuda")
b = torch.randn(6144, dtype=torch.bfloat16, device="cuda")
a = torch.randn(360000, 1280, dtype=torch.bfloat16, device="cuda")
out = tgemm.mm(a, w, b)  # padded-M row M=8192 -> FlyDSL
torch.cuda.synchronize()
print((out - torch.nn.functional.linear(a, w, b)).abs().max())
python op_tests/test_flydsl_hgemm.py
python op_tests/flydsl_tests/test_gemm_a16w16.py
python -m unittest op_tests.tuning_tests.test_a16w16_tuned_config_lookup
python -m aiter.aot.flydsl.gemm
v0.1.26 This PR
Repro, M = 360000 / 768000 memory fault FlyDSL, matches torch (bf16 rounding)
M = 1677721 / 1677722 (A just under / over 4 GiB) — FlyDSL / torch fallback
Memory still held after one M=340000 call 4.71 GiB 0.01 GiB (FlyDSL 0.4.0 and 0.4.1)
op_tests/test_flydsl_hgemm.py 64 tuned rows plus 4 new checks pass
op_tests/flydsl_tests/test_gemm_a16w16.py 48 pass, then fails on strided out 86/86 pass
test_a16w16_tuned_config_lookup.py pass
python -m aiter.aot.flydsl.gemm 2505 ok, 0 failed

Documentation

  • Docstrings updated (gemm_a16w16: a 2D out may be row-strided)
  • User guide updated
  • Performance guide updated
  • No documentation change is needed, with the reason stated below

Dependencies

  • No new third-party dependencies added
  • New dependencies are listed and justified below

Breaking Changes

None for existing correct calls. Behavior changes on gfx950 gemm_a16w16:

  • A strided A or B whose span reaches 4 GiB now raises ValueError; before, it computed wrong results.
  • A non-contiguous 2D out is now accepted (it used to fail an assert), and a 2D out is returned as the same tensor instead of a new view.
  • A tuned FlyDSL row the kernel can't run at the actual M/N/K now falls back with a warning instead of launching.

PR Self-Review

Reviewed revision

14b38ab

Behavior and intent

  • The gfx950 FlyDSL A16W16 kernel computed C offsets in int32, so any M·N ≥ 2^31 wrote outside C; MiniMax-M3's 768000-row ViT GEMM makes vLLM fault at startup on v0.1.26. C offsets are now 64-bit.
  • Shapes the kernel can't address are rejected: A or B reaching 4 GiB, including strided views whose real span reaches 4 GiB. The tuned-config lookup skips such FlyDSL rows and falls back; direct gemm_a16w16 calls raise ValueError.
  • Kernel arguments use from_torch_tensor, so FlyDSL 0.4.x no longer keeps the last call's tensors alive (10.6 GiB after MiniMax's profile run).
  • A row-strided out is accepted on gfx950, as on gfx1250, through a compile-time variant; a contiguous out compiles to the same code as before.

Validation and unverified scope

On MI355X, in the vLLM image from the failing CI run (AITER v0.1.26 with this PR's changes applied), on FlyDSL 0.4.0 and 0.4.1:

  • python op_tests/test_flydsl_hgemm.py: all tuned rows and this PR's checks passed.
  • python op_tests/flydsl_tests/test_gemm_a16w16.py: 86/86 passed.
  • python -m unittest op_tests.tuning_tests.test_a16w16_tuned_config_lookup: OK.
  • python -m aiter.aot.flydsl.gemm: 2505 ok, 0 failed.
  • The tgemm repro, the memory check, the kernel A/B against v0.1.26, the host-time measurement and the vLLM perf-eval run, all in Performance and Testing above.

Unverified:

Not caused by this PR: op_tests/test_gemm_a16w16.py has two direct gemm_a16w16_asm checks at 64x256x5120 (bf16 output) that also fail on main CI, and op_tests/test_graph_alloc.py is fixed separately by #7039.

PR-specific question

Without this PR, the 360448x6144x1280 row crashes, the strided-out and A/B span checks fail, and the config table in flydsl_tests/test_gemm_a16w16.py stops at its first gfx950 config. No existing test covered these cases; details per file are in Changed op tests.

Changed op tests

  • op_tests/test_flydsl_hgemm.py: the canonical flydsl_hgemm test, extended rather than duplicated. It adds the M·N > 2^31 row, the shape-limit and real-span checks, the M = 1677721 result check, and strided-out checks for standard tiles, large interleaved tiles and split-K. The extra checks run whenever -s isn't given.
  • op_tests/flydsl_tests/test_gemm_a16w16.py: skips (with a warning) config/output pairs the gfx950 kernel can't build (a 256x256 fp32 C tile exceeds LDS). Without the skip, the config table, the only test that sweeps explicit flydsl_hgemm configs against triton, can't run on gfx950.

This PR was authored with assistance from Claude Code (AI-assisted). Results were run and checked on MI355X.

🤖 Generated with Claude Code

@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:windows Windows JIT op smoke test on the gfx1201 ROCm runner
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 6853 --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.

@josh-schell-amd josh-schell-amd changed the title [FlyDSL] [Bugfix] gfx950 A16W16: int64 C offsets and skip tuned rows the kernel cannot address [FlyDSL] [Bugfix] gfx950 A16W16: fix int32 C-offset overflow and stop pinning the last call's tensors Oct 23, 2026
josh-schell-amd added a commit to josh-schell-amd/aiter that referenced this pull request Oct 23, 2026
josh-schell-amd and others added 7 commits October 23, 2026 16:57
…past buffer limits

The gfx950 A16W16 epilogue computed C offsets as int32 `row * n + col`, so
any GEMM with M * N >= 2^31 stored to a wrapped address. Tuned BF16 rows are
looked up by padded M, and getPaddedM(gl=1) maps every M > 8192 with N > 4096
to the M=8192 row, so a row tuned at M=8192 serves arbitrarily large M. With
the MiniMax-M3 ViT rows from ROCm#6235, vLLM's encoder-cache profile run
(M=768000, N=6144, K=1280) hits this and faults with "Write access to a
read-only page" on MI355X (reproduced standalone via tgemm.mm at M>=360000).

- Compute the non-split-K C offsets in int64 in both the full-tile and
  half-tile-interleaved epilogues.
- Reject shapes whose A or B exceeds the 2 GiB buffer-resource range in
  make_gemm_a16w16_param_and_validate (also prunes them from tuning).
- Skip a FlyDSL tuned row in get_GEMM_A16W16_tuned_config when the kernel
  cannot run the actual shape, falling back like an unrecognized kernel.
- Add the large-M MiniMax-M3 ViT shape and a shape-limit check to
  op_tests/test_flydsl_hgemm.py.

Co-Authored-By: Claude <noreply@anthropic.com>
The buffer-resource loads index elements with int32 offsets, so the limit is
2^31 elements (4 GiB of 16-bit data), not 2^31 bytes. The byte bound rejected
the tuned Kimi-K3 row M=32768 N=7168 K=35840 (A = 2.19 GiB, tuned with
err_ratio 0), which failed `python -m aiter.aot.flydsl.gemm` and would have
fallen back to torch at runtime. All 2543 tuned gfx950 FlyDSL rows now pass.

Co-Authored-By: Claude <noreply@anthropic.com>
…nning the last call

FlyDSL 0.4.0's layout-dynamic `from_dlpack` argument stores each launch's
DLTensorAdaptor in a thread-local (`_shared.dladaptor`) that is never cleared,
and the fill closure lives as long as the cached CallState. Every tensor argument
of the A16W16 kernel therefore keeps its most recent call's tensor alive. After
vLLM's MiniMax-M3 encoder profile run (M=768000 ViT projector) that pins A+C =
10.6 GiB, which vLLM counts as persistent memory: 7.4 GiB less KV cache than
when the GEMM ran on torch.

`from_torch_tensor` reads data_ptr/shape/stride straight from the tensor and
retains nothing. `_dynamic_tensor_arg` is shared with the AOT driver, so runtime
and AOT keep the same argument type and cache key.

Measured on MI355X: torch reserved after `del` + `empty_cache` drops from 10.64
to 0.02 GiB; output matches torch at M=8192/360000/768000.

Co-Authored-By: Claude <noreply@anthropic.com>
…B limit

A and B are read through one flat buffer descriptor each, so like the A8W8
preshuffle GEMM's _check_preshuffle_flat_buffer_capacity, operands must stay
below 4 GiB. State the bound in bytes against the same limit instead of 2^31
elements (equivalent for the 16-bit inputs this kernel takes), and explain why
C needs none. Note that flydsl_hgemm_supports_shape only models the gfx950
kernel; gfx1250 rows keep their previous behavior. No behavior change.

Co-Authored-By: Claude <noreply@anthropic.com>
Shorten the new comments to the codebase's style and test the A/B bound at
its exact boundary instead of narrated example shapes. No behavior change.

Co-Authored-By: Claude <noreply@anthropic.com>
Co-Authored-By: Claude <noreply@anthropic.com>
…riant

flydsl_hgemm accepts a row-strided `out` on gfx1250 and returns the
caller's tensor, but the gfx950 kernel asserted `out.is_contiguous()` and
returned a new view, so op_tests/flydsl_tests/test_gemm_a16w16.py's
kernel-config table failed on gfx950 and tgemm.mm(out=) with a strided
view would hit the assert.

A contiguous `out` keeps C's row stride as N and compiles exactly as
before. A row-strided `out` selects a separate variant (`_ldc`) that reads
the stride from `out`'s layout, so only strided calls pay for it. Split-K's
zero-fill and atomics use the same stride; the host checks it and split-K's
4 GiB atomic range. A 2D `out` is returned as-is.

Tests: the config table skips (otype, config) pairs the gfx950 kernel
cannot build (a 256x256 fp32 C tile exceeds LDS), and test_flydsl_hgemm.py
checks ft, hti and split-K writes into a strided view.

Co-Authored-By: Claude <noreply@anthropic.com>
@josh-schell-amd
josh-schell-amd force-pushed the fix/flydsl-hgemm-int64-offsets branch from 38f564e to 7b73ede Compare October 23, 2026 21:57
@josh-schell-amd
josh-schell-amd marked this pull request as ready for review October 23, 2026 21:57
@josh-schell-amd
josh-schell-amd requested a review from a team October 23, 2026 21:57
…st 2^31

- gemm_a16w16 accepts strided A/B views, whose i32 buffer offsets are
  row * stride + k; reject operands whose real span reaches 4 GiB even
  when M * K * 2 is below it. The shape-only check stays for tuned_gemm
  and AOT, which only have M/N/K.
- A row-strided `out` must have a row stride below 2^31: the kernel reads
  it as i32.
- test_flydsl_hgemm.py: a meta-device check for the A span, a numeric
  check of the first and last rows at M = 1677721 (A just under 4 GiB,
  byte offsets past 2^31), and the extra checks only run on the default
  sweep.

Co-Authored-By: Claude <noreply@anthropic.com>
Conflict: gemm_a16w16_gfx950_utils.py, where ROCm#7067 raised
SPLIT_K_SEMAPHORE_MAX_LEN to 2048 next to the new
GFX950_FLAT_BUFFER_LIMIT_BYTES; both kept.
@afriedri

Copy link
Copy Markdown
Contributor

Found this fix independently; I think int64 output offset plus a fallback to torch for exceeding buffer is the right approach. My equivalent fix solved the Minimax issue on MI350. Nice work

@zufayu
zufayu removed the request for review from yadaish October 29, 2026 10:17
@josh-schell-amd josh-schell-amd changed the title [FlyDSL] [Bugfix] gfx950 A16W16: fix int32 C-offset overflow and stop pinning the last call's tensors [FlyDSL] [Bugfix] gfx950 A16W16: fix int32 C-offset overflow Oct 29, 2026
@aiter-bot

aiter-bot commented Oct 29, 2026 •

Copy link
Copy Markdown

READY FOR HUMAN REVIEW

PR Self-Review

Revision 14b38ab5bc30 · 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

- The gfx950 FlyDSL A16W16 kernel computed C offsets in int32, so any M·N ≥ 2^31 wrote outside C; MiniMax-M3's 768000-row ViT GEMM makes vLLM fault at startup on v0.1.26. C offsets are now 64-bit.
- Shapes the kernel can't address are rejected: A or B reaching 4 GiB, including strided views whose real span reaches 4 GiB. The tuned-config lookup skips such FlyDSL rows and falls back; direct `gemm_a16w16` calls raise `ValueError`.
- Kernel arguments use `from_torch_tensor`, so FlyDSL 0.4.x no longer keeps the last call's tensors alive (10.6 GiB after MiniMax's profile run).
- A row-strided `out` is accepted on gfx950, as on gfx1250, through a compile-time variant; a contiguous `out` compiles to the same code as before.

Validation and unverified scope

On MI355X, in the vLLM image from the failing CI run (AITER v0.1.26 with this PR's changes applied), on FlyDSL 0.4.0 and 0.4.1:
- `python op_tests/test_flydsl_hgemm.py`: all tuned rows and this PR's checks passed.
- `python op_tests/flydsl_tests/test_gemm_a16w16.py`: 86/86 passed.
- `python -m unittest op_tests.tuning_tests.test_a16w16_tuned_config_lookup`: OK.
- `python -m aiter.aot.flydsl.gemm`: 2505 ok, 0 failed.
- The `tgemm` repro, the memory check, the kernel A/B against v0.1.26, the host-time measurement and the vLLM perf-eval run, all in Performance and Testing above.

Unverified:
- AITER main itself: the test box runs v0.1.26 plus this PR's changes, which are line-for-line the same against main; this PR's CI covers main. On that base, `test_flydsl_hgemm.py`'s new `test_hgemm_concurrent_copy` (#7042) fails because the base lacks #7042's kernel fix; it is not exercised against main here.
- gfx942 and gfx1250: the kernel and the new dispatch check are gfx950-only.
- `op_tests/test_gemm_a16w16.py -t out`: needs `tgemm.mm(out=)` from after v0.1.26.

Not caused by this PR: `op_tests/test_gemm_a16w16.py` has two direct `gemm_a16w16_asm` checks at 64x256x5120 (bf16 output) that also fail on main CI, and `op_tests/test_graph_alloc.py` is fixed separately by #7039.

PR-specific question

Without this PR, the 360448x6144x1280 row crashes, the strided-`out` and A/B span checks fail, and the config table in `flydsl_tests/test_gemm_a16w16.py` stops at its first gfx950 config. No existing test covered these cases; details per file are in Changed op tests.

Changed op tests

- `op_tests/test_flydsl_hgemm.py`: the canonical `flydsl_hgemm` test, extended rather than duplicated. It adds the M·N > 2^31 row, the shape-limit and real-span checks, the M = 1677721 result check, and strided-`out` checks for standard tiles, large interleaved tiles and split-K. The extra checks run whenever `-s` isn't given.
- `op_tests/flydsl_tests/test_gemm_a16w16.py`: skips (with a warning) config/output pairs the gfx950 kernel can't build (a 256x256 fp32 C tile exceeds LDS). Without the skip, the config table, the only test that sweeps explicit `flydsl_hgemm` configs against triton, can't run on gfx950.

This PR was authored with assistance from Claude Code (AI-assisted). Results were run and checked on MI355X.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

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.

Conflict: op_tests/test_flydsl_hgemm.py, where ROCm#7042 added
test_hgemm_concurrent_copy at the same spot as this PR's
check_input_span / check_large_a / check_strided_out; both kept.
- Dense operands (row- or column-major) are covered by the shape check,
  so the per-call span computation now runs only for strided views,
  keeping host work on the launch path flat.
- test_flydsl_hgemm.py: run the extra checks whenever -s is not given.
  The default shape list now also holds the concurrent-copy shape, so
  comparing against _default_mnk() skipped them.

Co-Authored-By: Claude <noreply@anthropic.com>
@josh-schell-amd
josh-schell-amd force-pushed the fix/flydsl-hgemm-int64-offsets branch from bf9caa7 to 8b6061a Compare October 29, 2026 18:35
micah-wil added a commit to ROCm/vllm that referenced this pull request Oct 29, 2026
AITER's gfx950 FlyDSL A16W16 kernel addresses C with int32 offsets and
faults once M * N >= 2^31 (ROCm/aiter#6853). With
--mm-encoder-tp-mode data, the MiniMax-M3 ViT profile run issues an
M=768000, N=6144, K=1280 GEMM that AITER 0.1.26 routes to that kernel,
crashing engine startup with a memory access fault.

Fall back to torch (hipBLASLt) for those shapes. They are compute-bound
and hipBLASLt matches the FlyDSL kernel's throughput there.

Signed-off-by: Micah Williamson <micah.williamson@amd.com>
micah-wil added a commit to ROCm/vllm that referenced this pull request Oct 29, 2026
AITER's gfx950 FlyDSL A16W16 kernel reads A and B through flat buffer
descriptors with i32 offsets, so an operand of 4 GiB or more is read
out of bounds and the kernel silently returns wrong rows. For example,
Llama-405B's TP8 gate_up GEMM (N=13312, K=16384) maps to a FlyDSL row
and returns wrong output at 147456 tokens, while C is still under the
2^31 limit. Route these shapes to torch as well, matching the limit
ROCm/aiter#6853 enforces. Drop the on_gfx950() check, which
is_tgemm_enabled() already implies.

Signed-off-by: Micah Williamson <micah.williamson@amd.com>

@xytpai xytpai left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review result: Request changes

Reviewed commit 8b6061a64836fa3ca7a782ea1211f857c55bac57, including all six changed files and the relevant call paths. I found one correctness issue that should be addressed before merging: the newly accepted strided output views can violate the alignment required by 16-bit split-K packed atomics. Details and requested changes are in the inline comment.

Validation and limitations

  • Executed the PR's host-side validation with the launch mocked and confirmed that an odd-column-offset output view reaches dispatch.
  • An independent fp16 BufferAtomicPkAdd test on MI308X/gfx942 succeeded with aligned addresses but triggered HSA_STATUS_ERROR_MEMORY_APERTURE_VIOLATION with a one-element offset.
  • This was a primitive-level reproduction using FlyDSL 0.3.4.1, not an end-to-end gfx950/FlyDSL 0.4.x GEMM reproduction.
  • The host-side input-size boundary checks behaved as expected (M=1677721 accepted and M=1677722 rejected for N=6144, K=1280 with the tested configuration). No other actionable findings were identified within the reviewed scope.

Comment on lines +1663 to +1666
out.stride(1) != 1
or out.stride(0) < n
or out.stride(0) >= 1 << 31
or out.stride(0) % param.cshuffle_r2g_vec_size != 0

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[P1] Validate output pointer alignment for strided split-K outputs

The new validation checks output strides but not the starting address alignment. For example:

parent = torch.empty((16, 5184), dtype=torch.float16, device="cuda")
out = parent[:, 1:5121]
# shape: (16, 5120)
# stride: (5184, 1)
# data_ptr() % 4 == 2

With the existing M=16, N=5120, K=2880, split_k=3 configuration, this view passes validation. However, the fp16/bf16 split-K path uses BufferAtomicPkAdd, which requires a 4-byte-aligned address for each pair of 16-bit elements. An odd column offset violates that requirement even when the row stride is valid, potentially causing a GPU fault.

This case is newly accepted by this PR; previously, the non-contiguous output was rejected by out.is_contiguous().

Please check out.data_ptr() % 4 == 0 for 16-bit split-K outputs before launching, rejecting unsupported alignment or providing a safe alternative. Please also add an odd-column-offset rejection test and an aligned nonzero-offset positive test, checking that elements outside the view remain unchanged. The current check_strided_out() only covers parent[:m, :n].

I confirmed that this view reaches dispatch in the PR's host-side validation with the launch mocked. An independent fp16 packed-atomic test on gfx942 reproduced a GPU memory-aperture fault for the misaligned address; this is not an end-to-end gfx950 GEMM reproduction (see the review summary for environment details).

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

ack. I made the check dtype-independent (split_k > 1 and out.data_ptr() % 4 != 0): the fp32 split-K path also uses 4-byte atomics, so it has the same requirement. fp32 tensors are always 4-byte aligned, so in practice this only rejects 16-bit outputs.

- Split-K's buffer atomics write 4-byte units: an fp32 element or a packed
  16-bit pair. A 16-bit out that is not 4-byte aligned faults
  (HSA_STATUS_ERROR_MEMORY_APERTURE_VIOLATION on MI355X). Reject a misaligned
  out before launch, for contiguous and strided out alike. Non-split-K stores
  handle any element offset.
- test_flydsl_hgemm.py: check_out_column_offset writes an out that starts at a
  nonzero column and checks nothing outside it changes;
  check_split_k_out_alignment expects a ValueError for a misaligned split-K out,
  strided and contiguous.

Co-Authored-By: Claude <noreply@anthropic.com>
@josh-schell-amd
josh-schell-amd force-pushed the fix/flydsl-hgemm-int64-offsets branch from b9682ad to 14b38ab Compare October 30, 2026 04:48
@xytpai

xytpai commented Oct 30, 2026

Copy link
Copy Markdown
Contributor

LGTM.

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

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants