# Licensed to the Apache Software Foundation (ASF) under one # or more contributor license agreements. See the NOTICE file # distributed with this work for additional information # regarding copyright ownership. The ASF licenses this file # to you under the Apache License, Version 2.0 (the # "License"); you may not use this file except in compliance # with the License. You may obtain a copy of the License at # # http://www.apache.org/licenses/LICENSE-2.0 # # Unless required by applicable law or agreed to in writing, # software distributed under the License is distributed on an # "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. # pylint: disable=redefined-builtin, invalid-name, too-many-arguments, too-many-locals, too-many-positional-arguments """PTX WGMMA operations (Hopper warpgroup MMA). One ``device_intrinsic`` registration per PTX form table entry. Bodies are hand-written ``asm volatile(...)`` strings. Variable-arity register vectors (``mma_async`` accumulators / A fragments) materialize via the same device_intrinsic with an attr-driven ``c_signature`` callable. """ import tvm from ._schema import device_intrinsic from .registry import CODEGEN_REGISTRY, register_codegen from .types import PTXDataType from .utils import parse_str # ============================================================================= # wgmma.fence / commit_group / wait_group — one PTX form each. # ============================================================================= device_intrinsic( "ptx_wgmma_fence", helper_name="ptx_wgmma_fence", body=' asm volatile("wgmma.fence.sync.aligned;" ::: "memory");', ) device_intrinsic( "ptx_wgmma_commit_group", helper_name="ptx_wgmma_commit_group", body=' asm volatile("wgmma.commit_group.sync.aligned;" ::: "memory");', ) device_intrinsic( "ptx_wgmma_wait_group", n_attrs=1, helper_name=lambda n: f"ptx_wgmma_wait_group_{int(n)}", body=lambda n: f' asm volatile("wgmma.wait_group.sync.aligned {int(n)};" ::: "memory");', ) # ============================================================================= # wgmma_encode_matrix_descriptor — pure-C bitfield struct fill (no asm). # ============================================================================= device_intrinsic( "ptx_wgmma_encode_matrix_descriptor", helper_name="ptx_wgmma_encode_matrix_descriptor", c_signature="(uint64_t* desc, void* addr, int ldo, int sdo, int swizzle)", body=( " GmmaDescriptor _desc{}; // value-init: reading uncovered pad bits is UB\n" "\n" " switch (swizzle) {\n" " case 0: _desc.bitfield.layout_type_ = uint8_t(0); break; // No swizzle\n" " case 1: _desc.bitfield.layout_type_ = uint8_t(3); break; // 32B swizzle\n" " case 2: _desc.bitfield.layout_type_ = uint8_t(2); break; // 64B swizzle\n" " case 3: _desc.bitfield.layout_type_ = uint8_t(1); break; // 128B swizzle\n" " }\n" "\n" " uint32_t start_address = __cvta_generic_to_shared(addr);\n" " _desc.bitfield.start_address_ = static_cast(start_address >> 4);\n" "\n" " constexpr uint8_t base_offset = 0;\n" " _desc.bitfield.base_offset_ = base_offset;\n" "\n" " _desc.bitfield.stride_byte_offset_ = static_cast(sdo);\n" " _desc.bitfield.leading_byte_offset_ = static_cast(ldo);\n" "\n" " *desc = (uint64_t)_desc;" ), extra_deps=("gmma_descriptor",), ) # ============================================================================= # wgmma_noop_barrier — empty asm with one inout register operand. Two # device_intrinsic calls, one per supported dtype; dispatcher picks the form # based on the operand's runtime dtype. # ============================================================================= device_intrinsic( "ptx_wgmma_noop_barrier_uint32", helper_name="ptx_wgmma_fence_uint32_t", c_signature="(uint32_t reg)", body=' asm volatile("" : "+r"(reg) :: "memory");', ) device_intrinsic( "ptx_wgmma_noop_barrier_float32", helper_name="ptx_wgmma_fence_float", c_signature="(float reg)", body=' asm volatile("" : "+f"(reg) :: "memory");', ) @register_codegen("ptx_wgmma_noop_barrier") def codegen_ptx_wgmma_noop_barrier(reg): dtype = str(reg.dtype) dtype_enum = PTXDataType.from_string(dtype) if dtype_enum == PTXDataType.UINT32: op_name = "tirx.ptx_wgmma_noop_barrier_uint32" elif dtype_enum == PTXDataType.FLOAT32: op_name = "tirx.ptx_wgmma_noop_barrier_float32" else: raise ValueError(f"Only support uint32/float32 for wgmma_fence, but got {dtype}.") result = CODEGEN_REGISTRY[op_name]([reg]) return result[0] if isinstance(result, tuple) else result # ============================================================================= # wgmma.mma_async ss / rs — 2 PTX form table entries. Accumulator count and # A-register count vary with (M, N, K, in_dtype) but are fully determined by # attrs at codegen time. # # Args layout for ss form (forwarded operand args first, then 9 attr args): # *p_acc[0..num_accums-1], p_descA, p_descB, p_scaleD, # M, N, K, in_dtype, out_dtype, transA, transB, scaleA, scaleB # # Args layout for rs form: # *p_acc[0..num_accums-1], *p_A[0..num_A_regs-1], p_descB, p_scaleD, # M, N, K, in_dtype, out_dtype, transA, transB, scaleA, scaleB # ============================================================================= def _coerce_wgmma_attrs(attrs): """Decode the trailing 9 attrs (M, N, K, in_dtype, out_dtype, transA, transB, scaleA, scaleB) into native Python types.""" M, N, K = int(attrs[0]), int(attrs[1]), int(attrs[2]) in_dtype = parse_str(attrs[3]) out_dtype = parse_str(attrs[4]) transA = bool(int(attrs[5])) if hasattr(attrs[5], "value") else bool(attrs[5]) transB = bool(int(attrs[6])) if hasattr(attrs[6], "value") else bool(attrs[6]) scaleA = bool(int(float(attrs[7]))) scaleB = bool(int(float(attrs[8]))) if out_dtype != "float32": raise ValueError("WGMMA codegen only supports float32 as output dtype.") allow_transpose = in_dtype in {"float16", "bfloat16"} if not allow_transpose and (transA or transB): raise ValueError("Transpose is only supported for .f16/.bf16 types in WGMMA.") return M, N, K, in_dtype, out_dtype, transA, transB, scaleA, scaleB, allow_transpose def _safe(s): return s.replace("::", "_").replace(".", "_") def _wgmma_helper_name(prefix, M, N, K, in_dtype, out_dtype, transA, transB, scaleA, scaleB): return ( f"{prefix}_{M}x{N}x{K}_{_safe(in_dtype)}_{_safe(out_dtype)}" f"_{1 if scaleA else 0}_{1 if scaleB else 0}" f"_{1 if transA else 0}_{1 if transB else 0}" ) def _wgmma_in_bits(in_dtype): return tvm.runtime.DataType(in_dtype).bits def _wgmma_ss_parts(*args): M, N, K, in_dtype, out_dtype, transA, transB, scaleA, scaleB, allow_transpose = ( _coerce_wgmma_attrs(args[-9:]) ) num_accums = M * N // 128 name = _wgmma_helper_name( "ptx_wgmma_mma_async_ss", M, N, K, in_dtype, out_dtype, transA, transB, scaleA, scaleB ) sig = ( "(" + ", ".join( [f"float& p_acc{i}" for i in range(num_accums)] + ["uint64_t p_descA", "uint64_t p_descB", "int p_scaleD"] ) + ")" ) descA_idx = num_accums descB_idx = num_accums + 1 scaleD_idx = num_accums + 2 scaleA_idx = num_accums + 3 scaleB_idx = num_accums + 4 transA_idx = num_accums + 5 transB_idx = num_accums + 6 accum_r_list = ", ".join(f"%{i}" for i in range(num_accums)) accum_constraints = ", ".join(f'"+f"(p_acc{i})' for i in range(num_accums)) itype = PTXDataType.from_string(in_dtype) otype = PTXDataType.from_string(out_dtype) if allow_transpose: transpose_r_code = f", %{transA_idx}, %{transB_idx}" transpose_constraints = f', "n"({1 if transA else 0}), "n"({1 if transB else 0})' else: transpose_r_code = "" transpose_constraints = "" instr = ( f"wgmma.mma_async.sync.aligned.m{M}n{N}k{K}" f"{otype.to_string()}{itype.to_string()}{itype.to_string()}" ) asm_inputs = ( f'"l"(p_descA), "l"(p_descB), "r"(p_scaleD),' f' "n"({1 if scaleA else 0}), "n"({1 if scaleB else 0})' f"{transpose_constraints}" ) body = ( " asm volatile(\n" ' "{ \\n"\n' ' ".reg .pred p;\\n"\n' f' "setp.ne.b32 p, %{scaleD_idx}, 0;\\n"\n' f' "{instr} "\n' f' "{{{accum_r_list}}},"\n' f' "%{descA_idx}, %{descB_idx},"\n' f' "p, %{scaleA_idx}, %{scaleB_idx}{transpose_r_code};\\n"\n' ' "}\\n"\n' f" : {accum_constraints}\n" f" : {asm_inputs}\n" " );" ) return name, sig, body device_intrinsic( "_ptx_wgmma_mma_async_ss_impl", n_attrs=9, helper_name=lambda *a: _wgmma_ss_parts(*a)[0], c_signature=lambda *a: _wgmma_ss_parts(*a)[1], body=lambda *a: _wgmma_ss_parts(*a)[2], ) def _wgmma_rs_parts(*args): M, N, K, in_dtype, out_dtype, transA, transB, scaleA, scaleB, allow_transpose = ( _coerce_wgmma_attrs(args[-9:]) ) num_accums = M * N // 128 in_bits = _wgmma_in_bits(in_dtype) num_A_regs = M * K // 128 // (32 // in_bits) name = _wgmma_helper_name( "ptx_wgmma_mma_async_rs", M, N, K, in_dtype, out_dtype, transA, transB, scaleA, scaleB ) sig = ( "(" + ", ".join( [f"float& p_acc{i}" for i in range(num_accums)] + [f"uint32_t& p_A{i}" for i in range(num_A_regs)] + ["uint64_t p_descB", "int p_scaleD"] ) + ")" ) accum_r_list = ", ".join(f"%{i}" for i in range(num_accums)) A_reg_r_list = ", ".join(f"%{num_accums + i}" for i in range(num_A_regs)) base_idx = num_accums + num_A_regs descB_idx = base_idx scaleD_idx = base_idx + 1 scaleA_idx = base_idx + 2 scaleB_idx = base_idx + 3 transB_idx = base_idx + 4 accum_constraints = ", ".join(f'"+f"(p_acc{i})' for i in range(num_accums)) A_reg_constraints = ", ".join(f'"r"(p_A{i})' for i in range(num_A_regs)) itype = PTXDataType.from_string(in_dtype) otype = PTXDataType.from_string(out_dtype) if allow_transpose: transpose_r_code = f", %{transB_idx}" transpose_constraints = f', "n"({1 if transB else 0})' else: transpose_r_code, transpose_constraints = "", "" instr = ( f"wgmma.mma_async.sync.aligned.m{M}n{N}k{K}" f"{otype.to_string()}{itype.to_string()}{itype.to_string()}" ) asm_inputs = ( f'{A_reg_constraints}, "l"(p_descB), "r"(p_scaleD),' f' "n"({1 if scaleA else 0}), "n"({1 if scaleB else 0})' f"{transpose_constraints}" ) body = ( " asm volatile(\n" ' "{ \\n"\n' ' ".reg .pred p;\\n"\n' f' "setp.ne.b32 p, %{scaleD_idx}, 0;\\n"\n' f' "{instr} "\n' f' "{{{accum_r_list}}},"\n' f' "{{{A_reg_r_list}}}, %{descB_idx},"\n' f' "p, %{scaleA_idx}, %{scaleB_idx}{transpose_r_code};\\n"\n' ' "}\\n"\n' f" : {accum_constraints}\n" f" : {asm_inputs}\n" " );" ) return name, sig, body device_intrinsic( "_ptx_wgmma_mma_async_rs_impl", n_attrs=9, helper_name=lambda *a: _wgmma_rs_parts(*a)[0], c_signature=lambda *a: _wgmma_rs_parts(*a)[1], body=lambda *a: _wgmma_rs_parts(*a)[2], ) # User-facing wrappers: just normalise types + reorder positional args to # put operands first, then attrs, matching the schema convention. def _wgmma_user_wrapper_ss(*args): M, N, K, in_dtype, out_dtype, transA, transB, scaleA, scaleB, scaleD, descA, descB, *accums = ( args ) M = int(M) N = int(N) K = int(K) in_dtype = parse_str(in_dtype) out_dtype = parse_str(out_dtype) transA = bool(transA) transB = bool(transB) scaleA = bool(int(float(scaleA))) scaleB = bool(int(float(scaleB))) expected = M * N // 128 if len(accums) != expected: raise ValueError( "The number of arguments is incorrect. Expected " f"{12 + expected} total args (meaning {expected} accumulator args), " f"but got {len(accums)}." ) return [ *accums, descA, descB, scaleD, M, N, K, in_dtype, out_dtype, transA, transB, scaleA, scaleB, ] @register_codegen("ptx_wgmma_mma_async_ss") def codegen_ptx_wgmma_mma_async_ss(*args): forwarded = _wgmma_user_wrapper_ss(*args) result = CODEGEN_REGISTRY["tirx._ptx_wgmma_mma_async_ss_impl"](forwarded) return result[0] if isinstance(result, tuple) else result def _wgmma_user_wrapper_rs(*args): M, N, K, in_dtype, out_dtype, transA, transB, scaleA, scaleB, scaleD, descB, *reg_list = args M = int(M) N = int(N) K = int(K) in_dtype = parse_str(in_dtype) out_dtype = parse_str(out_dtype) transA = bool(transA) transB = bool(transB) scaleA = bool(int(float(scaleA))) scaleB = bool(int(float(scaleB))) if out_dtype != "float32": raise ValueError("This generator only supports float32 as the output dtype for WGMMA.") in_dtype_bits = tvm.runtime.DataType(in_dtype).bits if in_dtype_bits is None: raise ValueError(f"Bit width not defined for input dtype: {in_dtype}") expected_A_cnt = M * K // 128 // (32 // in_dtype_bits) expected_accm_cnt = M * N // 128 if len(reg_list) != expected_A_cnt + expected_accm_cnt: raise ValueError( f"Incorrect number of A registers. Expected {expected_A_cnt}, got {len(reg_list)}" ) A_regs = reg_list[:expected_A_cnt] accums = reg_list[expected_A_cnt:] return [ *accums, *A_regs, descB, scaleD, M, N, K, in_dtype, out_dtype, transA, transB, scaleA, scaleB, ] @register_codegen("ptx_wgmma_mma_async_rs") def codegen_ptx_wgmma_mma_async_rs(*args): forwarded = _wgmma_user_wrapper_rs(*args) result = CODEGEN_REGISTRY["tirx._ptx_wgmma_mma_async_rs_impl"](forwarded) return result[0] if isinstance(result, tuple) else result