Auto-generate matmul tiling schedules in the driver - #87
Open
erwei-xilinx wants to merge 2 commits into
Open
Conversation
erwei-xilinx
force-pushed
the
feat-matmul-autogen
branch
from
July 31, 2026 21:49
354d0d6 to
19ac287
Compare
Running a GEMM on the NPU previously required hand-writing (or manually generating) a transform-dialect tiling script and passing it via AIR_TRANSFORM_TILING_SCRIPT; the built-in default is only a trivial non-vectorized 32x32 tiling. This makes f32/bf16/i8 matmuls work with zero configuration. When no user script is supplied and the lowered TritonShared IR is a single plain linalg.matmul, the driver now derives tiling parameters straight from the IR (element types + tile shapes) and calls the existing matmul transform generator. f32 additionally enables bf16 emulation automatically (no native f32 MAC on AIE). - Move the generator into the backend package (amd_triton_npu/backend/matmul_transform.py) so it ships in the wheel. - Add _detect_matmul / _matmul_transform_params; wire them into _get_transform_ir_string and the launch() path. User-supplied scripts still take priority; non-matmul IR is untouched. - Derive pack sizes and herd caps per NPU generation; cap the L2 K tile at 2*pack_k (larger tiles miscompute padded boundary tiles under air-split-launch-for-padding). - setup.py: ship config.py and matmul_transform.py in the pre-installed triton copy path. Validated on NPU2/Strix (scriptless): f32 padded (500x500x1024), bf16, and i8 matmuls all pass; compile time and latency at parity with the committed hand-written scripts. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
erwei-xilinx
force-pushed
the
feat-matmul-autogen
branch
from
July 31, 2026 21:52
19ac287 to
cbdf416
Compare
Contributor
There was a problem hiding this comment.
Pull request overview
This PR adds a “scriptless” path for NPU matmul kernels by detecting plain linalg.matmul in lowered TritonShared IR and auto-generating an MLIR Transform dialect tiling script in the driver, while still respecting user-provided AIR_TRANSFORM_TILING_SCRIPT overrides.
Changes:
- Add matmul detection + parameter derivation in
driver.py, and wire auto-generated transform scripts into the AIR lowering/launch path. - Move the matmul transform generator into the backend (
matmul_transform.py) so it ships with the wheel and can be invoked programmatically. - Update
setup.pyto copy additional backend Python files into Triton’s installed backend directory.
Reviewed changes
Copilot reviewed 2 out of 3 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| setup.py | Ensures the backend copy step includes config.py and the new matmul transform generator. |
| amd_triton_npu/backend/matmul_transform.py | Adds a parameterized generator that emits the transform-dialect script used for matmul tiling/vectorization. |
| amd_triton_npu/backend/driver.py | Detects eligible matmul IR, derives schedule params, enables bf16 emulation for f32 matmul, and routes the generated script into AIR compilation. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
…che key - Derive the bf16-emulation decision from the detected matmul input dtype rather than from whether a schedule could be generated, so an f32 matmul still enables emulation when parameter derivation fails and we fall back to the default tiling. - Include the transform tiling script path in the in-process module cache key so the same kernel compiled with different schedules (or scriptless vs a user script) does not collide on the fast path. Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
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
Makes f32/bf16/i8 GEMMs run on the NPU with zero configuration. Previously each matmul needed a hand-written (or manually generated) transform-dialect tiling script passed via
AIR_TRANSFORM_TILING_SCRIPT; the built-in default is only a trivial non-vectorized 32×32 tiling.When no user script is supplied and the lowered TritonShared IR is a single plain
linalg.matmul, the driver now derives tiling parameters directly from the IR (operand element types + tile shapes) and calls the existing matmul transform generator.amd_triton_npu/backend/matmul_transform.py) so it ships in the wheel. No logic change; it already produced every committedmatmul_*script._detect_matmul/_matmul_transform_paramsindriver.py, wired into_get_transform_ir_stringand thelaunch()path.accum=f32, contract_in=bf16+ auto-enable bf16 emulation (no native f32 MAC on AIE); bf16 ⇒accum=f32; i8 ⇒accum=i32, contract_in=i16.(8,8,8)/4×4, npu1(4,4,8)/4×2).2*pack_k— larger tiles miscompute padded boundary tiles underair-split-launch-for-padding.AIR_TRANSFORM_TILING_SCRIPTstill wins; non-matmul IR (elementwise, softmax, matvec, …) is untouched (_detect_matmulreturnsNone).setup.py: shipconfig.pyandmatmul_transform.pyin the pre-installed-triton copy path.Validation (NPU2/Strix, scriptless)
Correctness passes for f32 padded (500×500×1024), bf16, and i8. Same-driver A/B (committed script vs auto-gen) shows parity in compile time and per-dispatch latency, including a K=2048 stress where the L2 K-tile choice matters most.
Validation (NPU1/Phoenix, scriptless)
Tested on NPU Phoenix (XRT 2.23.0, fw 1.5.5.391),
detect_npu_version() == "npu1", xclbin output. Run against this branch's pinned toolchain (mlir-aird3c5f87/ mlir-aie v1.4.0).pack_sizes=(4,4,8)aircc/aiecc, pre-existing — the committed hand-writtentransform_aie2.mlirfails at the identical stage, so auto-gen is not the causebf16 A/B, fresh
TRITON_CACHE_DIReach, full 27-shape sweep: committed script 392 s vs scriptless 352 s. No compile-time regression. (Wall-clock over the sweep; per-dispatch latency not separately measured on npu1.)npu1 i8 needs a pack-size change in this PR
pack_sizesis ordered(M, N, K)while the hardware MAC shape is(M, K, N). The two coincide only when the shape is cubic, which is why npu2(8,8,8)hides the distinction. On npu1 the current(4,4,8)means(M,K,N) = (4,8,4), and:IsValidAIE2MatMulShapeAndTypein mlir-aie'sAIEVecTypeConstraints.tdhas exactly one i8×i8→i32 entry for AIE2:4x8 × 8x8 → 4x8, i.e.(M,K,N) = (4,8,8). It has exactly one bf16 entry,(M,K,N) = (4,8,4)— which is what(4,4,8)produces, so bf16 is correct as written. Pack sizes on AIE2 are dtype-dependent, not just generation-dependent:pack_sizes(8,8,8)(8,8,8)✅(4,4,8)(4,4,8)✅(4,8,8)(4,4,8)❌mlir-air's
programming_examples/matrix_multiplication/i8agrees:run.pysetsmmul_mkn = [4,8,8]foraie2and[8,8,8]foraie2p.Separately,
contract_input_type="i16"is inert for i8 and can be dropped —getSourceOfWideningOpin mlir-aie peels the extension off the contract operands either way. I measured identical results forNone/"i8"/"i16".Dependency: npu1 i8 also needs Xilinx/mlir-aie#3489
Correcting the pack size makes npu1 i8 compile, but results are still wrong — an upstream AIE2 defect, not something this PR can fix. The lowered MAC gets configuration word
0x108(signX=0, signY=1), so the LHS is zero-extended while the RHS is sign-extended, computingzext8(A) * sext8(B):Non-negative A is always correct, so this is invisible to positive-only smoke tests. I confirmed layout, K-reduction and herd distribution are all fine in isolation (single-element placement exact, K-sweep exact to K=256,
A=ones,B=onesexact) — only signed LHS values are wrong. Fix filed as Xilinx/mlir-aie#3489.Suggested sequencing: land the
(4,8,8)pack fix here, and until #3489 is in the pinned mlir-aie, gate i8 off on npu1 with an explicit error rather than emitting a kernel that silently returns wrong values for negative inputs. bf16 on npu1 is unaffected and ready.Notes
(4,8,8)pack fix above; bf16 on npu1 is validated and unblocked.fix-xrt-smi-memoization) — the scriptless path reachesdetect_npu_version(), and that PR removes the per-dispatchxrt-smisubprocess it would otherwise trigger. Retarget this PR tomainonce Cache NPU device probe to avoid per-dispatch xrt-smi subprocess #86 merges.Test plan
py_compile+black --checkon changed filesscripts/run_tests.pyon NPU hardware (examples still ship their scripts, so behavior is unchanged there)🤖 Generated with Claude Code