483 lines
17 KiB
Python
483 lines
17 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 MMA / ldmatrix / stmatrix intrinsics.
|
|
|
|
mma.sync.aligned has 7 form_kinds per the PTX docs (f16 / tf32 / bf16 / fp64
|
|
/ int8 / fp8 / subbyte). Each form_kind is one ``device_intrinsic`` registration;
|
|
the (shape, layouts, dtypes) modifier slots are attrs. Body computes the per-
|
|
fragment register counts at codegen time from M*N*bits/threads/frag_size and
|
|
hand-builds the asm constraint list.
|
|
|
|
ldmatrix / stmatrix each have a single PTX form (the .m8n8 .b16/.b8 variant
|
|
that TIRx uses); ``num`` and ``trans`` are modifier attrs.
|
|
"""
|
|
|
|
import re
|
|
from dataclasses import dataclass
|
|
|
|
from tvm import DataType
|
|
|
|
from ._schema import device_intrinsic
|
|
from .registry import CODEGEN_REGISTRY, register_codegen
|
|
from .types import PTXDataType
|
|
from .utils import parse_str
|
|
|
|
|
|
@dataclass
|
|
class FragAttrs:
|
|
reg_type: str # asm constraint letter (r / f / d)
|
|
size: int # bit width per register slot (32 or 64)
|
|
ptr_type: str # C type for the cast
|
|
|
|
|
|
_FRAG_ATTRS_MAP = {
|
|
PTXDataType.BIT1: FragAttrs("r", 32, "uint32_t"),
|
|
PTXDataType.INT4: FragAttrs("r", 32, "uint32_t"),
|
|
PTXDataType.UINT4: FragAttrs("r", 32, "uint32_t"),
|
|
PTXDataType.INT8: FragAttrs("r", 32, "uint32_t"),
|
|
PTXDataType.UINT8: FragAttrs("r", 32, "uint32_t"),
|
|
PTXDataType.FLOAT8_E4M3FN: FragAttrs("r", 32, "uint32_t"),
|
|
PTXDataType.FLOAT8_E5M2: FragAttrs("r", 32, "uint32_t"),
|
|
PTXDataType.BIT16: FragAttrs("r", 32, "uint32_t"),
|
|
PTXDataType.FLOAT16: FragAttrs("r", 32, "uint32_t"),
|
|
PTXDataType.BFLOAT16: FragAttrs("r", 32, "uint32_t"),
|
|
PTXDataType.TENSOR_FLOAT32: FragAttrs("r", 32, "uint32_t"),
|
|
PTXDataType.INT32: FragAttrs("r", 32, "int32_t"),
|
|
PTXDataType.FLOAT32: FragAttrs("f", 32, "float"),
|
|
PTXDataType.FLOAT64: FragAttrs("d", 64, "double"),
|
|
}
|
|
|
|
|
|
def _parse_mma_shape(shape_str):
|
|
match = re.search(r"m(\d+)n(\d+)k(\d+)", shape_str)
|
|
if not match:
|
|
raise ValueError(f"Cannot parse MMA shape: {shape_str!r}")
|
|
return tuple(map(int, match.groups()))
|
|
|
|
|
|
def _classify_mma_form(d_type, a_type, b_type):
|
|
"""Map (d, a, b) dtype triple to one of the 7 PTX form_kind tags."""
|
|
fp16 = {"float16", "fp16"}
|
|
tf32 = {"tensor_float32", "tf32"}
|
|
bf16 = {"bfloat16", "bf16"}
|
|
fp64 = {"float64", "fp64"}
|
|
int_a = {"int8", "uint8", "s8", "u8"}
|
|
fp8 = {"e4m3", "e5m2", "float8_e4m3fn", "float8_e4m3fnuz", "float8_e5m2"}
|
|
subbyte = {"int4", "uint4", "bit1", "s4", "u4", "b1", "int1", "uint1"}
|
|
if a_type in fp16 and b_type in fp16:
|
|
return "f16"
|
|
if a_type in tf32 and b_type in tf32:
|
|
return "tf32"
|
|
if a_type in bf16 and b_type in bf16:
|
|
return "bf16"
|
|
if a_type in fp64 and b_type in fp64:
|
|
return "fp64"
|
|
if a_type in int_a and b_type in int_a:
|
|
return "int8"
|
|
if a_type in fp8 and b_type in fp8:
|
|
return "fp8"
|
|
if a_type in subbyte and b_type in subbyte:
|
|
return "subbyte"
|
|
raise ValueError(
|
|
f"Unknown ptx.mma form for d_type={d_type!r}, a_type={a_type!r}, b_type={b_type!r}"
|
|
)
|
|
|
|
|
|
def _frag(dtype_str):
|
|
return _FRAG_ATTRS_MAP[PTXDataType.from_string(dtype_str)]
|
|
|
|
|
|
def _mma_threads(shape, a_type):
|
|
"""Special case: m8n8k4 with f16 a/b uses 8 threads per fragment."""
|
|
m, n, k = _parse_mma_shape(shape)
|
|
if m == 8 and n == 8 and k == 4 and a_type == "float16":
|
|
return 8
|
|
return 32
|
|
|
|
|
|
# PTX dtype abbreviation -> element bit width. Used by _frag_count so that
|
|
# callers passing the PTX abbreviation (e.g. "fp32") don't blow up in
|
|
# ``DataType("fp32")``.
|
|
_PTX_BITS = {
|
|
"fp16": 16,
|
|
"fp32": 32,
|
|
"fp64": 64,
|
|
"bf16": 16,
|
|
"tf32": 32, # tensor-float32 packs 19 significant bits into a 32-bit slot
|
|
"s8": 8,
|
|
"u8": 8,
|
|
"s32": 32,
|
|
"s4": 4,
|
|
"u4": 4,
|
|
"b1": 1,
|
|
"b16": 16,
|
|
"e4m3": 8,
|
|
"e5m2": 8,
|
|
}
|
|
|
|
|
|
def _frag_count(dtype, dim_a, dim_b, threads):
|
|
if dtype in _PTX_BITS:
|
|
bits = _PTX_BITS[dtype]
|
|
else:
|
|
bits = DataType(dtype).bits
|
|
size = _frag(dtype).size
|
|
return dim_a * dim_b * bits // threads // size
|
|
|
|
|
|
# =============================================================================
|
|
# Shared helpers for the 7 mma form_kinds.
|
|
# Args layout for each form:
|
|
# (d_ptr_in, a_ptr_in, b_ptr_in [, c_ptr_in], shape, a_layout, b_layout,
|
|
# d_type, a_type, b_type, c_type, no_c_ptr [, saturate or bit_op])
|
|
# n_attrs = 8 for f16/tf32/bf16/fp64/fp8 (last 8 = shape, layouts, 4 dtypes, no_c_ptr)
|
|
# n_attrs = 9 for int8 (+ saturate) and subbyte (+ bit_op)
|
|
# =============================================================================
|
|
|
|
|
|
def _mma_form_parts(args, *, has_saturate=False, has_bit_op=False):
|
|
"""Compute (helper_name, c_signature, body) for one mma form invocation.
|
|
|
|
``args`` is the full positional arg tuple as received by codegen.
|
|
The trailing ``n_attrs`` (8 or 9) entries are attrs.
|
|
"""
|
|
n_extra = (1 if has_saturate else 0) + (1 if has_bit_op else 0)
|
|
n_attrs = 8 + n_extra
|
|
# Split off attr args from the tail (operand args are ahead).
|
|
attrs = args[-n_attrs:]
|
|
shape = parse_str(attrs[0])
|
|
a_layout = parse_str(attrs[1])
|
|
b_layout = parse_str(attrs[2])
|
|
d_type = parse_str(attrs[3])
|
|
a_type = parse_str(attrs[4])
|
|
b_type = parse_str(attrs[5])
|
|
c_type = parse_str(attrs[6])
|
|
no_c_ptr_raw = attrs[7]
|
|
no_c_ptr = bool(int(no_c_ptr_raw)) if hasattr(no_c_ptr_raw, "value") else bool(no_c_ptr_raw)
|
|
saturate = False
|
|
bit_op = ""
|
|
if has_saturate:
|
|
s = attrs[8]
|
|
saturate = bool(int(s)) if hasattr(s, "value") else bool(s)
|
|
if has_bit_op:
|
|
bit_op = parse_str(attrs[8])
|
|
|
|
# Fragment counts (same derivation as the contiguous form).
|
|
m, n, k = _parse_mma_shape(shape)
|
|
threads = _mma_threads(shape, a_type)
|
|
d_cnt = _frag_count(d_type, m, n, threads)
|
|
a_cnt = _frag_count(a_type, m, k, threads)
|
|
b_cnt = _frag_count(b_type, k, n, threads)
|
|
c_cnt = _frag_count(c_type, m, n, threads)
|
|
|
|
# C signature: one void* per register, ordered D regs, A regs, B regs,
|
|
# then C regs (only when the accumulator is used).
|
|
sig_parts = (
|
|
[f"void* d_ptr{i}" for i in range(d_cnt)]
|
|
+ [f"void* a_ptr{i}" for i in range(a_cnt)]
|
|
+ [f"void* b_ptr{i}" for i in range(b_cnt)]
|
|
)
|
|
if not no_c_ptr:
|
|
sig_parts += [f"void* c_ptr{i}" for i in range(c_cnt)]
|
|
sig = "(" + ", ".join(sig_parts) + ")"
|
|
|
|
def _safe(s):
|
|
return s.replace("::", "_").replace(".", "_")
|
|
|
|
name = (
|
|
f"ptx_mma_{shape}_{a_layout}_{b_layout}"
|
|
f"_{_safe(d_type)}_{_safe(a_type)}_{_safe(b_type)}_{_safe(c_type)}"
|
|
f"{'_no_c_ptr' if no_c_ptr else ''}"
|
|
f"{'_saturate' if saturate else ''}"
|
|
)
|
|
|
|
d_frag = _frag(d_type)
|
|
a_frag = _frag(a_type)
|
|
b_frag = _frag(b_type)
|
|
c_frag = _frag(c_type)
|
|
|
|
saturate_inst = ".satfinite" if saturate else ""
|
|
# PTX b1 mma requires a `.popc` suffix after the bit op (e.g. `.xor.popc`).
|
|
bit_op_inst = f".{bit_op}.popc" if bit_op else ""
|
|
|
|
d_type_inst = PTXDataType.from_string(d_type).to_string()
|
|
c_type_inst = PTXDataType.from_string(c_type).to_string()
|
|
a_type_inst = PTXDataType.from_string(a_type).to_string()
|
|
b_type_inst = PTXDataType.from_string(b_type).to_string()
|
|
|
|
def _slot_arr(start, cnt):
|
|
return "{" + ", ".join(f"%{start + i}" for i in range(cnt)) + "}"
|
|
|
|
args_template = (
|
|
f"{_slot_arr(0, d_cnt)}, {_slot_arr(d_cnt, a_cnt)}, "
|
|
f"{_slot_arr(d_cnt + a_cnt, b_cnt)}, {_slot_arr(d_cnt + a_cnt + b_cnt, c_cnt)}"
|
|
)
|
|
|
|
# Each register binds to its OWN pointer via *(T*)X_ptrN (scatter).
|
|
d_outs = ", ".join(
|
|
f'"=r"(*({d_frag.ptr_type}*)d_ptr{i})'
|
|
if d_frag.reg_type == "r"
|
|
else f'"={d_frag.reg_type}"(*({d_frag.ptr_type}*)d_ptr{i})'
|
|
for i in range(d_cnt)
|
|
)
|
|
a_inputs = ", ".join(
|
|
f'"{a_frag.reg_type}"(*({a_frag.ptr_type}*)a_ptr{i})' for i in range(a_cnt)
|
|
)
|
|
b_inputs = ", ".join(
|
|
f'"{b_frag.reg_type}"(*({b_frag.ptr_type}*)b_ptr{i})' for i in range(b_cnt)
|
|
)
|
|
if no_c_ptr:
|
|
c_value = "0.f" if c_frag.reg_type == "f" else "0"
|
|
c_inputs = ", ".join(f'"{c_frag.reg_type}"({c_value})' for _ in range(c_cnt))
|
|
else:
|
|
c_inputs = ", ".join(
|
|
f'"{c_frag.reg_type}"(*({c_frag.ptr_type}*)c_ptr{i})' for i in range(c_cnt)
|
|
)
|
|
|
|
body = (
|
|
" asm volatile(\n"
|
|
f' "mma.sync.aligned.{shape}.{a_layout}.{b_layout}{saturate_inst}'
|
|
f'{d_type_inst}{a_type_inst}{b_type_inst}{c_type_inst}{bit_op_inst} "\n'
|
|
f' "{args_template};\\n"\n'
|
|
f" : {d_outs}\n"
|
|
f" : {a_inputs}, {b_inputs}, {c_inputs}\n"
|
|
" );"
|
|
)
|
|
return name, sig, body
|
|
|
|
|
|
def _register_mma_form(form_kind, *, has_saturate=False, has_bit_op=False):
|
|
n_attrs = 8 + (1 if has_saturate else 0) + (1 if has_bit_op else 0)
|
|
|
|
def _parts(*args, hs=has_saturate, hb=has_bit_op):
|
|
return _mma_form_parts(args, has_saturate=hs, has_bit_op=hb)
|
|
|
|
device_intrinsic(
|
|
f"_ptx_mma_{form_kind}",
|
|
n_attrs=n_attrs,
|
|
helper_name=lambda *a: _parts(*a)[0],
|
|
c_signature=lambda *a: _parts(*a)[1],
|
|
body=lambda *a: _parts(*a)[2],
|
|
)
|
|
|
|
|
|
# Form 1 — f16. Form 2 — tf32. Form 3 — bf16. Form 4 — fp64. Form 6 — fp8.
|
|
# All share the same 8-attr layout (no saturate / bit_op).
|
|
for _kind in ("f16", "tf32", "bf16", "fp64", "fp8"):
|
|
_register_mma_form(_kind)
|
|
del _kind
|
|
|
|
# Form 5 — int8 (+ saturate).
|
|
_register_mma_form("int8", has_saturate=True)
|
|
|
|
# Form 7 — subbyte (+ bit_op for b1).
|
|
_register_mma_form("subbyte", has_bit_op=True)
|
|
|
|
|
|
@register_codegen("ptx_mma")
|
|
def codegen_ptx_mma(
|
|
shape,
|
|
a_layout,
|
|
b_layout,
|
|
d_type,
|
|
a_type,
|
|
b_type,
|
|
c_type,
|
|
d_cnt,
|
|
a_cnt,
|
|
b_cnt,
|
|
c_cnt,
|
|
no_c_ptr,
|
|
*rest,
|
|
):
|
|
"""Classify (d, a, b) dtype triple to one of 7 form_kinds and forward.
|
|
|
|
``rest`` = flattened per-register pointers (d_cnt + a_cnt + b_cnt + c_cnt of
|
|
them) followed by ``saturate`` and optionally ``bit_op``.
|
|
"""
|
|
shape = parse_str(shape)
|
|
a_layout = parse_str(a_layout)
|
|
b_layout = parse_str(b_layout)
|
|
d_type = parse_str(d_type)
|
|
a_type = parse_str(a_type)
|
|
b_type = parse_str(b_type)
|
|
c_type = parse_str(c_type)
|
|
d_cnt = int(d_cnt)
|
|
a_cnt = int(a_cnt)
|
|
b_cnt = int(b_cnt)
|
|
c_cnt = int(c_cnt)
|
|
no_c_ptr = bool(int(no_c_ptr)) if hasattr(no_c_ptr, "value") else bool(no_c_ptr)
|
|
|
|
n_ptrs = d_cnt + a_cnt + b_cnt + (0 if no_c_ptr else c_cnt)
|
|
ptrs = list(rest[:n_ptrs])
|
|
trailing = list(rest[n_ptrs:])
|
|
saturate = bool(trailing[0]) if trailing else False
|
|
bit_op_v = ""
|
|
if len(trailing) >= 2:
|
|
bo = trailing[1]
|
|
bit_op_v = parse_str(bo) if isinstance(bo, str) else (bo if bo is not None else "")
|
|
|
|
kind = _classify_mma_form(d_type, a_type, b_type)
|
|
|
|
# op_args are the flattened per-register pointers (already in PTX order:
|
|
# D regs, A regs, B regs, then C regs unless no_c_ptr).
|
|
op_args = ptrs
|
|
|
|
attr_args = [shape, a_layout, b_layout, d_type, a_type, b_type, c_type, no_c_ptr]
|
|
if kind == "int8":
|
|
attr_args.append(saturate)
|
|
elif kind == "subbyte":
|
|
attr_args.append(bit_op_v)
|
|
|
|
result = CODEGEN_REGISTRY[f"tirx._ptx_mma_{kind}"](op_args + attr_args)
|
|
return result[0] if isinstance(result, tuple) else result
|
|
|
|
|
|
# =============================================================================
|
|
# ldmatrix / stmatrix — m8n8 fragment load/store. PTX docs lists 3 ldmatrix
|
|
# forms (m8n8 + m8n16 + m16n16); TIRx uses only the m8n8 form. 1
|
|
# device_intrinsic each. ``num`` (.x1/.x2/.x4) and ``trans`` are modifier
|
|
# attrs; the asm body loops over per-register constraints based on
|
|
# (num, dtype).
|
|
# =============================================================================
|
|
|
|
|
|
def _ldmatrix_parts(*args):
|
|
# args = (smem_ptr, dst0, dst1, ..., dst{N-1}, num, dtype, trans)
|
|
# The last 3 entries are the codegen attrs (n_attrs=3).
|
|
num = int(args[-3])
|
|
dtype = parse_str(args[-2])
|
|
trans_b = bool(int(args[-1])) if hasattr(args[-1], "value") else bool(args[-1])
|
|
if num not in (1, 2, 4):
|
|
raise ValueError(f"ldmatrix .num must be one of {{1, 2, 4}}, got {num}")
|
|
if dtype not in ("b16", "b8"):
|
|
raise ValueError(f"ldmatrix dtype must be 'b16' or 'b8', got {dtype!r}")
|
|
n_regs = num if dtype == "b16" else num // 2
|
|
trans_inst = ".trans" if trans_b else ""
|
|
slot_list = "{" + ", ".join(f"%{i}" for i in range(n_regs)) + "}"
|
|
reg_decls = ", ".join(f"r{i}" for i in range(n_regs))
|
|
out_constraints = ", ".join(f'"=r"(r{i})' for i in range(n_regs))
|
|
dst_assigns = "\n".join(f" *(uint32_t*)dst{i} = r{i};" for i in range(n_regs))
|
|
name = f"ptx_ldmatrix_{num}_{dtype.replace('::', '_').replace('.', '_')}_{1 if trans_b else 0}"
|
|
sig = "(void* smem_ptr, " + ", ".join(f"void* dst{i}" for i in range(n_regs)) + ")"
|
|
body = (
|
|
f" uint32_t {reg_decls};\n"
|
|
" unsigned int addr = __cvta_generic_to_shared(smem_ptr);\n"
|
|
" asm volatile(\n"
|
|
f' "ldmatrix.sync.aligned.m8n8.x{num}{trans_inst}.shared.{dtype} '
|
|
f'{slot_list}, [%{n_regs}];"\n'
|
|
f" : {out_constraints}\n"
|
|
f' : "r"(addr));\n'
|
|
f"{dst_assigns}"
|
|
)
|
|
return name, sig, body
|
|
|
|
|
|
device_intrinsic(
|
|
"_ptx_ldmatrix_impl",
|
|
n_attrs=3,
|
|
c_signature=lambda *a: _ldmatrix_parts(*a)[1],
|
|
helper_name=lambda *a: _ldmatrix_parts(*a)[0],
|
|
body=lambda *a: _ldmatrix_parts(*a)[2],
|
|
)
|
|
|
|
|
|
@register_codegen("ptx_ldmatrix")
|
|
def codegen_ptx_ldmatrix(trans, num, dtype, smem_ptr, *dst_handles):
|
|
trans = bool(trans)
|
|
num = int(num)
|
|
dtype = parse_str(dtype)
|
|
if dtype.startswith("."):
|
|
dtype = dtype[1:]
|
|
n_regs = num if dtype == "b16" else num // 2
|
|
if len(dst_handles) != n_regs:
|
|
raise ValueError(
|
|
f"ldmatrix .x{num}.{dtype} codegen expects {n_regs} dst handles, got {len(dst_handles)}"
|
|
)
|
|
result = CODEGEN_REGISTRY["tirx._ptx_ldmatrix_impl"](
|
|
[smem_ptr, *dst_handles, num, dtype, trans]
|
|
)
|
|
return result[0] if isinstance(result, tuple) else result
|
|
|
|
|
|
def _stmatrix_parts(*args):
|
|
# args = (smem_ptr, src0, src1, ..., src{N-1}, trans, num, dtype, shape, space)
|
|
# The last 5 entries are codegen attrs (n_attrs=5).
|
|
trans_arg, num_arg, dtype_arg, shape_arg, space_arg = args[-5:]
|
|
n_regs = int(num_arg)
|
|
trans_b = bool(int(trans_arg)) if hasattr(trans_arg, "value") else bool(trans_arg)
|
|
dtype = parse_str(dtype_arg)
|
|
shape = parse_str(shape_arg)
|
|
space = parse_str(space_arg)
|
|
if dtype.startswith("."):
|
|
dtype = dtype[1:]
|
|
if n_regs not in (1, 2, 4):
|
|
raise ValueError(f"stmatrix .num must be one of {{1, 2, 4}}, got {n_regs}")
|
|
if dtype not in ("b16", "b8"):
|
|
raise ValueError(f"stmatrix .type must be b16 or b8, got {dtype!r}")
|
|
if shape not in ("m8n8", "m16n8"):
|
|
raise ValueError(f"stmatrix .shape must be m8n8 or m16n8, got {shape!r}")
|
|
if space not in ("shared", "shared::cta"):
|
|
raise ValueError(f"stmatrix state space must be shared or shared::cta, got {space!r}")
|
|
if shape == "m16n8" and not trans_b:
|
|
raise ValueError("stmatrix .m16n8 requires .trans")
|
|
trans_inst = ".trans" if trans_b else ""
|
|
slot_list = "{" + ", ".join(f"%{i}" for i in range(n_regs)) + "}"
|
|
src_loads = "\n".join(f" uint32_t r{i} = *(uint32_t*)src{i};" for i in range(n_regs))
|
|
in_constraints = ", ".join(f'"r"(r{i})' for i in range(n_regs))
|
|
name = f"ptx_stmatrix_{shape}_{n_regs}_{1 if trans_b else 0}_{space.replace('::', '_')}_{dtype}"
|
|
sig = "(void* smem_ptr, " + ", ".join(f"void* src{i}" for i in range(n_regs)) + ")"
|
|
body = (
|
|
f"{src_loads}\n"
|
|
" unsigned int addr = __cvta_generic_to_shared(smem_ptr);\n"
|
|
" asm volatile(\n"
|
|
f' "stmatrix.sync.aligned.{shape}.x{n_regs}{trans_inst}.{space}.{dtype} '
|
|
f'[%{n_regs}], {slot_list};"\n'
|
|
" :\n"
|
|
f' : {in_constraints}, "r"(addr));'
|
|
)
|
|
return name, sig, body
|
|
|
|
|
|
device_intrinsic(
|
|
"_ptx_stmatrix_impl",
|
|
n_attrs=5,
|
|
c_signature=lambda *a: _stmatrix_parts(*a)[1],
|
|
helper_name=lambda *a: _stmatrix_parts(*a)[0],
|
|
body=lambda *a: _stmatrix_parts(*a)[2],
|
|
)
|
|
|
|
|
|
@register_codegen("ptx_stmatrix")
|
|
def codegen_ptx_stmatrix(trans, num, dtype, shape, space, smem_ptr, *src_handles):
|
|
trans = bool(trans)
|
|
num = int(num)
|
|
dtype_str = parse_str(dtype)
|
|
if dtype_str.startswith("."):
|
|
dtype_str = dtype_str[1:]
|
|
n_regs = num
|
|
if len(src_handles) != n_regs:
|
|
raise ValueError(
|
|
f"stmatrix .x{num}.{dtype_str} codegen expects {n_regs} src handles, "
|
|
f"got {len(src_handles)}"
|
|
)
|
|
result = CODEGEN_REGISTRY["tirx._ptx_stmatrix_impl"](
|
|
[smem_ptr, *src_handles, trans, num, dtype, shape, space]
|
|
)
|
|
return result[0] if isinstance(result, tuple) else result
|