GEMM + Amax (SM100)
GEMM + Amax (SM100)
This is an experimental API and subject to change.
Overview
Block-scaled GEMM + amax: A persistent, batched dense GEMM on NVIDIA Blackwell GPUs (SM100+) that supports low-precision inputs (FP8, FP4) with per-block scale factors, producing the full GEMM output C and global amax reduction. Implemented with CUTLASS/CUTE.
- Inputs: quantized
AandB(FP8 or FP4), and corresponding scale-factor tensorsSFAandSFBthat dequantize along theKdimension in groups of sizesf_vec_size. - Outputs: full GEMM result
CandAmax.
Shapes
-
Inputs
A: shape(M, K, L)B: shape(N, K, L)SFA: shape(32, 4, ceil_div(M, 128), 4, ceil_div(K, 4·sf_vec_size), L)SFB: shape(32, 4, ceil_div(N, 128), 4, ceil_div(K, 4·sf_vec_size), L)
-
Outputs
C: shape(M, N, L)Amax: shape(1, 1, 1)
L is the batch dimension.
Equations
Let block size along K be sf_vec_size ∈ {16, 32}. Dequantization is performed using the provided scale factors for groups of sf_vec_size along K (per M/N blocks defined by the atom tiling):
Diagram
API Usage
The tensor parameters are type-erased: torch tensors and JAX arrays are both accepted. torch is only imported when torch tensors/dtypes are passed, and jax only when JAX arrays are passed. Dtype parameters (c_dtype, acc_dtype) accept torch dtypes, numpy/ml_dtypes dtypes, dtype name strings, or cutlass types.
High-level wrapper
Class API
Using JAX arrays
Two integration levels are available:
gemm_amax_jax_sm100(recommended for jitted programs) — an XLA custom call built oncudnn.jax.call(CuTeDSL’s nativecutlass.jaxbridge). The kernel runs on XLA’s compute stream (correctly ordered with surrounding ops), outputs are XLA-managed fresh arrays, and the call composes withjax.jit. No manual synchronization is needed. Requires thejaxdependency group (pip install --group jax; jax >= 0.5).
Calling it eagerly works but re-traces the custom call on every invocation; call it from inside a jitted function in hot loops.
- The eager entry points below (
gemm_amax_wrapper_sm100,GemmAmaxSm100) also accept JAX arrays via DLPack. In hot loops prefer the class API with pre-allocated output buffers (~15 µs CPU per launch) over the wrapper — per-calljnpoutput allocation in the wrapper costs hundreds of µs of XLA dispatch.
JAX arrays are always row-major (C-contiguous) and immutable-by-contract, so the JAX contract differs from torch in a few ways (all entry points):
A/Bmust be k-major(M, K, 1)/(N, K, 1)C-contiguous arrays, and the batch dim must beL == 1(batch-outermost layouts are not expressible as JAX arrays).Csupportsc_major="n"only (the wrapper raises for"m").SFA/SFBare passed in the physical C-contiguous atom shape(L, ceil_div(MN, 128), ceil_div(K, 4·sf_vec_size), 32, 4, 4)— byte-identical in memory to the torch-style logical view above (which is this allocation permuted by(3, 4, 1, 5, 2, 0)). Both forms are accepted for either framework.- Packed fp4 has no JAX dtype; the intended vehicle is a
uint8container tensor ((M, K // 2, 1)), but that container path is currently disabled kernel-side for torch and JAX alike. FP8 flavors (float8_e4m3fn,float8_e5m2,float8_e8m0fnuvia ml_dtypes) are fully supported. - Eager entry points only: with no explicit stream, the kernel launches on the CUDA legacy default stream, which XLA does not track.
jax.block_until_ready(...)your inputs before calling, and synchronize the device (or the stream you passed) before reading the outputs. (gemm_amax_jax_sm100has neither caveat — XLA orders it on its own stream.) - The eager wrapper allocates outputs with
jnp.empty/jnp.fulland the kernel writes into them via DLPack. This is outside JAX’s functional model: eager use only — do not call the wrapper underjax.jitor with donated buffers. Usegemm_amax_jax_sm100underjit.
Parameters
Input/Output tensors
- Input tensor A:
a_tensor(wrapper) orsample_a/a_tensor(class)- Shape:
(M, K, L) - Stride:
(1, M, M·K)form-major or(K, 1, M·K)fork-major - Dtype:
{float4_e2m1fn_x2, uint8, float8_e4m3fn, float8_e5m2}(uint8is interpreted as packed fp4x2)
- Shape:
- Input tensor B:
b_tensor(wrapper) orsample_b/b_tensor(class)- Shape:
(N, K, L) - Stride:
(1, N, N·K)forn-major or(K, 1, N·K)fork-major - Dtype: Must match
A
- Shape:
- Input tensor SFA:
sfa_tensor(wrapper) orsample_sfa/sfa_tensor(class)- Shape:
(ATOM_M0, ATOM_M1, ceil_div(M, ATOM_M0·ATOM_M1), ATOM_K, ceil_div(K, ATOM_K·sf_vec_size), L) - Dtype:
{float8_e8m0fnu, float8_e4m3fn, int8}(int8is interpreted asfloat8_e8m0fnu)
- Shape:
- Input tensor SFB:
sfb_tensor(wrapper) orsample_sfb/sfb_tensor(class)- Shape:
(ATOM_M0, ATOM_M1, ceil_div(N, ATOM_M0·ATOM_M1), ATOM_K, ceil_div(K, ATOM_K·sf_vec_size), L) - Dtype:
{float8_e8m0fnu, float8_e4m3fn, int8}(int8is interpreted asfloat8_e8m0fnu)
- Shape:
- Output tensor C:
result["c_tensor"](wrapper) orsample_c/c_tensor(class)- Shape:
(M, N, L) - Stride:
(1, M, M·N)form-major or(N, 1, M·N)forn-major. Provided asc_majorargument for wrapper - Dtype:
{float32, float16, bfloat16, float8_e5m2, float8_e4m3fn, float4_e2m1fn_x2, uint8}. Provided asc_dtypeargument for wrapper
- Shape:
- Output tensor Amax:
result["amax_tensor"](wrapper) orsample_amax/amax_tensor(class)- Shape:
(1, 1, 1) - Dtype:
float32
- Shape:
Common parameters
acc_dtype: torch.dtype- Accumulator dtype. Default:
torch.float32(only supported value)
- Accumulator dtype. Default:
mma_tiler_mn: Tuple[int, int]- Kernel tile size
(TILE_M, TILE_N). Default:(128, 128) TILE_M ∈ {128};TILE_M = 256is currently disabledTILE_N ∈ {128, 256}
- Kernel tile size
cluster_shape_mn: Tuple[int, int]- Thread Block cluster shape
(CLUSTER_M, CLUSTER_N). Default:(1, 1) - Constraints: values in
{1, 2, 4}
- Thread Block cluster shape
sf_vec_size: int- Size of K-group per scale factor:
{16, 32}. Default:32
- Size of K-group per scale factor:
- CUDA stream (
current_streamin class API,streamin wrapper)
Wrapper-specific parameters: gemm_amax_wrapper_sm100
a_tensor,b_tensor,sfa_tensor,sfb_tensor: see Input/Output tensorsc_major: str: see Input/Output tensors. Default:"n"c_dtype: torch.dtype: see Input/Output tensors. Default:torch.float32
Wrapper return values
Returns a TupleDict with keys:
c_tensor: GEMM output tensorCamax_tensor: Max-abs reduction output
Tuple unpacking order is: (c_tensor, amax_tensor).
Class-specific parameters: GemmAmaxSm100
GemmAmaxSm100 (constructor)
sample_a,sample_b,sample_sfa,sample_sfb,sample_c,sample_amax: see Input/Output tensors
GemmAmaxSm100.execute
a_tensor,b_tensor,sfa_tensor,sfb_tensor,c_tensor,amax_tensor: see Input/Output tensors
Support surface and constraints
Layouts and strides
- For
A/B ∈ {float4_e2m1fn_x2, uint8}(packed FP4),AandBmust bek-major. - For
C ∈ {float4_e2m1fn_x2, uint8}(packed FP4),Cmust ben-major. - For all
float4_e2m1fn_x2/uint8cases, the innermost tensor dimension will be divided by 2 due to 2x packing. i.e.Awould be shaped(M, K // 2, L)instead of(M, K, L). A,B,Cmust be 16-byte aligned along the contiguous dimension.
Dtypes
A/Bmust have the same dtype.sf_vec_size ∈ {16, 32}with coupling:sf_dtype == float8_e4m3fnis unsupported withsf_vec_size == 32A/B ∈ {float8_e4m3fn, float8_e5m2}is unsupported withsf_vec_size == 16
A/B ∈ FP8andC ∈ FP8together are currently disabledC ∈ {float4_e2m1fn_x2, uint8}requiresA/B ∈ {float4_e2m1fn_x2, uint8}
Tiling and cluster
A/B ∈ {float4_e2m1fn_x2, uint8}andN_tile == 256requiresK > 128mma_tiler_mn == (128, 256),sf_vec_size == 16,C ∈ {float32, float16, bfloat16}is currently disabled
Shapes and divisibility
SFA/SFBshapes must follow the atom tiling andsf_vec_sizerules above- When
Cis packed FP4, use(M, ceil_div(N, 2), L)andn-major strides
Environment
- Requires CUDA with SM100+ compute capability
- All tensors must reside on the same CUDA device
JAX-specific constraints
L == 1;A/Bk-major;Cn-major onlySFA/SFBin the physical atom shape(L, MN', K', 32, 4, 4)(see “Using JAX arrays”)- The wrapper/class entry points are eager-only (use
gemm_amax_jax_sm100underjax.jit); synchronize before reading outputs
Usage examples
For usage examples, see test cases in test/python/fe_api/gemm/test_gemm_amax.py (torch) and test/python/fe_api/gemm/test_gemm_amax_jax.py (JAX)