Skip to content

[CUDA] Add native SM75 MMA GEMM support for FP16, INT8 and INT4 - #2198

Merged
LeiWang1999 merged 10 commits into
tile-ai:mainfrom
Tokimorphling:main
May 20, 2026
Merged

LeiWang1999 merged 10 commits into
tile-ai:mainfrom
Tokimorphling:main

Conversation

@Tokimorphling

@Tokimorphling Tokimorphling commented May 13, 2026 •

Copy link
Copy Markdown
Contributor

Summary

This PR adds native SM75/Turing MMA GEMM support for FP16, INT8, and INT4.

It addresses the same SM75 instruction-selection issue reported in #1529 and #1992, where T.gemm() could lower to SM80-only MMA instructions on Turing GPUs. In addition, this PR adds native INT8 and INT4 SM75 MMA paths.

Changes include:

  • Add SM75 FP16 m16n8k8 dispatch.
  • Add SM75 INT8 m8n8k16 dispatch.
  • Add SM75 INT4/UINT4 m8n8k32 dispatch.
  • Route SM75 through the regular CUDA MMA GEMM path instead of the SM70 path.
  • Update the MMA emitter to select Turing-specific instruction shapes.
  • Add SM75 emitter and kernel correctness tests.
  • Include CUDA lowering/templates in the kernel cache key to avoid stale generated kernels after lowering/template changes.

Related to #1529 and #1992.

Validation

Validated on RTX 2080 Ti / SM75.

INT8

Before 192a118967a376ee58eb8db6bdf07fccb329fb61, SM75 INT8 GEMM is lowered through the SM70 MMA path and fails to compile:

#include <tl_templates/cuda/instruction/mma_sm70.h>
static assertion failed with "tl::mma_sync_sm70: unsupported configuration..."

After 192a118967a376ee58eb8db6bdf07fccb329fb61:

uses SM75 INT8 m8n8k16: True
uses SM80 INT8 m16n8k32: False
INT8 correctness: PASS

INT4

Before a6769c3dc5267f8fbf6139cb2da551d009904964, SM75 INT4 GEMM is lowered to the SM80-only m16n8k64 path:

uses SM75 INT4 m8n8k32: False
uses SM80 INT4 m16n8k64: True
AssertionError

After a6769c3dc5267f8fbf6139cb2da551d009904964:

uses SM75 INT4 m8n8k32: True
uses SM80 INT4 m16n8k64: False
INT4 correctness: PASS

Tests

PYTHONPATH=/home/asuka/codes/tilelang \
python -m pytest testing/python/cuda/test_cuda_mma_sm75_dispatch.py -q
5 passed
PYTHONPATH=/home/asuka/codes/tilelang \
LD_LIBRARY_PATH=/home/asuka/codes/tilelang/build/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH} \
TL_TEMPLATE_PATH=/home/asuka/codes/tilelang/src \
python -m pytest testing/python/kernel/test_tilelang_kernel_gemm_sm75.py -q
3 passed
git diff --check
passed

Summary by CodeRabbit

  • New Features

    • Added NVIDIA Turing (SM75) GPU optimization support for GEMM operations with FP16, INT8, and INT4 data types.
    • Turing-specific optimized matrix multiply-accumulate dispatch now automatically activates for compatible GPUs.
  • Tests

    • Added comprehensive test suite for SM75 GEMM operations across FP16, INT8, and INT4 data types with validation against reference implementations.

Review Change Stack

@github-actions

Copy link
Copy Markdown

👋 Hi! Thank you for contributing to the TileLang project.

Please remember to run pre-commit run --all-files in the root directory of the project to ensure your changes are properly linted and formatted. This will help ensure your contribution passes the format check.

We appreciate you taking this step! Our team will review your contribution, and we look forward to your awesome work! 🚀

@coderabbitai

coderabbitai Bot commented May 13, 2026 •

Copy link
Copy Markdown
Contributor

Note

Reviews paused

It looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the reviews.auto_review.auto_pause_after_reviewed_commits setting.

Use the following commands to manage reviews:

  • @coderabbitai resume to resume automatic reviews.
  • @coderabbitai review to trigger a single review.

Use the checkboxes below for quick actions:

  • ▶️ Resume reviews
  • 🔍 Trigger review
📝 Walkthrough

Walkthrough

This PR adds complete SM75/Turing support for TileLang CUDA MMA GEMM: routing GEMM dispatch to select Turing-specific emitter, implementing SM75-aware MMA shape policies with optimized configurations for FP16, INT8, and INT4, adding inline-PTX hardware MMA implementations, and validating end-to-end correctness with kernel and unit tests.

Changes

SM75 Turing MMA Support

