3631 lines
104 KiB
Python
3631 lines
104 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.
|
|
"""IRBuilder for TIR"""
|
|
|
|
import contextlib
|
|
import functools
|
|
import inspect
|
|
import threading
|
|
from collections.abc import Callable
|
|
from functools import partial
|
|
from numbers import Integral
|
|
from typing import TYPE_CHECKING, Any, ParamSpec, TypeVar, Union
|
|
|
|
# isort: off
|
|
from typing import Literal
|
|
|
|
# isort: on
|
|
|
|
from tvm_ffi.core import String
|
|
|
|
from tvm import DataType, ir
|
|
from tvm import tirx as tir
|
|
from tvm.ir import Call, Type, is_prim_expr
|
|
from tvm.ir import register_op_attr as _register_op_attr
|
|
from tvm.ir.base import deprecated
|
|
from tvm.runtime import convert
|
|
from tvm.script.ir_builder.base import IRBuilder
|
|
from tvm.target import Target
|
|
|
|
# pylint: disable=unused-import
|
|
from tvm.target.codegen import llvm_lookup_intrinsic_id
|
|
from tvm.tirx import Buffer, BufferRegion, Expr, IndexMap, type_annotation
|
|
from tvm.tirx import _ffi_api as _tirx_ffi_api
|
|
from tvm.tirx import op as _tir_op
|
|
from tvm.tirx.exec_scope import ExecScope, ScopeIdDef, Var
|
|
|
|
# import tirx.expr for direct ir construction to pass structural_equal comparison
|
|
from tvm.tirx.expr import (
|
|
EQ,
|
|
GE,
|
|
GT,
|
|
LE,
|
|
LT,
|
|
NE,
|
|
Add,
|
|
And,
|
|
Broadcast,
|
|
BufferLoad,
|
|
CallEffectKind,
|
|
Cast,
|
|
CommReducer,
|
|
Div,
|
|
FloatImm,
|
|
FloorDiv,
|
|
FloorMod,
|
|
IntImm,
|
|
IterVar,
|
|
Max,
|
|
Min,
|
|
Mod,
|
|
Mul,
|
|
Not,
|
|
Or,
|
|
ProducerLoad,
|
|
Ramp,
|
|
Reduce,
|
|
Select,
|
|
Shuffle,
|
|
StringImm,
|
|
Sub,
|
|
)
|
|
from tvm.tirx.layout import (
|
|
ComposeLayout,
|
|
Iter,
|
|
Layout,
|
|
R,
|
|
S,
|
|
SwizzleLayout,
|
|
TileLayout,
|
|
wg_local_layout,
|
|
)
|
|
|
|
from . import _ffi_api, frame, utils
|
|
from .external_kernel import call_kernel
|
|
|
|
# pylint: enable=unused-import
|
|
|
|
|
|
def cast(value, dtype, span=None):
|
|
"""Cast an expression to the requested data type."""
|
|
return _tirx_ffi_api._cast(dtype, value, span) # type: ignore[attr-defined]
|
|
|
|
|
|
def _current_s_tir() -> bool:
|
|
"""Return True if the innermost enclosing PrimFuncFrame has ``s_tir=True``.
|
|
|
|
Gates the parser's default layout fill: ``s_tir=True`` PrimFuncs leave
|
|
``layout=None`` (so s_tir-style passes that don't touch layout round-trip
|
|
cleanly); ``s_tir=False`` (default, tirx) get ``DefaultLayout(shape)``.
|
|
"""
|
|
from tvm.script.ir_builder.base import IRBuilder # local import to avoid cycle
|
|
|
|
if not IRBuilder.is_in_scope():
|
|
return False
|
|
builder = IRBuilder.current()
|
|
for f in reversed(list(builder.frames)):
|
|
if isinstance(f, frame.PrimFuncFrame):
|
|
return bool(f.s_tir)
|
|
return False
|
|
|
|
|
|
def _get_layout(layout: str | Layout | None, shape: list[Expr], scope: str) -> Layout | None:
|
|
if layout is None:
|
|
return None
|
|
if isinstance(layout, Layout):
|
|
return layout
|
|
assert isinstance(layout, str)
|
|
if layout == "default":
|
|
if _current_s_tir():
|
|
return None
|
|
if scope in ["trn.sbuf", "trn.psum"]:
|
|
return None
|
|
return TileLayout(S[tuple(shape)])
|
|
shape = tuple(shape)
|
|
if scope == "trn.sbuf":
|
|
layout = TileLayout.trainium(layout, shape)
|
|
elif scope == "trn.psum":
|
|
layout = TileLayout.trainium(layout, shape).to_psum()
|
|
return layout
|
|
|
|
|
|
def _normalize_prim_type(dtype) -> ir.PrimType:
|
|
if isinstance(dtype, ir.PrimType):
|
|
return dtype
|
|
dtype_str = getattr(dtype, "_dtype_str", None)
|
|
if dtype_str is not None:
|
|
return ir.PrimType(dtype_str)
|
|
if callable(dtype):
|
|
value = dtype()
|
|
ty = getattr(value, "ty", None)
|
|
if isinstance(ty, ir.PrimType):
|
|
return ty
|
|
return ir.PrimType(dtype)
|
|
|
|
|
|
def _get_elem_offset(elem_offset, byte_offset, dtype: str):
|
|
assert elem_offset is None or byte_offset is None, (
|
|
"elem_offset and byte_offset cannot be set at the same time"
|
|
)
|
|
if elem_offset is not None:
|
|
return elem_offset
|
|
if byte_offset is None:
|
|
return None
|
|
return byte_offset * 8 // (DataType(_normalize_prim_type(dtype).dtype).bits)
|
|
|
|
|
|
_block_name_suffix = threading.local()
|
|
_meta_construction_state = threading.local()
|
|
_THIS_FILE = __file__
|
|
|
|
|
|
class _MetaResourceRecord:
|
|
"""Resource created while constructing a meta_class instance."""
|
|
|
|
def __init__(
|
|
self, value: Any, filename: str, lineno: int, colno: int | None, code: str
|
|
) -> None:
|
|
self.value = value
|
|
self.filename = filename
|
|
self.lineno = lineno
|
|
self.colno = colno
|
|
self.code = code
|
|
|
|
|
|
class _MetaConstructionScope:
|
|
"""Thread-local construction scope for a single meta_class __init__ call."""
|
|
|
|
def __init__(self, instance: Any, cls: type) -> None:
|
|
self.instance = instance
|
|
self.cls = cls
|
|
self.created: list[_MetaResourceRecord] = []
|
|
|
|
def record(self, value: Any, frame_info: inspect.FrameInfo) -> None:
|
|
positions = getattr(frame_info, "positions", None)
|
|
colno = None
|
|
if positions is not None and positions.col_offset is not None:
|
|
colno = positions.col_offset + 1
|
|
code = frame_info.code_context[0].strip() if frame_info.code_context else ""
|
|
self.created.append(
|
|
_MetaResourceRecord(
|
|
value=value,
|
|
filename=frame_info.filename,
|
|
lineno=frame_info.lineno,
|
|
colno=colno,
|
|
code=code,
|
|
)
|
|
)
|
|
|
|
|
|
def _meta_construction_stack() -> list[_MetaConstructionScope]:
|
|
stack = getattr(_meta_construction_state, "stack", None)
|
|
if stack is None:
|
|
stack = []
|
|
_meta_construction_state.stack = stack
|
|
return stack
|
|
|
|
|
|
def _current_meta_construction_scope() -> _MetaConstructionScope | None:
|
|
stack = _meta_construction_stack()
|
|
return stack[-1] if stack else None
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def _with_meta_construction_scope(instance: Any, cls: type):
|
|
scope = _MetaConstructionScope(instance, cls)
|
|
stack = _meta_construction_stack()
|
|
stack.append(scope)
|
|
try:
|
|
yield scope
|
|
finally:
|
|
stack.pop()
|
|
|
|
|
|
def _record_meta_resource(value: Any, skip_frames: int = 2) -> None:
|
|
scope = _current_meta_construction_scope()
|
|
if scope is not None:
|
|
stack = inspect.stack(context=1)
|
|
frame_info = None
|
|
for candidate in stack[2:]:
|
|
if candidate.filename != _THIS_FILE:
|
|
frame_info = candidate
|
|
break
|
|
if frame_info is None:
|
|
frame_info = stack[min(skip_frames + 1, len(stack) - 1)]
|
|
scope.record(value, frame_info)
|
|
|
|
|
|
def _get_sblock_name_suffix() -> str:
|
|
"""Get the current block name suffix for macro expansion."""
|
|
return getattr(_block_name_suffix, "value", "")
|
|
|
|
|
|
@contextlib.contextmanager
|
|
def block_name_suffix_context(block_suffix: str):
|
|
"""Context manager to set block name suffix during macro expansion.
|
|
|
|
Parameters
|
|
----------
|
|
block_suffix : str
|
|
The suffix to append to block names (e.g., "_1", "_2").
|
|
|
|
Yields
|
|
------
|
|
None
|
|
"""
|
|
old_suffix = getattr(_block_name_suffix, "value", "")
|
|
_block_name_suffix.value = block_suffix
|
|
try:
|
|
yield
|
|
finally:
|
|
_block_name_suffix.value = old_suffix
|
|
|
|
|
|
def buffer(
|
|
shape: list[Expr] | tuple[Expr] | Expr | Integral,
|
|
dtype: str = "float32",
|
|
data: Var = None,
|
|
strides: list[Expr] | None = None,
|
|
elem_offset: Expr = None,
|
|
byte_offset: Expr = None,
|
|
scope: str = "global",
|
|
align: int = 0,
|
|
offset_factor: int = 0,
|
|
buffer_type: str = "",
|
|
axis_separators: list[int] | None = None,
|
|
layout: str | Layout | None = "default",
|
|
allocated_addr: int | tuple[int, ...] | None = None,
|
|
buffer_name: str = "",
|
|
) -> Buffer:
|
|
"""The buffer declaration function.
|
|
|
|
Parameters
|
|
----------
|
|
shape : Union[List[Expr], Tuple[Expr], Expr, Integral]
|
|
The type of the buffer prior to flattening.
|
|
|
|
dtype : str
|
|
The data type in the content of the buffer.
|
|
|
|
data : Var
|
|
The pointer to the head of the data.
|
|
|
|
strides : List[Expr]
|
|
The strides of each dimension.
|
|
|
|
elem_offset : Expr
|
|
The offset in terms of number of dtype elements (including lanes).
|
|
|
|
scope : str
|
|
The optional storage scope of buffer data pointer.
|
|
|
|
align : int
|
|
The alignment requirement of data pointer in bytes.
|
|
|
|
offset_factor : int
|
|
The factor of elem_offset field.
|
|
|
|
buffer_type : str
|
|
The buffer type.
|
|
|
|
axis_separators : List[int]
|
|
The separators between input axes when generating flattened output axes.
|
|
|
|
buffer_name : str
|
|
The name of the buffer.
|
|
|
|
Returns
|
|
-------
|
|
res : Buffer
|
|
The declared buffer.
|
|
"""
|
|
shape = (shape,) if is_prim_expr(shape) or isinstance(shape, Integral) else shape
|
|
if strides is not None:
|
|
strides = [Var(s, "int32") if isinstance(s, str) else s for s in strides]
|
|
else:
|
|
strides = []
|
|
if allocated_addr is None:
|
|
allocated_addr = []
|
|
if not isinstance(allocated_addr, list | tuple):
|
|
allocated_addr = [allocated_addr]
|
|
return _ffi_api.Buffer( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
shape,
|
|
dtype,
|
|
buffer_name,
|
|
data,
|
|
strides,
|
|
_get_elem_offset(elem_offset, byte_offset, dtype),
|
|
scope,
|
|
align,
|
|
offset_factor,
|
|
buffer_type,
|
|
axis_separators,
|
|
_get_layout(layout, shape, scope),
|
|
allocated_addr,
|
|
)
|
|
|
|
|
|
@deprecated("T.buffer_decl(...)", "T.Buffer(...)")
|
|
def buffer_decl(*args, **kwargs):
|
|
return buffer(*args, **kwargs)
|
|
|
|
|
|
def prim_func(
|
|
is_private: bool = False,
|
|
s_tir: bool = False,
|
|
persistent: bool = False,
|
|
*,
|
|
private: bool | None = None,
|
|
) -> frame.PrimFuncFrame:
|
|
"""The primitive function statement.
|
|
|
|
Parameters
|
|
----------
|
|
is_private : bool
|
|
Whether the PrimFunc is annotated as private.
|
|
s_tir : bool
|
|
Whether this PrimFunc uses s_tir (apache-derived TIR) semantics:
|
|
parser fills layout=None on buffers, ScriptComplete wraps body in a
|
|
root SBlock. Default (False) selects tirx semantics: parser fills
|
|
``DefaultLayout(shape)`` and no root-block wrapping.
|
|
persistent : bool
|
|
Whether this is a persistent kernel.
|
|
private : bool
|
|
Alias for ``is_private`` (used in decorator syntax).
|
|
|
|
Returns
|
|
-------
|
|
res : frame.PrimFuncFrame
|
|
The PrimFuncFrame.
|
|
"""
|
|
if private is not None:
|
|
is_private = private
|
|
return _ffi_api.PrimFunc(is_private, s_tir, persistent) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def arg(name: str, obj: Var | Buffer) -> Var | Buffer:
|
|
"""The PrimFunc arguments adding function.
|
|
|
|
Parameters
|
|
----------
|
|
name : str
|
|
The name of the argument.
|
|
|
|
var : Union[Var, Buffer]
|
|
The argument of Var or Buffer.
|
|
|
|
Returns
|
|
-------
|
|
res : Union[Var, Buffer]
|
|
The argument.
|
|
"""
|
|
return _ffi_api.Arg(name, obj) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def func_name(name: str) -> None:
|
|
"""The PrimFunc naming statement.
|
|
|
|
Parameters
|
|
----------
|
|
name : str
|
|
The name of the PrimFunc.
|
|
"""
|
|
_ffi_api.FuncName(name) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def func_attr(attrs: dict[str, Any]) -> None:
|
|
"""The PrimFunc annotation statement.
|
|
|
|
Parameters
|
|
----------
|
|
attrs : Dict[str, Any]
|
|
The annotations of the PrimFunc.
|
|
"""
|
|
_ffi_api.FuncAttrs(attrs) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def func_ret(ret_type: Type | None) -> Type:
|
|
"""The PrimFunc return type statement.
|
|
|
|
Parameters
|
|
----------
|
|
ret_type : Type
|
|
The return type of the PrimFunc.
|
|
|
|
Returns
|
|
-------
|
|
res : Type
|
|
The return type.
|
|
"""
|
|
if ret_type is None:
|
|
ret_type = Type.missing()
|
|
return _ffi_api.FuncRet(ret_type) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def match_buffer(
|
|
param: Var | BufferLoad | BufferRegion,
|
|
shape: list[Expr] | tuple[Expr] | Expr | Integral = None,
|
|
dtype: str = "float32",
|
|
data: Var = None,
|
|
strides: list[Expr] | None = None,
|
|
elem_offset: Expr = None,
|
|
scope: str = "global",
|
|
align: int = -1,
|
|
offset_factor: int = 0,
|
|
buffer_type: str = "default",
|
|
axis_separators: list[int] | None = None,
|
|
layout: str | Layout | None = "default",
|
|
) -> Buffer:
|
|
"""The buffer match function.
|
|
|
|
Note
|
|
----
|
|
This function will perform different behavior, depending on the type of param.
|
|
If the param is a var in function parameter, it will create a buffer from DLTensor.
|
|
Else if the param is a subregion of other buffers, then create a subregion match inside a block.
|
|
|
|
Example
|
|
-------
|
|
Match buffer from function parameter
|
|
.. code-block:: python
|
|
A = T.match_buffer(a, (128, 128), dtype="float32")
|
|
|
|
Match buffer from Buffer subregion
|
|
.. code-block:: python
|
|
A = T.match_buffer(B[0:128, i * 128 : i * 128 + 128], (128, 128), dtype="float32")
|
|
|
|
Parameters
|
|
----------
|
|
param : Union[Var, BufferLoad, BufferRegion]
|
|
The parameter of the PrimFunc to match.
|
|
|
|
shape : Union[List[Expr], Tuple[Expr], Expr, Integral]
|
|
The type of the buffer prior to flattening.
|
|
|
|
dtype : str
|
|
The data type in the content of the buffer.
|
|
|
|
data : Var
|
|
The pointer to the head of the data.
|
|
|
|
strides : List[Expr]
|
|
The strides of each dimension.
|
|
|
|
elem_offset : Expr
|
|
The offset in terms of number of dtype elements (including lanes).
|
|
|
|
scope : str
|
|
The optional storage scope of buffer data pointer.
|
|
|
|
align : int
|
|
The alignment requirement of data pointer in bytes.
|
|
|
|
offset_factor : int
|
|
The factor of elem_offset field.
|
|
|
|
buffer_type : str
|
|
The buffer type.
|
|
|
|
axis_separators : List[int]
|
|
The separators between input axes when generating flattened output axes.
|
|
|
|
layout: Optional[Union[str, Layout]]
|
|
The layout of the buffer.
|
|
|
|
Returns
|
|
-------
|
|
res : Buffer
|
|
The matched buffer.
|
|
"""
|
|
if shape is None:
|
|
if isinstance(param, BufferRegion):
|
|
dtype = param.buffer.dtype
|
|
shape = [region.extent for region in param.region]
|
|
else:
|
|
raise ValueError("Shape must be specified when binding input param")
|
|
shape = (shape,) if is_prim_expr(shape) or isinstance(shape, Integral) else shape
|
|
if strides is not None:
|
|
idx_dtype = shape[0].ty if is_prim_expr(shape[0]) else "int32"
|
|
strides = [Var(s, idx_dtype) if isinstance(s, str) else s for s in strides]
|
|
else:
|
|
strides = []
|
|
return _ffi_api.MatchBuffer( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
param,
|
|
shape,
|
|
dtype,
|
|
data,
|
|
strides,
|
|
elem_offset,
|
|
scope,
|
|
align,
|
|
offset_factor,
|
|
buffer_type,
|
|
axis_separators,
|
|
_get_layout(layout, shape, scope),
|
|
)
|
|
|
|
|
|
def sblock(name: str = "", no_realize: bool = False, exec_scope: str = "") -> frame.SBlockFrame:
|
|
"""The sblock declaration statement.
|
|
|
|
Parameters
|
|
----------
|
|
name : str
|
|
The name of the sblock.
|
|
|
|
no_realize : bool
|
|
The flag whether to construct SBlockRealize or SBlock.
|
|
|
|
exec_scope : str
|
|
The execution scope of the block.
|
|
|
|
Returns
|
|
-------
|
|
res : frame.SBlockFrame
|
|
The SBlockFrame.
|
|
"""
|
|
if isinstance(name, list):
|
|
# tir+
|
|
return _ffi_api.ScopeSlice(name, no_realize)
|
|
block_suffix = _get_sblock_name_suffix()
|
|
if block_suffix and name:
|
|
name = name + block_suffix
|
|
return _ffi_api.Block(name, no_realize, exec_scope) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def device_entry() -> None:
|
|
"""Mark the device-region entry within the enclosing PrimFunc body.
|
|
|
|
Flat marker (no ``with``). Subsequent statements in the function body
|
|
accumulate into an ``AttrStmt("tirx.device_entry", True, body=...)``;
|
|
the wrapping is closed by the PrimFunc frame at function end.
|
|
|
|
Anything written before this marker is host code (e.g. ``T.match_buffer``);
|
|
anything after is device code.
|
|
|
|
Example::
|
|
|
|
@T.prim_func
|
|
def kernel(...):
|
|
A = T.match_buffer(...)
|
|
T.device_entry() # device region starts here
|
|
bx = T.cta_id([SM_COUNT]) # standalone scope-id def
|
|
...
|
|
"""
|
|
attr_frame = _ffi_api.DeviceEntry() # type: ignore[attr-defined] # pylint: disable=no-member
|
|
attr_frame.__enter__()
|
|
# No return: the frame is registered on the IRBuilder stack; the
|
|
# PrimFunc frame's exit drains it.
|
|
|
|
|
|
def elected():
|
|
"""Stub that rejects the removed ``T.elected()`` sugar.
|
|
|
|
Write the explicit form instead::
|
|
|
|
if T.ptx.elect_sync():
|
|
... # thread is the default scope
|
|
"""
|
|
raise RuntimeError(
|
|
"T.elected() is no longer available. Write explicitly: "
|
|
"`if T.ptx.elect_sync(): ...` (thread is the default scope)"
|
|
)
|
|
|
|
|
|
def scope_id(extents: list[Expr | int] | None, parent: str, cur: str) -> Var | list[Var]:
|
|
ret = _ffi_api.ScopeId(extents, parent, "T.scope_id", cur) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
if len(ret) == 1:
|
|
return ret[0]
|
|
return ret
|
|
|
|
|
|
def cluster_id(extents: list[Expr | int] | None = None) -> Var | list[Var]:
|
|
"""Define a kernel→cluster scope id. Pass ``None`` (the default) to defer the
|
|
extent; it will be inferred at LowerTIRx from sibling ScopeIdDef closure."""
|
|
ret = _ffi_api.ClusterId(extents, "kernel") # type: ignore[attr-defined] # pylint: disable=no-member
|
|
if len(ret) == 1:
|
|
return ret[0]
|
|
return ret
|
|
|
|
|
|
def cta_id(extents: list[Expr | int] | None = None, preferred=None) -> Var | list[Var]:
|
|
"""Define a kernel→cta scope id. Pass ``None`` (the default) to defer the
|
|
extent; it will be inferred at LowerTIRx from sibling ScopeIdDef closure."""
|
|
ret = _ffi_api.CtaId(extents, "kernel", preferred) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
if len(ret) == 1:
|
|
return ret[0]
|
|
return ret
|
|
|
|
|
|
def cta_id_in_cluster(extents: list[Expr | int] | None = None, preferred=None) -> Var | list[Var]:
|
|
"""Define a cluster→cta scope id. Pass ``None`` (the default) to defer the
|
|
extent; it will be inferred at LowerTIRx from sibling ScopeIdDef closure."""
|
|
ret = _ffi_api.CtaId(extents, "cluster", preferred) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
if len(ret) == 1:
|
|
return ret[0]
|
|
return ret
|
|
|
|
|
|
def cta_id_in_pair() -> Var:
|
|
ret = _ffi_api.CtaIdInPair() # type: ignore[attr-defined] # pylint: disable=no-member
|
|
return ret[0]
|
|
|
|
|
|
def warpgroup_id(extents: list[Expr | int] | None = None) -> Var | list[Var]:
|
|
"""Define a cta→warpgroup scope id. Pass ``None`` (the default) to defer
|
|
the extent; it will be inferred at LowerTIRx from sibling closure."""
|
|
ret = _ffi_api.WarpgroupId(extents, "cta") # type: ignore[attr-defined] # pylint: disable=no-member
|
|
if len(ret) == 1:
|
|
return ret[0]
|
|
return ret
|
|
|
|
|
|
def warp_id(extents: list[Expr | int] | None = None) -> Var | list[Var]:
|
|
"""Define a cta→warp scope id. Pass ``None`` (the default) to defer the
|
|
extent; it will be inferred at LowerTIRx from sibling closure."""
|
|
ret = _ffi_api.WarpId(extents, "cta") # type: ignore[attr-defined] # pylint: disable=no-member
|
|
if len(ret) == 1:
|
|
return ret[0]
|
|
return ret
|
|
|
|
|
|
def warp_id_in_wg(extents: list[Expr | int] | None = None) -> Var | list[Var]:
|
|
"""Define a warpgroup→warp scope id. Pass ``None`` (the default) to defer
|
|
the extent; it will be inferred at LowerTIRx from sibling closure."""
|
|
ret = _ffi_api.WarpId(extents, "warpgroup") # type: ignore[attr-defined] # pylint: disable=no-member
|
|
if len(ret) == 1:
|
|
return ret[0]
|
|
return ret
|
|
|
|
|
|
def lane_id(extents: list[Expr | int] | None = None) -> Var | list[Var]:
|
|
"""Define a warp→thread scope id. Pass ``None`` (the default) to defer the
|
|
extent; it will be inferred at LowerTIRx from sibling closure."""
|
|
ret = _ffi_api.ThreadId(extents, "warp") # type: ignore[attr-defined] # pylint: disable=no-member
|
|
if len(ret) == 1:
|
|
return ret[0]
|
|
return ret
|
|
|
|
|
|
def thread_id(extents: list[Expr | int] | None = None) -> Var | list[Var]:
|
|
"""Define a cta→thread scope id. Pass ``None`` (the default) to defer the
|
|
extent; it will be inferred at LowerTIRx from sibling closure."""
|
|
ret = _ffi_api.ThreadId(extents, "cta") # type: ignore[attr-defined] # pylint: disable=no-member
|
|
if len(ret) == 1:
|
|
return ret[0]
|
|
return ret
|
|
|
|
|
|
def thread_id_in_wg(extents: list[Expr | int] | None = None) -> Var | list[Var]:
|
|
"""Define a warpgroup→thread scope id. Pass ``None`` (the default) to defer
|
|
the extent; it will be inferred at LowerTIRx from sibling closure."""
|
|
ret = _ffi_api.ThreadId(extents, "warpgroup") # type: ignore[attr-defined] # pylint: disable=no-member
|
|
if len(ret) == 1:
|
|
return ret[0]
|
|
return ret
|
|
|
|
|
|
def init() -> frame.BlockInitFrame:
|
|
"""The block initialization statement.
|
|
|
|
Returns
|
|
-------
|
|
res : frame.BlockInitFrame
|
|
The BlockInitFrame.
|
|
"""
|
|
return _ffi_api.Init() # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def where(predicate: Expr | int) -> None:
|
|
"""The block predicate statement.
|
|
|
|
Parameters
|
|
----------
|
|
predicate : Union[Expr, Literal[0, 1]]
|
|
The predicate condition.
|
|
"""
|
|
if isinstance(predicate, bool):
|
|
predicate = IntImm("bool", predicate)
|
|
if isinstance(predicate, int):
|
|
if predicate in [0, 1]:
|
|
predicate = IntImm("bool", predicate)
|
|
else:
|
|
raise ValueError(f"Invalid value for predicate: {predicate}")
|
|
_ffi_api.Where(predicate) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def reads(*buffer_slices: list[BufferRegion | BufferLoad]) -> None:
|
|
"""The block buffer region reading statement.
|
|
|
|
Parameters
|
|
----------
|
|
buffer_slices : List[Union[BufferRegion, BufferLoad]]
|
|
The array of buffer regions to read.
|
|
"""
|
|
if len(buffer_slices) == 1:
|
|
if isinstance(buffer_slices[0], tuple):
|
|
buffer_slices = list(buffer_slices[0])
|
|
elif isinstance(buffer_slices[0], list):
|
|
buffer_slices = buffer_slices[0] # type: ignore[assignment]
|
|
else:
|
|
buffer_slices = [buffer_slices[0]]
|
|
else:
|
|
buffer_slices = list(buffer_slices) # type: ignore[assignment]
|
|
_ffi_api.Reads(buffer_slices) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def writes(*buffer_slices: list[BufferRegion | BufferLoad]) -> None:
|
|
"""The block buffer region writing statement.
|
|
|
|
Parameters
|
|
----------
|
|
buffer_slices : List[Union[BufferRegion, BufferLoad]]
|
|
The array of buffer regions to write.
|
|
"""
|
|
if len(buffer_slices) == 1:
|
|
if isinstance(buffer_slices[0], tuple):
|
|
buffer_slices = list(buffer_slices[0])
|
|
elif isinstance(buffer_slices[0], list):
|
|
buffer_slices = buffer_slices[0] # type: ignore[assignment]
|
|
else:
|
|
buffer_slices = [buffer_slices[0]]
|
|
else:
|
|
buffer_slices = list(buffer_slices) # type: ignore[assignment]
|
|
_ffi_api.Writes(buffer_slices) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def sblock_attr(attrs: dict[str, Any]) -> None:
|
|
"""The block annotation statement (for non-tirx SBlock usage).
|
|
|
|
Parameters
|
|
----------
|
|
attrs : Dict[str, Any]
|
|
The annotation of the block.
|
|
"""
|
|
return _ffi_api.BlockAttrs(attrs) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def alloc_buffer(
|
|
shape: list[Expr] | tuple[Expr] | Expr | Integral,
|
|
dtype: str = "float32",
|
|
data: Var | None = None,
|
|
strides: list[Expr] | None = None,
|
|
elem_offset: Expr | None = None,
|
|
byte_offset: Expr | None = None,
|
|
scope: str = "global",
|
|
align: int = -1,
|
|
offset_factor: int = 0,
|
|
buffer_type: str = "default",
|
|
axis_separators: list[int] | None = None,
|
|
layout: str | Layout | None = "default",
|
|
allocated_addr: int | tuple[int, ...] | None = None,
|
|
annotations: dict[str, Any] | None = None,
|
|
) -> Buffer:
|
|
"""Statement-level buffer allocation (creates an AllocBuffer IR node).
|
|
|
|
Emits an AllocBuffer statement and returns the Buffer directly::
|
|
|
|
buf = T.alloc_buffer((128, 128))
|
|
|
|
For SBlock-level buffer allocation (added to SBlock.alloc_buffers),
|
|
use T.sblock_alloc_buffer() instead.
|
|
|
|
Parameters
|
|
----------
|
|
shape : Union[List[Expr], Tuple[Expr], Expr, Integral]
|
|
The shape of the buffer to allocate.
|
|
dtype : str
|
|
The data type of the buffer elements.
|
|
scope : str
|
|
The storage scope of the buffer (e.g., "global", "shared").
|
|
data : Optional[Var]
|
|
Optional explicit data pointer.
|
|
strides : Optional[List[Expr]]
|
|
Optional strides.
|
|
elem_offset : Optional[Expr]
|
|
Optional element offset.
|
|
byte_offset : Optional[Expr]
|
|
Optional byte offset.
|
|
align : int
|
|
Alignment requirement in bytes.
|
|
offset_factor : int
|
|
Offset factor.
|
|
buffer_type : str
|
|
Buffer type.
|
|
axis_separators : Optional[List[int]]
|
|
Optional axis separators.
|
|
layout : Optional[Union[str, Layout]]
|
|
Optional layout.
|
|
allocated_addr : Optional[Union[int, Tuple[int, ...]]]
|
|
Optional pre-allocated address metadata.
|
|
annotations : Optional[Dict[str, Any]]
|
|
Optional annotations for the allocation.
|
|
|
|
Returns
|
|
-------
|
|
res : Buffer
|
|
The allocated buffer.
|
|
"""
|
|
shape = (shape,) if is_prim_expr(shape) or isinstance(shape, Integral) else shape
|
|
buf = buffer(
|
|
shape=shape,
|
|
dtype=dtype,
|
|
data=data,
|
|
strides=strides,
|
|
elem_offset=elem_offset,
|
|
byte_offset=byte_offset,
|
|
scope=scope,
|
|
align=align,
|
|
offset_factor=offset_factor,
|
|
buffer_type=buffer_type,
|
|
axis_separators=axis_separators,
|
|
layout=layout,
|
|
allocated_addr=allocated_addr,
|
|
buffer_name="",
|
|
)
|
|
_record_meta_resource(buf, skip_frames=2)
|
|
|
|
# AllocBuffer.annotations holds typed IR values. The C++ side stores
|
|
# alignment / shape-like ints as ``IntImm(int32, ...)``; if the user
|
|
# (or a parsed-source round-trip) passes a bare Python int, normalize
|
|
# it so structural equality is preserved against the LowerOpaqueBlock
|
|
# output. Booleans must stay as IntImm("bool", ...).
|
|
def _normalize_ann_value(v):
|
|
if isinstance(v, bool):
|
|
return tir.IntImm("bool", int(v))
|
|
if isinstance(v, int):
|
|
return tir.IntImm("int32", v)
|
|
if isinstance(v, float):
|
|
return tir.FloatImm("float32", v)
|
|
return v
|
|
|
|
norm_annotations = {k: _normalize_ann_value(v) for k, v in (annotations or {}).items()}
|
|
_ffi_api.AddToParent(tir.AllocBuffer(buf, norm_annotations)) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
return buf
|
|
|
|
|
|
def wg_reg_tile(elem_per_thread: int, dtype: str = "float32") -> Buffer:
|
|
"""Warpgroup-wide ``(128, elem_per_thread)`` register tile in local scope.
|
|
|
|
Sugar for the recurring pattern::
|
|
|
|
T.alloc_buffer(
|
|
(128, elem_per_thread), dtype,
|
|
layout=wg_local_layout(elem_per_thread),
|
|
scope="local",
|
|
)
|
|
|
|
Used to stage a tcgen05 load: each of the 128 threads in a warpgroup
|
|
owns one row of ``elem_per_thread`` contiguous elements.
|
|
"""
|
|
return alloc_buffer(
|
|
(128, elem_per_thread),
|
|
dtype,
|
|
layout=wg_local_layout(elem_per_thread),
|
|
scope="local",
|
|
)
|
|
|
|
|
|
def sblock_alloc_buffer(
|
|
shape: list[Expr] | tuple[Expr] | Expr | Integral,
|
|
dtype: str = "float32",
|
|
data: Var = None,
|
|
strides: list[Expr] | None = None,
|
|
elem_offset: Expr = None,
|
|
scope: str = "global",
|
|
align: int = -1,
|
|
offset_factor: int = 0,
|
|
buffer_type: str = "default",
|
|
axis_separators: list[int] | None = None,
|
|
layout: str | Layout | None = "default",
|
|
allocated_addr: int | tuple[int, ...] | None = None,
|
|
) -> Buffer:
|
|
"""SBlock-level buffer allocation function.
|
|
|
|
Parameters
|
|
----------
|
|
shape : Union[List[Expr], Tuple[Expr], Expr, Integral]
|
|
The type of the buffer prior to flattening.
|
|
dtype : str
|
|
The data type in the content of the buffer.
|
|
data : Var
|
|
The pointer to the head of the data.
|
|
strides : List[Expr]
|
|
The strides of each dimension.
|
|
elem_offset : Expr
|
|
The offset in terms of number of dtype elements (including lanes).
|
|
scope : str
|
|
The optional storage scope of buffer data pointer.
|
|
align : int
|
|
The alignment requirement of data pointer in bytes.
|
|
offset_factor : int
|
|
The factor of elem_offset field.
|
|
buffer_type : str
|
|
The buffer type.
|
|
axis_separators : List[int]
|
|
The separators between input axes when generating flattened output axes.
|
|
|
|
layout: Optional[Union[str, Layout]]
|
|
The layout of the buffer.
|
|
|
|
allocated_addr: Optional[Union[int, Tuple[int]]]
|
|
The address of the allocated buffer. Might be multi-dimensional.
|
|
There can be pooled storage scopes on some devices. For example,
|
|
the Trainium device has a pooled storage scope for the SRAN buffers. ("trn.sbuf")
|
|
CUDA has a pooled storage scope for the shared memory ("shared.dyn")
|
|
|
|
Returns
|
|
-------
|
|
res : Buffer
|
|
The allocated buffer.
|
|
"""
|
|
shape = (shape,) if is_prim_expr(shape) or isinstance(shape, Integral) else shape
|
|
if strides is not None:
|
|
strides = [Var(s, "int32") if isinstance(s, str) else s for s in strides]
|
|
else:
|
|
strides = []
|
|
if axis_separators is None:
|
|
axis_separators = []
|
|
if allocated_addr is None:
|
|
allocated_addr = []
|
|
if not isinstance(allocated_addr, list | tuple):
|
|
allocated_addr = [allocated_addr]
|
|
alloc_frame = _ffi_api.SBlockAllocBuffer( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
shape,
|
|
dtype,
|
|
data,
|
|
strides,
|
|
elem_offset,
|
|
scope,
|
|
align,
|
|
offset_factor,
|
|
buffer_type,
|
|
axis_separators,
|
|
_get_layout(layout, shape, scope),
|
|
allocated_addr,
|
|
)
|
|
if isinstance(alloc_frame, frame.AllocBufferFrame):
|
|
alloc_frame.add_callback(partial(alloc_frame.__exit__, None, None, None))
|
|
buf = alloc_frame.__enter__()
|
|
else:
|
|
buf = alloc_frame
|
|
_record_meta_resource(buf, skip_frames=2)
|
|
return buf
|
|
|
|
|
|
def _as_range(dom: ir.Range | list[Expr]) -> ir.Range:
|
|
"""The range constructor.
|
|
|
|
Parameters
|
|
----------
|
|
dom : Union[Range, List[Expr]]
|
|
The domain.
|
|
|
|
Returns
|
|
-------
|
|
res : Range
|
|
The Range.
|
|
"""
|
|
if isinstance(dom, ir.Range):
|
|
return dom
|
|
if isinstance(dom, list | tuple):
|
|
from tvm.arith import Analyzer # pylint: disable=import-outside-toplevel
|
|
|
|
extent = Analyzer().simplify(dom[1] - dom[0])
|
|
if isinstance(extent, tir.IntImm):
|
|
return ir.Range.from_min_extent(dom[0], extent)
|
|
return ir.Range(dom[0], dom[1])
|
|
if is_prim_expr(dom):
|
|
return ir.Range(IntImm(dom.ty, 0), dom)
|
|
return ir.Range(0, dom)
|
|
|
|
|
|
class axis: # pylint: disable=invalid-name
|
|
"""The axis class"""
|
|
|
|
@staticmethod
|
|
def spatial(
|
|
dom: ir.Range | list[Expr] | tuple[Expr],
|
|
binding: Expr,
|
|
dtype: str = "int32",
|
|
) -> Var:
|
|
"""The spatial block axis defining function.
|
|
|
|
Parameters
|
|
----------
|
|
dom : Union[Range, List[Expr], Tuple[Expr]]
|
|
The domain of the iteration variable.
|
|
|
|
binding : Expr
|
|
The binding value of the iteration variable.
|
|
|
|
dtype : str
|
|
The data type of the iteration variable.
|
|
|
|
Returns
|
|
-------
|
|
res : Var
|
|
The iteration variable.
|
|
"""
|
|
return _ffi_api.AxisSpatial( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
_as_range(dom), binding, dtype
|
|
)
|
|
|
|
@staticmethod
|
|
def reduce(
|
|
dom: ir.Range | list[Expr] | tuple[Expr],
|
|
binding: Expr,
|
|
dtype: str = "int32",
|
|
) -> Var:
|
|
"""The reduced block axis defining function.
|
|
|
|
Parameters
|
|
----------
|
|
dom : Union[Range, List[Expr], Tuple[Expr]]
|
|
The domain of the iteration variable.
|
|
|
|
binding : Expr
|
|
The binding value of the iteration variable.
|
|
|
|
dtype : str
|
|
The data type of the iteration variable.
|
|
|
|
Returns
|
|
-------
|
|
res : Var
|
|
The iteration variable.
|
|
"""
|
|
return _ffi_api.AxisReduce( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
_as_range(dom), binding, dtype
|
|
)
|
|
|
|
@staticmethod
|
|
def scan(
|
|
dom: ir.Range | list[Expr] | tuple[Expr],
|
|
binding: Expr,
|
|
dtype: str = "int32",
|
|
) -> Var:
|
|
"""The scanning block axis defining function.
|
|
|
|
Parameters
|
|
----------
|
|
dom : Union[Range, List[Expr], Tuple[Expr]]
|
|
The domain of the iteration variable.
|
|
|
|
binding : Expr
|
|
The binding value of the iteration variable.
|
|
|
|
dtype : str
|
|
The data type of the iteration variable.
|
|
|
|
Returns
|
|
-------
|
|
res : Var
|
|
The iteration variable.
|
|
"""
|
|
return _ffi_api.AxisScan( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
_as_range(dom), binding, dtype
|
|
)
|
|
|
|
@staticmethod
|
|
def opaque(
|
|
dom: ir.Range | list[Expr] | tuple[Expr],
|
|
binding: Expr,
|
|
dtype: str = "int32",
|
|
) -> Var:
|
|
"""The opaque block axis defining function.
|
|
|
|
Parameters
|
|
----------
|
|
dom : Union[Range, List[Expr], Tuple[Expr]]
|
|
The domain of the iteration variable.
|
|
|
|
binding : Expr
|
|
The binding value of the iteration variable.
|
|
|
|
dtype : str
|
|
The data type of the iteration variable.
|
|
|
|
Returns
|
|
-------
|
|
res : Var
|
|
The iteration variable.
|
|
"""
|
|
return _ffi_api.AxisOpaque( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
_as_range(dom), binding, dtype
|
|
)
|
|
|
|
@staticmethod
|
|
def remap(kinds: str, bindings: list[Expr], dtype: str = "int32") -> list[Var] | Var:
|
|
"""The block axis remapping function.
|
|
|
|
Parameters
|
|
----------
|
|
kinds : str
|
|
The types of the iteration variables.
|
|
|
|
bindings : List[Expr]
|
|
The binding values of the iteration variables.
|
|
|
|
dtype : str
|
|
The data types of the iteration variables.
|
|
|
|
Returns
|
|
-------
|
|
res : Var
|
|
The iteration variables.
|
|
"""
|
|
iter_vars = _ffi_api.AxisRemap( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
kinds, bindings, dtype
|
|
)
|
|
return iter_vars[0] if len(iter_vars) == 1 else iter_vars
|
|
|
|
S = spatial # pylint: disable=invalid-name
|
|
R = reduce # pylint: disable=invalid-name
|
|
|
|
|
|
def serial(
|
|
start: Expr,
|
|
stop: Expr = None,
|
|
*,
|
|
annotations: dict[str, Any] | None = None,
|
|
step: Expr | None = None,
|
|
unroll: bool | None = None,
|
|
) -> frame.ForFrame:
|
|
"""The serial For statement.
|
|
|
|
Parameters
|
|
----------
|
|
start : Expr
|
|
The minimum value of iteration.
|
|
|
|
stop : Expr
|
|
The maximum value of iteration.
|
|
|
|
annotations : Dict[str, Any]
|
|
The optional annotations of the For statement.
|
|
|
|
step : Expr
|
|
The optional step value of iteration.
|
|
|
|
unroll : bool, optional
|
|
If True, adds ``{"pragma_unroll": True}`` annotation, which asks CUDA codegen
|
|
to emit ``#pragma unroll`` while preserving the loop as a C++ ``for``.
|
|
If False, adds ``{"disable_unroll": True}`` annotation.
|
|
Shorthand for ``annotations={"disable_unroll": True}``.
|
|
|
|
Returns
|
|
-------
|
|
res : frame.ForFrame
|
|
The ForFrame.
|
|
"""
|
|
if unroll is not None:
|
|
annotations = dict(annotations) if annotations else {}
|
|
if unroll:
|
|
annotations["pragma_unroll"] = True
|
|
else:
|
|
annotations["disable_unroll"] = True
|
|
if stop is None:
|
|
stop = start
|
|
if is_prim_expr(start):
|
|
start = IntImm(start.ty, 0)
|
|
else:
|
|
start = 0
|
|
return _ffi_api.Serial(start, stop, annotations, step) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def parallel(
|
|
start: Expr,
|
|
stop: Expr = None,
|
|
*,
|
|
annotations: dict[str, Any] | None = None,
|
|
step: Expr | None = None,
|
|
) -> frame.ForFrame:
|
|
"""The parallel For statement.
|
|
|
|
Parameters
|
|
----------
|
|
start : Expr
|
|
The minimum value of iteration.
|
|
|
|
stop : Expr
|
|
The maximum value of iteration.
|
|
|
|
annotations : Dict[str, Any]
|
|
The optional annotations of the For statement.
|
|
|
|
step : Expr
|
|
The optional step value of iteration.
|
|
|
|
Returns
|
|
-------
|
|
res : frame.ForFrame
|
|
The ForFrame.
|
|
"""
|
|
if stop is None:
|
|
stop = start
|
|
if is_prim_expr(start):
|
|
start = IntImm(start.ty, 0)
|
|
else:
|
|
start = 0
|
|
return _ffi_api.Parallel(start, stop, annotations, step) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def vectorized(
|
|
start: Expr,
|
|
stop: Expr = None,
|
|
*,
|
|
annotations: dict[str, Any] | None = None,
|
|
step: Expr | None = None,
|
|
) -> frame.ForFrame:
|
|
"""The vectorized For statement.
|
|
|
|
Parameters
|
|
----------
|
|
start : Expr
|
|
The minimum value of iteration.
|
|
|
|
stop : Expr
|
|
The maximum value of iteration.
|
|
|
|
annotations : Dict[str, Any]
|
|
The optional annotations of the For statement.
|
|
|
|
step : Expr
|
|
The optional step value of iteration.
|
|
|
|
Returns
|
|
-------
|
|
res : frame.ForFrame
|
|
The ForFrame.
|
|
"""
|
|
if stop is None:
|
|
stop = start
|
|
if is_prim_expr(start):
|
|
start = IntImm(start.ty, 0)
|
|
else:
|
|
start = 0
|
|
return _ffi_api.Vectorized(start, stop, annotations, step) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def unroll(
|
|
start: Expr,
|
|
stop: Expr = None,
|
|
*,
|
|
annotations: dict[str, Any] | None = None,
|
|
step: Expr | None = None,
|
|
) -> frame.ForFrame:
|
|
"""The unrolled For statement.
|
|
|
|
Parameters
|
|
----------
|
|
start : Expr
|
|
The minimum value of iteration.
|
|
|
|
stop : Expr
|
|
The maximum value of iteration.
|
|
|
|
annotations : Dict[str, Any]
|
|
The optional annotations of the For statement.
|
|
|
|
step : Expr
|
|
The optional step value of iteration.
|
|
|
|
Returns
|
|
-------
|
|
res : frame.ForFrame
|
|
The ForFrame.
|
|
"""
|
|
if stop is None:
|
|
stop = start
|
|
if is_prim_expr(start):
|
|
start = IntImm(start.ty, 0)
|
|
else:
|
|
start = 0
|
|
return _ffi_api.Unroll(start, stop, annotations, step) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def thread_binding(
|
|
start: Expr,
|
|
stop: Expr = None,
|
|
thread: str | None = None,
|
|
*,
|
|
annotations: dict[str, Any] | None = None,
|
|
) -> frame.ForFrame:
|
|
"""The thread-binding For statement.
|
|
|
|
Parameters
|
|
----------
|
|
start : Expr
|
|
The minimum value of iteration.
|
|
|
|
stop : Expr
|
|
The maximum value of iteration.
|
|
|
|
thread : str
|
|
The thread for loop variable to bind.
|
|
|
|
annotations : Dict[str, Any]
|
|
The optional annotations of the For statement.
|
|
|
|
Returns
|
|
-------
|
|
res : frame.ForFrame
|
|
The ForFrame.
|
|
"""
|
|
if thread is None:
|
|
if not isinstance(stop, str):
|
|
raise ValueError("Thread cannot be None for thread_binding")
|
|
thread = stop
|
|
stop = start
|
|
if is_prim_expr(start):
|
|
start = IntImm(start.ty, 0)
|
|
else:
|
|
start = 0
|
|
elif stop is None:
|
|
stop = start
|
|
if is_prim_expr(start):
|
|
start = IntImm(start.ty, 0)
|
|
else:
|
|
start = 0
|
|
return _ffi_api.ThreadBinding( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
start, stop, thread, annotations
|
|
)
|
|
|
|
|
|
def grid(*extents: tuple[Expr | tuple[Expr, Expr]]) -> frame.ForFrame:
|
|
"""The grid For statement.
|
|
|
|
Parameters
|
|
----------
|
|
extents : Tuple[Union[Expr, Tuple[Expr, Expr]]]
|
|
If a single Expr is provided, it is used as the extent of the iteration.
|
|
If a tuple of two Expr is provided, the first is the start of the iteration,
|
|
and the second is the extent of the iteration.
|
|
|
|
Returns
|
|
-------
|
|
res : frame.ForFrame
|
|
The ForFrame.
|
|
"""
|
|
# Convert integer extents to IntImm
|
|
# TODO(@bohan): fix this after FFI refactor
|
|
processed_extents = []
|
|
for extent in extents:
|
|
if isinstance(extent, tuple):
|
|
start, extent = extent
|
|
start = IntImm("int32", start) if isinstance(start, int) else start
|
|
extent = IntImm("int32", extent) if isinstance(extent, int) else extent
|
|
processed_extents.append((start, extent))
|
|
else:
|
|
processed_extents.append(IntImm("int32", extent) if isinstance(extent, int) else extent)
|
|
extents = tuple(processed_extents)
|
|
return _ffi_api.Grid(extents) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def Assert(condition: Expr, message, error_kind: str = "RuntimeError") -> frame.AssertFrame: # pylint: disable=invalid-name
|
|
"""Create an assertion statement.
|
|
|
|
Parameters
|
|
----------
|
|
condition : Expr
|
|
The Expr to test.
|
|
|
|
message : str or list[str]
|
|
The error message when the assertion fails. Can be a single string
|
|
or a list of string parts (fragments stored separately in the IR
|
|
for binary size reduction through string reuse).
|
|
|
|
error_kind : str
|
|
The error kind (e.g. "RuntimeError", "TypeError", "ValueError").
|
|
|
|
Returns
|
|
-------
|
|
res : frame.AssertFrame
|
|
The result AssertFrame.
|
|
"""
|
|
if isinstance(condition, bool):
|
|
condition = IntImm("bool", condition)
|
|
if not isinstance(message, list | tuple):
|
|
message = [message]
|
|
return _ffi_api.Assert(condition, error_kind, message) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def Bind( # pylint: disable=invalid-name
|
|
value: Expr,
|
|
type_annotation: Type | None = None, # pylint: disable=redefined-outer-name
|
|
*,
|
|
var: Var | None = None, # pylint: disable=redefined-outer-name
|
|
) -> Var:
|
|
"""Create a Bind (variable binding).
|
|
|
|
Emits a flat Bind statement to the current frame and returns the bound variable.
|
|
|
|
Parameters
|
|
----------
|
|
value : Expr
|
|
The value to be bound.
|
|
type_annotation : Optional[Type] = None
|
|
The type annotation of the binding. Usually it is used for fine-grained var typing,
|
|
particularly, PointerType.
|
|
var : Optional[Var] = None
|
|
The variable to bind. If not specified, a new variable will be created.
|
|
|
|
Returns
|
|
-------
|
|
var : Var
|
|
The bound variable.
|
|
"""
|
|
if type_annotation is not None:
|
|
if callable(type_annotation):
|
|
type_annotation = type_annotation()
|
|
if isinstance(type_annotation, Var):
|
|
type_annotation = type_annotation.ty
|
|
return _ffi_api.Bind(value, type_annotation, var) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def Let( # pylint: disable=invalid-name
|
|
expr: Expr,
|
|
where: dict[Var, Expr], # pylint: disable=redefined-outer-name
|
|
) -> Expr:
|
|
"""Create a Let expression binding"""
|
|
assert len(where) == 1, "T.Let only allows `where` to have exactly one element"
|
|
var, value = next(iter(where.items())) # pylint: disable=redefined-outer-name
|
|
return tir.Let(var, value, expr)
|
|
|
|
|
|
bind = Bind
|
|
|
|
|
|
class LetAnnotation:
|
|
"""Marker for explicit LetStmt. Created by T.let or T.let[type].
|
|
Usage in TVMScript:
|
|
x: T.let[T.int32] = expr # LetStmt with explicit type
|
|
x: T.let = expr # LetStmt with auto-typed RHS
|
|
"""
|
|
|
|
def __init__(self, type_spec=None):
|
|
self.type_spec = type_spec
|
|
|
|
def __class_getitem__(cls, item):
|
|
return LetAnnotation(item)
|
|
|
|
def __getitem__(self, item):
|
|
return LetAnnotation(item)
|
|
|
|
def as_var(self, rhs_dtype=None):
|
|
"""Resolve to a tir.Var."""
|
|
if self.type_spec is not None:
|
|
if isinstance(self.type_spec, Var):
|
|
return self.type_spec # Already a Var (e.g. T.handle(...))
|
|
elif callable(self.type_spec):
|
|
return self.type_spec() # e.g. T.int32() -> Var
|
|
elif isinstance(self.type_spec, Type):
|
|
return Var("", self.type_spec)
|
|
else:
|
|
raise TypeError(f"Invalid type for T.let: {self.type_spec}")
|
|
elif rhs_dtype is not None:
|
|
rhs_ty = rhs_dtype if isinstance(rhs_dtype, Type) else ir.PrimType(rhs_dtype)
|
|
return Var("", rhs_ty)
|
|
else:
|
|
raise TypeError("T.let requires either a type or an RHS value")
|
|
|
|
|
|
let = LetAnnotation() # Singleton for T.let (no subscript)
|
|
|
|
|
|
class LocalVectorAnnotation:
|
|
"""Marker for local vector/tensor allocation via type annotation subscript.
|
|
|
|
Created when a DtypeConstructor is subscripted, e.g. ``T.float32[N]`` or
|
|
``T.float32[M, N]``. The parser's ``visit_ann_assign`` recognises this
|
|
object and lowers it to ``T.alloc_local(shape=..., dtype=...)``.
|
|
"""
|
|
|
|
__slots__ = ("dtype", "shape")
|
|
|
|
def __init__(self, dtype: str, shape: tuple):
|
|
self.dtype = dtype
|
|
self.shape = shape
|
|
|
|
|
|
class DtypeConstructor:
|
|
"""Callable + subscriptable dtype object.
|
|
|
|
Replaces the plain functions previously returned by ``func_gen``.
|
|
|
|
* ``T.float32()`` — same FFI call as before (returns ``Var``).
|
|
* ``T.float32[N]`` — returns ``LocalVectorAnnotation("float32", (N,))``.
|
|
* ``T.float32[M, N]`` — returns ``LocalVectorAnnotation("float32", (M, N))``.
|
|
* ``x: T.float32`` — parser calls this object, gets a ``Var``.
|
|
"""
|
|
|
|
def __init__(self, ffi_name: str, dtype_str: str):
|
|
self._ffi_name = ffi_name
|
|
self._dtype_str = dtype_str
|
|
|
|
def __call__(
|
|
self,
|
|
expr: "None | Expr | Literal['inf', '-inf', 'nan'] | int | float" = None,
|
|
) -> "Expr":
|
|
if isinstance(expr, str):
|
|
expr = float(expr)
|
|
return getattr(_ffi_api, self._ffi_name)(expr)
|
|
|
|
def __getitem__(self, shape):
|
|
if isinstance(shape, tuple):
|
|
return LocalVectorAnnotation(self._dtype_str, shape)
|
|
return LocalVectorAnnotation(self._dtype_str, (shape,))
|
|
|
|
def __repr__(self):
|
|
return f"DtypeConstructor({self._dtype_str!r})"
|
|
|
|
|
|
def allocate(
|
|
extents: list[Expr],
|
|
dtype: str,
|
|
scope: str = "global",
|
|
condition: Expr = None,
|
|
annotations=None,
|
|
) -> frame.AllocateFrame:
|
|
"""Allocate node.
|
|
|
|
Parameters
|
|
----------
|
|
extents : List[Expr]
|
|
The extents of the allocate.
|
|
|
|
dtype : str
|
|
The data type of the buffer.
|
|
|
|
scope : str
|
|
The storage scope.
|
|
|
|
condition : Expr
|
|
The condition.
|
|
|
|
annotations: Optional[Mapping[str, Object]]
|
|
Additional annotation hints.
|
|
"""
|
|
if isinstance(condition, bool):
|
|
condition = IntImm("bool", condition)
|
|
return _ffi_api.Allocate( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
extents, dtype, scope, condition, annotations
|
|
)
|
|
|
|
|
|
def attr(
|
|
node_or_dict: Any, attr_key: str | None = None, value: Expr | str | None = None
|
|
) -> Union[frame.AttrFrame, "utils._FrameScope"]:
|
|
"""Create an attribute node, or multiple attribute nodes from a dict.
|
|
|
|
Usage 1 — single attr::
|
|
|
|
with T.attr(node, key, value):
|
|
...
|
|
|
|
Usage 2 — dict sugar (node defaults to ``T.int32(0)``)::
|
|
|
|
with T.attr({"key1": value1, "key2": value2}):
|
|
...
|
|
|
|
Parameters
|
|
----------
|
|
node_or_dict : Any
|
|
If a dict, each key-value pair becomes an AttrStmt with
|
|
``node=T.int32(0)``. Otherwise the node to annotate.
|
|
|
|
attr_key : str, optional
|
|
Attribute type key (required when ``node_or_dict`` is not a dict).
|
|
|
|
value : Union[Expr, str], optional
|
|
The attribute value (required when ``node_or_dict`` is not a dict).
|
|
|
|
Returns
|
|
-------
|
|
res : Union[frame.AttrFrame, _FrameScope]
|
|
A single AttrFrame, or a _FrameScope wrapping multiple AttrFrames.
|
|
"""
|
|
if isinstance(node_or_dict, dict):
|
|
frames = []
|
|
for k, v in node_or_dict.items():
|
|
if isinstance(v, bool):
|
|
v = IntImm("bool", v)
|
|
frames.append(
|
|
_ffi_api.Attr( # type: ignore[attr-defined]
|
|
convert(IntImm("int32", 0)), k, convert(v)
|
|
)
|
|
)
|
|
if len(frames) == 1:
|
|
return frames[0]
|
|
return utils._FrameScope(frames)
|
|
else:
|
|
if attr_key is None or value is None:
|
|
raise ValueError("T.attr(node, attr_key, value) requires all three arguments")
|
|
node_or_dict = convert(node_or_dict)
|
|
value = convert(value)
|
|
return _ffi_api.Attr(node_or_dict, attr_key, value) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def hint(message: str = "", **attrs) -> frame.HintFrame:
|
|
"""Universal directive primitive for the sketch language.
|
|
|
|
Parameters
|
|
----------
|
|
message : str
|
|
Free-form directive string that the agent interprets.
|
|
**attrs
|
|
Optional structured key-value attributes for known patterns.
|
|
|
|
Returns
|
|
-------
|
|
res : frame.HintFrame
|
|
Usable as context manager (with T.hint("msg"):) or bare statement (T.hint("msg")).
|
|
"""
|
|
return _ffi_api.Hint(message, attrs or {}) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def While(condition: Expr) -> frame.WhileFrame: # pylint: disable=invalid-name
|
|
"""Create a while node.
|
|
|
|
Parameters
|
|
----------
|
|
condition : Expr
|
|
The termination condition of the loop.
|
|
|
|
Returns
|
|
-------
|
|
res : frame.WhileFrame
|
|
The result WhileFrame.
|
|
"""
|
|
if isinstance(condition, bool):
|
|
condition = IntImm("bool", condition)
|
|
return _ffi_api.While(condition) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def Break() -> None: # pylint: disable=invalid-name
|
|
"""Create a break node."""
|
|
return _ffi_api.Break() # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def Continue() -> None: # pylint: disable=invalid-name
|
|
"""Create a continue node."""
|
|
return _ffi_api.Continue() # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def If(condition: Expr) -> frame.IfFrame: # pylint: disable=invalid-name
|
|
"""Create an if node.
|
|
|
|
Parameters
|
|
----------
|
|
condition : Expr
|
|
The condition of if statement, executes the true branch if the condition is true,
|
|
otherwise jump into the false branch.
|
|
|
|
Returns
|
|
-------
|
|
res : frame.IfFrame
|
|
The result IfFrame.
|
|
"""
|
|
if isinstance(condition, bool):
|
|
condition = IntImm("bool", condition)
|
|
return _ffi_api.If(condition) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def Then() -> frame.ThenFrame: # pylint: disable=invalid-name
|
|
"""Create a then.
|
|
|
|
Returns
|
|
-------
|
|
res : frame.ThenFrame
|
|
The result ThenFrame.
|
|
"""
|
|
return _ffi_api.Then() # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def Else() -> frame.ElseFrame: # pylint: disable=invalid-name
|
|
"""Create an else.
|
|
|
|
Returns
|
|
-------
|
|
res : frame.ElseFrame
|
|
The result ElseFrame.
|
|
"""
|
|
return _ffi_api.Else() # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def decl_buffer(
|
|
shape,
|
|
dtype="float32",
|
|
data=None,
|
|
strides=None,
|
|
elem_offset=None,
|
|
byte_offset=None,
|
|
scope="global",
|
|
align=0,
|
|
offset_factor=0,
|
|
buffer_type="",
|
|
axis_separators=None,
|
|
layout="default",
|
|
allocated_addr=None,
|
|
) -> Buffer:
|
|
"""Create a buffer declaration node.
|
|
|
|
When ``data`` is provided, creates a DeclBuffer (alias to existing data).
|
|
When ``data`` is None, creates an AllocBuffer (new allocation).
|
|
|
|
Parameters
|
|
----------
|
|
shape : Union[List[Expr], Tuple[Expr], Expr, Integral]
|
|
The type of the buffer prior to flattening.
|
|
|
|
dtype : str
|
|
The data type in the content of the buffer.
|
|
|
|
data : Var
|
|
The pointer to the head of the data.
|
|
|
|
strides : List[Expr]
|
|
The strides of each dimension.
|
|
|
|
elem_offset : Expr
|
|
The offset in terms of number of dtype elements (including lanes).
|
|
|
|
byte_offset : Expr
|
|
The offset in terms of number of bytes.
|
|
|
|
scope : str
|
|
The optional storage scope of buffer data pointer.
|
|
|
|
align : int
|
|
The alignment requirement of data pointer in bytes.
|
|
|
|
offset_factor : int
|
|
The factor of elem_offset field.
|
|
|
|
buffer_type : str
|
|
The buffer type.
|
|
|
|
axis_separators : List[int]
|
|
The separators between input axes when generating flattened output axes.
|
|
|
|
layout : Layout
|
|
The layout of the buffer.
|
|
|
|
Returns
|
|
-------
|
|
res : Buffer
|
|
The declared buffer.
|
|
"""
|
|
shape = (shape,) if is_prim_expr(shape) or isinstance(shape, Integral) else shape
|
|
if strides is not None:
|
|
strides = [Var(s, "int32") if isinstance(s, str) else s for s in strides]
|
|
else:
|
|
strides = []
|
|
dtype = _normalize_prim_type(dtype)
|
|
decl_frame = _ffi_api.DeclBuffer( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
shape,
|
|
dtype,
|
|
"",
|
|
data,
|
|
strides,
|
|
_get_elem_offset(elem_offset, byte_offset, dtype),
|
|
scope,
|
|
align,
|
|
offset_factor,
|
|
buffer_type,
|
|
axis_separators,
|
|
_get_layout(layout, shape, scope),
|
|
allocated_addr,
|
|
)
|
|
if isinstance(decl_frame, frame.DeclBufferFrame):
|
|
decl_frame.add_callback(partial(decl_frame.__exit__, None, None, None))
|
|
buf = decl_frame.__enter__()
|
|
else:
|
|
buf = decl_frame
|
|
_record_meta_resource(buf, skip_frames=2)
|
|
return buf
|
|
|
|
|
|
alloc_shared = functools.partial(alloc_buffer, scope="shared")
|
|
alloc_local = functools.partial(alloc_buffer, scope="local")
|
|
smem = alloc_shared
|
|
tmem = functools.partial(alloc_buffer, scope="tmem")
|
|
|
|
|
|
def alloc_tcgen05_ldst_frag(instr_shape, tensor_shape, dtype):
|
|
"""Allocate a register fragment for ``tcgen05.{ld,st}`` atoms.
|
|
|
|
Sizes the per-thread storage, allocates ``local`` scope memory, and returns
|
|
a 2-D view of shape ``tensor_shape`` with a matching ``tcgen05_atom_layout``.
|
|
Pass the result to ``Tx.copy_async`` (with a ``(128, W)``-shaped TMEM
|
|
buffer) to trigger the corresponding dispatch path.
|
|
|
|
Parameters
|
|
----------
|
|
instr_shape : str
|
|
``"32x32b"`` (M=128 fragment, 128 row warpgroup tile, layout
|
|
``(128, K):(1@tid_in_wg, 1)``); or ``"16x64b"`` / ``"16x128b"`` /
|
|
``"16x256b"`` (M=64 fragments, 64 row warpgroup tile with the
|
|
per-shape per-lane register decomposition).
|
|
tensor_shape : tuple[int, int]
|
|
Logical fragment shape ``(frag_rows, K)`` in element units. ``frag_rows``
|
|
is ``128`` for ``.32x32b`` and ``64`` for the ``.16x*b`` shapes.
|
|
dtype : str
|
|
``"float32"``, ``"float16"``, or ``"bfloat16"``.
|
|
|
|
Returns
|
|
-------
|
|
Buffer
|
|
2-D view of shape ``tensor_shape`` whose layout matches
|
|
``tcgen05_atom_layout(instr_shape, tensor_shape, dtype)``.
|
|
|
|
Examples
|
|
--------
|
|
M=128 readback (existing dispatch):
|
|
``frag = T.alloc_tcgen05_ldst_frag("32x32b", (128, 64), "float32")``
|
|
``Tx.copy_async(frag[:, :], tmem[:, 0:64])``
|
|
|
|
M=64 readback (.16x64b dispatch):
|
|
``frag = T.alloc_tcgen05_ldst_frag("16x64b", (64, 64), "float32")``
|
|
``Tx.copy_async(frag[:, :], tmem[0:64, 0:64])``
|
|
"""
|
|
from tvm.tirx.layout import tcgen05_atom_layout # local import to avoid cycle
|
|
|
|
rows, cols = tensor_shape
|
|
bits = DataType(dtype).bits
|
|
# Per-warpgroup total bits = 64 rows x K cols x bits. Divided across 128
|
|
# threads gives per-thread bits; convert to element count.
|
|
per_thread_bits = (rows * cols * bits) // 128
|
|
if per_thread_bits % bits != 0:
|
|
raise ValueError(
|
|
f"alloc_tcgen05_ldst_frag tensor_shape={tensor_shape} dtype={dtype!r} "
|
|
f"does not evenly divide across 128 threads"
|
|
)
|
|
per_thread_elems = per_thread_bits // bits
|
|
|
|
layout = tcgen05_atom_layout(instr_shape, tensor_shape, dtype)
|
|
flat = alloc_local((per_thread_elems,), dtype)
|
|
return flat.view(rows, cols, layout=layout)
|
|
|
|
|
|
def alloc_cast_frag(src, dtype):
|
|
"""Allocate a register frag holding ``src`` value-cast to ``dtype``.
|
|
|
|
Inherits ``src``'s logical shape and its ``(lane, register)`` layout — only
|
|
the element dtype changes — so ``Tx.cast(dst, src)`` is a per-thread
|
|
element-wise cast with no cross-lane movement. ``.permute(...)`` the result
|
|
to the axis order a downstream consumer (e.g. ``stmatrix`` via
|
|
``Tx.copy(dispatch="ldstmatrix")``) expects.
|
|
|
|
Parameters
|
|
----------
|
|
src : Buffer
|
|
Source register frag (e.g. from ``alloc_tcgen05_ldst_frag``).
|
|
dtype : str
|
|
Destination element dtype.
|
|
|
|
Returns
|
|
-------
|
|
Buffer
|
|
Fresh ``local`` frag, ``src.shape`` shaped, ``src.layout``, dtype-cast.
|
|
"""
|
|
rows, cols = src.shape
|
|
per_thread_elems = (rows * cols) // 128
|
|
flat = alloc_local((per_thread_elems,), dtype)
|
|
return flat.view(rows, cols, layout=src.layout)
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
ScalarT = TypeVar("ScalarT")
|
|
|
|
# Keep type checking/linting simple by treating wrapper as identity.
|
|
def scalar_wrapper(x: ScalarT) -> ScalarT:
|
|
return x
|
|
|
|
else:
|
|
|
|
class scalar_wrapper:
|
|
"""Internal wrapper to allow IRBuilder auto-naming on scalar assignment."""
|
|
|
|
def __init__(self, scalar: BufferLoad):
|
|
assert isinstance(scalar, BufferLoad)
|
|
self.scalar = scalar
|
|
|
|
def __getattr__(self, name: str) -> Any:
|
|
return getattr(self.scalar, name)
|
|
|
|
def __add__(self, other):
|
|
return self.scalar + other
|
|
|
|
def __radd__(self, other):
|
|
return other + self.scalar
|
|
|
|
def __sub__(self, other):
|
|
return self.scalar - other
|
|
|
|
def __rsub__(self, other):
|
|
return other - self.scalar
|
|
|
|
def __mul__(self, other):
|
|
return self.scalar * other
|
|
|
|
def __rmul__(self, other):
|
|
return other * self.scalar
|
|
|
|
def __truediv__(self, other):
|
|
return self.scalar / other
|
|
|
|
def __rtruediv__(self, other):
|
|
return other / self.scalar
|
|
|
|
def __floordiv__(self, other):
|
|
return self.scalar // other
|
|
|
|
def __rfloordiv__(self, other):
|
|
return other // self.scalar
|
|
|
|
def __mod__(self, other):
|
|
return self.scalar % other
|
|
|
|
def __rmod__(self, other):
|
|
return other % self.scalar
|
|
|
|
def __lt__(self, other):
|
|
return self.scalar < other
|
|
|
|
def __le__(self, other):
|
|
return self.scalar <= other
|
|
|
|
def __gt__(self, other):
|
|
return self.scalar > other
|
|
|
|
def __ge__(self, other):
|
|
return self.scalar >= other
|
|
|
|
def __eq__(self, other):
|
|
return self.scalar == other
|
|
|
|
def __ne__(self, other):
|
|
return self.scalar != other
|
|
|
|
def __and__(self, other):
|
|
return self.scalar & other
|
|
|
|
def __rand__(self, other):
|
|
return other & self.scalar
|
|
|
|
def __or__(self, other):
|
|
return self.scalar | other
|
|
|
|
def __ror__(self, other):
|
|
return other | self.scalar
|
|
|
|
def __xor__(self, other):
|
|
return self.scalar ^ other
|
|
|
|
def __rxor__(self, other):
|
|
return other ^ self.scalar
|
|
|
|
def __neg__(self):
|
|
return -self.scalar
|
|
|
|
def __invert__(self):
|
|
return ~self.scalar
|
|
|
|
|
|
def alloc_scalar(dtype: str = "float32", scope: str = "global") -> BufferLoad:
|
|
"""Allocate a zero-dimensional buffer (scalar)."""
|
|
buf = alloc_buffer(shape=(1,), dtype=dtype, scope=scope, layout=TileLayout(S[1]))
|
|
assert isinstance(buf, Buffer)
|
|
scalar = buf[0]
|
|
if _current_meta_construction_scope() is not None:
|
|
return scalar
|
|
return scalar_wrapper(scalar)
|
|
|
|
|
|
def decl_scalar(dtype, data, scope, elem_offset=None, byte_offset=None) -> BufferLoad:
|
|
"""Declare a zero-dimensional buffer (scalar) from a pointer."""
|
|
buf = decl_buffer(
|
|
shape=(1,),
|
|
dtype=dtype,
|
|
data=data,
|
|
scope=scope,
|
|
elem_offset=_get_elem_offset(elem_offset, byte_offset, dtype),
|
|
strides=None,
|
|
align=-1,
|
|
offset_factor=0,
|
|
buffer_type="default",
|
|
axis_separators=None,
|
|
layout=TileLayout(S[1]),
|
|
)
|
|
assert isinstance(buf, Buffer)
|
|
scalar = buf[0]
|
|
if _current_meta_construction_scope() is not None:
|
|
return scalar
|
|
return scalar_wrapper(scalar)
|
|
|
|
|
|
def shared_scalar(dtype: str = "float32") -> BufferLoad:
|
|
"""Allocate a zero-dimensional buffer in shared memory."""
|
|
return alloc_scalar(dtype=dtype, scope="shared")
|
|
|
|
|
|
def local_scalar(dtype: str = "float32") -> BufferLoad:
|
|
"""Allocate a zero-dimensional buffer in local memory."""
|
|
return alloc_scalar(dtype=dtype, scope="local")
|
|
|
|
|
|
def _is_meta_class_instance(value: Any) -> bool:
|
|
return getattr(type(value), "_is_meta_class", False)
|
|
|
|
|
|
def _sanitize_meta_name_part(value: Any, fallback: str) -> str:
|
|
if isinstance(value, str) and value.isidentifier():
|
|
return value
|
|
if isinstance(value, str):
|
|
sanitized = "".join(c if c.isalnum() or c == "_" else "_" for c in value)
|
|
if sanitized and sanitized[0].isalpha():
|
|
return sanitized
|
|
return fallback
|
|
|
|
|
|
def _meta_resource_for_value(value: Any) -> Any | None:
|
|
if isinstance(value, scalar_wrapper):
|
|
return value.scalar.buffer
|
|
if isinstance(value, BufferLoad):
|
|
return value.buffer
|
|
if isinstance(value, Buffer):
|
|
return value
|
|
return None
|
|
|
|
|
|
def _resource_in(resource: Any, resources: list[Any]) -> bool:
|
|
return any(_same_meta_resource(resource, other) for other in resources)
|
|
|
|
|
|
def _name_meta_value(
|
|
prefix: str,
|
|
value: Any,
|
|
visited: set[int] | None = None,
|
|
owned_resources: list[Any] | None = None,
|
|
named_resources: list[Any] | None = None,
|
|
) -> None:
|
|
if visited is None:
|
|
visited = set()
|
|
if named_resources is None:
|
|
named_resources = []
|
|
obj_id = id(value)
|
|
if obj_id in visited:
|
|
return
|
|
visited.add(obj_id)
|
|
|
|
resource = _meta_resource_for_value(value)
|
|
if resource is not None:
|
|
if owned_resources is not None and not _resource_in(resource, owned_resources):
|
|
return
|
|
if _resource_in(resource, named_resources):
|
|
return
|
|
IRBuilder.name(prefix, resource)
|
|
named_resources.append(resource)
|
|
return
|
|
if isinstance(value, Var | IterVar):
|
|
if owned_resources is not None:
|
|
return
|
|
IRBuilder.name(prefix, value)
|
|
return
|
|
if _is_meta_class_instance(value):
|
|
existing_prefix = getattr(value, "_tirx_meta_name", None)
|
|
if existing_prefix is not None and existing_prefix != prefix:
|
|
return
|
|
object.__setattr__(value, "_tirx_meta_name", prefix)
|
|
instance_owned_resources = getattr(value, "_tirx_meta_owned_resources", [])
|
|
for field_name, field_value in vars(value).items():
|
|
if field_name.startswith("_tirx_"):
|
|
continue
|
|
_name_meta_value(
|
|
f"{prefix}_{field_name}",
|
|
field_value,
|
|
visited,
|
|
instance_owned_resources,
|
|
named_resources,
|
|
)
|
|
return
|
|
if isinstance(value, list | tuple):
|
|
for i, item in enumerate(value):
|
|
_name_meta_value(f"{prefix}_{i}", item, visited, owned_resources, named_resources)
|
|
return
|
|
if isinstance(value, dict):
|
|
for i, (key, item) in enumerate(value.items()):
|
|
part = _sanitize_meta_name_part(key, f"item{i}")
|
|
_name_meta_value(f"{prefix}_{part}", item, visited, owned_resources, named_resources)
|
|
|
|
|
|
def _same_meta_resource(lhs: Any, rhs: Any) -> bool:
|
|
same_as = getattr(lhs, "same_as", None)
|
|
if same_as is not None:
|
|
try:
|
|
return bool(same_as(rhs))
|
|
except TypeError:
|
|
pass
|
|
return lhs is rhs
|
|
|
|
|
|
def _collect_meta_resources(value: Any, visited: set[int] | None = None) -> list[Any]:
|
|
if visited is None:
|
|
visited = set()
|
|
obj_id = id(value)
|
|
if obj_id in visited:
|
|
return []
|
|
visited.add(obj_id)
|
|
|
|
resource = _meta_resource_for_value(value)
|
|
if resource is not None:
|
|
return [resource]
|
|
if _is_meta_class_instance(value):
|
|
owned = []
|
|
for field_name, field_value in vars(value).items():
|
|
if field_name.startswith("_tirx_"):
|
|
continue
|
|
owned.extend(_collect_meta_resources(field_value, visited))
|
|
return owned
|
|
if isinstance(value, list | tuple):
|
|
owned = []
|
|
for item in value:
|
|
owned.extend(_collect_meta_resources(item, visited))
|
|
return owned
|
|
if isinstance(value, dict):
|
|
owned = []
|
|
for item in value.values():
|
|
owned.extend(_collect_meta_resources(item, visited))
|
|
return owned
|
|
return []
|
|
|
|
|
|
def _format_unowned_meta_resource_error(cls: type, record: _MetaResourceRecord, total: int) -> str:
|
|
count = "" if total == 1 else f" ({total} total)"
|
|
location = f"{record.filename}:{record.lineno}"
|
|
if record.colno is not None:
|
|
location = f"{location}:{record.colno}"
|
|
message = [
|
|
f"TIRx meta_class constructor created an unowned resource{count}.",
|
|
f" class: {cls.__name__}",
|
|
f" location: {location}",
|
|
]
|
|
if record.code:
|
|
message.extend(["", f" {record.code}", " ^ resource must be assigned to self.<field>"])
|
|
message.extend(
|
|
[
|
|
"",
|
|
"Resources created in a meta_class constructor must be reachable from the",
|
|
"constructed instance.",
|
|
"unowned resource at "
|
|
f"{location}: assign it to self.<field>, or move the allocation into a "
|
|
"parser-owned assignment.",
|
|
]
|
|
)
|
|
return "\n".join(message)
|
|
|
|
|
|
def _validate_meta_construction_scope(scope: _MetaConstructionScope) -> None:
|
|
if not scope.created:
|
|
object.__setattr__(scope.instance, "_tirx_meta_owned_resources", [])
|
|
return
|
|
created_resources = [record.value for record in scope.created]
|
|
owned_resources = _collect_meta_resources(scope.instance)
|
|
missing = [
|
|
record
|
|
for record in scope.created
|
|
if not any(_same_meta_resource(record.value, owned) for owned in owned_resources)
|
|
]
|
|
if missing:
|
|
raise ValueError(_format_unowned_meta_resource_error(scope.cls, missing[0], len(missing)))
|
|
object.__setattr__(scope.instance, "_tirx_meta_owned_resources", created_resources)
|
|
|
|
|
|
def name_meta_class_value(prefix: str, value: Any) -> None:
|
|
"""Name all TIR resources owned by a meta_class instance."""
|
|
_name_meta_value(prefix, value)
|
|
|
|
|
|
def launch_thread(
|
|
thread: IterVar | str, # pylint: disable=redefined-outer-name
|
|
extent: Expr,
|
|
) -> frame.LaunchThreadFrame:
|
|
"""Launch a thread.
|
|
|
|
Parameters
|
|
----------
|
|
thread : Union[IterVar, str]
|
|
The iteration variable.
|
|
|
|
extent : Expr
|
|
The extent of environment thread.
|
|
|
|
Returns
|
|
-------
|
|
res : frame.LaunchThreadFrame
|
|
The result LaunchThreadFrame.
|
|
|
|
Examples
|
|
--------
|
|
|
|
.. code-block:: python
|
|
|
|
from tvm.script.ir_builder import tirx as T
|
|
brow = T.env_thread("blockIdx.y")
|
|
T.launch_thread(brow, 1)
|
|
|
|
"""
|
|
|
|
if isinstance(thread, str):
|
|
thread = String(thread)
|
|
return _ffi_api.LaunchThread(thread, extent) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def env_thread(thread_tag: str, dtype: str = "int32") -> IterVar:
|
|
"""Bind a var to thread env
|
|
|
|
Parameters
|
|
----------
|
|
thread_tag : str
|
|
The thread type tag.
|
|
|
|
dtype : str
|
|
The data type of the thread env.
|
|
|
|
Returns
|
|
-------
|
|
res : IterVar
|
|
The result iteration variable gets bound to the thread env.
|
|
|
|
"""
|
|
return _ffi_api.EnvThread(thread_tag, dtype) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def buffer_store(
|
|
buffer: Buffer, # pylint: disable=redefined-outer-name
|
|
value: Expr,
|
|
indices: list[Expr | slice],
|
|
predicate: Expr | None = None,
|
|
) -> None:
|
|
"""Buffer store node.
|
|
|
|
Parameters
|
|
----------
|
|
buffer : Buffer
|
|
The buffer.
|
|
|
|
value : Expr
|
|
The value to be stored.
|
|
|
|
indices : List[Union[Expr, slice]]
|
|
The indices location to be stored.
|
|
|
|
predicate : Optional[Expr]
|
|
A vector mask of boolean values indicating which lanes of a vector are to be
|
|
stored. The number lanes of the mask must be equal to the number of lanes in
|
|
value.
|
|
"""
|
|
from tvm.arith import Analyzer # pylint: disable=import-outside-toplevel
|
|
|
|
if not isinstance(indices, list | tuple | ir.Array):
|
|
indices = [indices]
|
|
|
|
expr_indices = []
|
|
for index in indices:
|
|
if isinstance(index, slice):
|
|
step = 1 if index.step is None else index.step
|
|
lanes = Analyzer().simplify( # pylint: disable=redefined-outer-name
|
|
(index.stop - index.start + step - 1) // step
|
|
)
|
|
if lanes == 1:
|
|
expr_indices.append(index.start)
|
|
else:
|
|
expr_indices.append(ramp(index.start, step, lanes))
|
|
else:
|
|
expr_indices.append(index)
|
|
if isinstance(value, bool) and buffer.dtype == "bool":
|
|
value = IntImm("bool", value)
|
|
return _ffi_api.BufferStore( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
buffer, value, expr_indices, predicate
|
|
)
|
|
|
|
|
|
def evaluate(value: Expr) -> None:
|
|
"""Evaluate the input expression.
|
|
|
|
Parameters
|
|
----------
|
|
value: Expr
|
|
The input expression to evaluate.
|
|
"""
|
|
if isinstance(value, str):
|
|
value = StringImm(value)
|
|
if isinstance(value, bool):
|
|
value = IntImm("bool", value)
|
|
return _ffi_api.Evaluate(value) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def _ffi_name_to_dtype(name: str) -> str:
|
|
"""Convert an FFI type name to its TVM dtype string.
|
|
|
|
Examples: "Float32" -> "float32", "Int8x4" -> "int8x4",
|
|
"Float8E4M3" -> "float8_e4m3", "Float8E4M3B11FNUZ" -> "float8_e4m3b11fnuz".
|
|
"""
|
|
import re
|
|
|
|
# Insert underscore before E-notation in float8 names (E3M4, E4M3, etc.)
|
|
s = re.sub(r"(?<=[a-z0-9])E(\d)", r"_e\1", name, flags=re.IGNORECASE)
|
|
return s.lower()
|
|
|
|
|
|
def func_gen(name: str):
|
|
"""Generate a DtypeConstructor for each Expr dtype.
|
|
|
|
Parameters
|
|
----------
|
|
name: str
|
|
The ffi function name to call, e.g. "Float32", "Int32".
|
|
"""
|
|
return DtypeConstructor(name, _ffi_name_to_dtype(name))
|
|
|
|
|
|
def static_assert(x: Any, message: str = ""):
|
|
assert x, message
|
|
|
|
|
|
def add_to_parent(stmt: tir.Stmt) -> None:
|
|
"""Add a statement to the parent frame."""
|
|
_ffi_api.AddToParent(stmt) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
# pylint: disable=invalid-name
|
|
int8 = func_gen("Int8")
|
|
int16 = func_gen("Int16")
|
|
int32 = func_gen("Int32")
|
|
int64 = func_gen("Int64")
|
|
int8x2 = func_gen("Int8x2")
|
|
int16x2 = func_gen("Int16x2")
|
|
int32x2 = func_gen("Int32x2")
|
|
int64x2 = func_gen("Int64x2")
|
|
int8x4 = func_gen("Int8x4")
|
|
int16x4 = func_gen("Int16x4")
|
|
int32x4 = func_gen("Int32x4")
|
|
int64x4 = func_gen("Int64x4")
|
|
int8x8 = func_gen("Int8x8")
|
|
int16x8 = func_gen("Int16x8")
|
|
int32x8 = func_gen("Int32x8")
|
|
int64x8 = func_gen("Int64x8")
|
|
int8x16 = func_gen("Int8x16")
|
|
int16x16 = func_gen("Int16x16")
|
|
int32x16 = func_gen("Int32x16")
|
|
int64x16 = func_gen("Int64x16")
|
|
int8x32 = func_gen("Int8x32")
|
|
int16x32 = func_gen("Int16x32")
|
|
int32x32 = func_gen("Int32x32")
|
|
int64x32 = func_gen("Int64x32")
|
|
int8x64 = func_gen("Int8x64")
|
|
int16x64 = func_gen("Int16x64")
|
|
int32x64 = func_gen("Int32x64")
|
|
int64x64 = func_gen("Int64x64")
|
|
|
|
uint8 = func_gen("UInt8")
|
|
uint16 = func_gen("UInt16")
|
|
uint32 = func_gen("UInt32")
|
|
uint64 = func_gen("UInt64")
|
|
uint8x2 = func_gen("UInt8x2")
|
|
uint16x2 = func_gen("UInt16x2")
|
|
uint32x2 = func_gen("UInt32x2")
|
|
uint64x2 = func_gen("UInt64x2")
|
|
uint8x4 = func_gen("UInt8x4")
|
|
uint16x4 = func_gen("UInt16x4")
|
|
uint32x4 = func_gen("UInt32x4")
|
|
uint64x4 = func_gen("UInt64x4")
|
|
uint8x8 = func_gen("UInt8x8")
|
|
uint16x8 = func_gen("UInt16x8")
|
|
uint32x8 = func_gen("UInt32x8")
|
|
uint64x8 = func_gen("UInt64x8")
|
|
uint8x16 = func_gen("UInt8x16")
|
|
uint16x16 = func_gen("UInt16x16")
|
|
uint32x16 = func_gen("UInt32x16")
|
|
uint64x16 = func_gen("UInt64x16")
|
|
uint8x32 = func_gen("UInt8x32")
|
|
uint16x32 = func_gen("UInt16x32")
|
|
uint32x32 = func_gen("UInt32x32")
|
|
uint64x32 = func_gen("UInt64x32")
|
|
uint8x64 = func_gen("UInt8x64")
|
|
uint16x64 = func_gen("UInt16x64")
|
|
uint32x64 = func_gen("UInt32x64")
|
|
uint64x64 = func_gen("UInt64x64")
|
|
|
|
float16 = func_gen("Float16")
|
|
float32 = func_gen("Float32")
|
|
float64 = func_gen("Float64")
|
|
float16x2 = func_gen("Float16x2")
|
|
float32x2 = func_gen("Float32x2")
|
|
float64x2 = func_gen("Float64x2")
|
|
float16x4 = func_gen("Float16x4")
|
|
float32x4 = func_gen("Float32x4")
|
|
float64x4 = func_gen("Float64x4")
|
|
float16x8 = func_gen("Float16x8")
|
|
float32x8 = func_gen("Float32x8")
|
|
float64x8 = func_gen("Float64x8")
|
|
float16x16 = func_gen("Float16x16")
|
|
float32x16 = func_gen("Float32x16")
|
|
float64x16 = func_gen("Float64x16")
|
|
float16x32 = func_gen("Float16x32")
|
|
float32x32 = func_gen("Float32x32")
|
|
float64x32 = func_gen("Float64x32")
|
|
float16x64 = func_gen("Float16x64")
|
|
float32x64 = func_gen("Float32x64")
|
|
float64x64 = func_gen("Float64x64")
|
|
|
|
# Float8 variants
|
|
float8_e3m4 = func_gen("Float8E3M4")
|
|
float8_e3m4x2 = func_gen("Float8E3M4x2")
|
|
float8_e3m4x4 = func_gen("Float8E3M4x4")
|
|
float8_e3m4x8 = func_gen("Float8E3M4x8")
|
|
float8_e3m4x16 = func_gen("Float8E3M4x16")
|
|
float8_e3m4x32 = func_gen("Float8E3M4x32")
|
|
float8_e3m4x64 = func_gen("Float8E3M4x64")
|
|
|
|
float8_e4m3 = func_gen("Float8E4M3")
|
|
float8_e4m3x2 = func_gen("Float8E4M3x2")
|
|
float8_e4m3x4 = func_gen("Float8E4M3x4")
|
|
float8_e4m3x8 = func_gen("Float8E4M3x8")
|
|
float8_e4m3x16 = func_gen("Float8E4M3x16")
|
|
float8_e4m3x32 = func_gen("Float8E4M3x32")
|
|
float8_e4m3x64 = func_gen("Float8E4M3x64")
|
|
|
|
float8_e4m3b11fnuz = func_gen("Float8E4M3B11FNUZ")
|
|
float8_e4m3b11fnuzx2 = func_gen("Float8E4M3B11FNUZx2")
|
|
float8_e4m3b11fnuzx4 = func_gen("Float8E4M3B11FNUZx4")
|
|
float8_e4m3b11fnuzx8 = func_gen("Float8E4M3B11FNUZx8")
|
|
float8_e4m3b11fnuzx16 = func_gen("Float8E4M3B11FNUZx16")
|
|
float8_e4m3b11fnuzx32 = func_gen("Float8E4M3B11FNUZx32")
|
|
float8_e4m3b11fnuzx64 = func_gen("Float8E4M3B11FNUZx64")
|
|
|
|
float8_e4m3fn = func_gen("Float8E4M3FN")
|
|
float8_e4m3fnx2 = func_gen("Float8E4M3FNx2")
|
|
float8_e4m3fnx4 = func_gen("Float8E4M3FNx4")
|
|
float8_e4m3fnx8 = func_gen("Float8E4M3FNx8")
|
|
float8_e4m3fnx16 = func_gen("Float8E4M3FNx16")
|
|
float8_e4m3fnx32 = func_gen("Float8E4M3FNx32")
|
|
float8_e4m3fnx64 = func_gen("Float8E4M3FNx64")
|
|
|
|
float8_e4m3fnuz = func_gen("Float8E4M3FNUZ")
|
|
float8_e4m3fnuzx2 = func_gen("Float8E4M3FNUZx2")
|
|
float8_e4m3fnuzx4 = func_gen("Float8E4M3FNUZx4")
|
|
float8_e4m3fnuzx8 = func_gen("Float8E4M3FNUZx8")
|
|
float8_e4m3fnuzx16 = func_gen("Float8E4M3FNUZx16")
|
|
float8_e4m3fnuzx32 = func_gen("Float8E4M3FNUZx32")
|
|
float8_e4m3fnuzx64 = func_gen("Float8E4M3FNUZx64")
|
|
|
|
float8_e5m2 = func_gen("Float8E5M2")
|
|
float8_e5m2x2 = func_gen("Float8E5M2x2")
|
|
float8_e5m2x4 = func_gen("Float8E5M2x4")
|
|
float8_e5m2x8 = func_gen("Float8E5M2x8")
|
|
float8_e5m2x16 = func_gen("Float8E5M2x16")
|
|
float8_e5m2x32 = func_gen("Float8E5M2x32")
|
|
float8_e5m2x64 = func_gen("Float8E5M2x64")
|
|
|
|
float8_e5m2fnuz = func_gen("Float8E5M2FNUZ")
|
|
float8_e5m2fnuzx2 = func_gen("Float8E5M2FNUZx2")
|
|
float8_e5m2fnuzx4 = func_gen("Float8E5M2FNUZx4")
|
|
float8_e5m2fnuzx8 = func_gen("Float8E5M2FNUZx8")
|
|
float8_e5m2fnuzx16 = func_gen("Float8E5M2FNUZx16")
|
|
float8_e5m2fnuzx32 = func_gen("Float8E5M2FNUZx32")
|
|
float8_e5m2fnuzx64 = func_gen("Float8E5M2FNUZx64")
|
|
|
|
float8_e8m0fnu = func_gen("Float8E8M0FNU")
|
|
float8_e8m0fnux2 = func_gen("Float8E8M0FNUx2")
|
|
float8_e8m0fnux4 = func_gen("Float8E8M0FNUx4")
|
|
float8_e8m0fnux8 = func_gen("Float8E8M0FNUx8")
|
|
float8_e8m0fnux16 = func_gen("Float8E8M0FNUx16")
|
|
float8_e8m0fnux32 = func_gen("Float8E8M0FNUx32")
|
|
float8_e8m0fnux64 = func_gen("Float8E8M0FNUx64")
|
|
|
|
# Float6 variants
|
|
float6_e2m3fn = func_gen("Float6E2M3FN")
|
|
float6_e2m3fnx2 = func_gen("Float6E2M3FNx2")
|
|
float6_e2m3fnx4 = func_gen("Float6E2M3FNx4")
|
|
float6_e2m3fnx8 = func_gen("Float6E2M3FNx8")
|
|
float6_e2m3fnx16 = func_gen("Float6E2M3FNx16")
|
|
float6_e2m3fnx32 = func_gen("Float6E2M3FNx32")
|
|
float6_e2m3fnx64 = func_gen("Float6E2M3FNx64")
|
|
|
|
float6_e3m2fn = func_gen("Float6E3M2FN")
|
|
float6_e3m2fnx2 = func_gen("Float6E3M2FNx2")
|
|
float6_e3m2fnx4 = func_gen("Float6E3M2FNx4")
|
|
float6_e3m2fnx8 = func_gen("Float6E3M2FNx8")
|
|
float6_e3m2fnx16 = func_gen("Float6E3M2FNx16")
|
|
float6_e3m2fnx32 = func_gen("Float6E3M2FNx32")
|
|
float6_e3m2fnx64 = func_gen("Float6E3M2FNx64")
|
|
|
|
# Float4 variants
|
|
float4_e2m1fn = func_gen("Float4E2M1FN")
|
|
float4_e2m1fnx2 = func_gen("Float4E2M1FNx2")
|
|
float4_e2m1fnx4 = func_gen("Float4E2M1FNx4")
|
|
float4_e2m1fnx8 = func_gen("Float4E2M1FNx8")
|
|
float4_e2m1fnx16 = func_gen("Float4E2M1FNx16")
|
|
float4_e2m1fnx32 = func_gen("Float4E2M1FNx32")
|
|
float4_e2m1fnx64 = func_gen("Float4E2M1FNx64")
|
|
|
|
bfloat16 = func_gen("BFloat16")
|
|
|
|
# Shorthand aliases
|
|
f16 = float16
|
|
f32 = float32
|
|
f64 = float64
|
|
bf16 = bfloat16
|
|
i8 = int8
|
|
i16 = int16
|
|
i32 = int32
|
|
i64 = int64
|
|
u8 = uint8
|
|
u16 = uint16
|
|
u32 = uint32
|
|
u64 = uint64
|
|
# pylint: enable=invalid-name
|
|
|
|
|
|
def boolean(expr: Expr | None = None) -> Expr:
|
|
"""Construct a new tirx.Var with type boolean or cast expression to type boolean.
|
|
|
|
Parameters
|
|
----------
|
|
expr: Expr
|
|
The expression to be cast.
|
|
|
|
Returns
|
|
-------
|
|
res : Expr
|
|
The new tirx.Var with type boolean or casted expression with type boolean.
|
|
"""
|
|
return _ffi_api.Boolean(expr) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def handle(
|
|
dtype: str | None = None,
|
|
storage_scope: str = "global",
|
|
) -> Var:
|
|
"""Create a TIR var that represents a pointer.
|
|
|
|
Parameters
|
|
----------
|
|
dtype: str | None
|
|
The data type of the pointer. If omitted, construct an opaque handle.
|
|
|
|
storage_scope: str
|
|
The storage scope of the pointer.
|
|
|
|
Returns
|
|
-------
|
|
res : Expr
|
|
The new tirx.Var with type handle or casted expression with type handle.
|
|
"""
|
|
if dtype in ("TensorMap", "tensormap", "CUtensorMap", "cuTensorMap"):
|
|
return _ffi_api.TensorMap() # type: ignore[attr-defined] # pylint: disable=no-member
|
|
return _ffi_api.Handle( # type: ignore[attr-defined] # pylint: disable=no-member
|
|
dtype,
|
|
storage_scope,
|
|
)
|
|
|
|
|
|
def TensorMap() -> Var: # pylint: disable=invalid-name
|
|
"""Create a TIRx var that represents a CUDA tensor-map descriptor.
|
|
|
|
The host/runtime ABI passes a handle to descriptor storage. CUDA kernel
|
|
codegen lowers this type to ``const __grid_constant__ CUtensorMap`` when it
|
|
appears as a kernel parameter.
|
|
"""
|
|
return _ffi_api.TensorMap() # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def void(expr: Expr | None = None) -> Expr:
|
|
"""Construct a new tirx.Var with type void or cast expression to type void.
|
|
|
|
Parameters
|
|
----------
|
|
expr: Expr
|
|
The expression to be cast.
|
|
|
|
Returns
|
|
-------
|
|
res : Expr
|
|
The new tirx.Var with type void or casted expression with type void.
|
|
"""
|
|
return _ffi_api.Void(expr) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
@deprecated("T.var", "T.{dtype}")
|
|
def var(dtype: str, name: str = "") -> Var:
|
|
"""Construct a new tirx.Var.
|
|
|
|
Parameters
|
|
----------
|
|
dtype: str
|
|
The dtype of the Var.
|
|
|
|
name: str
|
|
The name of the Var.
|
|
|
|
Returns
|
|
-------
|
|
res : Var
|
|
The result tirx.Var.
|
|
"""
|
|
return Var(name, dtype) # pylint: disable=no-member
|
|
|
|
|
|
def ptr(dtype: str, storage_scope: str = "global") -> Var:
|
|
"""The pointer declaration function.
|
|
|
|
Parameters
|
|
----------
|
|
dtype : str
|
|
The data type of the pointer.
|
|
|
|
storage_scope : str
|
|
The storage scope of the pointer.
|
|
|
|
Returns
|
|
-------
|
|
res : Var
|
|
The pointer.
|
|
"""
|
|
return _ffi_api.Ptr(dtype, storage_scope) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
@deprecated("T.buffer_var", "T.handle")
|
|
def buffer_var(dtype: str, storage_scope: str = "global") -> Var:
|
|
"""The pointer declaration function.
|
|
|
|
Parameters
|
|
----------
|
|
dtype : str
|
|
The data type of the pointer.
|
|
|
|
storage_scope : str
|
|
The storage scope of the pointer.
|
|
|
|
Returns
|
|
-------
|
|
res : Var
|
|
The pointer.
|
|
"""
|
|
return _ffi_api.Ptr(dtype, storage_scope) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def min(a: Expr, b: Expr) -> Expr: # pylint: disable=redefined-builtin
|
|
"""Compute the minimum value of two expressions.
|
|
|
|
Parameters
|
|
----------
|
|
a : Expr
|
|
The left hand operand
|
|
|
|
b : Expr
|
|
The right hand operand
|
|
|
|
Returns
|
|
-------
|
|
res : Expr
|
|
The result expression.
|
|
"""
|
|
return _ffi_api.min(a, b) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def max(a: Expr, b: Expr) -> Expr: # pylint: disable=redefined-builtin
|
|
"""Compute the maximum value of two expressions.
|
|
|
|
Parameters
|
|
----------
|
|
a : Expr
|
|
The left hand operand
|
|
|
|
b : Expr
|
|
The right hand operand
|
|
|
|
Returns
|
|
-------
|
|
res : Expr
|
|
The result expression.
|
|
"""
|
|
return _ffi_api.max(a, b) # type: ignore[attr-defined] # pylint: disable=no-member
|
|
|
|
|
|
def iter_var(v: Var | str, dom: ir.Range, iter_type: str, thread_tag: str) -> IterVar:
|
|
"""The iteration variable.
|
|
|
|
Parameters
|
|
----------
|
|
var : Union[Var, str]
|
|
The internal variable that is used for iteration.
|
|
|
|
dom : Range
|
|
The domain of the iteration.
|
|
|
|
iter_type : str
|
|
The iteration type.
|
|
|
|
thread_tag : str
|
|
The thread type tag.
|
|
|
|
Returns
|
|
-------
|
|
res : IterVar
|
|
The iteration variable.
|
|
"""
|
|
iter_type = getattr(IterVar, iter_type)
|
|
return IterVar(dom, v, iter_type, thread_tag)
|
|
|
|
|
|
def comm_reducer(combiner: Callable, identity: list[Expr]) -> CommReducer:
|
|
"""
|
|
Create a CommReducer from lambda inputs/outputs and the identities
|
|
|
|
Parameters
|
|
----------
|
|
combiner : Callable
|
|
A binary function which takes two Expr as input to return a Expr.
|
|
|
|
identity : List[Expr]
|
|
A list of types of output Expr.
|
|
|
|
Returns
|
|
-------
|
|
res : CommReducer
|
|
The CommReducer.
|
|
"""
|
|
params = inspect.signature(combiner).parameters
|
|
num_args = len(params)
|
|
args = []
|
|
for name, i in zip(params.keys(), identity + identity):
|
|
if isinstance(i, int):
|
|
args.append(Var(name, "int32"))
|
|
else:
|
|
args.append(Var(name, i.ty))
|
|
res = combiner(*args)
|
|
if not isinstance(res, tuple):
|
|
res = (res,)
|
|
return CommReducer(args[: num_args // 2], args[num_args // 2 :], res, identity)
|
|
|
|
|
|
def index_map(
|
|
mapping: Callable,
|
|
*,
|
|
inverse_index_map: Callable | None = None,
|
|
index_dtype: str = "int64",
|
|
) -> IndexMap:
|
|
"""Create a TIR Index mapping"""
|
|
return IndexMap.from_func(mapping, inverse_index_map=inverse_index_map, index_dtype=index_dtype)
|
|
|
|
|
|
def target(
|
|
target_config: dict | str,
|
|
host: dict | str | Target | None = None,
|
|
) -> Target:
|
|
"""
|
|
Create a target
|
|
|
|
Parameters
|
|
----------
|
|
target_config : Union[Dict, str]
|
|
The target configuration.
|
|
|
|
host : Optional[Union[Dict, str, Target]]
|
|
The target configuration.
|
|
|
|
Returns
|
|
-------
|
|
res : Target
|
|
The target.
|
|
"""
|
|
if not isinstance(target_config, str | dict):
|
|
raise ValueError(
|
|
f"T.target expected a config dict or string, but got {type(target_config)}"
|
|
)
|
|
if host is not None and not isinstance(host, str | dict | Target):
|
|
raise ValueError(
|
|
"T.target expected the host to be "
|
|
"a config dict, string, or T.target, "
|
|
f"but got {type(host)}"
|
|
)
|
|
if isinstance(target_config, dict) and "host" in target_config and host is not None:
|
|
raise ValueError(
|
|
"T.target expects to either receive the host "
|
|
"as part of the target's config dictionary, "
|
|
"or as a separate argument, but not both."
|
|
)
|
|
return Target(target_config, host)
|
|
|
|
|
|
def Range(begin: Expr, end: Expr) -> ir.Range: # pylint: disable=invalid-name
|
|
"""
|
|
Create a Range object.
|
|
|
|
Parameters
|
|
----------
|
|
begin : Expr
|
|
The begin value of the range.
|
|
|
|
end : Optional[Expr]
|
|
The end value of the range.
|
|
"""
|
|
return ir.Range(begin, end)
|
|
|
|
|
|
if TYPE_CHECKING:
|
|
T = TypeVar("T")
|
|
C = TypeVar("C")
|
|
|
|
# When type checking (and by extension, for linters like Pylint), treat
|
|
# meta_var as an identity function.
|
|
def meta_var(x: T) -> T:
|
|
return x
|
|
|
|
def meta_class(cls: C) -> C:
|
|
return cls
|
|
|
|
else:
|
|
|
|
def _install_meta_class(cls):
|
|
if cls.__dict__.get("_tirx_meta_class_installed", False):
|
|
cls._is_meta_class = True
|
|
return cls
|
|
|
|
original_init = getattr(cls, "__init__", object.__init__)
|
|
original_setattr = getattr(cls, "__setattr__", object.__setattr__)
|
|
original_init_subclass = getattr(cls, "__init_subclass__", None)
|
|
|
|
def __init__(self, *args, **kwargs):
|
|
with _with_meta_construction_scope(self, type(self)) as scope:
|
|
original_init(self, *args, **kwargs)
|
|
_validate_meta_construction_scope(scope)
|
|
|
|
def __setattr__(self, name, value):
|
|
if isinstance(value, scalar_wrapper):
|
|
value = value.scalar
|
|
original_setattr(self, name, value)
|
|
|
|
@classmethod
|
|
def __init_subclass__(subcls, **kwargs):
|
|
if original_init_subclass is not None:
|
|
original_init_subclass(**kwargs)
|
|
_install_meta_class(subcls)
|
|
|
|
cls.__init__ = __init__
|
|
cls.__setattr__ = __setattr__
|
|
cls.__init_subclass__ = __init_subclass__
|
|
cls._is_meta_class = True
|
|
cls._tirx_meta_class_installed = True
|
|
return cls
|
|
|
|
def meta_class(cls):
|
|
"""Decorator for utility classes used inside @T.prim_func.
|
|
|
|
Instances of decorated classes are treated as parser meta values.
|
|
"""
|
|
return _install_meta_class(cls)
|
|
|
|
class meta_var:
|
|
"""A meta variable used in TVMScript metaprogramming.
|
|
|
|
The value does not appear in the final TIR and only exists in the parser.
|
|
|
|
Parameters
|
|
----------
|
|
value: Any
|
|
The meta variable.
|
|
"""
|
|
|
|
def __init__(self, value: Any) -> None:
|
|
self.value = value
|
|
|
|
def __iter__(self):
|
|
# Return a generator that yields wrapped items.
|
|
return (meta_var(i) for i in self.value)
|
|
|
|
|
|
# pylint: disable=invalid-name
|
|
|
|
|
|
T = TypeVar("T")
|
|
P = ParamSpec("P")
|
|
|
|
|
|
def _op_wrapper(func: Callable[P, T]) -> Callable[P, T]:
|
|
@functools.wraps(func)
|
|
def wrapped(*args, **kwargs) -> T:
|
|
if "dtype" in kwargs:
|
|
kwargs.pop("dtype")
|
|
return func(*args, **kwargs)
|
|
|
|
# Expose underlying tir op name for printer registration
|
|
try:
|
|
wrapped.__tir_op_name__ = getattr(func, "__name__", None)
|
|
except Exception: # pragma: no cover
|
|
pass
|
|
return wrapped
|
|
|
|
|
|
def _dtype_forward(func):
|
|
@functools.wraps(func)
|
|
def wrapped(*args, **kwargs):
|
|
if "dtype" in kwargs:
|
|
args = (kwargs.pop("dtype"), *args)
|
|
return func(*args, **kwargs)
|
|
|
|
# Expose underlying tir op name for printer registration
|
|
try:
|
|
wrapped.__tir_op_name__ = getattr(func, "__name__", None)
|
|
except Exception: # pragma: no cover
|
|
pass
|
|
return wrapped
|
|
|
|
|
|
class WebGPUNamespace:
|
|
"""The WebGPU intrinsics submodule."""
|
|
|
|
@staticmethod
|
|
def subgroup_shuffle(var, lane):
|
|
if isinstance(var, Buffer):
|
|
var = var[0]
|
|
return _tir_op.call_intrin(var.ty, "tirx.webgpu.subgroup_shuffle", var, lane)
|
|
|
|
@staticmethod
|
|
def subgroup_shuffle_up(var, delta):
|
|
if isinstance(var, Buffer):
|
|
var = var[0]
|
|
return _tir_op.call_intrin(var.ty, "tirx.webgpu.subgroup_shuffle_up", var, delta)
|
|
|
|
@staticmethod
|
|
def subgroup_shuffle_down(var, delta):
|
|
if isinstance(var, Buffer):
|
|
var = var[0]
|
|
return _tir_op.call_intrin(var.ty, "tirx.webgpu.subgroup_shuffle_down", var, delta)
|
|
|
|
|
|
webgpu = WebGPUNamespace()
|
|
|
|
|
|
#
|
|
# Register printer namespace mapping from the builder namespaces so the
|
|
# TVMScript printer emits dotted names that match parser namespaces.
|
|
#
|
|
def _register_script_namespace_printer_names(ns_obj, dotted_prefix):
|
|
def register_printer_name(op_name, script_name):
|
|
try:
|
|
ir.Op.get(op_name)
|
|
except Exception:
|
|
return
|
|
try:
|
|
_register_op_attr(op_name, "TScriptPrinterName", script_name, level=20)
|
|
except Exception:
|
|
pass
|
|
|
|
def visit(ns_obj, dotted_prefix):
|
|
# If the namespace object itself maps to an op via __call__
|
|
call_op = getattr(ns_obj, "__tir_call_op_name__", None)
|
|
if call_op:
|
|
flat_name = f"tirx.{call_op}"
|
|
for op_name in {flat_name, _tir_op._canonical_device_intrin_name(flat_name)}:
|
|
register_printer_name(op_name, dotted_prefix)
|
|
# Walk attributes to find wrapped ops and sub-namespaces
|
|
for name in dir(ns_obj):
|
|
if name.startswith("_"):
|
|
continue
|
|
try:
|
|
val = getattr(ns_obj, name)
|
|
except Exception:
|
|
continue
|
|
# Sub-namespace: recurse
|
|
if hasattr(val, "__dict__") and val.__class__.__name__.endswith("Namespace"):
|
|
visit(val, f"{dotted_prefix}.{name}")
|
|
continue
|
|
# Wrapped op (callable with attached __tir_op_name__)
|
|
op_name = getattr(val, "__tir_op_name__", None)
|
|
if callable(val) and op_name:
|
|
flat_name = f"tirx.{op_name}"
|
|
script_name = f"{dotted_prefix}.{name}"
|
|
for full_op_name in {flat_name, _tir_op._canonical_device_intrin_name(flat_name)}:
|
|
register_printer_name(full_op_name, script_name)
|
|
|
|
visit(ns_obj, dotted_prefix)
|
|
|
|
|
|
def register_script_namespace(name: str, namespace: object) -> object:
|
|
"""Register a TVMScript namespace on the TIRx builder facade."""
|
|
globals()[name] = namespace
|
|
if "__all__" in globals() and name not in __all__:
|
|
__all__.append(name)
|
|
|
|
import sys # pylint: disable=import-outside-toplevel
|
|
|
|
for module_name in [
|
|
"tvm.tirx.script.builder",
|
|
"tvm.tirx.script.parser",
|
|
"tvm.tirx.script",
|
|
"tvm.script.tirx",
|
|
]:
|
|
module = sys.modules.get(module_name)
|
|
if module is None:
|
|
continue
|
|
setattr(module, name, namespace)
|
|
module_all = getattr(module, "__all__", None)
|
|
if isinstance(module_all, list) and name not in module_all:
|
|
module_all.append(name)
|
|
|
|
_register_script_namespace_printer_names(namespace, name)
|
|
return namespace
|
|
|
|
|
|
def _register_tir_namespace_printer_names():
|
|
try:
|
|
_register_script_namespace_printer_names(webgpu, "webgpu")
|
|
except Exception:
|
|
# Best-effort registration; avoid import-time hard failure
|
|
pass
|
|
|
|
|
|
# Execute registration on import so printer picks up dotted names
|
|
_register_tir_namespace_printer_names()
|
|
|
|
|
|
abs = _op_wrapper(_tir_op.abs) # pylint: disable=redefined-builtin
|
|
acos = _op_wrapper(_tir_op.acos)
|
|
acosh = _op_wrapper(_tir_op.acosh)
|
|
address_of = _op_wrapper(_tir_op.address_of)
|
|
asin = _op_wrapper(_tir_op.asin)
|
|
asinh = _op_wrapper(_tir_op.asinh)
|
|
atan = _op_wrapper(_tir_op.atan)
|
|
atan2 = _op_wrapper(_tir_op.atan2)
|
|
atanh = _op_wrapper(_tir_op.atanh)
|
|
bitwise_and = _op_wrapper(_tir_op.bitwise_and)
|
|
bitwise_not = _op_wrapper(_tir_op.bitwise_not)
|
|
bitwise_or = _op_wrapper(_tir_op.bitwise_or)
|
|
bitwise_xor = _op_wrapper(_tir_op.bitwise_xor)
|
|
ceil = _op_wrapper(_tir_op.ceil)
|
|
clz = _op_wrapper(_tir_op.clz)
|
|
copysign = _op_wrapper(_tir_op.copysign)
|
|
cos = _op_wrapper(_tir_op.cos)
|
|
cosh = _op_wrapper(_tir_op.cosh)
|
|
erf = _op_wrapper(_tir_op.erf)
|
|
exp = _op_wrapper(_tir_op.exp)
|
|
exp2 = _op_wrapper(_tir_op.exp2)
|
|
exp10 = _op_wrapper(_tir_op.exp10)
|
|
filter = _op_wrapper(_tir_op.filter) # pylint: disable=redefined-builtin
|
|
selector = _op_wrapper(_tir_op.selector)
|
|
floor = _op_wrapper(_tir_op.floor)
|
|
ceildiv = _op_wrapper(_tir_op.ceildiv)
|
|
floordiv = _op_wrapper(_tir_op.floordiv)
|
|
floormod = _op_wrapper(_tir_op.floormod)
|
|
fmod = _op_wrapper(_tir_op.fmod)
|
|
fma = _op_wrapper(_tir_op.fma)
|
|
hypot = _op_wrapper(_tir_op.hypot)
|
|
if_then_else = _op_wrapper(_tir_op.if_then_else)
|
|
infinity = _op_wrapper(_tir_op.infinity)
|
|
isfinite = _op_wrapper(_tir_op.isfinite)
|
|
isinf = _op_wrapper(_tir_op.isinf)
|
|
isnan = _op_wrapper(_tir_op.isnan)
|
|
isnullptr = _op_wrapper(_tir_op.isnullptr)
|
|
ldexp = _op_wrapper(_tir_op.ldexp)
|
|
likely = _op_wrapper(_tir_op.likely)
|
|
log = _op_wrapper(_tir_op.log)
|
|
log1p = _op_wrapper(_tir_op.log1p)
|
|
log2 = _op_wrapper(_tir_op.log2)
|
|
log10 = _op_wrapper(_tir_op.log10)
|
|
lookup_param = _op_wrapper(_tir_op.lookup_param)
|
|
max_value = _op_wrapper(_tir_op.max_value)
|
|
min_value = _op_wrapper(_tir_op.min_value)
|
|
nearbyint = _op_wrapper(_tir_op.nearbyint)
|
|
nextafter = _op_wrapper(_tir_op.nextafter)
|
|
popcount = _op_wrapper(_tir_op.popcount)
|
|
pow = _op_wrapper(_tir_op.pow) # pylint: disable=redefined-builtin
|
|
q_multiply_shift = _op_wrapper(_tir_op.q_multiply_shift)
|
|
q_multiply_shift_per_axis = _op_wrapper(_tir_op.q_multiply_shift_per_axis)
|
|
ret = _op_wrapper(_tir_op.ret)
|
|
continue_loop = _op_wrapper(_tir_op.continue_loop)
|
|
break_loop = _op_wrapper(_tir_op.break_loop)
|
|
round = _op_wrapper(_tir_op.round) # pylint: disable=redefined-builtin
|
|
rsqrt = _op_wrapper(_tir_op.rsqrt)
|
|
shift_left = _op_wrapper(_tir_op.shift_left)
|
|
shift_right = _op_wrapper(_tir_op.shift_right)
|
|
sigmoid = _op_wrapper(_tir_op.sigmoid)
|
|
sin = _op_wrapper(_tir_op.sin)
|
|
sinh = _op_wrapper(_tir_op.sinh)
|
|
sqrt = _op_wrapper(_tir_op.sqrt)
|
|
tan = _op_wrapper(_tir_op.tan)
|
|
tanh = _op_wrapper(_tir_op.tanh)
|
|
thread_return = _op_wrapper(_tir_op.thread_return)
|
|
trunc = _op_wrapper(_tir_op.trunc)
|
|
truncdiv = _op_wrapper(_tir_op.truncdiv)
|
|
truncmod = _op_wrapper(_tir_op.truncmod)
|
|
tvm_access_ptr = _op_wrapper(_tir_op.tvm_access_ptr)
|
|
ptr_byte_offset = _op_wrapper(_tir_op.ptr_byte_offset)
|
|
tvm_throw_last_error = _op_wrapper(_tir_op.tvm_throw_last_error)
|
|
print_buffer = _op_wrapper(_tir_op.print_buffer)
|
|
tvm_stack_alloca = _op_wrapper(_tir_op.tvm_stack_alloca)
|
|
tvm_stack_make_shape = _op_wrapper(_tir_op.tvm_stack_make_shape)
|
|
tvm_stack_make_array = _op_wrapper(_tir_op.tvm_stack_make_array)
|
|
call_packed = _op_wrapper(_tir_op.call_packed)
|
|
call_cpacked = _op_wrapper(_tir_op.call_cpacked)
|
|
call_packed_lowered = _op_wrapper(_tir_op.call_packed_lowered)
|
|
call_cpacked_lowered = _op_wrapper(_tir_op.call_cpacked_lowered)
|
|
tvm_tuple = _op_wrapper(_tir_op.tvm_tuple)
|
|
handle_add_byte_offset = _op_wrapper(_tir_op.handle_add_byte_offset)
|
|
tvm_struct_set = _op_wrapper(_tir_op.tvm_struct_set)
|
|
tvm_struct_get = _tir_op.tvm_struct_get
|
|
tvm_thread_invariant = _op_wrapper(_tir_op.tvm_thread_invariant)
|
|
tvm_thread_allreduce = _op_wrapper(_tir_op.tvm_thread_allreduce)
|
|
tvm_load_matrix_sync = _op_wrapper(_tir_op.tvm_load_matrix_sync)
|
|
tvm_mma_sync = _op_wrapper(_tir_op.tvm_mma_sync)
|
|
tvm_bmma_sync = _op_wrapper(_tir_op.tvm_bmma_sync)
|
|
tvm_fill_fragment = _op_wrapper(_tir_op.tvm_fill_fragment)
|
|
tvm_store_matrix_sync = _op_wrapper(_tir_op.tvm_store_matrix_sync)
|
|
tvm_storage_sync = _tir_op.tvm_storage_sync
|
|
tvm_kernel_replace_point = _op_wrapper(_tir_op.tvm_kernel_replace_point)
|
|
tvm_global_barrier_kinit = _tir_op.tvm_global_barrier_kinit
|
|
tvm_warp_shuffle = _tir_op.tvm_warp_shuffle
|
|
tvm_warp_shuffle_up = _tir_op.tvm_warp_shuffle_up
|
|
tvm_warp_shuffle_down = _tir_op.tvm_warp_shuffle_down
|
|
tvm_warp_shuffle_xor = _tir_op.tvm_warp_shuffle_xor
|
|
tvm_warp_activemask = _tir_op.tvm_warp_activemask
|
|
cooperative_tensor_fill = _op_wrapper(_tir_op.cooperative_tensor_fill)
|
|
cooperative_tensor_load = _op_wrapper(_tir_op.cooperative_tensor_load)
|
|
cooperative_tensor_store = _op_wrapper(_tir_op.cooperative_tensor_store)
|
|
cooperative_tensor_multiply_accumulate = _op_wrapper(_tir_op.cooperative_tensor_multiply_accumulate)
|
|
assume = _op_wrapper(_tir_op.assume)
|
|
undef = _op_wrapper(_tir_op.undef)
|
|
TVMBackendAllocWorkspace = _op_wrapper(_tir_op.TVMBackendAllocWorkspace)
|
|
TVMBackendFreeWorkspace = _op_wrapper(_tir_op.TVMBackendFreeWorkspace)
|
|
start_profile_intrinsic = _op_wrapper(_tir_op.start_profile_intrinsic)
|
|
end_profile_intrinsic = _op_wrapper(_tir_op.end_profile_intrinsic)
|
|
anylist_getitem = _op_wrapper(_tir_op.anylist_getitem)
|
|
anylist_resetitem = _op_wrapper(_tir_op.anylist_resetitem)
|
|
anylist_setitem_call_packed = _op_wrapper(_tir_op.anylist_setitem_call_packed)
|
|
anylist_setitem_call_cpacked = _op_wrapper(_tir_op.anylist_setitem_call_cpacked)
|
|
vscale = _op_wrapper(_tir_op.vscale)
|
|
ignore_loop_partition = _op_wrapper(_tir_op.ignore_loop_partition)
|
|
|
|
reinterpret = _dtype_forward(_tir_op.reinterpret)
|
|
call_extern = _dtype_forward(_tir_op.call_extern)
|
|
call_intrin = _dtype_forward(_tir_op.call_intrin)
|
|
call_llvm_intrin = _dtype_forward(_tir_op.call_llvm_intrin)
|
|
call_llvm_pure_intrin = _dtype_forward(_tir_op.call_llvm_pure_intrin)
|
|
call_pure_extern = _dtype_forward(_tir_op.call_pure_extern)
|
|
vectorlow = _dtype_forward(_tir_op.vectorlow)
|
|
vectorhigh = _dtype_forward(_tir_op.vectorhigh)
|
|
vectorcombine = _dtype_forward(_tir_op.vectorcombine)
|
|
get_active_lane_mask = _dtype_forward(_tir_op.get_active_lane_mask)
|
|
dp4a = _dtype_forward(_tir_op.dp4a)
|
|
|
|
|
|
broadcast = Broadcast
|
|
ramp = Ramp
|
|
fabs = abs
|
|
tvm_call_packed = call_packed
|
|
tvm_call_cpacked = call_cpacked
|
|
tvm_call_packed_lowered = call_packed_lowered
|
|
tvm_call_cpacked_lowered = call_cpacked_lowered
|
|
|
|
# pylint: enable=invalid-name
|
|
|
|
bases = [
|
|
"float8_e3m4",
|
|
"float8_e4m3",
|
|
"float8_e4m3b11fnuz",
|
|
"float8_e4m3fn",
|
|
"float8_e4m3fnuz",
|
|
"float8_e5m2",
|
|
"float8_e5m2fnuz",
|
|
"float8_e8m0fnu",
|
|
"float6_e2m3fn",
|
|
"float6_e3m2fn",
|
|
"float4_e2m1fn",
|
|
"float16",
|
|
"float32",
|
|
"float64",
|
|
]
|
|
lanes = [1, 2, 4, 8, 16, 32, 64]
|
|
|
|
float_types = []
|
|
for base in bases:
|
|
for lane in lanes:
|
|
suffix = f"x{lane}" if lane != 1 else ""
|
|
float_types.append(f"{base}{suffix}")
|
|
|
|
__all__ = [
|
|
*float_types,
|
|
"int8",
|
|
"int16",
|
|
"int32",
|
|
"int64",
|
|
"int8x2",
|
|
"int16x2",
|
|
"int32x2",
|
|
"int64x2",
|
|
"int8x4",
|
|
"int16x4",
|
|
"int32x4",
|
|
"int64x4",
|
|
"int8x8",
|
|
"int16x8",
|
|
"int32x8",
|
|
"int64x8",
|
|
"int8x16",
|
|
"int16x16",
|
|
"int32x16",
|
|
"int64x16",
|
|
"int8x32",
|
|
"int16x32",
|
|
"int32x32",
|
|
"int64x32",
|
|
"int8x64",
|
|
"int16x64",
|
|
"int32x64",
|
|
"int64x64",
|
|
"uint8",
|
|
"uint16",
|
|
"uint32",
|
|
"uint64",
|
|
"uint8x2",
|
|
"uint16x2",
|
|
"uint32x2",
|
|
"uint64x2",
|
|
"uint8x4",
|
|
"uint16x4",
|
|
"uint32x4",
|
|
"uint64x4",
|
|
"uint8x8",
|
|
"uint16x8",
|
|
"uint32x8",
|
|
"uint64x8",
|
|
"uint8x16",
|
|
"uint16x16",
|
|
"uint32x16",
|
|
"uint64x16",
|
|
"uint8x32",
|
|
"uint16x32",
|
|
"uint32x32",
|
|
"uint64x32",
|
|
"uint8x64",
|
|
"uint16x64",
|
|
"uint32x64",
|
|
"uint64x64",
|
|
"float8_e4m3fn",
|
|
"float8_e5m2",
|
|
"float4_e2m1fn",
|
|
"float16",
|
|
"float32",
|
|
"float64",
|
|
"float4_e2m1fnx2",
|
|
"float8_e4m3fnx4",
|
|
"float8_e5m2x4",
|
|
"float4_e2m1fnx4",
|
|
"float16x2",
|
|
"float32x2",
|
|
"float64x2",
|
|
"float16x4",
|
|
"float32x4",
|
|
"float64x4",
|
|
"float8_e4m3fnx8",
|
|
"float8_e5m2x8",
|
|
"float4_e2m1fnx8",
|
|
"float16x8",
|
|
"float32x8",
|
|
"float64x8",
|
|
"float8_e4m3fnx16",
|
|
"float8_e5m2x16",
|
|
"float4_e2m1fnx16",
|
|
"float16x16",
|
|
"float32x16",
|
|
"float64x16",
|
|
"float8_e4m3fnx32",
|
|
"float8_e5m2x32",
|
|
"float4_e2m1fnx32",
|
|
"float16x32",
|
|
"float32x32",
|
|
"float64x32",
|
|
"float8_e4m3fnx64",
|
|
"float8_e5m2x64",
|
|
"float4_e2m1fnx64",
|
|
"float16x64",
|
|
"float32x64",
|
|
"float64x64",
|
|
"bfloat16",
|
|
"buffer",
|
|
"buffer_decl",
|
|
"prim_func",
|
|
"arg",
|
|
"func_name",
|
|
"func_attr",
|
|
"func_ret",
|
|
"match_buffer",
|
|
"sblock",
|
|
"block_name_suffix_context",
|
|
"init",
|
|
"where",
|
|
"reads",
|
|
"writes",
|
|
"sblock_attr",
|
|
"alloc_buffer",
|
|
"sblock_alloc_buffer",
|
|
"wg_reg_tile",
|
|
"axis",
|
|
"serial",
|
|
"parallel",
|
|
"vectorized",
|
|
"unroll",
|
|
"thread_binding",
|
|
"grid",
|
|
"Assert",
|
|
"attr",
|
|
"hint",
|
|
"While",
|
|
"Break",
|
|
"Continue",
|
|
"If",
|
|
"Then",
|
|
"Else",
|
|
"decl_buffer",
|
|
"launch_thread",
|
|
"env_thread",
|
|
"buffer_store",
|
|
"evaluate",
|
|
"boolean",
|
|
"handle",
|
|
"void",
|
|
"var",
|
|
"ptr",
|
|
"min",
|
|
"max",
|
|
"iter_var",
|
|
"comm_reducer",
|
|
"index_map",
|
|
"target",
|
|
"buffer_var",
|
|
"abs",
|
|
"fabs",
|
|
"acos",
|
|
"acosh",
|
|
"address_of",
|
|
"asin",
|
|
"asinh",
|
|
"atan",
|
|
"atan2",
|
|
"atanh",
|
|
"bitwise_and",
|
|
"bitwise_not",
|
|
"bitwise_or",
|
|
"bitwise_xor",
|
|
"ceil",
|
|
"clz",
|
|
"copysign",
|
|
"cos",
|
|
"cosh",
|
|
"erf",
|
|
"exp",
|
|
"exp2",
|
|
"exp10",
|
|
"floor",
|
|
"ceildiv",
|
|
"floordiv",
|
|
"floormod",
|
|
"fmod",
|
|
"fma",
|
|
"filter",
|
|
"selector",
|
|
"hypot",
|
|
"if_then_else",
|
|
"infinity",
|
|
"isfinite",
|
|
"isinf",
|
|
"isnan",
|
|
"isnullptr",
|
|
"ldexp",
|
|
"likely",
|
|
"log",
|
|
"log1p",
|
|
"log2",
|
|
"log10",
|
|
"lookup_param",
|
|
"max_value",
|
|
"min_value",
|
|
"nearbyint",
|
|
"nextafter",
|
|
"popcount",
|
|
"pow",
|
|
"q_multiply_shift",
|
|
"q_multiply_shift_per_axis",
|
|
"ret",
|
|
"continue_loop",
|
|
"break_loop",
|
|
"reinterpret",
|
|
"round",
|
|
"rsqrt",
|
|
"shift_left",
|
|
"shift_right",
|
|
"sigmoid",
|
|
"sin",
|
|
"sinh",
|
|
"sqrt",
|
|
"tan",
|
|
"tanh",
|
|
"thread_return",
|
|
"trunc",
|
|
"truncdiv",
|
|
"truncmod",
|
|
"tvm_access_ptr",
|
|
"ptr_byte_offset",
|
|
"tvm_throw_last_error",
|
|
"print_buffer",
|
|
"tvm_stack_alloca",
|
|
"tvm_stack_make_shape",
|
|
"tvm_stack_make_array",
|
|
"call_packed",
|
|
"call_cpacked",
|
|
"call_packed_lowered",
|
|
"call_cpacked_lowered",
|
|
"call_extern",
|
|
"call_intrin",
|
|
"call_llvm_intrin",
|
|
"call_llvm_pure_intrin",
|
|
"call_pure_extern",
|
|
"tvm_tuple",
|
|
"handle_add_byte_offset",
|
|
"tvm_struct_set",
|
|
"tvm_struct_get",
|
|
"tvm_thread_invariant",
|
|
"tvm_thread_allreduce",
|
|
"tvm_load_matrix_sync",
|
|
"tvm_mma_sync",
|
|
"tvm_bmma_sync",
|
|
"tvm_fill_fragment",
|
|
"tvm_store_matrix_sync",
|
|
"tvm_storage_sync",
|
|
"tvm_kernel_replace_point",
|
|
"tvm_global_barrier_kinit",
|
|
"tvm_warp_shuffle",
|
|
"tvm_warp_shuffle_up",
|
|
"tvm_warp_shuffle_down",
|
|
"tvm_warp_shuffle_xor",
|
|
"tvm_warp_activemask",
|
|
"cooperative_tensor_fill",
|
|
"cooperative_tensor_load",
|
|
"cooperative_tensor_store",
|
|
"cooperative_tensor_multiply_accumulate",
|
|
"vectorlow",
|
|
"vectorhigh",
|
|
"vectorcombine",
|
|
"dp4a",
|
|
"assume",
|
|
"undef",
|
|
"tvm_call_packed",
|
|
"tvm_call_cpacked",
|
|
"tvm_call_packed_lowered",
|
|
"tvm_call_cpacked_lowered",
|
|
"TVMBackendAllocWorkspace",
|
|
"TVMBackendFreeWorkspace",
|
|
"start_profile_intrinsic",
|
|
"end_profile_intrinsic",
|
|
"meta_var",
|
|
"anylist_getitem",
|
|
"anylist_resetitem",
|
|
"anylist_setitem_call_packed",
|
|
"anylist_setitem_call_cpacked",
|
|
"llvm_lookup_intrinsic_id",
|
|
"type_annotation",
|
|
"broadcast",
|
|
"ramp",
|
|
"cast",
|
|
# tvm.tirx.expr
|
|
"Var",
|
|
"Reduce",
|
|
"FloatImm",
|
|
"IntImm",
|
|
"StringImm",
|
|
"Cast",
|
|
"Add",
|
|
"Sub",
|
|
"Mul",
|
|
"Div",
|
|
"Mod",
|
|
"FloorDiv",
|
|
"FloorMod",
|
|
"Min",
|
|
"Max",
|
|
"EQ",
|
|
"NE",
|
|
"LT",
|
|
"LE",
|
|
"GT",
|
|
"GE",
|
|
"And",
|
|
"Or",
|
|
"Not",
|
|
"Select",
|
|
"BufferLoad",
|
|
"ProducerLoad",
|
|
"Ramp",
|
|
"Broadcast",
|
|
"Shuffle",
|
|
"Call",
|
|
"CallEffectKind",
|
|
"let",
|
|
"Bind",
|
|
"bind",
|
|
"LetAnnotation",
|
|
"LocalVectorAnnotation",
|
|
"DtypeConstructor",
|
|
"Let",
|
|
"IterVar",
|
|
"CommReducer",
|
|
"Range",
|
|
"vscale",
|
|
"get_active_lane_mask",
|
|
"call_kernel",
|
|
"ignore_loop_partition",
|
|
]
|
|
|
|
__all__ += [
|
|
"ComposeLayout",
|
|
"ExecScope",
|
|
"Iter",
|
|
"Layout",
|
|
"R",
|
|
"S",
|
|
"ScopeIdDef",
|
|
"SwizzleLayout",
|
|
"TensorMap",
|
|
"TileLayout",
|
|
"Var",
|
|
"add_to_parent",
|
|
"alloc_cast_frag",
|
|
"alloc_local",
|
|
"alloc_scalar",
|
|
"alloc_shared",
|
|
"alloc_tcgen05_ldst_frag",
|
|
"cluster_id",
|
|
"cta_id",
|
|
"cta_id_in_cluster",
|
|
"cta_id_in_pair",
|
|
"decl_scalar",
|
|
"device_entry",
|
|
"lane_id",
|
|
"local_scalar",
|
|
"meta_class",
|
|
"register_script_namespace",
|
|
"scalar_wrapper",
|
|
"scope_id",
|
|
"shared_scalar",
|
|
"smem",
|
|
"static_assert",
|
|
"thread_id",
|
|
"thread_id_in_wg",
|
|
"tmem",
|
|
"warp_id",
|
|
"warp_id_in_wg",
|
|
"warpgroup_id",
|
|
"webgpu",
|
|
]
|
|
|
|
# Shorthand dtype aliases
|
|
__all__ += ["bf16", "f16", "f32", "f64", "i8", "i16", "i32", "i64", "u8", "u16", "u32", "u64"]
|