Skip to content

[Feature] Add gemm_a8w8_bpreshuffle_out for caller-owned bf16 outputs - #6437

Merged
demonsan merged 3 commits into
ROCm:mainfrom
yuychang:k3/gemm-a8w8-bpreshuffle-out
Oct 29, 2026
Merged

demonsan merged 3 commits into
ROCm:mainfrom
yuychang:k3/gemm-a8w8-bpreshuffle-out

Conversation

@yuychang

@yuychang yuychang commented Oct 12, 2026 •

Copy link
Copy Markdown
Contributor

Motivation

SGLang's Kimi-K3 ROCm path writes its PTPC FP8 shared-expert down-projection straight into a preallocated buffer (shared_output = buf[offset:].view(M, hidden)), which is later all-reduced together with the MoE latent. gemm_a8w8_bpreshuffle always allocates its output, so today that result has to be copied. This PR adds an op that writes into a caller-owned buffer.

Technical Details

  • New op gemm_a8w8_bpreshuffle_out(out, XQ, WQ, x_scale, w_scale) -> None. It writes into out and picks the same kernel as gemm_a8w8_bpreshuffle, because both share one dispatch helper (_gemm_a8w8_bpreshuffle_into).
  • torch.compile:
    • The op declares only out as mutated (mutates_args=["out"]) and returns nothing. Its schema is (Tensor(a0!) out, Tensor XQ, Tensor WQ, Tensor x_scale, Tensor w_scale) -> ().
    • Compiled graphs therefore add no writebacks for the read-only inputs, and no op output aliases an input.
    • It is a separate op rather than an optional out= on gemm_a8w8_bpreshuffle: narrowing that op's mutates_args with an optional out broke inductor for its existing no-out callers (getitem is not an OpOverload).
  • Inputs:
    • Rejected with ValueError before any launch, because no kernel supports them: non-2-D or non-fp8 XQ/WQ, non-fp32 scales or scales without M/N elements, operands that are not all on one CUDA/HIP device, WQ K < XQ K, and an out that is not a contiguous bf16 [M, N] tensor.
      • bf16 only: the FlyDSL 8-wave kernels, selected by many tuned rows, write bf16 only, so an fp16 out would fail at launch on those shapes.
      • Contiguous out only: CK and CKTile write a dense row-major [M, N], so a strided out (for example a column slice of a wider buffer) would be written past its bounds.
      • out must start 16-byte aligned. A dense view into a larger buffer is fine at an aligned offset; at an odd bf16 offset the CK split-K kernel faulted. The rule applies to every backend so that a valid buffer never depends on which tuned row is selected. torch.empty storage and SGLang's buf[M * latent:] view are always aligned.
  • Arch: only gfx942 and gfx950 are accepted. Elsewhere the op raises NotImplementedError before launch, because the shared dispatch would reach backends this op was never validated on (the gfx1250 WMMA path and the CK MFMA fallback on RDNA); use gemm_a8w8_bpreshuffle there. This check runs after the argument checks, so invalid arguments always get ValueError.
    • Normalized, not rejected: a strided XQ, WQ or scale is copied to a dense tensor first, because CK and CKTile read only row strides and raw scale pointers. Dense inputs pass through without a copy.
    • Any device: when the operands are not on the current device, the op runs under torch.cuda.device(XQ.device), because the launchers use the current device's stream. On the normal path it adds no guard.
    • An empty out (M or N = 0) returns after validation. M=0 matches tuned FlyDSL rows whose launch would otherwise fail.
    • out must not overlap any input. A K-padded WQ keeps gemm_a8w8_bpreshuffle's padded-K handling unchanged.
  • gemm_a8w8_bpreshuffle is unchanged: same signature, same custom-op schema, same results.

Test Plan