Layer / File(s) Summary
Target dispatch & emitter wiring
tilelang/cuda/op/gemm/__init__.py, tilelang/cuda/op/gemm/gemm_mma.py
CUDA GEMM now dispatches differently for Turing targets: _match_mma excludes Volta (allowing Turing to use GemmMMA), _match_mma_sm70 matches Volta only, and GemmMMA._make_mma_emitter conditionally selects TensorCoreIntrinEmitterSM75 when target_is_turing(target) is true.
MMA emitter core updates & store-index map
tilelang/cuda/intrinsics/macro/mma_macro_generator.py
Refactors FP64 M_DIM initialization into _initialize_m_dim(), introduces _use_fp64_store_index_map() helper to select FP64-style store-index mapping when accum_dtype is 64-bit or local_size_out == 2, integrates selected map into shared/global stmatrix paths, and simplifies TensorCoreIntrinEmitterWithLadderTransform._initialize_micro_size by removing explicit n_dim parameter.
SM75-specific emitter class
tilelang/cuda/intrinsics/macro/mma_sm75_macro_generator.py
Adds TensorCoreIntrinEmitterSM75 subclass that overrides k_dim, M_DIM, mma_prefix, and micro-size initialization with dtype-aware logic: FP16 uses m16n8k8, INT8 uses m8n8k16, INT4/UINT4 use m8n8k32, with string-to-DataType normalization and integer-shape assertions.
SM75 instruction implementations & dispatchers
src/tl_templates/cuda/instruction/mma.h
Includes cute/arch/mma_sm75.hpp, defines two SM75 MMA implementation structs (SM75_8x8x32_S32S4S4S32_TN and SM75_8x8x32_S32U4U4S32_TN) emitting inline PTX when CUTE_ARCH_MMA_SM75_ENABLED, and registers SM75 dispatchers for FP16×FP16→FP32 (m16n8k8), INT8 (m8n8k16), and INT4/UINT4 (m8n8k32) configurations.
SM75 GEMM kernel integration tests
testing/python/kernel/test_tilelang_kernel_gemm_sm75.py
Provides parameterized GEMM kernel generator, asserts expected tl::mma_sync shapes in compiled source, packs INT4 inputs, and runs FP16 (vs PyTorch), INT8 (vs CPU int32), and INT4 (vs CPU int32) end-to-end tests locked to compute 7.5.
Dispatch & emitter unit tests
testing/python/cuda/test_cuda_mma_sm75_dispatch.py
Validates resolve_gemm_impl routing for Volta vs. Turing, tests TensorCoreIntrinEmitterSM75 parameterization for FP16/INT8/INT4, verifies store-index map presence for integer types, confirms non-SM75 INT8 retains SM80-style m16n8k32 shape, rejects uint4 with expected error, and checks ladder-transform INT8 sizing.
CMake reordering
CMakeLists.txt
Moves project(TILE_LANG C CXX) before FindPipCUDAToolkit.cmake include and removes obsolete CMake 4.x scoping comment.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • tile-ai/tilelang#1956: Extends SM75/Turing MMA support by including cute/arch/mma_sm75.hpp and adding SM75-specific MMA dispatch and emit paths in a parallel codebase location.
  • tile-ai/tilelang#2173: Also changes CUDA GEMM/tl::mma_sync dispatch for SM75/Turing by modifying target matching and MMA emitter selection logic, but reroutes SM75 to Volta/SM70 path instead of adding native SM75 support.
  • tile-ai/tilelang#2073: Extends tl_templates/cuda/instruction/mma.h with new int4/uint4 dispatcher specializations and k=64 variants, overlapping at the template-level MMA dispatcher changes.

Suggested reviewers

  • LeiWang1999

Poem

🐰 Hops with joy! SM75 now bounds through Turing's gates,
With m8n8 poise and m16n8k8 waits,
INT4 nibbles packed, FP16 glows bright,
Dispatched and tested into the night! 🎉

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 4.17% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title clearly and specifically describes the main change: adding native SM75 MMA GEMM support for three data types (FP16, INT8, INT4).
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🧹 Nitpick comments (5)
src/tl_templates/cuda/instruction/mma.h (1)

165-168: 💤 Low value

Inconsistent namespace qualification.

