885 lines
26 KiB
Python
885 lines
26 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.
|
|
# ruff: noqa: F401
|
|
"""The expression nodes of Relax."""
|
|
|
|
import typing
|
|
from collections.abc import Callable, Mapping
|
|
from numbers import Integral, Number, Real
|
|
from typing import Any, Optional, Union
|
|
|
|
import numpy as _np # type: ignore
|
|
import tvm_ffi
|
|
from tvm_ffi.core import String
|
|
|
|
import tvm.ir
|
|
import tvm.relax
|
|
import tvm.runtime
|
|
from tvm import DataType
|
|
|
|
from ..ir import BaseFunc, Node, Span
|
|
from ..runtime import Scriptable
|
|
from . import _ffi_api
|
|
|
|
# It is a workaround for mypy: https://github.com/python/mypy/issues/7866#issuecomment-549454370
|
|
# This feature is not supported until python 3.10:
|
|
# https://docs.python.org/3.10/whatsnew/3.10.html#pep-613-typealias
|
|
Expr = tvm.ir.Expr
|
|
Type = tvm.ir.Type # pylint: disable=invalid-name
|
|
GlobalVar = tvm.ir.GlobalVar
|
|
|
|
|
|
def prim_value(value: Expr | int | float, dtype: str | None = None) -> Expr:
|
|
"""Convert a Python scalar or primitive expression to ``Expr``.
|
|
|
|
Parameters
|
|
----------
|
|
value : Expr | int | float
|
|
The value to convert.
|
|
|
|
dtype : Optional[str]
|
|
The dtype to use when converting Python numeric values.
|
|
|
|
Returns
|
|
-------
|
|
result : Expr
|
|
The converted primitive expression. Existing ``Expr`` inputs are
|
|
returned unchanged.
|
|
"""
|
|
if tvm.ir.is_prim_expr(value):
|
|
return value
|
|
if isinstance(value, bool | _np.bool_):
|
|
return tvm.tirx.IntImm(dtype or "bool", int(value))
|
|
if isinstance(value, Integral):
|
|
return tvm.tirx.IntImm(dtype or "int64", int(value))
|
|
if isinstance(value, Real):
|
|
return tvm.tirx.FloatImm(dtype or "float64", float(value))
|
|
tvm_value = tvm_ffi.convert(value)
|
|
if tvm.ir.is_prim_expr(tvm_value):
|
|
return tvm_value
|
|
raise TypeError(f"Cannot convert {value} with type {type(value)} to `Expr`")
|
|
|
|
|
|
def _relax_type_is_base_of(self: Type, derived: Type) -> bool:
|
|
"""Check if this Relax type is a base of another Relax type."""
|
|
|
|
return _ffi_api.TypeIsBaseOf(self, derived) # type: ignore
|
|
|
|
|
|
Type.is_base_of = _relax_type_is_base_of # type: ignore[attr-defined]
|
|
|
|
|
|
# will be registered afterwards in python/tvm/relax/op/init.py
|
|
_op_ffi_api = None # pylint: disable=invalid-name
|
|
|
|
|
|
def _binary_op_helper(lhs: "ExprWithOp", rhs: "ExprWithOp", op: Callable) -> "ExprWithOp":
|
|
if not isinstance(lhs, Expr): # type: ignore
|
|
raise ValueError("lhs must be Expr")
|
|
if isinstance(rhs, Expr): # type: ignore
|
|
return op(lhs, rhs)
|
|
elif isinstance(rhs, Number):
|
|
raise TypeError(f"Please convert {rhs} with `const` first")
|
|
else:
|
|
raise TypeError(f"type {type(rhs)} not supported")
|
|
|
|
|
|
def _binary_rhs_helper(rhs: "ExprWithOp") -> "ExprWithOp":
|
|
if isinstance(rhs, Number):
|
|
raise TypeError(f"Please convert {rhs} with `const` first")
|
|
raise TypeError(f"type {type(rhs)} not supported")
|
|
|
|
|
|
class ExprWithOp(Expr, Scriptable):
|
|
"""Basetype of all relax expressions that defines op overloading."""
|
|
|
|
def astype(self, dtype: str | DataType) -> "ExprWithOp":
|
|
"""Cast the content type of the current data to dtype.
|
|
|
|
Parameters
|
|
----------
|
|
dtype : str
|
|
The target data type.
|
|
|
|
Note
|
|
----
|
|
This function only works for TensorType Exprs.
|
|
|
|
Returns
|
|
-------
|
|
result : ExprWithOp
|
|
The result expression.
|
|
"""
|
|
return _op_ffi_api.astype(self, dtype) # type: ignore
|
|
|
|
def __neg__(self) -> "ExprWithOp":
|
|
return _op_ffi_api.negative(self) # type: ignore
|
|
|
|
def __lt__(self, other: Expr) -> "ExprWithOp":
|
|
return _binary_op_helper(self, other, _op_ffi_api.less) # type: ignore
|
|
|
|
def __gt__(self, other: Expr) -> "ExprWithOp":
|
|
return _binary_op_helper(self, other, _op_ffi_api.greater) # type: ignore
|
|
|
|
def __ge__(self, other: Expr) -> "ExprWithOp":
|
|
return _binary_op_helper(self, other, _op_ffi_api.greater_equal) # type: ignore
|
|
|
|
def __le__(self, other: Expr) -> "ExprWithOp":
|
|
return _binary_op_helper(self, other, _op_ffi_api.less_equal) # type: ignore
|
|
|
|
# NOTE: Cannot override __eq__ and __ne__, which will influence object equal
|
|
|
|
def __add__(self, other: Expr) -> "ExprWithOp":
|
|
if isinstance(self.ty, tvm.relax.TupleType) and isinstance(other, tuple):
|
|
return tuple([*self, *other])
|
|
|
|
return _binary_op_helper(self, other, _op_ffi_api.add) # type: ignore
|
|
|
|
def __radd__(self, other: Expr) -> "ExprWithOp":
|
|
return self.__add__(other)
|
|
|
|
def __sub__(self, other: Expr) -> "ExprWithOp":
|
|
return _binary_op_helper(self, other, _op_ffi_api.subtract) # type: ignore
|
|
|
|
def __rsub__(self, other: Expr) -> "ExprWithOp":
|
|
return _binary_rhs_helper(other)
|
|
|
|
def __mul__(self, other: Expr) -> "ExprWithOp":
|
|
return _binary_op_helper(self, other, _op_ffi_api.multiply) # type: ignore
|
|
|
|
def __rmul__(self, other: Expr) -> "ExprWithOp":
|
|
return self.__mul__(other)
|
|
|
|
def __truediv__(self, other: Expr) -> "ExprWithOp":
|
|
return _binary_op_helper(self, other, _op_ffi_api.divide) # type: ignore
|
|
|
|
def __rtruediv__(self, other: Expr) -> "ExprWithOp":
|
|
return _binary_rhs_helper(other)
|
|
|
|
def __floordiv__(self, other: Expr) -> "ExprWithOp":
|
|
return _binary_op_helper(self, other, _op_ffi_api.floor_divide) # type: ignore
|
|
|
|
def __rfloordiv__(self, other: Expr) -> "ExprWithOp":
|
|
return _binary_rhs_helper(other)
|
|
|
|
def __mod__(self, other: Expr) -> "ExprWithOp":
|
|
return _binary_op_helper(self, other, _op_ffi_api.mod) # type: ignore
|
|
|
|
def __rmod__(self, other: Expr) -> "ExprWithOp":
|
|
return _binary_rhs_helper(other)
|
|
|
|
def __pow__(self, other: Expr) -> "ExprWithOp":
|
|
return _binary_op_helper(self, other, _op_ffi_api.power) # type: ignore
|
|
|
|
def __rpow__(self, other: Expr) -> "ExprWithOp":
|
|
return _binary_rhs_helper(other)
|
|
|
|
def __call__(self, *args: list[Expr], attrs: dict[str, Any] | None = None) -> "ExprWithOp":
|
|
"""Call the variable (if it represents a function).
|
|
|
|
Parameters
|
|
----------
|
|
args: List[Expr]
|
|
The arguments to the call.
|
|
|
|
attr: Optional[Dict[str, object]]
|
|
The additional attributes to the call.
|
|
|
|
Returns
|
|
-------
|
|
call: ExprWithOp
|
|
A call taking the variable as a function.
|
|
"""
|
|
return tvm.ir.Call(self, args, attrs=attrs)
|
|
|
|
def __getitem__(self, index: int) -> "ExprWithOp":
|
|
"""Get the i-th element of the tuple or Expr with TupleType.
|
|
|
|
Parameters
|
|
----------
|
|
index: int
|
|
The index of the element to be retrieved.
|
|
|
|
Note
|
|
----
|
|
This function will be overridden by Tuple and ShapeExpr
|
|
|
|
Returns
|
|
-------
|
|
result: ExprWithOp
|
|
The result expression.
|
|
"""
|
|
try:
|
|
return TupleGetItem(self, index)
|
|
except RuntimeError as err:
|
|
# For Python objects with __getitem__, but without
|
|
# __len__, tuple unpacking is done by iterating over
|
|
# sequential indices until IndexError is raised.
|
|
# Therefore, convert from RuntimeError to IndexError for
|
|
# compatibility.
|
|
if "Index out of bounds" in err.args[0]:
|
|
raise IndexError from err
|
|
raise
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.If")
|
|
class If(ExprWithOp):
|
|
"""A conditional expression in Relax.
|
|
|
|
Parameters
|
|
----------
|
|
cond: Expr
|
|
The condition.
|
|
|
|
true_branch: Expr
|
|
The expression evaluated when condition is true.
|
|
|
|
false_branch: Expr
|
|
The expression evaluated when condition is false.
|
|
|
|
span: Optional[Span]
|
|
Span that points to original source code
|
|
"""
|
|
|
|
cond: Expr
|
|
true_branch: Expr
|
|
false_branch: Expr
|
|
span: Span | None
|
|
|
|
def __init__(self, cond: Expr, true_branch: Expr, false_branch: Expr, span: Span | None = None):
|
|
self.__init_handle_by_constructor__(
|
|
_ffi_api.If,
|
|
cond,
|
|
true_branch,
|
|
false_branch,
|
|
span, # type: ignore
|
|
)
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.Tuple")
|
|
class Tuple(ExprWithOp):
|
|
"""Tuple expression that groups several fields together.
|
|
|
|
Parameters
|
|
----------
|
|
fields : Union[List[Expr], typing.Tuple[Expr, ...]]
|
|
The fields in the tuple.
|
|
|
|
span: Optional[Span]
|
|
Span that points to original source code
|
|
"""
|
|
|
|
fields: list[Expr]
|
|
span: Span | None
|
|
|
|
def __init__(self, fields: list[Expr] | tuple[Expr, ...], span: Span | None = None):
|
|
if isinstance(fields, tvm.relax.Tuple):
|
|
fields = fields.fields
|
|
elif isinstance(getattr(fields, "ty", None), tvm.relax.TupleType):
|
|
fields = [*fields]
|
|
|
|
self.__init_handle_by_constructor__(_ffi_api.Tuple, fields, span) # type: ignore
|
|
|
|
def __getitem__(self, index: int) -> Expr:
|
|
if index >= len(self) or index < -len(self):
|
|
raise IndexError("Tuple index out of range")
|
|
return self.fields[index]
|
|
|
|
def __len__(self) -> int:
|
|
return len(self.fields)
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.TupleGetItem")
|
|
class TupleGetItem(ExprWithOp):
|
|
"""Get index-th item from a tuple.
|
|
|
|
Parameters
|
|
----------
|
|
tuple_value: Expr
|
|
The input tuple expression.
|
|
|
|
index: int
|
|
The index.
|
|
|
|
span: Optional[Span]
|
|
Span that points to original source code
|
|
"""
|
|
|
|
tuple_value: Expr
|
|
index: int
|
|
span: Span | None
|
|
|
|
def __init__(self, tuple_value: Expr, index: int, span: Span | None = None):
|
|
self.__init_handle_by_constructor__(
|
|
_ffi_api.TupleGetItem,
|
|
tuple_value,
|
|
index,
|
|
span, # type: ignore
|
|
)
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.ShapeExpr")
|
|
class ShapeExpr(ExprWithOp):
|
|
"""A shape expression which allows users to construct a shape containing Expr.
|
|
|
|
Parameters
|
|
----------
|
|
values: Union[List[Expr], typing.Tuple[Expr, ...], tvm_ffi.Array]
|
|
The values of the shape expression.
|
|
|
|
span: Optional[Span]
|
|
Span that points to original source code
|
|
"""
|
|
|
|
values: list[Expr]
|
|
span: Span | None
|
|
|
|
def __init__(
|
|
self,
|
|
values: list[Expr] | tuple[Expr, ...] | tvm_ffi.Array,
|
|
span: Span | None = None,
|
|
) -> None:
|
|
self.__init_handle_by_constructor__(_ffi_api.ShapeExpr, values, span) # type: ignore
|
|
|
|
def __getitem__(self, index):
|
|
if index >= len(self) or index < -len(self):
|
|
raise IndexError("ShapeExpr index out of range")
|
|
return self.values[index]
|
|
|
|
def __len__(self):
|
|
return len(self.values)
|
|
|
|
|
|
def make_shape(shape: list[Any] | tuple[Any, ...]) -> ShapeExpr:
|
|
if isinstance(shape, list | tuple):
|
|
return ShapeExpr(shape)
|
|
raise TypeError(
|
|
"make_shape expects a list or tuple of shape values, "
|
|
f"but received type {type(shape).__name__}"
|
|
)
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.Constant")
|
|
class Constant(ExprWithOp):
|
|
"""Constant Tensor
|
|
|
|
Parameters
|
|
----------
|
|
data: tvm.runtime.Tensor
|
|
The data of the constant tensor.
|
|
|
|
ty: Optional[Type]
|
|
The type of the constant tensor. If not specified, infer it from data.
|
|
|
|
span: Optional[Span]
|
|
Span that points to original source code
|
|
|
|
Note
|
|
----
|
|
Scalar constants are represented by ndim-0 constant tensors.
|
|
"""
|
|
|
|
data: tvm.runtime.Tensor
|
|
span: Span | None
|
|
|
|
def __init__(
|
|
self,
|
|
data: tvm.runtime.Tensor,
|
|
ty: Type | None = None,
|
|
span: Span | None = None,
|
|
) -> None:
|
|
self.__init_handle_by_constructor__(
|
|
_ffi_api.Constant,
|
|
data,
|
|
ty,
|
|
span, # type: ignore
|
|
)
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.Var")
|
|
class Var(ExprWithOp):
|
|
"""The variable class for all Relax bindings.
|
|
|
|
Parameters
|
|
----------
|
|
name_hint: str
|
|
The name hint of the variable.
|
|
|
|
ty: Optional[Type]
|
|
The type annotation of the variable.
|
|
|
|
span: Optional[Span]
|
|
Span that points to original source code
|
|
"""
|
|
|
|
name_hint: str
|
|
span: Span | None
|
|
|
|
def __init__(
|
|
self,
|
|
name_hint: str,
|
|
ty: Type | None = None,
|
|
span: Span | None = None,
|
|
) -> None:
|
|
if ty is not None:
|
|
ty = tvm.runtime.convert(ty)
|
|
if not isinstance(ty, Type):
|
|
raise TypeError(
|
|
"ty needs to be an instance of Type. "
|
|
"If you attempt to pass in shape, "
|
|
"use relax.TensorType(shape, dtype)."
|
|
)
|
|
self.__init_handle_by_constructor__(
|
|
_ffi_api.Var, # type: ignore
|
|
name_hint,
|
|
ty,
|
|
span,
|
|
)
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.DataflowVar")
|
|
class DataflowVar(Var):
|
|
"""A sub-type of the variable node used to mark dataflow variables from
|
|
normal visible "function local" bindings.
|
|
|
|
|
|
Parameters
|
|
----------
|
|
name_hint: str
|
|
The name hint of the variable.
|
|
|
|
ty: Optional[Type]
|
|
The type annotation of the variable.
|
|
|
|
span: Optional[Span]
|
|
Span that points to original source code
|
|
"""
|
|
|
|
name_hint: str
|
|
span: Span | None
|
|
|
|
def __init__(
|
|
self,
|
|
name_hint: str,
|
|
ty: Type | None = None,
|
|
span: Span | None = None,
|
|
) -> None:
|
|
# pylint: disable=super-init-not-called
|
|
if ty is not None:
|
|
ty = tvm.runtime.convert(ty)
|
|
if not isinstance(ty, Type):
|
|
raise TypeError(
|
|
"ty needs to be an instance of Type. "
|
|
"If you attempt to pass in shape, "
|
|
"use relax.TensorType(shape, dtype)."
|
|
)
|
|
|
|
self.__init_handle_by_constructor__(_ffi_api.DataflowVar, name_hint, ty, span) # type: ignore
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.StringImm")
|
|
class StringImm(Expr, Scriptable):
|
|
"""Represent a string literal constant."""
|
|
|
|
value: str
|
|
span: Span | None
|
|
|
|
def __init__(self, value: str, span: Span | None = None) -> None:
|
|
self.__init_handle_by_constructor__(_ffi_api.StringImm, value, span) # type: ignore
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.DataTypeImm")
|
|
class DataTypeImm(Expr, Scriptable):
|
|
"""Represent a data type constant."""
|
|
|
|
value: DataType
|
|
span: Span | None
|
|
|
|
def __init__(self, value: DataType | str, span: Span | None = None) -> None:
|
|
self.__init_handle_by_constructor__(_ffi_api.DataTypeImm, value, span) # type: ignore
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.Binding")
|
|
class Binding(Node, Scriptable):
|
|
"""The base class of a binding in Relax."""
|
|
|
|
var: Var
|
|
span: Span | None
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.MatchCast")
|
|
class MatchCast(Binding):
|
|
"""Runtime-match the value to the type.
|
|
|
|
This operation does runtime check, populates the un-defined symbolic shape vars
|
|
and vars in ty in the first occurrence, and insert equality assertions in
|
|
other cases.
|
|
|
|
Parameters
|
|
----------
|
|
var: Var
|
|
The return variable that the match cast bind to.
|
|
|
|
value: Expr
|
|
The input value expression.
|
|
|
|
ty: tvm.relax.Type
|
|
The type to match cast to.
|
|
"""
|
|
|
|
ty: Type
|
|
value: Expr
|
|
span: Span | None
|
|
|
|
def __init__(self, var: Var, value: Expr, ty: Type, span: Span | None = None) -> None:
|
|
self.__init_handle_by_constructor__(
|
|
_ffi_api.MatchCast,
|
|
var,
|
|
value,
|
|
ty,
|
|
span, # type: ignore
|
|
)
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.VarBinding")
|
|
class VarBinding(Binding):
|
|
"""Variable binding, bind he variable of the lhs with the rhs.
|
|
|
|
Parameters
|
|
----------
|
|
var: Var
|
|
The return variable that the match cast bind to.
|
|
|
|
value: Expr
|
|
The input value expression.
|
|
|
|
"""
|
|
|
|
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.VarBinding, var, value, span) # type: ignore
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.BindingBlock")
|
|
class BindingBlock(Node, Scriptable):
|
|
"""base class of binding block, bindings inside can be impure
|
|
(with side effect or control flow)"""
|
|
|
|
bindings: list[Binding]
|
|
span: Span | None
|
|
|
|
def __init__(self, bindings: list[Binding], span: Span | None = None) -> None:
|
|
self.__init_handle_by_constructor__(_ffi_api.BindingBlock, bindings, span) # type: ignore
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.DataflowBlock")
|
|
class DataflowBlock(BindingBlock):
|
|
"""dataflow block, bindings inside are pure (no side effect and no control flow)"""
|
|
|
|
bindings: list[Binding]
|
|
span: Span | None
|
|
|
|
def __init__(self, bindings: list[Binding], span: Span | None = None) -> None:
|
|
# pylint: disable=super-init-not-called
|
|
self.__init_handle_by_constructor__(_ffi_api.DataflowBlock, bindings, span) # type: ignore
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.SeqExpr")
|
|
class SeqExpr(ExprWithOp):
|
|
"""A sequence of binding blocks followed by an expression."""
|
|
|
|
blocks: list[BindingBlock]
|
|
body: Expr
|
|
span: Span | None
|
|
|
|
def __init__(self, blocks: list[BindingBlock], body: Expr, span: Span | None = None) -> None:
|
|
self.__init_handle_by_constructor__(_ffi_api.SeqExpr, blocks, body, span) # type: ignore
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.Function")
|
|
class Function(BaseFunc, Scriptable):
|
|
"""A Relax function."""
|
|
|
|
params: list[Var]
|
|
body: Expr
|
|
ret_ty: Type
|
|
is_pure: bool
|
|
attrs: tvm.ir.DictAttrs
|
|
span: Span | None
|
|
|
|
def __init__(
|
|
self,
|
|
params: list[Var],
|
|
body: Expr,
|
|
ret_ty: Type | None = None,
|
|
is_pure: bool | None = True,
|
|
attrs: tvm.ir.DictAttrs | None = None,
|
|
span: Span | None = None,
|
|
) -> None:
|
|
if attrs is None:
|
|
attrs = tvm.ir.DictAttrs({})
|
|
self.__init_handle_by_constructor__(
|
|
_ffi_api.Function,
|
|
params,
|
|
body,
|
|
ret_ty,
|
|
is_pure,
|
|
attrs,
|
|
span,
|
|
) # type: ignore
|
|
|
|
@staticmethod
|
|
def create_empty(
|
|
params: list[Var],
|
|
ret_ty: Type,
|
|
is_pure: bool | None = True,
|
|
attrs: tvm.ir.DictAttrs | None = None,
|
|
span: Span | None = None,
|
|
):
|
|
"""Construct a relax.Function but without body"""
|
|
if attrs is None:
|
|
attrs = tvm.ir.DictAttrs({})
|
|
return _ffi_api.FunctionCreateEmpty(params, ret_ty, is_pure, attrs, span) # type: ignore
|
|
|
|
def __call__(self, *args):
|
|
"""Invoke the global function.
|
|
|
|
Parameters
|
|
----------
|
|
args: List[relax.Expr]
|
|
Arguments.
|
|
"""
|
|
return tvm.ir.Call(self, args, None, None)
|
|
|
|
def bind_symbolic_vars(self, binding_map: Mapping[str | tvm.tirx.Var, Expr]) -> "Function":
|
|
"""Return a new function with updated symbolic variable
|
|
|
|
Parameters
|
|
----------
|
|
binding_map: Mapping[str | tvm.tirx.Var, Expr]
|
|
|
|
The mapping of values to be replaced. Keys may be either
|
|
a `tirx.Var` or a string name of the variable. If the
|
|
variables are referred to by name, the name must uniquely
|
|
identify a symbolic variable in the function.
|
|
|
|
Returns
|
|
-------
|
|
func: Function
|
|
|
|
The updated function
|
|
"""
|
|
|
|
# Relax uses int64 for symbolic variables, but the FFI
|
|
# converts python integers into int32.
|
|
binding_map = {
|
|
key: tvm.tirx.const(value, "int64") if isinstance(value, int) else value
|
|
for key, value in binding_map.items()
|
|
}
|
|
|
|
return _ffi_api.FunctionBindSymbolicVars(self, binding_map) # type: ignore
|
|
|
|
def bind_params(
|
|
self,
|
|
binding_map: Mapping[
|
|
str | Var,
|
|
int | float | Expr | tvm.runtime.Tensor | _np.ndarray,
|
|
],
|
|
) -> "Function":
|
|
"""Return a new function with updated symbolic variable
|
|
|
|
Parameters
|
|
----------
|
|
binding_map: Mapping[
|
|
str | Var,
|
|
int | float | Expr | tvm.runtime.Tensor | _np.ndarray,
|
|
]
|
|
|
|
The mapping of values to be replaced.
|
|
|
|
Keys may be either a `relax.Var` or a string name of the
|
|
Relax variable. If the variables are referred to by name,
|
|
the name must uniquely identify a parameter in the
|
|
function.
|
|
|
|
Values must be a relax expression, or a value that is
|
|
convertible into a relax expression. The value must be
|
|
compatible with the variable being replaced.
|
|
|
|
Returns
|
|
-------
|
|
func: Function
|
|
|
|
The updated function
|
|
"""
|
|
|
|
def _normalize_value(value):
|
|
# Conversions that must occur prior to the FFI
|
|
# conversions.
|
|
if isinstance(value, int):
|
|
# Relax uses int64 for symbolic variables, but the FFI
|
|
# converts python integers into int32.
|
|
return tvm.tirx.const(value, "int64")
|
|
elif isinstance(value, _np.ndarray | tvm.runtime.Tensor):
|
|
return tvm.relax.const(value)
|
|
else:
|
|
return value
|
|
|
|
binding_map = {key: _normalize_value(value) for key, value in binding_map.items()}
|
|
|
|
return _ffi_api.FunctionBindParams(self, binding_map) # type: ignore
|
|
|
|
def inline_functions(
|
|
self, function_map: Mapping[str | tvm.ir.GlobalVar, "Function"]
|
|
) -> "Function":
|
|
return _ffi_api.FunctionInlineFunctions(self, function_map) # type: ignore
|
|
|
|
|
|
@tvm_ffi.register_object("relax.expr.ExternFunc")
|
|
class ExternFunc(BaseFunc, ExprWithOp):
|
|
"""extern function, which represents a PackedFunc."""
|
|
|
|
global_symbol: String
|
|
span: Span | None
|
|
|
|
def __init__(
|
|
self,
|
|
global_symbol: String,
|
|
ty: Type | None = None,
|
|
span: Span | None = None,
|
|
) -> None:
|
|
self.__init_handle_by_constructor__(
|
|
_ffi_api.ExternFunc,
|
|
global_symbol,
|
|
ty,
|
|
span, # type: ignore
|
|
)
|
|
|
|
|
|
def extern(name: str, ty: Type | None = None, span: Span | None = None):
|
|
"""Create extern function."""
|
|
return ExternFunc(name, ty, span)
|
|
|
|
|
|
def const(
|
|
value: bool | int | float | _np.ndarray | tvm.runtime.Tensor, dtype: str | None = None
|
|
) -> Constant:
|
|
"""Create a constant value.
|
|
|
|
Parameters
|
|
----------
|
|
value: bool | int | float | numpy.ndarray | tvm.runtime.Tensor
|
|
The constant value.
|
|
|
|
dtype: Optional[str]
|
|
The data type of the resulting constant.
|
|
|
|
Note
|
|
----
|
|
When dtype is None, we use the following rule:
|
|
|
|
- int maps to "int32"
|
|
- float maps to "float32"
|
|
- bool maps to "bool"
|
|
- other using the same default rule as numpy.
|
|
"""
|
|
# Needed for bf16 and fp8 support (does not come with numpy)
|
|
import ml_dtypes # pylint: disable=unused-import,import-outside-toplevel
|
|
|
|
if isinstance(dtype, tvm.ir.PrimType):
|
|
dtype = dtype.dtype
|
|
|
|
if isinstance(value, Number | (bool | list)):
|
|
value = _np.array(value, dtype=dtype)
|
|
|
|
if not dtype:
|
|
# when dtype is None: int maps to "int32", float maps to "float32"
|
|
dtype = { # type: ignore
|
|
_np.dtype("int64"): _np.int32, # type: ignore
|
|
_np.dtype("float64"): _np.float32, # type: ignore
|
|
}.get(
|
|
value.dtype,
|
|
None, # type: ignore
|
|
)
|
|
|
|
if isinstance(value, _np.ndarray | _np.generic):
|
|
if dtype is not None:
|
|
value = value.astype(dtype)
|
|
value = tvm.runtime.tensor(value)
|
|
|
|
if not isinstance(value, tvm.runtime.Tensor):
|
|
raise ValueError("value has to be scalar or Tensor")
|
|
|
|
return Constant(value)
|
|
|
|
|
|
@tvm_ffi.register_object("relax.TEPlaceholderOp")
|
|
class TEPlaceholderOp(tvm.te.tensor.Operation):
|
|
"""The placeholder op that represents a relax expression."""
|
|
|
|
|
|
def te_tensor(
|
|
value: Expr, tir_var_map: dict[tvm.tirx.Var, tvm.tirx.Expr], name: str = "rxplaceholder"
|
|
):
|
|
"""Create a TE tensor from relax expression, with TIR variables in the
|
|
tensor shape substituted by the given mapping
|
|
|
|
Parameters
|
|
----------
|
|
value : Expr
|
|
The relax expression, which is required to have TensorType.
|
|
|
|
tir_var_map : Dict[tvm.tirx.Var, tvm.tirx.Expr]
|
|
The mapping to substitute the TIR variables appeared in the
|
|
shape of the input Expr.
|
|
|
|
name : str
|
|
The name of the created tensor.
|
|
"""
|
|
return _ffi_api.TETensor(value, tir_var_map, name) # type: ignore
|
|
|
|
|
|
def get_shape_of(expr: Expr) -> Expr:
|
|
"""Get shape of expr.
|
|
|
|
Parameters
|
|
----------
|
|
expr: Expr
|
|
The input expr.
|
|
|
|
Returns
|
|
-------
|
|
shape: Expr
|
|
The shape expression
|
|
|
|
Note
|
|
----
|
|
This function requires expr to be normalized.
|
|
The function will report an error if expr's Type is not TensorType.
|
|
It will try to return symbolic function when possible. If the tensor do not
|
|
have a compile-time symbolic shape, the function will then choose to return
|
|
`Call(relax.op.shape_of, [expr])`.
|
|
"""
|
|
return _ffi_api.GetShapeOf(expr) # type: ignore
|
|
|
|
|
|
def _update_type(expr: Expr, ty: Type | None) -> None:
|
|
_ffi_api.UpdateType(expr, ty) # type: ignore
|