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

1094 lines
31 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.
"""Statement AST Node in TVM.
Each statement node have subfields that can be visited from python side.
.. code-block:: python
x = tvm.tirx.Var("n", "int32")
buffer = tvm.tirx.decl_buffer((16,), "float32")
st = tvm.tirx.stmt.BufferStore(buffer, 1, (x,))
assert isinstance(st, tvm.tirx.stmt.BufferStore)
assert(st.buffer == buffer)
"""
from collections.abc import Mapping
from enum import IntEnum
from typing import TYPE_CHECKING, Any, ClassVar
import tvm_ffi
from tvm.ir import Expr, Op, Range, Span, is_prim_expr
from tvm.runtime import Object, Scriptable, const
from tvm.tirx import FloatImm, IntImm
from . import _ffi_api
from .buffer import Buffer
from .exec_scope import ExecScope, ScopeIdDef
from .expr import IterVar, StringImm, Var
if TYPE_CHECKING:
from tvm.tirx.operator.tile_primitive.dispatch_context import DispatchContext
@tvm_ffi.register_object("tirx.Stmt")
class Stmt(Object, Scriptable):
"""Base class of all the statements."""
def _normalize_legacy_stmt(stmt: Stmt | None) -> Stmt | None:
"""Expand legacy body-carrying leaf stmt wrappers into SeqStmt form.
Legacy python compatibility may attach a `body` attribute to leaf statements
(Bind/DeclBuffer/AllocBuffer). This helper converts such wrappers to the new
leaf + SeqStmt representation when embedding inside another statement node.
"""
if stmt is None:
return None
prefix: list[Stmt] = []
cur = stmt
while True:
if isinstance(cur, DeclBuffer) and hasattr(cur, "body"):
prefix.append(DeclBuffer(cur.buffer, cur.span))
cur = cur.body
continue
if isinstance(cur, AllocBuffer) and hasattr(cur, "body"):
prefix.append(AllocBuffer(cur.buffer, cur.annotations, cur.span))
cur = cur.body
continue
break
if not prefix:
return stmt
normalized_tail = _normalize_legacy_stmt(cur)
if normalized_tail is not None:
prefix.append(normalized_tail)
if len(prefix) == 1:
return prefix[0]
return SeqStmt(prefix)
@tvm_ffi.register_object("tirx.Bind")
class Bind(Stmt):
"""Bind node.
Bind a variable to a value in the enclosing scope.
Bind has no body field.
The bound variable is visible in all subsequent statements
within the same enclosing scope (SeqStmt, ForNode.body, etc.).
Parameters
----------
var : Var
The variable in the binding.
value : Expr
The value to be bound.
span : Optional[Span]
The location of the stmt in the source code.
"""
var: Var
value: Expr
span: Span | None
def __init__(self, var: Var, value: Expr, span: Span | None = None) -> None:
self.__init_handle_by_constructor__(
_ffi_api.Bind,
var,
value,
span, # type: ignore
)
@tvm_ffi.register_object("tirx.AssertStmt")
class AssertStmt(Stmt):
"""AssertStmt node.
Parameters
----------
kind : StringImm
The error kind, e.g. "RuntimeError", "TypeError", "ValueError".
condition : Expr
The assert condition.
message_parts : list[StringImm]
Error message fragments, concatenated at runtime when assertion fails.
span : Span | None
The location of the stmt in the source code.
"""
kind: StringImm
condition: Expr
message_parts: list
span: Span | None
def __init__(
self,
kind: StringImm,
condition: Expr,
message_parts: list | None = None,
span: Span | None = None,
) -> None:
if message_parts is None:
message_parts = []
self.__init_handle_by_constructor__(
_ffi_api.AssertStmt,
kind,
condition,
message_parts,
span, # type: ignore
)
class ForKind(IntEnum):
"""The kind of the for loop.
note
----
ForKind can change the control flow semantics
of the loop and need to be considered in all TIR passes.
"""
SERIAL = 0
PARALLEL = 1
VECTORIZED = 2
UNROLLED = 3
THREAD_BINDING = 4 # pylint: disable=invalid-name
@tvm_ffi.register_object("tirx.For")
class For(Stmt):
"""For node.
Parameters
----------
loop_var : Var
The loop variable.
min : Expr
The beginning value.
extent : Expr
The length of the loop.
kind : ForKind
The type of the for.
body : Stmt
The body statement.
thread_binding: Optional[tirx.IterVar]
The thread this loop binds to. Only valid
if kind is ThreadBinding
step : Expr
The loop step. Default to none which
represent one.
annotations: Optional[Mapping[str, Object]]
Additional annotation hints.
span : Optional[Span]
The location of the stmt in the source code.
"""
loop_var: Var
min: Expr
extent: Expr
kind: ForKind
body: Stmt
thread_binding: IterVar | None
annotations: Mapping[str, Object]
step: Expr | None
span: Span | None
def __init__(
self,
loop_var: Var,
min: Expr, # pylint: disable=redefined-builtin
extent: Expr,
kind: ForKind,
body: Stmt,
thread_binding: IterVar | None = None,
annotations: Mapping[str, Object] | None = None,
step: Expr | None = None,
span: Span | None = None,
) -> None:
body = _normalize_legacy_stmt(body)
self.__init_handle_by_constructor__(
_ffi_api.For, # type: ignore
loop_var,
min,
extent,
kind,
body,
thread_binding,
annotations,
step,
span,
)
@tvm_ffi.register_object("tirx.While")
class While(Stmt):
"""While node.
Parameters
----------
condition : Expr
The termination condition.
body : Stmt
The body statement.
span : Optional[Span]
The location of the stmt in the source code.
"""
condition: Expr
body: Stmt
span: Span | None
def __init__(self, condition: Expr, body: Stmt, span: Span | None = None) -> None:
body = _normalize_legacy_stmt(body)
self.__init_handle_by_constructor__(_ffi_api.While, condition, body, span) # type: ignore
@tvm_ffi.register_object("tirx.BufferStore")
class BufferStore(Stmt):
"""Buffer store node.
Parameters
----------
buffer : Buffer
The buffer.
value : Expr
The value we to be stored.
indices : List[Expr]
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.
span : Optional[Span]
The location of the stmt in the source code.
"""
buffer: Buffer
value: Expr
indices: list[Expr]
predicate: Expr | None
span: Span | None
def __init__(
self,
buffer: Buffer,
value: Expr,
indices: list[Expr],
predicate: Expr | None = None,
span: Span | None = None,
) -> None:
self.__init_handle_by_constructor__(
_ffi_api.BufferStore,
buffer,
value,
indices,
predicate,
span, # type: ignore
)
@tvm_ffi.register_object("tirx.AllocBuffer")
class AllocBuffer(Stmt):
"""AllocBuffer node.
Allocates a buffer and declares it in scope.
Parameters
----------
buffer: Buffer
The buffer being allocated and declared.
annotations: Optional[dict]
Additional annotations about the allocation.
span: Optional[Span]
The location of this AllocBuffer in the source code.
"""
buffer: Buffer
span: Span | None
def __init__(self, buffer: Buffer, *args, **kwargs) -> None:
body: Stmt | None = None
annotations: dict | None = None
span: Span | None = None
idx = 0
argc = len(args)
# Legacy form: AllocBuffer(buffer, body[, annotations][, span])
if idx < argc and isinstance(args[idx], Stmt):
body = args[idx]
idx += 1
if idx < argc:
arg = args[idx]
if isinstance(arg, Mapping):
annotations = dict(arg)
idx += 1
elif arg is None:
annotations = None
idx += 1
elif isinstance(arg, Span):
span = arg
idx += 1
else:
raise TypeError(
"AllocBuffer expects (buffer[, annotations][, span]) or "
"legacy (buffer, body[, annotations][, span])"
)
if idx < argc:
arg = args[idx]
if arg is None or isinstance(arg, Span):
span = arg
idx += 1
else:
raise TypeError("AllocBuffer span must be a Span or None")
if idx != argc:
raise TypeError(
"AllocBuffer expects (buffer[, annotations][, span]) or "
"legacy (buffer, body[, annotations][, span])"
)
if kwargs:
invalid_keys = set(kwargs.keys()) - {"body", "annotations", "span"}
if invalid_keys:
raise TypeError(f"Unexpected keyword arguments for AllocBuffer: {invalid_keys}")
if "body" in kwargs:
kw_body = kwargs["body"]
if kw_body is not None and not isinstance(kw_body, Stmt):
raise TypeError("AllocBuffer body must be a Stmt or None")
if body is not None and kw_body is not None and body is not kw_body:
raise TypeError("AllocBuffer body specified by both args and kwargs")
body = kw_body if kw_body is not None else body
if "annotations" in kwargs:
kw_ann = kwargs["annotations"]
if kw_ann is not None and not isinstance(kw_ann, Mapping):
raise TypeError("AllocBuffer annotations must be Mapping or None")
if annotations is not None and kw_ann is not None and annotations != dict(kw_ann):
raise TypeError("AllocBuffer annotations specified by both args and kwargs")
annotations = dict(kw_ann) if kw_ann is not None else annotations
if "span" in kwargs:
kw_span = kwargs["span"]
if kw_span is not None and not isinstance(kw_span, Span):
raise TypeError("AllocBuffer span must be a Span or None")
if span is not None and kw_span is not None and span is not kw_span:
raise TypeError("AllocBuffer span specified by both args and kwargs")
span = kw_span if kw_span is not None else span
self.__init_handle_by_constructor__(_ffi_api.AllocBuffer, buffer, annotations, span)
# Legacy compatibility. Body is carried on python side only.
if body is not None:
self.body = body
@tvm_ffi.register_object("tirx.DeclBuffer")
class DeclBuffer(Stmt):
"""DeclBuffer node.
Parameters
----------
buffer: Buffer
The buffer being declared.
span: Optional[Span]
The location of this DeclBuffer in the source code.
"""
buffer: Buffer
span: Span | None
def __init__(self, buffer: Buffer, *args, **kwargs) -> None:
body: Stmt | None = None
span: Span | None = None
if len(args) == 1:
arg0 = args[0]
if isinstance(arg0, Stmt):
body = arg0
elif arg0 is None or isinstance(arg0, Span):
span = arg0
else:
raise TypeError(
"DeclBuffer expects (buffer[, span]) or legacy (buffer, body[, span])"
)
elif len(args) == 2:
body, span = args
if body is not None and not isinstance(body, Stmt):
raise TypeError("Legacy DeclBuffer body must be a Stmt or None")
if span is not None and not isinstance(span, Span):
raise TypeError("DeclBuffer span must be a Span or None")
elif len(args) > 2:
raise TypeError("DeclBuffer expects (buffer[, span]) or legacy (buffer, body[, span])")
if kwargs:
invalid_keys = set(kwargs.keys()) - {"body", "span"}
if invalid_keys:
raise TypeError(f"Unexpected keyword arguments for DeclBuffer: {invalid_keys}")
if "body" in kwargs:
kw_body = kwargs["body"]
if kw_body is not None and not isinstance(kw_body, Stmt):
raise TypeError("DeclBuffer body must be a Stmt or None")
if body is not None and kw_body is not None and body is not kw_body:
raise TypeError("DeclBuffer body specified by both args and kwargs")
body = kw_body if kw_body is not None else body
if "span" in kwargs:
kw_span = kwargs["span"]
if kw_span is not None and not isinstance(kw_span, Span):
raise TypeError("DeclBuffer span must be a Span or None")
if span is not None and kw_span is not None and span is not kw_span:
raise TypeError("DeclBuffer span specified by both args and kwargs")
span = kw_span if kw_span is not None else span
self.__init_handle_by_constructor__(_ffi_api.DeclBuffer, buffer, span)
# Legacy compatibility. Body is carried on python side only.
if body is not None:
self.body = body
@tvm_ffi.register_object("tirx.AttrStmt")
class AttrStmt(Stmt):
"""AttrStmt node.
Parameters
----------
node : Object
The node to annotate the attribute
attr_key : str
Attribute type key.
value : Expr
The value of the attribute
body : Stmt
The body statement.
span : Optional[Span]
The location of the stmt in the source code.
"""
node: Object
attr_key: str
value: Expr
body: Stmt
span: Span | None
def __init__(
self, node: Object, attr_key: str, value: Expr, body: Stmt, span: Span | None = None
) -> None:
body = _normalize_legacy_stmt(body)
self.__init_handle_by_constructor__(
_ffi_api.AttrStmt,
node,
attr_key,
value,
body,
span, # type: ignore
)
@tvm_ffi.register_object("tirx.SeqStmt")
class SeqStmt(Stmt):
"""Sequence of statements.
Parameters
----------
seq : List[Stmt]
The statements
span : Optional[Span]
The location of the stmt in the source code.
"""
seq: list[Stmt]
span: Span | None
def __init__(self, seq: list[Stmt], span: Span | None = None) -> None:
seq = [_normalize_legacy_stmt(s) for s in seq]
self.__init_handle_by_constructor__(_ffi_api.SeqStmt, seq, span) # type: ignore
def __getitem__(self, i: int):
return self.seq[i]
def __len__(self):
return len(self.seq)
@tvm_ffi.register_object("tirx.IfThenElse")
class IfThenElse(Stmt):
"""IfThenElse node.
Parameters
----------
condition : Expr
The expression
then_case : Stmt
The statement to execute if condition is true.
else_case : Optional[Stmt]
The statement to execute if condition is false.
span : Optional[Span]
The location of the stmt in the source code.
"""
condition: Expr
then_case: Stmt
else_case: Stmt | None
def __init__(
self, condition: Expr, then_case: Stmt, else_case: Stmt | None, span: Span | None = None
) -> None:
then_case = _normalize_legacy_stmt(then_case)
else_case = _normalize_legacy_stmt(else_case)
self.__init_handle_by_constructor__(
_ffi_api.IfThenElse,
condition,
then_case,
else_case,
span, # type: ignore
)
@tvm_ffi.register_object("tirx.Evaluate")
class Evaluate(Stmt):
"""Evaluate node.
Parameters
----------
value : Expr
The expression to be evaluated.
span : Optional[Span]
The location of the stmt in the source code.
"""
value: Expr
span: Span | None
def __init__(self, value: Expr, span: Span | None = None) -> None:
self.__init_handle_by_constructor__(_ffi_api.Evaluate, value, span) # type: ignore
@tvm_ffi.register_object("tirx.BufferRegion")
class BufferRegion(Object, Scriptable):
"""BufferRegion node.
Parameters
----------
buffer : Buffer
The buffer of the buffer region
region : List[Range]
The region array of the buffer region
"""
buffer: Buffer
region: list[Range]
def __init__(self, buffer: Buffer, region: list[Range]) -> None:
self.__init_handle_by_constructor__(_ffi_api.BufferRegion, buffer, region) # type: ignore
def __getitem__(self, indices):
from ..arith import Analyzer
if not isinstance(indices, tuple | list):
indices = [indices]
has_step = any(
isinstance(i, slice) and (i.step is not None and i.step != 1) for i in indices
)
if has_step:
raise ValueError("BufferRegion slicing does not support steps")
analyzer = Analyzer()
new_region = []
for i, index in enumerate(indices):
old_range = self.region[i]
if isinstance(index, slice):
start = 0 if index.start is None else index.start
stop = old_range.extent if index.stop is None else index.stop
new_min = old_range.min + start
new_extent = analyzer.simplify(stop - start)
new_region.append(Range.from_min_extent(new_min, new_extent))
else:
new_min = old_range.min + index
new_region.append(
Range.from_min_extent(
new_min, IntImm(index.ty, 1) if is_prim_expr(index) else 1
)
)
# Fill remaining dimensions with their original ranges
for i in range(len(indices), len(self.region)):
new_region.append(self.region[i])
return BufferRegion(self.buffer, new_region)
@tvm_ffi.register_object("tirx.MatchBufferRegion")
class MatchBufferRegion(Object, Scriptable):
"""MatchBufferRegion node.
Parameters
----------
buffer : Buffer
The target buffer
source : BufferRegion
The region of source buffer
"""
buffer: Buffer
source: BufferRegion
def __init__(self, buffer: Buffer, source: BufferRegion) -> None:
self.__init_handle_by_constructor__(
_ffi_api.MatchBufferRegion,
buffer,
source, # type: ignore
)
@tvm_ffi.register_object("tirx.SBlock")
class SBlock(Stmt):
"""SBlock node.
Parameters
----------
iter_vars : List[IterVar]
The block Variable.
reads : List[BufferRegion]
The read buffer regions of the block.
writes: List[BufferRegion]
The write buffer regions of the block.
name_hint: str
the name_hint of the block.
body: Stmt
The body of the block.
init: Optional[Stmt]
The init block of the reduction block
alloc_buffers: Optional[list[Buffer]]
The buffer allocations
match_buffers: Optional[List[MatchBufferRegion]]
The subregion buffer match
annotations: Optional[Mapping[str, Object]]
Additional annotation hints.
span : Optional[Span]
The location of this block in the source code.
"""
iter_vars: list[IterVar]
reads: list[BufferRegion]
writes: list[BufferRegion]
name_hint: str
body: Stmt
init: Stmt | None
alloc_buffers: list[Buffer]
match_buffers: list[MatchBufferRegion]
annotations: Mapping[str, Object]
span: Span | None
def __init__(
self,
iter_vars: list[IterVar],
reads: list[BufferRegion],
writes: list[BufferRegion],
name_hint: str,
body: Stmt,
init: Stmt | None = None,
alloc_buffers: list[Buffer] | None = None,
match_buffers: list[MatchBufferRegion] | None = None,
annotations: Mapping[str, Object] | None = None,
span: Span | None = None,
) -> None:
if alloc_buffers is None:
alloc_buffers = []
if match_buffers is None:
match_buffers = []
if annotations is None:
annotations = {}
body = _normalize_legacy_stmt(body)
init = _normalize_legacy_stmt(init)
self.__init_handle_by_constructor__(
_ffi_api.SBlock, # type: ignore
iter_vars,
reads,
writes,
name_hint,
body,
init,
alloc_buffers,
match_buffers,
annotations,
span,
) # type: ignore
@tvm_ffi.register_object("tirx.SBlockRealize")
class SBlockRealize(Stmt):
"""SBlockRealize node.
Parameters
----------
iter_values : List[Expr]
The binding values of the block var.
predicate : Union[Expr, bool]
The predicate of the block.
block : SBlock
The block to realize
span : Optional[Span]
The location of this block_realize in the source code.
"""
iter_values: list[Expr]
predicate: Expr
block: SBlock
span: Span | None
def __init__(
self,
iter_values: list[Expr],
predicate: Expr | bool,
block: SBlock,
span: Span | None = None,
) -> None:
if isinstance(predicate, bool):
predicate = const(predicate, "bool")
self.__init_handle_by_constructor__(
_ffi_api.SBlockRealize, # type: ignore
iter_values,
predicate,
block,
span,
) # type: ignore
@tvm_ffi.register_object("tirx.ScopeIdDefStmt")
class ScopeIdDefStmt(Stmt):
"""ScopeIdDefStmt node.
Leaf statement that introduces scope-identifier vars
(``wg_id = Tx.warpgroup_id([N])``, ``warp_id = Tx.warp_id_in_wg([4])``,
``lane_id = Tx.lane_id([32])``, …) at the kernel-body top level. The
underlying ``ScopeIdDef`` carries the def vars, their extents, and
the parent/child scope binding.
Note: the C++ field is named ``def`` (a Python keyword). Access it
via ``getattr(stmt, "def")`` or ``stmt.__getattribute__("def")`` —
the type-annotation alias here is purely for documentation.
Parameters
----------
def_ : ScopeIdDef
The scope-id definition (def vars, extents, scope binding).
span : Optional[Span]
The location of this statement in the source code.
"""
span: Span | None
def __init__(self, def_: ScopeIdDef, span: Span | None = None) -> None:
self.__init_handle_by_constructor__(
_ffi_api.ScopeIdDefStmt, # type: ignore
def_,
span,
) # type: ignore
@tvm_ffi.register_object("tirx.Break")
class Break(Stmt):
"""Break node.
Parameters
----------
"""
def __init__(self, span: Span | None = None) -> None:
self.__init_handle_by_constructor__(_ffi_api.Break, span) # type: ignore
@tvm_ffi.register_object("tirx.Continue")
class Continue(Stmt):
"""Continue node.
Parameters
----------
"""
def __init__(self, span: Span | None = None) -> None:
self.__init_handle_by_constructor__(_ffi_api.Continue, span) # type: ignore
def stmt_seq(*args: Expr | Stmt) -> SeqStmt:
"""Make sequence of statements
Parameters
----------
*args : Union[Expr, Stmt]
List of statements to be combined as sequence.
Returns
-------
stmt : Stmt
The combined statement.
"""
ret = []
for value in args:
if not isinstance(value, Stmt):
value = Evaluate(value)
ret.append(value)
if len(ret) == 1:
return ret[0]
return SeqStmt(ret)
def stmt_list(stmt: Stmt) -> list[Stmt]:
"""Make list of stmt from blocks.
Parameters
----------
stmt : Stmt
The input statement.
Returns
-------
stmt_list : List[Stmt]
The unpacked list of statements
"""
if isinstance(stmt, SeqStmt):
res = []
for x in stmt:
res += stmt_list(x)
return res
return [stmt]
def normalize_const_arg(arg) -> Expr:
if isinstance(arg, float):
return FloatImm("float32", arg)
return arg
@tvm_ffi.register_object("tirx.TilePrimitiveCall")
class TilePrimitiveCall(Stmt):
"""TilePrimitiveCall node.
Parameters
----------
op : Op
The operator.
args : List[Expr]
The arguments.
workspace : Map[str, Buffer]
The workspace.
config : Map[str, ObjectRef]
The scheduler/config dictionary.
dispatch : Optional[str]
The explicit variant name to dispatch to.
scope : ExecScope
The cooperation scope of this call. Defaults to ``thread`` (an unscoped call).
"""
args: list[Expr]
workspace: dict[str, Buffer]
config: dict[str, Any]
dispatch: str | None
scope: ExecScope
_registry: ClassVar[dict[Op, type["TilePrimitiveCall"]]] = {}
def __init__(
self,
*args: list[Expr],
op: Op | None = None,
workspace: dict[str, Buffer] | None = None,
config: dict[str, Any] | None = None,
dispatch: str | None = None,
scope: ExecScope | None = None,
) -> None:
if workspace is None:
workspace = {}
if config is None:
config = {}
if scope is None:
scope = ExecScope("thread")
if op is None:
assert self.__class__ != TilePrimitiveCall, (
"Directly instantiating TilePrimitiveCall needs to specify the op"
)
op = self.__class__.op
args = list(map(normalize_const_arg, args))
self.__init_handle_by_constructor__(
_ffi_api.TilePrimitiveCall,
op,
args,
workspace,
config,
dispatch,
scope, # pylint: disable=no-member
)
def __init_subclass__(cls, **kwargs):
super().__init_subclass__(**kwargs)
if hasattr(cls, "op"):
cls._registry[cls.op] = cls
@classmethod
def downcast(cls, instance: "TilePrimitiveCall") -> "TilePrimitiveCall":
subclass = cls._registry.get(instance.op)
if subclass is None:
return instance # Unknown op: return as-is
new_instance = subclass.__new__(subclass)
new_instance.__init_handle_by_constructor__(
_ffi_api.TilePrimitiveCallCopyHandle,
instance, # pylint: disable=no-member
)
return new_instance
def replace(self, **changes: Any) -> "TilePrimitiveCall":
"""Return a copy of this call with selected fields replaced.
Every field that is not overridden in ``changes`` is preserved from
``self`` (including ``scope``), so rebuilds never silently drop fields.
The returned node is downcast to the registered subclass for ``op``.
Parameters
----------
**changes : Any
Field overrides; any of ``op``, ``args``, ``workspace``, ``config``,
``dispatch``, ``scope``.
Returns
-------
new_call : TilePrimitiveCall
A new call with the requested fields replaced.
"""
unknown = set(changes) - {"op", "args", "workspace", "config", "dispatch", "scope"}
if unknown:
raise TypeError(f"Unknown field(s) for TilePrimitiveCall.replace: {sorted(unknown)}")
new_call = TilePrimitiveCall(
*changes.get("args", self.args),
op=changes.get("op", self.op),
workspace=changes.get("workspace", self.workspace),
config=changes.get("config", self.config),
dispatch=changes.get("dispatch", self.dispatch),
scope=changes.get("scope", self.scope),
)
return TilePrimitiveCall.downcast(new_call)
def with_workspace(self, workspace: dict[str, Buffer]) -> "TilePrimitiveCall":
"""Return a copy with ``workspace`` replaced, preserving all other fields."""
return self.replace(workspace=workspace)
@property
def srcs(self) -> list[Expr]:
raise NotImplementedError("Subclass must implement this method")
@property
def dsts(self) -> list[Expr]:
raise NotImplementedError("Subclass must implement this method")
def get_private_buffers(
self, buffer_dict: dict[Any, tuple[Buffer, Stmt | None]], sctx: "DispatchContext"
) -> dict[str, Any]:
"""
Create private (intermediate) buffers needed in this operator.
Parameters
----------
buffer_dict: Dict[Any, Tuple[Buffer, Optional[Stmt]]]
A dictionary containing private buffers (and their init stmts) in other operators.
Key can be anything to reference the buffer.
This is used to reuse private buffers in other operators (like identity tensor etc.).
If the buffer is not found in the buffer_dict, it will be created and added to
the buffer_dict.
If the buffer is found in the buffer_dict but smaller than required, it will be
enlarged and updated.
sctx: DispatchContext
The dispatch context.
This is used to get the target and reuse op dispatch implementations.
Returns:
private_buffer_refs: Dict[str, Any]
The references to private buffers created in this operator.
Key will be the name to add into workspace.
private buffer can be accessed by buffer_dict[private_buffer_refs[name]]
"""
if sctx.target.kind.name == "trn":
return self.get_private_buffers_trn(buffer_dict, sctx)
elif sctx.target.kind.name == "cuda":
return self.get_private_buffers_cuda(buffer_dict, sctx)
else:
raise ValueError(f"Unsupported target: {sctx.target.kind.name}")
def get_private_buffers_trn(
self, buffer_dict: dict[Any, tuple[Buffer, Stmt | None]], sctx: "DispatchContext"
) -> dict[str, Any]:
return {}
def get_private_buffers_cuda(
self, buffer_dict: dict[Any, tuple[Buffer, Stmt | None]], sctx: "DispatchContext"
) -> dict[str, Any]:
return {}
def validate(self) -> None:
pass