New file op_tests/test_gemm_a8w8_bpreshuffle_out.py:

  • The schema marks only out as written and has no return value.
  • out is bit-identical to gemm_a8w8_bpreshuffle on gfx950 tuned rows that select each backend: CK, CK split-K, CKTile, FlyDSL, FlyDSL split-K and FlyDSL 8-wave. Each case asserts that its row still selects that backend and fails otherwise, so coverage cannot silently drop when tables change.
  • Bit-identical on an untuned shape and on the padded-K path (WQ K > XQ K). Both assert they have no tuned row.
  • A dense view of a larger flat buffer gets the right result, and the rest of the buffer is untouched.
  • torch.compile(fullgraph=True) with aot_eager and with inductor (caches disabled): out is filled and matches eager.
  • A wrong shape, fp16 or fp32 out, a column-slice or transposed out, WQ K < XQ K, WQ, x_scale or w_scale on another device, an all-CPU call, a rank-3 XQ or WQ, an int8 XQ, and a bf16 or short scale each raise ValueError and leave the buffer untouched.
  • A column-major or row-strided XQ, a row-strided WQ, strided scales and a non-contiguous [1, M] scale all give the same result as dense inputs.
  • With two GPUs visible, operands on cuda:1 called from cuda:0 give the right result and leave cuda:0 current.
  • An empty out (M=0 on a FlyDSL row and an untuned row, and N=0) is a no-op and leaves the device usable.
  • Running the file directly (python op_tests/test_gemm_a8w8_bpreshuffle_out.py) benchmarks both ops over a spread of N/K shapes, including the Kimi-K3 TP=8 shared-expert down projection (N=7168, K=768), writing out as a dense view into a caller-owned [latent | shared] buffer as SGLang does, and prints a markdown summary.
  • Existing op_tests/test_gemm_a8w8_bpreshuffle_pad_k.py still passes, and gemm_a8w8_bpreshuffle keeps main's schema and still compiles under inductor.
  • out at an odd bf16 offset raises ValueError and leaves the buffer untouched.
  • Every arch outside gfx942/gfx950 (gfx1250, gfx1201, unknown) raises NotImplementedError before launch, and an all-CPU call raises ValueError even when the arch probe fails.
  • The per-backend tuned-row cases run only on gfx950 with 256 CUs.
  • gemm_a8w8_bpreshuffle still compiles under inductor (fullgraph=True) and matches eager.

Test Result

MI355X (gfx950), ROCm 7.2, torch 2.11.0+rocm7.2:

  • op_tests/test_gemm_a8w8_bpreshuffle_out.py: 53 passed, 1 skipped with one GPU visible (the skipped test needs two GPUs and passes with two). With no GPU visible, all skip.
  • op_tests/test_gemm_a8w8_bpreshuffle_pad_k.py: 17 passed, 8 skipped (gfx942 tuned-table cases).
  • GPU kernel time per call (us), from the in-file benchmark, which now also times gemm_a8w8_bpreshuffle followed by the copy into the caller's buffer (run_perftest, FlyDSL rows at every shape below, error 0 everywhere). N=7168 K=768 is the Kimi-K3 TP=8 shared-expert down projection:
M N, K bpreshuffle bpreshuffle + copy bpreshuffle_out
1 7168, 768 2.75 4.10 2.75
4 7168, 768 2.74 4.76 2.74
16 7168, 768 2.78 4.71 2.76
64 7168, 768 3.33 5.16 3.34
4 7168, 2048 3.43 4.65 3.42
64 7168, 2048 4.68 9.94 4.66
  • Host time per call (M=4, best of 5 x 2000 calls, standalone script): K=768: 27.0 us with the copy vs 23.4 us for _out (22.6 us for bpreshuffle alone); K=2048: 24.9 vs 22.3 us (21.5 us alone). An earlier run of the same comparison read 33.0 -> 28.6 us host and 5.63 -> 3.92 us GPU at K=2048; the re-run above replaces it.
  • The SGLang caller was proposed in [Perf] [AMD] kimi-k3: Fold decode activation FP8 quant into its producers sgl-project/sglang#43125 (ptpc_fp8_aiter_hip.py, run(..., out=)), which was closed without merging; this op has no in-tree caller until that change is resubmitted.
  • The benchmark main() over M = 1 to 4096 and N/K = 4096/4096, 7168/2048, 2112/7168, 8192/1024 and 7168/768 selects CK, CKTile, FlyDSL and untuned rows. Error is 0 for both ops on every row, and kernel times match within noise.

