Files
wehub-resource-sync 26446540fa
Lint / lint (push) Has been cancelled
CI / MacOS (push) Has been cancelled
CI / Windows (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

404 lines
14 KiB
Python

# 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<uint16_t>(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<uint32_t>(sdo);\n"
" _desc.bitfield.leading_byte_offset_ = static_cast<uint32_t>(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