Every other TL_DEFINE_MMA_DISPATCHER row in this file references CuTe structs with cute:: (e.g., cute::SM80_16x8x32_S32S4S4S32_TN on lines 171/175). The two new INT4 rows omit the prefix because the structs are defined locally in tl::detail. It works thanks to ADL/current-namespace lookup, but adding an explicit tl::detail:: (or moving the struct definitions under cute:: alongside the SM75 includes if you intend them as CuTe extensions) makes the divergence obvious at the dispatch site.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@src/tl_templates/cuda/instruction/mma.h` around lines 165 - 168, The two
TL_DEFINE_MMA_DISPATCHER entries for INT4/UInt4 (SM75_8x8x32_S32S4S4S32_TN and
SM75_8x8x32_S32U4U4S32_TN) are missing the namespace qualifier used elsewhere;
update those dispatcher invocations to consistently reference the structs with
an explicit namespace (either prefix with tl::detail:: if the structs live in
tl::detail, or move/rename the struct definitions into the cute:: namespace and
use cute::) so the dispatch lines match the other entries (e.g., the same
pattern used for cute::SM80_16x8x32_S32S4S4S32_TN).
tilelang/cuda/intrinsics/macro/mma_macro_generator.py (2)

236-247: 💤 Low value

Document why local_size_c == 2 reuses the FP64 store map.

The added or local_size_c == 2 clause silently extends the FP64 8×8 store layout to Turing INT8/INT4 (which also produce 2 outputs per lane over an 8×8 tile). Functionally correct because the per-lane layout for m8n8k16/m8n8k32 matches m8n8k4, but the condition reads as a coincidental proxy. A brief comment (or a more explicit predicate like self.M_DIM == 8 and self.n_dim == 8) will make the intent clear to readers who only know mma_store_index_map_fp64 by name.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tilelang/cuda/intrinsics/macro/mma_macro_generator.py` around lines 236 -
247, The condition in get_store_index_map that checks "or local_size_c == 2"
silently reuses mma_store_index_map_fp64 for cases where two outputs per lane
occur (e.g., Turing INT8/INT4) but is unclear; update get_store_index_map to
either add a brief clarifying comment explaining why local_size_c == 2 implies
the FP64 8x8 store layout is valid, or replace the heuristic with an explicit
predicate (for example check self.M_DIM == 8 and self.n_dim == 8 or another
clear structural condition) so the intent is explicit; reference
get_store_index_map, local_size_c, mma_store_index_map_fp64 and
DataType(self.accum_dtype).bits when making the change.

190-202: 💤 Low value

Consider folding the Turing fp16 branch into the integer branch.

Both branches set the same n_dim, micro_size_y, warp_rows, warp_cols. The only meaningful difference is the assert m_dim == 8 in the first branch, which is invariant given the constructor already pins M_DIM for Turing 4-bit/8-bit dtypes. Optional consolidation:

♻️ Possible consolidation
-        if k_dim == 4 or (self.is_turing and k_dim in (16, 32)):
-            # fp64 or Turing integer path: m_dim must be 8, n_dim 8
-            assert m_dim == 8, f"For fp64/Turing integer MMA, m_dim must be 8, got {m_dim}"
-            self.n_dim = 8
-            self.micro_size_y = 8
-            self.warp_rows = warp_row_tiles // m_dim
-            self.warp_cols = warp_col_tiles // 8
-        elif k_dim == 8 and self.is_turing:
-            # Turing fp16 path
-            self.n_dim = 8
-            self.micro_size_y = 8
-            self.warp_rows = warp_row_tiles // m_dim
-            self.warp_cols = warp_col_tiles // 8
+        if k_dim == 4 or (self.is_turing and k_dim in (8, 16, 32)):
+            # fp64 (m8n8k4) or Turing path (m16n8k8 / m8n8k16 / m8n8k32):
+            # all use 8-wide N tiles.
+            if k_dim == 4 or (self.is_turing and k_dim in (16, 32)):
+                assert m_dim == 8, f"For fp64/Turing integer MMA, m_dim must be 8, got {m_dim}"
+            self.n_dim = 8
+            self.micro_size_y = 8
+            self.warp_rows = warp_row_tiles // m_dim
+            self.warp_cols = warp_col_tiles // 8
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tilelang/cuda/intrinsics/macro/mma_macro_generator.py` around lines 190 -
202, Combine the two branches that assign identical values into a single
conditional to remove duplication: detect the union condition (k_dim == 4 or
(self.is_turing and k_dim in (16, 32)) or (k_dim == 8 and self.is_turing)) and
then set self.n_dim = 8, self.micro_size_y = 8, self.warp_rows = warp_row_tiles
// m_dim, and self.warp_cols = warp_col_tiles // 8 in one place; preserve the
assert m_dim == 8 only for the fp64/Turing-integer case by guarding it (e.g., if
k_dim == 4 or (self.is_turing and k_dim in (16,32)): assert m_dim == 8 ...) so
you keep the invariant while removing duplicated assignments involving m_dim,
k_dim, self.is_turing, warp_row_tiles, and warp_col_tiles in
mma_macro_generator.py.
tilelang/cache/kernel_cache.py (1)

138-153: 💤 Low value

Sort dirnames to make the codegen stamp deterministic across runs and machines.

os.walk doesn't guarantee subdirectory traversal order—POSIX readdir returns entries in filesystem-dependent order, so two checkouts with identical contents can produce different tilelang_codegen digests (cache misses across machines/CI runners, or after a cp -r that changes inode allocation). You already iterate sorted(filenames); mirror that by mutating dirnames in place:

♻️ Proposed fix
         for root, suffixes in roots:
-            for dirpath, _, filenames in os.walk(root):
+            for dirpath, dirnames, filenames in os.walk(root):
+                dirnames.sort()
                 for filename in sorted(filenames):
                     if suffixes is not None and not filename.endswith(suffixes):
                         continue
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tilelang/cache/kernel_cache.py` around lines 138 - 153, The os.walk loop
currently ignores and doesn't sort subdirectories (it uses "for dirpath, _,
filenames in os.walk(root)"), causing non-deterministic traversal; update the
loop to capture dirnames ("for dirpath, dirnames, filenames in os.walk(root)")
and call dirnames.sort() immediately so subdirectory order is deterministic
before iterating filenames (keep existing sorted(filenames) logic and the rest
of the file hashing logic in the same block).
tilelang/cuda/op/gemm/gemm_mma.py (1)