Submission Checklist

🤖 Generated with Claude Code

@yuychang
yuychang requested review from a team and a balanced review from Copilot October 12, 2026 14:23
@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 6437 --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.

Copilot AI 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.

🟡 Changes recommended

Same-device validation is incomplete, and backend/fallback coverage can silently disappear.

2 open findings
What changed in this PR

Adds caller-owned BF16 output support for pre-shuffled A8W8 GEMM while sharing existing kernel dispatch.

Changes:

  • Adds gemm_a8w8_bpreshuffle_out.
  • Extracts shared dispatch logic.
  • Adds backend, validation, buffer-view, and compilation tests.
File Description
aiter/​ops/​gemm_op_a8w8.py Adds output-buffer API and shared dispatch helper.
op_tests/​test_gemm_a8w8_bpreshuffle_out.py Tests output semantics, dispatch, validation, and compilation.

🧠 Review effort: Balanced


💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread aiter/ops/gemm_op_a8w8.py Resolved
Copilot AI balanced review requested due to automatic review settings October 12, 2026 14:52

Copilot AI 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.

🟡 Changes recommended

GPU-device and input-dtype validation remain incomplete, and the test architecture and benchmark contracts need correction.

1 open finding
2 resolved since last review
Previously missed (4)

In code that hasn't changed since last review

Medium severity Validate both quantized operand dtypes

aiter/​ops/​gemm_op_a8w8.py:893

Only WQ is type-checked. On a tuned 8-wave row, an int8 XQ passes the launcher's one-byte-storage check and is then consumed by the FP8 kernel, producing incorrect values rather than rejecting the input. Validate both quantized operands here.

Medium severity Validate scale tensors are fp32

aiter/​ops/​gemm_op_a8w8.py:899

The documented fp32 scale contract is not enforced before dispatch. The standard FlyDSL path passes these tensors through unchanged but emits BufferCopy32b(..., Float32) scale loads (preshuffle_gemm.py:716), while the 8-wave path converts them, so non-fp32 scales have backend-dependent behavior and can be misread by the standard kernel. Validate both scale dtypes before calling the helper.

Low severity Add the standard runnable op-test benchmark flow

op_tests/​test_gemm_a8w8_bpreshuffle_out.py:19

This new op_tests/test_*.py suite omits the repository's standard runnable op-test entry point: there is no @benchmark/run_perftest candidate loop, checkAllclose, markdown summary, or main() gated by get_gfx(). As a result, the claimed allocation-overhead improvement has no in-repo benchmark reproducer and the suite cannot be exercised through the normal op-test workflow. Add the standard benchmark/main flow while retaining these pytest contract cases.

Low severity Use get_gfx consistently for architecture checks

op_tests/​test_gemm_a8w8_bpreshuffle_out.py:25

This test gate uses the live-device helper, but _backend() calls the tuned-table lookup whose architecture key comes from get_gfx(). When GPU_ARCHS and the live card differ, the gate and the backend assertion inspect different architecture contracts, so the intended gfx950 backend coverage can be silently skipped. Tests should use get_gfx() consistently with the lookup they verify.

🧠 Review effort: Balanced

Comment thread aiter/ops/gemm_op_a8w8.py Resolved
Copilot AI balanced review requested due to automatic review settings October 12, 2026 15:06

Copilot AI 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.

🟡 Changes recommended

Missing rank, FP8 operand, and FP32 scale validation can pass incompatible pointers or dimensions to GPU backends.

1 open finding
1 resolved since last review

🧠 Review effort: Balanced

Comment thread aiter/ops/gemm_op_a8w8.py Resolved
yuychang added a commit to yuychang/sglang that referenced this pull request Oct 12, 2026
…8 adapter

