/* * 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. */ #ifndef TVM_RELAX_EXPR_H_ #define TVM_RELAX_EXPR_H_ #include #include #include #include #include #include #include #include #include #include #include #include namespace tvm { namespace relax { /*! \brief Tuple container */ class TupleNode : public ExprNode { public: /*! \brief the fields of the tuple */ tvm::ffi::Array fields; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef().def_ro("fields", &TupleNode::fields); } TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.expr.Tuple", TupleNode, ExprNode); }; class Tuple : public Expr { public: /*! * \brief The constructor * \param fields The fields of a tuple. * \param span The source span of the expression. */ TVM_DLL explicit Tuple(tvm::ffi::Array fields, Span span = Span()); /*! * \brief Utility constructor to handle conversion to relax::Expr * * If the calling scope already has an array of a specific type of * relax expression (e.g. `ffi::Array`), it must be converted * into an array of base type. This constructor handles the * conversion to the base `ffi::Array`. * * \tparam ExprType The type of relax expression passed in as an argument. * * \param fields The fields of a tuple. * * \param span The source span of the expression. */ template >> TVM_DLL explicit Tuple(tvm::ffi::Array fields, Span span = Span()) : Tuple(fields.Map([](const ExprType& expr) -> Expr { return expr; }), span) {} TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Tuple, Expr, TupleNode); TVM_DEFINE_OBJECT_REF_COW_METHOD(TupleNode); }; /*! \brief Get index-th field out of a tuple. */ class TupleGetItemNode : public ExprNode { public: /*! \brief The tuple Expression */ Expr tuple; /*! \brief which value to get */ int index; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef() .def_ro("tuple_value", &TupleGetItemNode::tuple) .def_ro("index", &TupleGetItemNode::index); } TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.expr.TupleGetItem", TupleGetItemNode, ExprNode); }; class TupleGetItem : public Expr { public: /*! * \brief The constructor * \param tuple The tuple to get an element from. * \param index The index for extracting a value in the tuple. * \param span The source span of the expression. */ TVM_DLL TupleGetItem(Expr tuple, int index, Span span = Span()); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TupleGetItem, Expr, TupleGetItemNode); TVM_DEFINE_OBJECT_REF_COW_METHOD(TupleGetItemNode); }; /*! \brief A shape expression which allows users to construct a shape containing PrimExpr. */ class ShapeExprNode : public ExprNode { public: /*! The values of the shape expression. */ ffi::Array values; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef().def_ro("values", &ShapeExprNode::values); } TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.expr.ShapeExpr", ShapeExprNode, ExprNode); }; class ShapeExpr : public Expr { public: TVM_DLL explicit ShapeExpr(ffi::Array values, Span span = Span()); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(ShapeExpr, Expr, ShapeExprNode); TVM_DEFINE_OBJECT_REF_COW_METHOD(ShapeExprNode); }; /*! \brief The variable class for all Relax bindings. */ class VarNode : public ExprNode { public: /*! * \brief The hint to the variable name. * \note Each variable is uniquely identified by its address. */ ffi::String name_hint; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef().def_ro("name_hint", &VarNode::name_hint, refl::AttachFieldFlag::SEqHashIgnore()); } static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindFreeVar; static constexpr const uint32_t _type_child_slots = 1; TVM_FFI_DECLARE_OBJECT_INFO("relax.expr.Var", VarNode, ExprNode); }; class Var : public Expr { public: TVM_DLL explicit Var(ffi::String name_hint, ffi::Optional ty_annotation, Span span = Span()); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Var, Expr, VarNode); }; /*! \brief A sub-type of the variable node used to mark dataflow variables from * normal visible "function local" bindings. */ class DataflowVarNode : public VarNode { public: static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef(); } static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindFreeVar; TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.expr.DataflowVar", DataflowVarNode, VarNode); }; class DataflowVar : public Var { public: TVM_DLL explicit DataflowVar(ffi::String name_hint, ffi::Optional ty_annotation, Span span = Span()); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(DataflowVar, Var, DataflowVarNode); }; /*! * \brief Constant tensor. * * \note Scalar constants are represented by ndim-0 constant tensors. */ class ConstantNode : public ExprNode { public: /*! \brief The data of the tensor */ runtime::Tensor data; /*! \return The corresponding tensor type of the data */ TensorType tensor_type() const; /*! \return Whether it is scalar(ndim-0 tensor) */ bool is_scalar() const { return data->ndim == 0; } static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef().def_ro("data", &ConstantNode::data); } TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.expr.Constant", ConstantNode, ExprNode); }; class Constant : public Expr { public: /*! * \brief The constructor * \param data The data of the constant tensor. * \param ty_annotation The type of the constant tensor. * If not specified, infer it from data. * \param span The source span of the expression. */ TVM_DLL explicit Constant(runtime::Tensor data, ffi::Optional ty_annotation = std::nullopt, Span span = Span()); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Constant, Expr, ConstantNode); TVM_DEFINE_OBJECT_REF_COW_METHOD(ConstantNode); }; /*! * \brief Represent a string literal constant. */ class StringImmNode : public ExprNode { public: /*! \brief The data value. */ ffi::String value; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef().def_ro("value", &StringImmNode::value); } TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.expr.StringImm", StringImmNode, ExprNode); }; /*! * \brief Managed reference to StringImm * \sa StringImmNode */ class StringImm : public Expr { public: /*! * \brief The constructor * \param value The value input. * \param span The source span of the expression. */ TVM_DLL explicit StringImm(ffi::String value, Span span = Span()); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(StringImm, Expr, StringImmNode); TVM_DEFINE_OBJECT_REF_COW_METHOD(StringImmNode); }; /*! * \brief Represent a data type constant. */ class DataTypeImmNode : public ExprNode { public: /*! \brief The data value. */ DLDataType value; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef().def_ro("value", &DataTypeImmNode::value); } TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.expr.DataTypeImm", DataTypeImmNode, ExprNode); }; /*! * \brief Managed reference to DataTypeImm * \sa DataTypeImmNode */ class DataTypeImm : public Expr { public: /*! * \brief The constructor * \param value The value input. * \param span The source span of the expression. */ TVM_DLL explicit DataTypeImm(DLDataType value, Span span = Span()); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(DataTypeImm, Expr, DataTypeImmNode); TVM_DEFINE_OBJECT_REF_COW_METHOD(DataTypeImmNode); }; /*! \brief The base class of a variable binding in Relax. */ class BindingNode : public ffi::Object { public: mutable Span span; /*! \brief The return variable to bound to. */ Var var; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef() .def_ro("span", &BindingNode::span, refl::AttachFieldFlag::SEqHashIgnore()) // TODO(tqchen): use SEqHashDefNonRecursive after the next pypi tvm-ffi release .def_ro("var", &BindingNode::var, refl::AttachFieldFlag::SEqHashDefRecursive()); } static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode; TVM_FFI_DECLARE_OBJECT_INFO("relax.expr.Binding", BindingNode, ffi::Object); }; class Binding : public ffi::ObjectRef { protected: Binding() = default; public: explicit Binding(ffi::ObjectPtr n) : ffi::ObjectRef(n) {} explicit Binding(ffi::UnsafeInit tag) : ffi::ObjectRef(tag) {} Binding(const Binding&) = default; Binding(Binding&&) = default; Binding& operator=(const Binding&) = default; Binding& operator=(Binding&&) = default; const BindingNode* operator->() const { return static_cast(data_.get()); } const BindingNode* get() const { return operator->(); } using ContainerType = BindingNode; }; /*! * \brief Runtime-match the value to the type. * * This operation does runtime check, populates the un-defined symbolic shape vars * and vars in ty in first occurance, and insert equality assertions in * other cases. */ class MatchCastNode : public BindingNode { public: /*! \brief The input value to match cast. */ Expr value; /*! \brief The type pattern to match to. */ Type ty = Type::Missing(); static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef() .def_ro("value", &MatchCastNode::value) // TODO(tqchen): use SEqHashDefNonRecursive after the next pypi tvm-ffi release .def_ro("ty", &MatchCastNode::ty, refl::AttachFieldFlag::SEqHashDefRecursive()); } TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.expr.MatchCast", MatchCastNode, BindingNode); }; /*! * \brief Managed reference to MatchCastNode. * \sa MatchCastNode */ class MatchCast : public Binding { public: TVM_DLL explicit MatchCast(Var var, Expr value, Type ty, Span span = Span()); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(MatchCast, Binding, MatchCastNode); TVM_DEFINE_OBJECT_REF_COW_METHOD(MatchCastNode); }; class VarBindingNode : public BindingNode { public: /*! \brief The binding value. */ Expr value; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef().def_ro("value", &VarBindingNode::value); // customize the SEqual and SHash methods for better error messages refl::TypeAttrDef() .def("__s_equal__", &VarBindingNode::SEqual) .def("__s_hash__", &VarBindingNode::SHash); } bool SEqual(const VarBindingNode* other, ffi::TypedFunction equal) const; int64_t SHash(int64_t init_hash, ffi::TypedFunction hash) const; TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.expr.VarBinding", VarBindingNode, BindingNode); }; class VarBinding : public Binding { public: TVM_DLL explicit VarBinding(Var var, Expr value, Span span = Span()); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(VarBinding, Binding, VarBindingNode); TVM_DEFINE_OBJECT_REF_COW_METHOD(VarBindingNode); }; class BindingBlockNode : public ffi::Object { public: ffi::Array bindings; mutable Span span; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef() .def_ro("bindings", &BindingBlockNode::bindings) .def_ro("span", &BindingBlockNode::span, refl::AttachFieldFlag::SEqHashIgnore(), refl::DefaultValue(Span())); } static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode; TVM_FFI_DECLARE_OBJECT_INFO("relax.expr.BindingBlock", BindingBlockNode, ffi::Object); }; class BindingBlock : public ffi::ObjectRef { public: TVM_DLL explicit BindingBlock(ffi::Array bindings, Span span = Span()); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(BindingBlock, ffi::ObjectRef, BindingBlockNode); BindingBlockNode* CopyOnWrite(); }; class DataflowBlockNode : public BindingBlockNode { public: static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef(); } TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.expr.DataflowBlock", DataflowBlockNode, BindingBlockNode); }; class DataflowBlock : public BindingBlock { public: TVM_DLL explicit DataflowBlock(ffi::Array bindings, Span span = Span()); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(DataflowBlock, BindingBlock, DataflowBlockNode); TVM_DEFINE_OBJECT_REF_COW_METHOD(DataflowBlockNode); }; /*! \brief A sequence of blocks followed by an expression. * * The order of blocks enforces scoping and ordering. */ class SeqExprNode : public ExprNode { public: ffi::Array blocks; Expr body; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef() .def_ro("blocks", &SeqExprNode::blocks) .def_ro("body", &SeqExprNode::body); refl::TypeAttrDef() .def("__s_equal__", &SeqExprNode::SEqual) .def("__s_hash__", &SeqExprNode::SHash); } bool SEqual(const SeqExprNode* other, ffi::TypedFunction equal) const { // Establish mappings for symbolic variables defined by bindings before // comparing their uses in the SeqExpr result type and body. return equal(blocks, other->blocks, false, "blocks") && equal(ty, other->ty, false, "ty") && equal(body, other->body, false, "body"); } int64_t SHash(int64_t init_hash, ffi::TypedFunction hash) const { int64_t hash_value = init_hash; hash_value = hash(blocks, hash_value, false); hash_value = hash(ty, hash_value, false); hash_value = hash(body, hash_value, false); return hash_value; } TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.expr.SeqExpr", SeqExprNode, ExprNode); }; class SeqExpr : public Expr { public: /* \brief Implicit conversion constructor * * Relax nodes that introduce a new scope (e.g. `relax::Function`) * are required to be held as SeqExpr. This implicit conversion * provides allows callsites to use these member variables when the * C++ compile-time type is a `relax::Expr`. For example, * a transform may use `func.CopyOnWrite()->body = expr;`. * * If the expression is already a `relax::SeqExpr`, the same * underlying `relax::SeqExprNode` is used, and no copies are made. */ TVM_DLL SeqExpr(Expr body); // NOLINT(*) TVM_DLL explicit SeqExpr(ffi::Array blocks, Expr body, Span span = Span()); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(SeqExpr, Expr, SeqExprNode); TVM_DEFINE_OBJECT_REF_COW_METHOD(SeqExprNode); }; /*! * \brief Condition expression * * Unlike traditional statement `if`s, the if evalutes * to the result of the branch taken. * * x = if (true) { 1 } else { 0 }; // x is 1 * y = if (false) { 1 } else { 0 }; // y is 0 * * \note This is similar to C's ternary operator. */ class IfNode : public ExprNode { public: /*! \brief The condition. */ Expr cond; /*! \brief The expression evaluated when condition is true. */ SeqExpr true_branch; /*! \brief The expression evaluated when condition is false */ SeqExpr false_branch; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef() .def_ro("cond", &IfNode::cond) .def_ro("true_branch", &IfNode::true_branch) .def_ro("false_branch", &IfNode::false_branch); } static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindDAGNode; TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.expr.If", IfNode, ExprNode); }; class If : public Expr { public: /*! * \brief The constructor * * \param cond The condition of a if node. * * \param true_branch The fall through branch. If this is not a * SeqExpr, it will be wrapped in a SeqExpr, to satisfy the * Relax IR requirement that all scopes be contained in a * SeqExpr. * * \param false_branch The branch for execution when condition is * false. If this is not a SeqExpr, it will be wrapped in a * SeqExpr, to satisfy the Relax IR requirement that all scopes * be contained in a SeqExpr. * * \param span The source span of the expression. */ TVM_DLL If(Expr cond, Expr true_branch, Expr false_branch, Span span = Span()); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(If, Expr, IfNode); TVM_DEFINE_OBJECT_REF_COW_METHOD(IfNode); }; /*! \brief A Relax function. */ class FunctionNode : public BaseFuncNode { public: /*! \brief The parameters to the function. */ ffi::Array params; /*! \brief The body of the function. */ SeqExpr body; /*! \brief The return type of the function. */ Type ret_ty = Type::Missing(); /*! \brief Whether the function is annotated as pure or not. */ bool is_pure; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef() .def_ro("params", &FunctionNode::params, refl::AttachFieldFlag::SEqHashDefRecursive()) .def_ro("body", &FunctionNode::body) .def_ro("ret_ty", &FunctionNode::ret_ty) .def_ro("is_pure", &FunctionNode::is_pure); } static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindDAGNode; TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.expr.Function", FunctionNode, BaseFuncNode); }; class Function : public BaseFunc { public: /*! * \brief Construct a Relax Function * * \param params The parameters accepted by the function * * \param body The body of the function. If this is not a * SeqExpr, it will be wrapped in a SeqExpr, to satisfy the * Relax IR requirement that all scopes be contained in a * SeqExpr. * * \param ret_ty The Type returned by the function. * If std::nullopt, will be inferred from the Type of the * function's body. * * \param is_pure The purity of the function. * * \param attrs Any attributes associated with the function. * Defaults to an empty dictionary. * * \param span The source span of the expression. */ TVM_DLL explicit Function(ffi::Array params, Expr body, ffi::Optional ret_ty, bool is_pure = true, DictAttrs attrs = DictAttrs(), Span span = Span()); /*! * \brief Mimics the constructor but without body Expr. * \note ret_ty is required, since it can not deduced by the body. */ TVM_DLL static Function CreateEmpty(ffi::Array params, Type ret_ty, bool is_pure = true, DictAttrs attrs = DictAttrs(), Span span = Span()); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Function, BaseFunc, FunctionNode); TVM_DEFINE_OBJECT_REF_COW_METHOD(FunctionNode); }; // TODO(@sunggg): Investigate the exact usage of kComposite, kPartitionedFromPattern, and // kPrimitive. namespace attr { /*! \brief Mark the function as a primitive function. */ constexpr const char* kPrimitive = "Primitive"; /*! * \brief Indicate the codegen that should be used for building this function. * When this is unset or set to "default", the default compilation pipeline will be used. */ constexpr const char* kCodegen = "Codegen"; /*! \brief Treat the function as a composite operator. */ constexpr const char* kComposite = "Composite"; /*! \brief Indicate the function was created by the Pattern Partitioning Pass. */ constexpr const char* kPartitionedFromPattern = "PartitionedFromPattern"; /*! \brief The required workspace for an external function. */ constexpr const char* kWorkspaceSize = "WorkspaceSize"; // Note: in the future, we prefer snake_case instead of CamelCase for attributes. // Past ones will be kept for backwards compatibility. /*! \brief Override checking purity for this function and treat as pure * (is_pure must be set to true) */ constexpr const char* kForcePure = "relax.force_pure"; /*! * \brief The number of inputs of a function. * If a function has the num_input attribute, the last func->params.size() - num_inputs * arguments are assumed to be weights that are fixed across invocations. */ constexpr const char* kNumInput = "num_input"; } // namespace attr /*! \brief The extern function, which can represent packed function. */ class ExternFuncNode : public BaseFuncNode { public: /*! \brief The name of global symbol. */ ffi::String global_symbol; static void RegisterReflection() { namespace refl = tvm::ffi::reflection; refl::ObjectDef().def_ro("global_symbol", &ExternFuncNode::global_symbol); } TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.expr.ExternFunc", ExternFuncNode, BaseFuncNode); }; class ExternFunc : public BaseFunc { public: TVM_DLL ExternFunc(ffi::String global_symbol, Span span = Span()); TVM_DLL ExternFunc(ffi::String global_symbol, Type ty, Span span = Span()); TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(ExternFunc, BaseFunc, ExternFuncNode); TVM_DEFINE_OBJECT_REF_COW_METHOD(ExternFuncNode); }; /*! * \brief Get the shape of Expr. * \param expr The input expr. * \return The corresonding shape. * * \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]). */ TVM_DLL Expr GetShapeOf(const Expr& expr); } // namespace relax } // namespace tvm /* \brief Allow relax.Var as key in STL tables * * For most Relax expressions, it would be ambiguous whether the * expression should follow reference equality or structural equality. * This is not the case for variables, which do not contain nested * internal structure, and are frequently used as keys in lookup * tables. * * Providing `std::hash` and `std::equal_to` specializations for * `relax::Var` allows it to be used as a key in STL tables. For * `relax::Expr`, the user must specify the type of equality used * (e.g. `std::unordered_set` or * `std::unordered_set`). */ template <> struct std::hash { std::size_t operator()(const tvm::relax::Var& var) const { return tvm::ffi::ObjectPtrHash()(var); } }; template <> struct std::equal_to { bool operator()(const tvm::relax::Var& var_a, const tvm::relax::Var& var_b) const { return tvm::ffi::ObjectPtrEqual()(var_a, var_b); } }; #endif // TVM_RELAX_EXPR_H_