25-26: 💤 Low value

Hoist target_is_turing import to module top-level.

Other tilelang.utils.* symbols are imported at module scope (line 8). Placing this import inside the method is inconsistent and slightly slower per invocation; move it to the top of the file unless there is a circular-import concern.

♻️ Proposed refactor
 from tilelang.utils.language import is_shared, is_fragment, is_full_region
+from tilelang.utils.target import target_is_turing
 from tilelang import tvm as tvm
         warp_col_tiles = int(self.N // n_warp)
-        from tilelang.utils.target import target_is_turing
-
         emitter = TensorCoreIntrinEmitter(
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tilelang/cuda/op/gemm/gemm_mma.py` around lines 25 - 26, The import of
target_is_turing is currently inside a function/method in gemm_mma.py; hoist the
import to the module top alongside the other tilelang.utils imports (where other
tilelang.utils.* symbols are imported) so it's imported once at import-time
rather than per-call, updating the import list to include target_is_turing; if
moving it causes a circular import, keep the local import but add a comment
explaining the circular dependency and why it must remain local. Ensure
references in any function/method that used the in-function import (e.g., the
function that previously contained "from tilelang.utils.target import
target_is_turing") continue to use the now-top-level symbol.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@examples/gemm/example_gemm_int8_turing.py`:
- Around line 61-65: Add an explicit SM75 guard before invoking
matmul_int8_turing: call torch.cuda.get_device_capability() (or
torch.cuda.get_device_capability(0)) right after printing the device info and
verify it equals (7, 5); if not, raise a clear exception or sys.exit with a
message saying this example is Turing/SM75-only. Update the block around where
kernel = matmul_int8_turing(M, N, K) so the guard runs first and prevents
attempting to compile/run on non-SM75 GPUs.
- Around line 47-48: The test helper function ref_program uses the private
operator torch._int_mm which may break across PyTorch versions; replace it with
the public API torchao.kernel.intmm.int_matmul when available (import and call
int_matmul(A, B.t())), and add a conditional fallback in ref_program that
detects absence of torchao and either uses a safe alternative implementation or
raises a clear error message; update references to ref_program accordingly so
the code uses the public int_matmul when present and falls back gracefully if
not.

In `@src/tl_templates/cuda/instruction/mma.h`:
- Around line 57-105: The inline PTX in fma for SM75_8x8x32_S32S4S4S32_TN and
SM75_8x8x32_S32U4U4S32_TN hardcodes the saturating variant ".satfinite" but the
dispatcher/metadata declares Saturate=false; update the asm strings in these two
fma methods to the non-saturating mnemonics (remove ".satfinite" so they use
"mma.sync.aligned.m8n8k32.row.col.s32.s4.s4.s32" and
"mma.sync.aligned.m8n8k32.row.col.s32.u4.u4.s32" respectively) to match the
dispatcher, and ensure any dispatcher declarations for these types also state
Saturate=false or provide a separate _SATURATE variant; if saturation was
intentional, instead add a clear comment on the struct explaining why it
diverges from the dispatcher.

---

Nitpick comments:
In `@src/tl_templates/cuda/instruction/mma.h`:
- Around line 165-168: The two TL_DEFINE_MMA_DISPATCHER entries for INT4/UInt4
(SM75_8x8x32_S32S4S4S32_TN and SM75_8x8x32_S32U4U4S32_TN) are missing the
namespace qualifier used elsewhere; update those dispatcher invocations to
consistently reference the structs with an explicit namespace (either prefix
with tl::detail:: if the structs live in tl::detail, or move/rename the struct
definitions into the cute:: namespace and use cute::) so the dispatch lines
match the other entries (e.g., the same pattern used for
cute::SM80_16x8x32_S32S4S4S32_TN).

In `@tilelang/cache/kernel_cache.py`:
- Around line 138-153: The os.walk loop currently ignores and doesn't sort
subdirectories (it uses "for dirpath, _, filenames in os.walk(root)"), causing
non-deterministic traversal; update the loop to capture dirnames ("for dirpath,
dirnames, filenames in os.walk(root)") and call dirnames.sort() immediately so
subdirectory order is deterministic before iterating filenames (keep existing
sorted(filenames) logic and the rest of the file hashing logic in the same
block).

In `@tilelang/cuda/intrinsics/macro/mma_macro_generator.py`:
- Around line 236-247: The condition in get_store_index_map that checks "or
local_size_c == 2" silently reuses mma_store_index_map_fp64 for cases where two
outputs per lane occur (e.g., Turing INT8/INT4) but is unclear; update
get_store_index_map to either add a brief clarifying comment explaining why
local_size_c == 2 implies the FP64 8x8 store layout is valid, or replace the
heuristic with an explicit predicate (for example check self.M_DIM == 8 and
self.n_dim == 8 or another clear structural condition) so the intent is
explicit; reference get_store_index_map, local_size_c, mma_store_index_map_fp64
and DataType(self.accum_dtype).bits when making the change.
- Around line 190-202: Combine the two branches that assign identical values
into a single conditional to remove duplication: detect the union condition
(k_dim == 4 or (self.is_turing and k_dim in (16, 32)) or (k_dim == 8 and
self.is_turing)) and then set self.n_dim = 8, self.micro_size_y = 8,
self.warp_rows = warp_row_tiles // m_dim, and self.warp_cols = warp_col_tiles //
8 in one place; preserve the assert m_dim == 8 only for the fp64/Turing-integer
case by guarding it (e.g., if k_dim == 4 or (self.is_turing and k_dim in
(16,32)): assert m_dim == 8 ...) so you keep the invariant while removing
duplicated assignments involving m_dim, k_dim, self.is_turing, warp_row_tiles,
and warp_col_tiles in mma_macro_generator.py.

In `@tilelang/cuda/op/gemm/gemm_mma.py`:
- Around line 25-26: The import of target_is_turing is currently inside a
function/method in gemm_mma.py; hoist the import to the module top alongside the
other tilelang.utils imports (where other tilelang.utils.* symbols are imported)
so it's imported once at import-time rather than per-call, updating the import
list to include target_is_turing; if moving it causes a circular import, keep
the local import but add a comment explaining the circular dependency and why it
must remain local. Ensure references in any function/method that used the
in-function import (e.g., the function that previously contained "from
tilelang.utils.target import target_is_turing") continue to use the
now-top-level symbol.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 356792ee-db78-486f-9626-956be3e7b6a1

📥 Commits

Reviewing files that changed from the base of the PR and between bcb2da3 and be80ecc.

📒 Files selected for processing (9)
  • CMakeLists.txt
  • examples/gemm/example_gemm_int8_turing.py
  • src/tl_templates/cuda/instruction/mma.h
  • testing/python/cuda/test_cuda_mma_sm75_dispatch.py
  • testing/python/kernel/test_tilelang_kernel_gemm_sm75.py
  • tilelang/cache/kernel_cache.py
  • tilelang/cuda/intrinsics/macro/mma_macro_generator.py
  • tilelang/cuda/op/gemm/__init__.py
  • tilelang/cuda/op/gemm/gemm_mma.py

Comment on lines +47 to +48
def ref_program(A, B):
return torch._int_mm(A, B.t())

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Is torch._int_mm a stable public PyTorch API, and what is the recommended public alternative for INT8xINT8->INT32 GEMM on CUDA?

💡 Result:

No. torch._int_mm is an underscore-prefixed, non-public/internal PyTorch operator and is not part of PyTorch’s documented stable public API surface; PyTorch maintainers/dev discussions explicitly treat it as not a supported stable/documented API (e.g., requests/perf discussions note that “_int_mm and _scaled_mm are not part of a stable and documented API”) [1]. It can also have backend- or layout-specific behavior/performance issues (e.g., slower for row-major RHS) [2]. Recommended public alternative (public API) for INT8xINT8 -> INT32 GEMM on CUDA: - Use torchao’s integer matmul entry point instead of torch._int_mm. torchao implements “safe” integer GEMM with CUDA/cuBLAS/Triton paths and an explicit fallback, exposed via torchao.kernel.intmm.int_matmul (which returns int32 accumulations) [3]. This is a public, supported-by-project API (unlike torch._int_mm, which is internal). Concretely, the public alternative is torchao.kernel.intmm.int_matmul (or the torchao.quantization path that calls into torchao’s int-matmul). torchao’s int_matmul dispatches to a Triton-based kernel when available; otherwise it falls back to safe_int_mm, which for CUDA uses torch.ops.aten.mm.default with int32 accumulation as the fallback path [3]. If you specifically need a low-level CUDA INT8 GEMM kernel from PyTorch itself (rather than a third-party library), PyTorch does not currently expose a documented stable public INT8xINT8->INT32 GEMM API in the sources above; the internal operator torch._int_mm is the mechanism used by PyTorch/Inductor for int matmul lowering [4], but it remains not-stable/public per the discussion cited above [1].

Citations:


🏁 Script executed:

# Check if the file exists and examine the context around lines 47-48
if [ -f "examples/gemm/example_gemm_int8_turing.py" ]; then
  echo "=== File exists, checking lines around 47-48 ==="
  cat -n "examples/gemm/example_gemm_int8_turing.py" | sed -n '40,55p'
else
  echo "File not found, searching for it..."
  find . -name "example_gemm_int8_turing.py" -type f
fi

Repository: tile-ai/tilelang

Length of output: 612


Replace private torch._int_mm with a supported public API or conditional fallback.

torch._int_mm is an internal PyTorch operator that is not part of the stable public API and can change across PyTorch versions. The recommended public alternative is torchao.kernel.intmm.int_matmul (from the torchao library). If adding a dependency is not feasible, add a conditional fallback to prevent breakage:

Proposed change
 def ref_program(A, B):
-    return torch._int_mm(A, B.t())
+    # Prefer public torchao API; fall back if unavailable
+    try:
+        from torchao.kernel.intmm import int_matmul
+        return int_matmul(A, B.t())
+    except ImportError:
+        # Fallback: torch.matmul with int32 (may differ numerically from _int_mm)
+        return torch.matmul(A.to(torch.int32), B.t().to(torch.int32))
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
def ref_program(A, B):
return torch._int_mm(A, B.t())
def ref_program(A, B):
# Prefer public torchao API; fall back if unavailable
try:
from torchao.kernel.intmm import int_matmul
return int_matmul(A, B.t())
except ImportError:
# Fallback: torch.matmul with int32 (may differ numerically from _int_mm)
return torch.matmul(A.to(torch.int32), B.t().to(torch.int32))
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/gemm/example_gemm_int8_turing.py` around lines 47 - 48, The test
helper function ref_program uses the private operator torch._int_mm which may
break across PyTorch versions; replace it with the public API
torchao.kernel.intmm.int_matmul when available (import and call int_matmul(A,
B.t())), and add a conditional fallback in ref_program that detects absence of
torchao and either uses a safe alternative implementation or raises a clear
error message; update references to ref_program accordingly so the code uses the
public int_matmul when present and falls back gracefully if not.

Comment on lines +61 to +65
M, N, K = args.m, args.n, args.k
print(f"INT8 GEMM for Turing, M={M}, N={N}, K={K}")
print(f"Device: {torch.cuda.get_device_name()} capability={torch.cuda.get_device_capability()}")

kernel = matmul_int8_turing(M, N, K)

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.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add an explicit SM75 capability guard for clearer failures.

This example is Turing-specific, but it currently runs on any CUDA GPU. A check near Line 63 avoids confusing compile/runtime behavior on non-SM75 devices.

Proposed change
     M, N, K = args.m, args.n, args.k
     print(f"INT8 GEMM for Turing, M={M}, N={N}, K={K}")
-    print(f"Device: {torch.cuda.get_device_name()} capability={torch.cuda.get_device_capability()}")
+    capability = torch.cuda.get_device_capability()
+    print(f"Device: {torch.cuda.get_device_name()} capability={capability}")
+    if capability != (7, 5):
+        raise RuntimeError(
+            f"This example targets Turing SM75, but got capability={capability}."
+        )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@examples/gemm/example_gemm_int8_turing.py` around lines 61 - 65, Add an
explicit SM75 guard before invoking matmul_int8_turing: call
torch.cuda.get_device_capability() (or torch.cuda.get_device_capability(0))
right after printing the device info and verify it equals (7, 5); if not, raise
a clear exception or sys.exit with a message saying this example is
Turing/SM75-only. Update the block around where kernel = matmul_int8_turing(M,
N, K) so the guard runs first and prevents attempting to compile/run on non-SM75
GPUs.

Comment thread src/tl_templates/cuda/instruction/mma.h

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 3

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tilelang/cuda/intrinsics/macro/mma_macro_generator.py`:
- Around line 236-238: stmatrix() currently hardcodes mma_store_index_map while
get_store_index_map()/make_mma_store_layout() switch to mma_store_index_map_fp64
when DataType(self.accum_dtype).bits == 64 or local_size_c == 2, causing
inconsistent lane maps; update stmatrix() to use the same selection logic as
get_store_index_map() (i.e. when DataType(self.accum_dtype).bits == 64 or
local_size_c == 2 use IndexMap.from_func(mma_store_index_map_fp64, ...)
otherwise use the existing mma_store_index_map) or simply call/get the index map
via get_store_index_map() so both layout inference and the store macro use the
identical lane map.
- Line 50: The new "uint4" dtype entry in mma_macro_generator.py is advertised
even though make_mma_load_layout() still rejects dtype_bits == 4, causing
gemm_sr/gemm_rs/gemm_rr layout inference to fail; fix by either implementing the
4-bit load transforms inside make_mma_load_layout() to accept dtype_bits == 4
(add the same load transform logic used for 8/16-bit types but adapted for 4-bit
packing) or revert/gate the "uint4" mapping so it is only used for the
shared/shared fragment-layout path (restrict the "uint4" entry or add a
conditional where the dtype-to-register-type map is consulted so that "uint4" is
not returned for fragment paths); reference make_mma_load_layout(), the "uint4"
mapping in mma_macro_generator.py, and gemm_sr/gemm_rs/gemm_rr when making the
change.
- Around line 186-194: The subclass
TensorCoreIntrinEmitterWithLadderTransform._initialize_micro_size currently has
a parameter mismatch versus the base class: change its signature to match the
base caller (accept two args: m_dim and k_dim, e.g. def
_initialize_micro_size(self, m_dim: int = 16, k_dim: int = 16)) and update any
internal uses that referenced an n_dim parameter to instead derive the column
dimension from the correct variable (either the passed k_dim or self.N_DIM as
appropriate for ladder transform logic); ensure all assertions and conditionals
(e.g. the use_m8n8/use_n8 branches and the assert on m_dim) reference the
corrected k_dim/name so fragment shape inference uses the actual k_dim passed by
the base class.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 07738dd0-16bd-499e-b2ea-e7bb6f93f81a

📥 Commits

Reviewing files that changed from the base of the PR and between be80ecc and 23da0a4.

📒 Files selected for processing (4)
  • src/tl_templates/cuda/instruction/mma.h
  • tilelang/cache/kernel_cache.py
  • tilelang/cuda/intrinsics/macro/mma_macro_generator.py
  • tilelang/cuda/op/gemm/gemm_mma.py
🚧 Files skipped from review as they are similar to previous changes (2)
  • src/tl_templates/cuda/instruction/mma.h
  • tilelang/cache/kernel_cache.py

Comment thread tilelang/cuda/intrinsics/macro/mma_macro_generator.py Outdated
Comment thread tilelang/cuda/intrinsics/macro/mma_macro_generator.py Outdated
Comment thread tilelang/cuda/intrinsics/macro/mma_macro_generator.py Outdated

@LeiWang1999 LeiWang1999 left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

we can introduce mma_sm75_macro_generator instead of is_turing?

@LeiWang1999

Copy link
Copy Markdown
Member

Thanks for your contribution @Tokimorphling ! I left some comments.

@coderabbitai coderabbitai Bot 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.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@tilelang/cuda/intrinsics/macro/mma_sm75_macro_generator.py`:
- Around line 12-23: The _initialize_k_dim method currently allows k_dim to be
reduced below SM75 integer MMA requirements for small integer chunks; detect
when a_dtype is integer (e.g., a_dtype.bits in {4,8}) and if the computed k_dim
would be smaller than the minimum SM75 integer k (choose and document the
required minimum, e.g., 16), raise a clear ValueError (or assert) instructing
the caller to increase chunk or change dtype; locate and modify
_initialize_k_dim (referencing self.k_dim, self.chunk, and a_dtype) to perform
this validation after computing k_dim so downstream micro-tile selection logic
(the integer micro-tile path) never sees an unsupported small k_dim.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro

Run ID: 28063ae8-87ae-4ed5-a8fd-e3114b751fb5

📥 Commits

Reviewing files that changed from the base of the PR and between 327034b and 6489a44.

📒 Files selected for processing (4)
  • testing/python/cuda/test_cuda_mma_sm75_dispatch.py
  • tilelang/cuda/intrinsics/macro/mma_macro_generator.py
  • tilelang/cuda/intrinsics/macro/mma_sm75_macro_generator.py
  • tilelang/cuda/op/gemm/gemm_mma.py

Comment on lines +12 to +23
def _initialize_k_dim(self, a_dtype=T.float16):
if isinstance(a_dtype, str):
a_dtype = DataType(a_dtype)
if a_dtype.bits == 4:
self.k_dim = min(32, self.chunk)
elif a_dtype.bits == 8:
self.k_dim = min(16, self.chunk)
elif a_dtype.bits == 16:
self.k_dim = min(8, self.chunk)
else:
self.k_dim = min(256 // a_dtype.bits, self.chunk)

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.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Guard unsupported small-chunk integer SM75 MMA configs.

Line [16]-[19] can reduce integer k_dim below SM75 MMA requirements. For example, int8 with chunk=8 drives k_dim=8, but the integer micro-tile path (Line [50]-[58]) remains m8n8-style, while Line [32]-[33] selects m16n8k8. That mismatch can generate invalid instruction selection.

🔧 Proposed fix
 def _initialize_k_dim(self, a_dtype=T.float16):
     if isinstance(a_dtype, str):
         a_dtype = DataType(a_dtype)
     if a_dtype.bits == 4:
-        self.k_dim = min(32, self.chunk)
+        if self.chunk < 32:
+            raise ValueError(f"SM75 int4 MMA requires chunk >= 32, got {self.chunk}")
+        self.k_dim = 32
     elif a_dtype.bits == 8:
-        self.k_dim = min(16, self.chunk)
+        if self.chunk < 16:
+            raise ValueError(f"SM75 int8 MMA requires chunk >= 16, got {self.chunk}")
+        self.k_dim = 16
     elif a_dtype.bits == 16:
-        self.k_dim = min(8, self.chunk)
+        if self.chunk < 8:
+            raise ValueError(f"SM75 fp16 MMA requires chunk >= 8, got {self.chunk}")
+        self.k_dim = 8
     else:
         self.k_dim = min(256 // a_dtype.bits, self.chunk)

Also applies to: 31-39, 41-59

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tilelang/cuda/intrinsics/macro/mma_sm75_macro_generator.py` around lines 12 -
23, The _initialize_k_dim method currently allows k_dim to be reduced below SM75
integer MMA requirements for small integer chunks; detect when a_dtype is
integer (e.g., a_dtype.bits in {4,8}) and if the computed k_dim would be smaller
than the minimum SM75 integer k (choose and document the required minimum, e.g.,
16), raise a clear ValueError (or assert) instructing the caller to increase
chunk or change dtype; locate and modify _initialize_k_dim (referencing
self.k_dim, self.chunk, and a_dtype) to perform this validation after computing
k_dim so downstream micro-tile selection logic (the integer micro-tile path)
never sees an unsupported small k_dim.

@LeiWang1999
LeiWang1999 merged commit e9b7e21 into tile-ai:main May 20, 2026
2 checks passed
LeiWang1999 pushed a commit that referenced this pull request May 21, 2026
- gemm_sm90.h: B_type fallback incorrectly used A_type_cute instead of
  B_type_cute (copy-paste typo), affecting mixed-precision GEMM on SM90.

- gemm.cc: Remove TargetIsTuring from kNPerWarp=16 condition. PR #2173
  originally set this to 16 because SM75 used Volta-style 16x16x4
  instructions (n_dim=16). After #2198, SM75 now uses native m16n8k8
  instructions (n_dim=8) via GemmMMASm75, so kNPerWarp should be 8.
LeiWang1999 pushed a commit that referenced this pull request Jun 13, 2026
The SM75 GEMM emitter derives MMA shapes from input dtype bits alone, so it
already emits the (16,8,8) shape for fp16->fp16 accumulation (identical to the
registered fp16->fp32 case) and the (8,8,16) shape for uint8 inputs (identical
to signed int8). But the C++ dispatcher table only covered the three configs
tested by #2198 (fp16->fp32, int8, int4), so requesting fp16->fp16-accum or
uint8 GEMM on Turing failed at nvcc with an opaque
`tl::mma_sync: unsupported configuration` static_assert.

Add the two missing tl::detail SM75 atoms and register their dispatchers,
following the precedent set by #2198's hand-written int4/uint4 structs (CuTe
ships only fp16->fp32 and signed-int8 SM75 atoms). The uint8 atom uses the
non-saturating PTX form to match the existing signed-int8 path.

Verified on a TITAN RTX (SM75): emitted PTX is
mma.sync.aligned.m16n8k8.row.col.f16.f16.f16.f16 and
mma.sync.aligned.m8n8k16.row.col.s32.u8.u8.s32, and end-to-end GEMM numerics
match torch.
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.

2 participants