- run() passed out= on every call, even out=None; aiter builds without that kwarg failed at warmup.
- Without a buffer, call gemm_a8w8_bpreshuffle with no out=.
- With a buffer, call aiter's gemm_a8w8_bpreshuffle_out (ROCm/aiter#6437), or copy on builds that lack it.
- Reject an out whose dtype differs from the GEMM output dtype.
yuychang added a commit to yuychang/aiter that referenced this pull request Oct 12, 2026
- Same op as ROCm#6437: writes into out, returns None, mutates only out, bf16 contiguous [M, N] only.
- Share dispatch with gemm_a8w8_bpreshuffle via _gemm_a8w8_bpreshuffle_into.
- Keep the out= kwarg on gemm_a8w8_bpreshuffle until SGLang's ptpc wrapper no longer passes it.
yuychang added a commit to yuychang/aiter that referenced this pull request Oct 12, 2026
…coverage

- Same as ROCm#6437: reject out, WQ, x_scale or w_scale on a device other than XQ's before launch.
- Fail, not skip, when a per-backend test row no longer selects its backend; assert untuned rows stay untuned.
yuychang added a commit to yuychang/aiter that referenced this pull request Oct 12, 2026
- Same as ROCm#6437: require XQ to be a CUDA/HIP tensor, so an all-CPU call fails before any launch.
yuychang added a commit to yuychang/sglang that referenced this pull request Oct 12, 2026
…ller outputs

- Accept producer-quantized (fp8, per-token scale) inputs in apply_fp8_linear and Quark W8A8 FP8 on ROCm.
- Add ROCm probes for linears that take group-128 or PTPC FP8 tuples without re-quantization.
- Restore QuarkW4A4MXFP4.apply_into; the Kimi-K3 MoE path calls it on linear.scheme.
- Add the PTPC FP8 AITER GEMM wrapper with packing, per-channel detection and numerical tests.
- Write PTPC GEMM out= via aiter's gemm_a8w8_bpreshuffle_out (ROCm/aiter#6437), or copy on builds lacking it.
Copilot AI balanced review requested due to automatic review settings October 12, 2026 16:18

Copilot AI 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.

🟡 Changes recommended

Scale validation can permit invalid memory reads, and the new test suite does not follow the required op-test structure.

1 open finding
1 resolved since last review
Previously missed (2)

In code that hasn't changed since last review

Medium severity Integrate GEMM coverage with the standard op-test contract

op_tests/​test_gemm_a8w8_bpreshuffle_out.py:8

This new op_tests/test_*.py suite does not follow the repository's op-test contract: it is pytest-only and has no @benchmark/run_perftest candidate loop, checkAllclose result reporting, markdown summary, or main() architecture gate. Consequently the caller-owned path is not exercised by the standard correctness/performance sweep. Integrate this coverage into the existing GEMM op test (or reshape this file to the standard aiter-op-test structure).

Medium severity Use get_gfx for test-time architecture selection

op_tests/​test_gemm_a8w8_bpreshuffle_out.py:25

Tests and build-time architecture gates must use get_gfx(); get_gfx_runtime() is reserved for runtime dispatch. Using the runtime probe here bypasses GPU_ARCHS, so the backend rows selected by the test can disagree with the configured test target. Switch this test helper to the test/build-time architecture helper.

🧠 Review effort: Balanced

Comment thread aiter/ops/gemm_op_a8w8.py Resolved
Copilot AI balanced review requested due to automatic review settings October 12, 2026 16:46
@yuychang

Copy link
Copy Markdown
Contributor Author

Addressing the "previously missed" items from the Copilot review summaries, all in 043ebf3:

  • Validate both quantized operand dtypes: XQ must now be fp8, like WQ. New test test_int8_activation_rejected_before_launch.
  • Validate scale tensors are fp32: see the reply on the inline thread. Scales must be contiguous fp32 with M and N elements.
  • Use get_gfx() for architecture checks: the per-backend gate now uses get_gfx(), the same function the tuned-table lookup uses.
  • Standard op-test benchmark flow: the file now has a @benchmark / run_perftest / checkAllclose main() that compares gemm_a8w8_bpreshuffle and gemm_a8w8_bpreshuffle_out on the Kimi-K3 shared-expert down-projection shapes and prints a markdown summary. It is gated on get_gfx(). The pytest contract cases are unchanged. The benchmark function is named bench_out so pytest does not collect it.

Copilot AI 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.

🟡 Changes recommended

Input-layout validation and non-current GPU dispatch must be corrected to prevent invalid kernel reads or launches.

1 open finding
1 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity Set XQ.device as current device before launching kernels

aiter/​ops/​gemm_op_a8w8.py:923

This call does not establish XQ.device as the current device. The CK launcher and the ordinary FlyDSL launcher use the ambient current HIP stream/device, so tensors that are all on (for example) cuda:1 can still launch on cuda:0 when that is current. Run the shared dispatch under an XQ.device guard so the documented same-device contract works on non-current GPUs as well.

🧠 Review effort: Balanced

Comment thread aiter/ops/gemm_op_a8w8.py Resolved
Copilot AI balanced review requested due to automatic review settings October 12, 2026 17:02
@yuychang

Copy link
Copy Markdown
Contributor Author

On the "previously missed" device-guard item, fixed in d848178: with every operand on cuda:1 and cuda:0 current, the call caused a GPU memory access fault. The existing gemm_a8w8_bpreshuffle faults the same way. The op now raises ValueError unless the operands are on the current device. I chose a check over a torch.cuda.device guard because the guard cost about 1.7 us of host time per call, while the check costs about 0.4 us. SGLang already sets the current device per rank. New test test_non_current_device_rejected_before_launch checks the rejection, that out is untouched, and that the same call succeeds under torch.cuda.device(1). Fixing gemm_a8w8_bpreshuffle itself is left out of this PR.

Copilot AI 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.

🔵 Needs a closer look

The existing allocating operation lacks the claimed schema and inductor regression coverage.

0 open findings

1 resolved since last review
Previously missed (1)

In code that hasn't changed since last review

Medium severity Add regression test for original no-out op signature

op_tests/​test_gemm_a8w8_bpreshuffle_out.py:135

The compile regression that motivated keeping this as a separate op is not covered here: this test compiles only gemm_a8w8_bpreshuffle_out, and there is no repository test asserting that the existing no-out op still compiles or retains its schema. Add a fullgraph=True inductor regression test that invokes gemm_a8w8_bpreshuffle with its original signature, since the PR description notes that this exact caller path broke with the earlier design.

🧠 Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 12, 2026 17:19

Copilot AI 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.

🔵 Needs a closer look

The architecture guard needs correction, and multi-backend buffer writes and compiled mutation behavior warrant human confirmation of runtime validation.

1 open issue

Review effort: Balanced

Comment thread aiter/ops/gemm_op_a8w8.py Resolved
Copilot AI balanced review requested due to automatic review settings October 27, 2026 15:49

Copilot AI 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.

🔵 Needs a closer look

GPU buffer writes and compiler mutation semantics need final human confirmation, and the hardware-specific test gates still need correction.

2 open issues

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 27, 2026 16:27

Copilot AI 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.

🔵 Needs a closer look

Multi-backend GPU execution and compiler mutation behavior need final human review because the reported runtime results were not independently validated.

No open issues 🎉

2 resolved since last review

Review effort: Balanced

demonsan
demonsan previously approved these changes Oct 28, 2026
Copilot AI balanced review requested due to automatic review settings October 28, 2026 02:37
@yuychang

Copy link
Copy Markdown
Contributor Author

Two follow-ups in this push. test_allocating_op_still_compiles_under_inductor now compiles gemm_a8w8_bpreshuffle with inductor (fullgraph=True) and checks it against eager, so the no-out path that motivated a separate op is covered. The arch check in gemm_a8w8_bpreshuffle_out now runs after the argument checks, so a CPU-tensor call on a host with no GPU gets the documented ValueError. The earlier gfx1250 items are covered by rejecting every arch other than gfx942/gfx950.

Copilot AI 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.

🔵 Needs a closer look

Human assessment is needed because the standard CI runner executes only the benchmark, skipping schema, validation, aliasing, empty-output, and torch.compile contract tests.

No open issues 🎉

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 28, 2026 09:13

Copilot AI 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.

🔵 Needs a closer look

The mutable caller-owned-buffer op spans CK, CKTile, and FlyDSL, requiring GPU validation of correctness, torch.compile, and device/stream behavior on gfx942/gfx950 that this source-only review could not independently perform.

No open issues 🎉

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 28, 2026 14:27

Copilot AI 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.

🔵 Needs a closer look

GPU execution is unavailable, leaving the new mutating op’s caller-owned writes, torch.compile behavior, and non-current-device stream handling unverified on supported ROCm hardware.

No open issues 🎉

Review effort: Balanced

Copilot AI balanced review requested due to automatic review settings October 28, 2026 15:02

Copilot AI 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.

🔵 Needs a closer look

Caller-owned GPU writes and torch.compile mutation semantics across CK, CKTile, and FlyDSL require runtime validation on gfx942 and gfx950 that this source-only review cannot independently perform.

No open issues 🎉

Review effort: Balanced

@yuychang yuychang changed the title Add gemm_a8w8_bpreshuffle_out for caller-owned bf16 outputs [Feature] Add gemm_a8w8_bpreshuffle_out for caller-owned bf16 outputs Oct 28, 2026
- Add gemm_a8w8_bpreshuffle_out(out, XQ, WQ, x_scale, w_scale): writes into out, returns None, mutates only out.
- Share dispatch with gemm_a8w8_bpreshuffle via _gemm_a8w8_bpreshuffle_into, so both ops pick the same kernel.
- Keep gemm_a8w8_bpreshuffle's signature and schema; an optional out= on it broke inductor for its no-out callers.
- Accept only a contiguous, 16-byte aligned bf16 [M, N] out; FlyDSL 8-wave kernels write bf16 only.
- Reject a column-slice out that CK/CKTile would overrun, and an odd bf16 offset that faults CK split-K.
- Require 2-D fp8 XQ/WQ: dispatch reads K from shape[-1] but the CK launcher reads size(1).
- Require fp32 scales with M/N elements, every operand on one CUDA/HIP device, and WQ K >= XQ K.
- Raise ValueError for every bad argument before any kernel launch, so out is left untouched.
- Accept only gfx942 and gfx950; check the arch after the arguments so a GPU-less CPU call still gets ValueError.
- Copy strided XQ/WQ/scales to dense; dense inputs pass through without a copy.
- Reject an out that overlaps a dense input by byte range; the schema tells torch.compile only out is written.
- Return early for an empty out: tuned rows exist for M=0, but no launcher accepts an empty grid.
- Run under torch.cuda.device when the operands are not on the current device; the launchers use its stream.
- Check out is bit-identical to gemm_a8w8_bpreshuffle on CK, CK split-K, CKTile, FlyDSL, FlyDSL split-K and 8-wave.
- Fail, not skip, when a per-backend gfx950 256-CU row no longer selects its backend, so coverage cannot drop.
- Cover the untuned and padded-K default-kernel paths, asserting those shapes stay untuned.
- Check the schema marks only out as written and returns nothing.
- Run it under torch.compile (aot_eager and inductor) and keep gemm_a8w8_bpreshuffle compiling under inductor.
- Cover a dense view into a larger buffer, leaving the bytes before it untouched.
- Check bad out, mixed devices, all-CPU operands, unsupported arch, WQ K < XQ K, non-2-D, int8 XQ and bad scales.
- Assert every rejected call raises before launch and leaves the output storage unchanged.
- Reject an out overlapping each input; strided inputs that alias out still match, as the kernel reads a copy.
- Cover operands on a non-current device (two GPUs) and empty outputs, including an M=0 row that selects FlyDSL.
- Gate on get_gfx_runtime, which the op dispatches with, and skip the module outside gfx942/gfx950.
…lus a copy

- Add a @benchmark bench_out and main() comparing bpreshuffle, bpreshuffle+copy and bpreshuffle_out per shape.
- bpreshuffle+copy is what a caller without the out op pays, so the table shows the saved copy kernel time.
- Write out as a dense view past the MoE latent in a flat buffer, matching SGLang's Kimi-K3 [latent | shared] layout.
- Check each candidate against an fp32 torch reference with checkAllclose and log a markdown summary table.
- Count scale bytes and use out.element_size() in the TB/s byte total.
- Take shapes with the standard -s/--mnk M,N,K flag; default is the M token sweep crossed with five (N, K) weights.
- Include the Kimi-K3 TP=8 shared-expert down projection (N=7168, K=768) first in the default sweep.
@yuychang
yuychang force-pushed the k3/gemm-a8w8-bpreshuffle-out branch from b404714 to a55577f Compare October 29, 2026 02:10

@demonsan demonsan 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.

LGTM

@demonsan
demonsan merged commit 6fe1bfc into ROCm:main Oct 29, 2026
55 checks passed
solinzby1 pushed a commit that referenced this pull request Nov 2, 2026
…#6437)

* feat: add gemm_a8w8_bpreshuffle_out for caller-owned bf16 outputs

- Add gemm_a8w8_bpreshuffle_out(out, XQ, WQ, x_scale, w_scale): writes into out, returns None, mutates only out.
- Share dispatch with gemm_a8w8_bpreshuffle via _gemm_a8w8_bpreshuffle_into, so both ops pick the same kernel.
- Keep gemm_a8w8_bpreshuffle's signature and schema; an optional out= on it broke inductor for its no-out callers.
- Accept only a contiguous, 16-byte aligned bf16 [M, N] out; FlyDSL 8-wave kernels write bf16 only.
- Reject a column-slice out that CK/CKTile would overrun, and an odd bf16 offset that faults CK split-K.
- Require 2-D fp8 XQ/WQ: dispatch reads K from shape[-1] but the CK launcher reads size(1).
- Require fp32 scales with M/N elements, every operand on one CUDA/HIP device, and WQ K >= XQ K.
- Raise ValueError for every bad argument before any kernel launch, so out is left untouched.
- Accept only gfx942 and gfx950; check the arch after the arguments so a GPU-less CPU call still gets ValueError.
- Copy strided XQ/WQ/scales to dense; dense inputs pass through without a copy.
- Reject an out that overlaps a dense input by byte range; the schema tells torch.compile only out is written.
- Return early for an empty out: tuned rows exist for M=0, but no launcher accepts an empty grid.
- Run under torch.cuda.device when the operands are not on the current device; the launchers use its stream.

* test: cover the gemm_a8w8_bpreshuffle_out contract

- Check out is bit-identical to gemm_a8w8_bpreshuffle on CK, CK split-K, CKTile, FlyDSL, FlyDSL split-K and 8-wave.
- Fail, not skip, when a per-backend gfx950 256-CU row no longer selects its backend, so coverage cannot drop.
- Cover the untuned and padded-K default-kernel paths, asserting those shapes stay untuned.
- Check the schema marks only out as written and returns nothing.
- Run it under torch.compile (aot_eager and inductor) and keep gemm_a8w8_bpreshuffle compiling under inductor.
- Cover a dense view into a larger buffer, leaving the bytes before it untouched.
- Check bad out, mixed devices, all-CPU operands, unsupported arch, WQ K < XQ K, non-2-D, int8 XQ and bad scales.
- Assert every rejected call raises before launch and leaves the output storage unchanged.
- Reject an out overlapping each input; strided inputs that alias out still match, as the kernel reads a copy.
- Cover operands on a non-current device (two GPUs) and empty outputs, including an M=0 row that selects FlyDSL.
- Gate on get_gfx_runtime, which the op dispatches with, and skip the module outside gfx942/gfx950.

* test: benchmark gemm_a8w8_bpreshuffle_out against the allocating op plus a copy

- Add a @benchmark bench_out and main() comparing bpreshuffle, bpreshuffle+copy and bpreshuffle_out per shape.
- bpreshuffle+copy is what a caller without the out op pays, so the table shows the saved copy kernel time.
- Write out as a dense view past the MoE latent in a flat buffer, matching SGLang's Kimi-K3 [latent | shared] layout.
- Check each candidate against an fp32 torch reference with checkAllclose and log a markdown summary table.
- Count scale bytes and use out.element_size() in the TB/s byte total.
- Take shapes with the standard -s/--mnk M,N,K flag; default is the M token sweep crossed with five (N, K) weights.
- Include the Kimi-K3 TP=8 shared-expert down projection (N=7168, K=768) first in the default sweep.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants