chore: import upstream snapshot with attribution
Lint / lint (push) Waiting to run
CI / MacOS (push) Waiting to run
CI / Windows (push) Waiting to run

This commit is contained in:
wehub-resource-sync
2026-07-13 13:36:25 +08:00
commit 26446540fa
3151 changed files with 974126 additions and 0 deletions
+993
View File
@@ -0,0 +1,993 @@
/*
* 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.
*/
/*!
* \file tvm/arith/analyzer.h
* \brief Algebra expression simplifications.
*/
#ifndef TVM_ARITH_ANALYZER_H_
#define TVM_ARITH_ANALYZER_H_
#include <tvm/arith/int_set.h>
#include <tvm/ffi/cast.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include <tvm/ir/expr.h>
#include <tvm/ir/with_context.h>
#include <limits>
#include <memory>
#include <unordered_map>
#include <utility>
#include <vector>
namespace tvm {
/*! \brief namespace of arithmetic analysis. */
namespace arith {
//-------------------------------------------------------
// Base integer analysis API.
//
// We have multiple type of analyzers to do relaxed
// integer set analysis(bound analysis, modulo) and
// equivalence checking and simplification.
//
// Importantly, each analyzer may need result from
// another analyzer.
//-------------------------------------------------------
// Forward declare the analyzer object and its reference handle.
class AnalyzerObj;
class Analyzer;
class ConstraintContext;
using tirx::Var;
enum DivMode {
/*! \brief Truncated division. */
kTruncDiv,
/*! \brief Floor division. */
kFloorDiv
};
/*!
* \brief The strength used in top-level condition proves
* \note The higher, the more time consuming it can be.
*
* Do not use level beyond kDefault in internal recursive rewriting in arith
* analysis and only use it at top-level simplification to avoid speed issues.
*/
enum class ProofStrength : int {
/*! \brief default strength, can be used in. */
kDefault = 0,
/*!
* \brief Prove using symbolic bound analysis
*/
kSymbolicBound = 1,
};
/*!
* \brief Constant integer up and lower bound(inclusive).
* Useful for value bound analysis.
*
* set = [min_value, max_value]
*/
class ConstIntBoundNode : public ffi::Object {
public:
int64_t min_value;
int64_t max_value;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ConstIntBoundNode>()
.def_ro("min_value", &ConstIntBoundNode::min_value)
.def_ro("max_value", &ConstIntBoundNode::max_value);
}
/*! \brief Number to represent +inf */
static const constexpr int64_t kPosInf = std::numeric_limits<int64_t>::max();
/*!
* \brief Number to represent -inf
* \note We can make use the of fact that -kPosInf == kNegInf in the project.
*/
static const constexpr int64_t kNegInf = -kPosInf;
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("arith.ConstIntBound", ConstIntBoundNode, ffi::Object);
};
/*!
* \brief reference class to ConstIntBoundNode
* \sa ConstIntBoundNode
*/
class ConstIntBound : public ffi::ObjectRef {
public:
/*!
* \brief constructor by fields.
* \param min_value The mininum value.
* \param max_value The maximum value.
*/
TVM_DLL ConstIntBound(int64_t min_value, int64_t max_value);
static const constexpr int64_t kPosInf = ConstIntBoundNode::kPosInf;
static const constexpr int64_t kNegInf = ConstIntBoundNode::kNegInf;
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(ConstIntBound, ffi::ObjectRef, ConstIntBoundNode);
};
/*!
* \brief Analyzer to get constant integer bound over expression.
*/
class ConstIntBoundAnalyzer {
public:
using BoundMapType =
std::unordered_map<PrimExpr, ConstIntBound, ffi::ObjectPtrHash, ffi::ObjectPtrEqual>;
/*!
* \brief analyze the expr
* \param expr The expression of interest.
* \return the result of the analysis.
*/
TVM_DLL ConstIntBound operator()(const PrimExpr& expr) const;
/*!
* \brief analyze the expr with the intermediate memorized to avoid redundant computation
* \param expr The expression of interest.
* \param bound The lookup table to store the intermediate results
* \return the result of the analysis.
*/
TVM_DLL ConstIntBound operator()(const PrimExpr& expr, BoundMapType* bound);
/*!
* \brief Update constant int bound information of var.
*
* \param var The variable of interest.
* \param info The bound information.
* \param allow_override whether we allow override of existing information.
*/
TVM_DLL void Update(const Var& var, const ConstIntBound& info, bool allow_override = false);
/*!
* \brief Bind variable to a range.
*
* \param var The variable.
* \param range The range we bind to.
* \param allow_override Whether we allow overriding an existing var's range.
*/
TVM_DLL void Bind(const Var& var, const Range& range, bool allow_override = false);
/*!
* \brief Check if a variable is bound to a range.
* \param var The variable.
* \return Whether the variable is bound to a range.
*/
TVM_DLL bool IsBound(const Var& var) const;
private:
friend class AnalyzerObj;
friend class ConstraintContext;
explicit ConstIntBoundAnalyzer(AnalyzerObj* parent);
TVM_DLL ~ConstIntBoundAnalyzer();
void CopyFrom(const ConstIntBoundAnalyzer& other);
/*!
* \brief Update the internal state to enter constraint.
* \param constraint A constraint expression.
*
* \return an exit function that must be called to cleanup the constraint can be nullptr.
*/
std::function<void()> EnterConstraint(const PrimExpr& constraint);
struct Entry;
class Impl;
/*! \brief Internal impl */
Impl* impl_;
};
/*!
* \brief Range of a linear integer function.
* Use to do specify the possible index values.
*
* set = { coeff * x + base | x in Z }
*
* When coeff != 0, it can also be written as
* set = { n | n % coeff == base }
*
* This is useful to decide if the index is dividable by certain value.
* For example, if index = 0 + 4 x, then we know it can be divided by 4.
*/
class ModularSetNode : public ffi::Object {
public:
/*! \brief linear co-efficient */
int64_t coeff;
/*! \brief The base */
int64_t base;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ModularSetNode>()
.def_ro("coeff", &ModularSetNode::coeff)
.def_ro("base", &ModularSetNode::base);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("arith.ModularSet", ModularSetNode, ffi::Object);
};
/*!
* \brief reference of ModularSetNode
* \sa ModularSetNode
*/
class ModularSet : public ffi::ObjectRef {
public:
TVM_DLL ModularSet(int64_t coeff, int64_t base);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(ModularSet, ffi::ObjectRef, ModularSetNode);
};
/*!
* \brief Analyzer to get modular information over expression.
*/
class ModularSetAnalyzer {
public:
/*!
* \brief analyze the expr
* \param expr The expression of interest.
* \return the result of the analysis.
*/
TVM_DLL ModularSet operator()(const PrimExpr& expr);
/*!
* \brief Update constant int bound information of var.
*
* \param var The variable of interest.
* \param info The bound information.
* \param allow_override whether we allow override of existing information.
*/
TVM_DLL void Update(const Var& var, const ModularSet& info, bool allow_override = false);
private:
friend class AnalyzerObj;
friend class ConstraintContext;
explicit ModularSetAnalyzer(AnalyzerObj* parent);
TVM_DLL ~ModularSetAnalyzer();
void CopyFrom(const ModularSetAnalyzer& other);
/*!
* \brief Update the internal state to enter constraint.
* \param constraint A constraint expression.
*
* \return an exit function that must be called to cleanup the constraint can be nullptr.
*/
std::function<void()> EnterConstraint(const PrimExpr& constraint);
struct Entry;
class Impl;
/*! \brief Internal impl */
Impl* impl_;
};
/*!
* \brief Rewrite-rule based simplifier.
*/
class RewriteSimplifier {
public:
/*!
* \brief analyze the expr
* \param expr The expression of interest.
* \return the result of the analysis.
*/
TVM_DLL PrimExpr operator()(const PrimExpr& expr);
/*!
* \brief Update binding of var to a new expression.
*
* \param var The variable of interest.
* \param new_expr
* \param allow_override Whether we allow override of existing information.
*/
TVM_DLL void Update(const Var& var, const PrimExpr& new_expr, bool allow_override = false);
/*!
* \brief Update the internal state to enter constraint.
* \param constraint A constraint expression.
*
* \return an exit function that must be called to cleanup the constraint can be nullptr.
*/
TVM_DLL std::function<void()> EnterConstraint(const PrimExpr& constraint);
/*! \brief Flags to enable more computationally-intensive simplifications
*
* These simplifications may be required for specific schedules, but
* would impose too high a compile-time cost to enable by default.
* They can be enabled on an as-needed basis by calling
* `RewriteSimplifier::SetEnabledExtensions` prior to using
* `RewriteSimplifier::operator()`.
*
* Flags are defined as powers of two to allow future expansion. To
* enable multiple extensions, a user should pass a bitwise OR of the
* flags for each desired extension.
*/
enum Extension {
// No extensions enabled
kNone = 0,
/* When simplifying an inequality, attempt to use scope-based knowns.
*
* Example:
* if_then_else(i<j && j<k, i<k, false) => if_then_else(i<j && j<k, true, false)
*/
kTransitivelyProveInequalities = (1 << 0),
/* When simplifying a boolean expression, convert to an AND of ORs
* (conjunctive normal form).
*
* Example:
* (a && b) || c => (a || c) && (b || c)
*/
kConvertBooleanToAndOfOrs = (1 << 1),
/* When simplifying a boolean AND or a boolean OR, simplify each
* branch under the assumption that the other branch does not
* already dominate the result. That is, simplify each branch of
* (A && B) under the assumption that the other branch is true,
* and simplify each branch of (A || B) under the assumption that
* the other branch is false.
*
* Example:
* (n < 10) && (n < 5) => (n < 10)
* (n < 10) || (n < 5) => (n < 5)
*/
kApplyConstraintsToBooleanBranches = (1 << 2),
/* Special handling for expressions `(A+B)*C < (A*B)*D`
*
* Expressions of the form `(A+B)*C < (A*B)*D` can occur occur
* when comparing the number of operations required for two
* different orderings in which matrix multiplications can be
* performed. Proving or disproving this conditional allows an
* optimal order of execution to be selected, even for dynamic
* argument shapes.
*
* The default behavior of `ConstIntBounds` assumes that each term
* in an expression is independent, and is insufficient to prove
* these inequalities. For example, the maximum value of `(A+B)*C
* - (A*B)*D` is determined by taking the maximum value of
* `(A+B)*C` and subtracting the minimum value of `(A*B)*D`.
* While this algorithm can be applied in all cases, the bound it
* provides is looser than strictly required.
*
* This extension adds a check for this case. When `A`, `B`, `C`,
* and `D` are all positive values, as is the case for tensor
* shapes, the inequality can be written as `1/A + 1/B < D/C`. If
* this inequality holds for the minimum values of `A`, `B`, and
* `D`, along with the maximum value of `C`, then the inequality
* holds for all values.
*
* This extension requires little to no performance overhead, and
* may be enabled by default in future releases.
*/
kComparisonOfProductAndSum = (1 << 3),
};
/*! \brief Enable an optional extension or extensions
*
* \param flags A bitwise OR of all optional extensions that should
* be enabled.
*/
TVM_DLL void SetEnabledExtensions(Extension flags);
/*! \brief Return the currently enabled extensions */
TVM_DLL Extension GetEnabledExtensions() const;
/*! \brief Return the statistics counters */
TVM_DLL ffi::ObjectRef GetStatsCounters() const;
/*! \brief Reset the statistics counters */
TVM_DLL void ResetStatsCounters();
/*! \brief Set the maximum allowed number of rewrite steps
*
* By default, the simplifier may perform as many steps as are
* required. If a positive limit is set, then the simplifier will
* throw an exception when exceeding that number of rewrite steps.
* This allows tests to guard against performance regressions.
*
* Note: To maintain accurate usage counters, `Analyzer` instances
* should be re-used wherever possible. For example, TIR
* transformations should declare a single `Analyzer` that is used
* throughout the pass. Internal helper functions that only borrow
* the analyzer temporarily may receive the underlying `AnalyzerObj*`
* from their calling scope.
*/
TVM_DLL void SetMaximumRewriteSteps(int64_t maximum);
private:
friend class AnalyzerObj;
friend class ConstraintContext;
friend class CanonicalSimplifier;
explicit RewriteSimplifier(AnalyzerObj* parent);
TVM_DLL ~RewriteSimplifier();
void CopyFrom(const RewriteSimplifier& other);
class Impl;
/*! \brief Internal impl */
Impl* impl_;
};
/*!
* \brief Canonical-form based simplifier.
*/
class CanonicalSimplifier {
public:
/*!
* \brief analyze the expr
* \param expr The expression of interest.
* \return the result of the analysis.
*/
TVM_DLL PrimExpr operator()(const PrimExpr& expr);
/*!
* \brief Update binding of var to a new expression.
*
* \param var The variable of interest.
* \param new_expr
* \param allow_override whether we allow override of existing information.
*/
TVM_DLL void Update(const Var& var, const PrimExpr& new_expr, bool allow_override = false);
private:
friend class AnalyzerObj;
friend class ConstraintContext;
explicit CanonicalSimplifier(AnalyzerObj* parent);
TVM_DLL ~CanonicalSimplifier();
void CopyFrom(const CanonicalSimplifier& other);
class Impl;
/*! \brief Internal impl */
Impl* impl_;
};
/*! \brief Structure for representing result of known
*
* Values are assigned to allow these flags to be used in bitwise
* operations.
*/
enum class CompareResult : int {
kInconsistent = 0,
kEQ = 1,
kLT = 2,
kLE = 3,
kGT = 4,
kGE = 5,
kNE = 6,
kUnknown = 7
};
inline constexpr CompareResult operator&(CompareResult lhs, CompareResult rhs) {
return CompareResult(static_cast<int>(lhs) & static_cast<int>(rhs));
}
inline constexpr CompareResult operator|(CompareResult lhs, CompareResult rhs) {
return CompareResult(static_cast<int>(lhs) | static_cast<int>(rhs));
}
/*!
* \brief Using previously specified knowns, compare the expressions provided
*
* Given known expressions [(a OP b), (b OP c), ..., (y OP z)], search
* for a known result for `(a OP z)`.
*/
class TransitiveComparisonAnalyzer {
public:
/* \brief Using previously specified knowns, compare the expressions provided
*
* \param lhs The left-hand side of the comparison
*
* \param rhs The right-hand side of the comparison
*
* \param propagate_inequalities If true, attempt to find a sequence
* of transitive inequalities that allow the lhs and rhs to be
* compared. If false, only use the known comparison that have been
* directly provided. Using `propagate_inequalities = false` is
* roughly equivalent to comparing against all known inequality
* expressions using `ExprDeepEqual`, but also allows for constant
* offsets on either side of the inequality.
*
* \return The most specific result that can be proven about the
* comparison. If nothing can be proven, returns kUnknown.
*/
TVM_DLL CompareResult TryCompare(const PrimExpr& lhs, const PrimExpr& rhs,
bool propagate_inequalities = true);
/*! \brief Bind a variable as being equal to a known expression
*
* \param var The variable of interest.
* \param expr The bound expression
* \param allow_override Whether to allow override of existing information.
*/
TVM_DLL void Bind(const Var& var, const PrimExpr& expr, bool allow_override = false);
/*! \brief Bind a variable as being within a specified range
*
* \param var The variable of interest.
* \param range The known range
* \param allow_override Whether to allow override of existing information.
*/
TVM_DLL void Bind(const Var& var, const Range& range, bool allow_override = false);
/*!
* \brief Update the internal state to enter constraint.
* \param constraint A constraint expression.
*
* \return an exit function that must be called to cleanup the constraint can be nullptr.
*/
TVM_DLL std::function<void()> EnterConstraint(const PrimExpr& constraint);
private:
friend class AnalyzerObj;
friend class ConstraintContext;
TransitiveComparisonAnalyzer();
TVM_DLL ~TransitiveComparisonAnalyzer();
void CopyFrom(const TransitiveComparisonAnalyzer& other);
class Impl;
/*! \brief Internal impl */
std::unique_ptr<Impl> impl_;
};
/*!
* \brief Integer set analyzer.
*/
class IntSetAnalyzer {
public:
/*!
* \brief Find a symbolic integer set that contains all possible values of
* expr given the domain of each variables.
*
* \param expr The expression of interest.
* \param dom_map The domain map to indicate which variable to relax.
* \return the result of the analysis.
*/
TVM_DLL IntSet operator()(const PrimExpr& expr, const ffi::Map<Var, IntSet>& dom_map);
/*!
* \brief Find a symbolic integer set that contains all possible
* values of expr given the domain of each variables, using
* the domain map defined by bound variables.
*
* \param expr The expression of interest.
* \return the result of the analysis.
*/
TVM_DLL IntSet operator()(const PrimExpr& expr);
/*!
* \brief Update binding of var to a new expression.
*
* \param var The variable of interest.
* \param new_interval_set The set of allowed values for this var.
* \param allow_override whether we allow override of existing information.
*/
TVM_DLL void Update(const Var& var, const IntSet& new_interval_set, bool allow_override = false);
/*!
* \brief Update binding of var to a new expression.
*
* \param var The variable of interest.
* \param new_range The range of allowed values for this var.
* \param allow_override whether we allow override of existing information.
*/
TVM_DLL void Bind(const Var& var, const Range& new_range, bool allow_override = false);
std::function<void()> EnterConstraint(const PrimExpr& constraint);
private:
friend class AnalyzerObj;
explicit IntSetAnalyzer(AnalyzerObj* parent);
TVM_DLL ~IntSetAnalyzer();
void CopyFrom(const IntSetAnalyzer& other);
class Impl;
/*! \brief Internal impl */
Impl* impl_;
};
class Z3Prover {
public:
/*!
* \brief Update binding of var to a new expression.
*
* \param var The variable of interest.
* \param new_range The range of allowed values for this var.
* \param allow_override whether we allow override of existing information.
*/
TVM_DLL void Bind(const Var& var, const Range& new_range, bool allow_override = false);
/*!
* \brief Update binding of var to a new expression.
*
* \param var The variable of interest.
* \param expr The bound expression.
* \param allow_override whether we allow override of existing information.
*/
TVM_DLL void Bind(const Var& var, const PrimExpr& expr, bool allow_override = false);
/*!
* \brief Whether the Z3 backend is compiled into this build (USE_Z3=ON).
*
* \return true if the real Z3 prover is available, false for the stub.
*/
TVM_DLL bool IsEnabled() const;
/*!
* \brief Whether can we prove expr is always true.
*
* \param expr The expression.
* \return Whether we can prove it.
*/
TVM_DLL bool CanProve(const PrimExpr& expr);
/*!
* \brief Update the internal state to enter constraint.
*
* \param constraint A constraint expression.
* \return an exit function that must be called to cleanup the constraint can be nullptr.
*/
std::function<void()> EnterConstraint(const PrimExpr& constraint);
/*!
* \brief Get the SMTLIB2 representation of the current context.
*
* \param expr The optional expression to check.
* \return The SMTLIB2 string.
*/
ffi::String GetSMTLIB2(const ffi::Optional<PrimExpr> expr);
/*!
* \brief Get statistics about Z3 prover.
*
* \return The statistics string.
*/
ffi::String GetStats();
/*!
* \brief Set timeout in milliseconds for Z3 prover.
*
* \param timeout_ms The timeout in milliseconds.
*/
void SetTimeoutMs(unsigned timeout_ms);
/*!
* \brief Set resource limitation for Z3 prover.
*
* \param rlimit the resource limitation.
*/
void SetRLimit(unsigned rlimit);
/*!
* \brief Get the Z3 model for the given expression if satisfiable.
*
* \param expr The expression to get the model for.
* \return The model as a string.
*/
ffi::String GetModel(const PrimExpr& expr);
/*!
* \brief Count the number of integer values that satisfy the current constraints.
*
* This method uses Z3's model enumeration to count how many distinct values of
* the given variable satisfy all current constraints.
*
* \param var The variable to count satisfying values for.
* \param max_count Maximum number of solutions to enumerate.
* \param min_consecutive Minimum consecutive count requirement.
* \return The number of distinct values that satisfy the constraints, or a negative error code.
*/
TVM_DLL int64_t CountSatisfyingValues(const Var& var, int64_t max_count = 2048,
int64_t min_consecutive = 1);
private:
friend class AnalyzerObj;
friend class Analyzer;
explicit Z3Prover(AnalyzerObj* parent);
TVM_DLL ~Z3Prover();
void CopyFrom(const Z3Prover& other);
class Impl;
Impl* impl_;
};
/*!
* \brief Analyzer that contains bunch of sub-analyzers.
*
* Each sub-analyzer can make use of another sub-analyzer
* by weak reference of this.
*
* NOTE for sub-analyzer developers:
* If the analyzer uses memoization, we need to clear the internal
* cache when information about a Var has been overridden.
*/
class TVM_DLL AnalyzerObj : public ffi::Object {
public:
/*! \brief sub-analyzer: const integer bound */
ConstIntBoundAnalyzer const_int_bound;
/*! \brief sub-analyzer: modular set */
ModularSetAnalyzer modular_set;
/*! \brief sub-analyzer rewrite simplify */
RewriteSimplifier rewrite_simplify;
/*! \brief sub-analyzer canonical simplify */
CanonicalSimplifier canonical_simplify;
/*! \brief sub-analyzer: int set */
IntSetAnalyzer int_set;
/*! \brief sub-analyzer transitive comparisons */
TransitiveComparisonAnalyzer transitive_comparisons;
/*! \brief sub-analyzer using Z3 */
Z3Prover z3_prover;
/*! \brief constructor */
AnalyzerObj();
/*!
* \brief Mark the value as non-negative value globally in analyzer.
*
* Only call this function if the non-neg condition is global and
* not context-dependent.
*
* This function does best-effort propagations to the sub-analyzers
*
* A canonical use of MarkGlobalNonNegValue is to record a non-negativity
* fact at a Var's definition site. Because each Var identity is defined
* exactly once in canonical IR, the fact is globally valid for that identity.
*
* \note We expose this function because non-negative global values,
* such as symbolic buffer shapes in function arguments are really
* important to ensure the best simplification, and usually they
* can be handled in a simpler way than the generic constraints.
*
* This function may call into the Update function of the sub-analyzers.
*/
void MarkGlobalNonNegValue(const PrimExpr& value);
/*!
* \brief Notify all the sub-analyzers that var
* is created and binded to expr.
*
* Each var can only be bound once.
*
* \param var The variable.
* \param expr The expression we bind to.
* \param allow_override Whether we allow overriding an existing var's
* expression. This option should not be used if there is any dependency
* between variables.
*/
void Bind(const Var& var, const PrimExpr& expr, bool allow_override = false);
/*!
* \brief Notify all the sub-analyzers that var
* is created and bound to a range.
*
* Each var can only be bound once.
*
* \param var The variable.
* \param range The range we bind to.
* \param allow_override Whether we allow overriding an existing var's
* expression. This option should not be used if there is any dependency
* between variables.
*/
void Bind(const Var& var, const Range& range, bool allow_override = false);
/*!
* \brief Bind all the vars in the Map
*
* \param variables The {variable -> range} map.
* \param allow_override Whether we allow overriding an existing var's
* expression. This option should not be used if there is any dependency
* between variables.
*/
void Bind(const ffi::Map<Var, Range>& variables, bool allow_override = false);
/*!
* \brief Whether can we prove expr >= val.
* Non-negative proof is very useful in integer analysis
* to lower divisions and mods given difference in trunc and ceil mode.
*
* \param expr The expression.
* \param lower_bound The lower bound.
* \return Whether we can prove it.
*
* \note Analyzer will call into sub-analyzers to get the result.
*/
bool CanProveGreaterEqual(const PrimExpr& expr, int64_t lower_bound);
/*!
* \brief Whether can we prove expr < val.
* Non-negative proof is very useful in integer analysis
* to lower divisions and mods given difference in trunc and ceil mode.
*
* \param expr The expression.
* \param upper_bound The upper bound.
* \return Whether we can prove it.
*
* \note Analyzer will call into sub-analyzers to get the result.
*/
bool CanProveLess(const PrimExpr& expr, int64_t upper_bound);
/*!
* \brief Whether can we prove lhs == rhs.
*
* \param lhs The input lhs.
* \param rhs The input rhs.
* \return Whether we can prove lhs == rhs.
*
* \note Analyzer will call into sub-analyzers to get the result.
*/
bool CanProveEqual(const PrimExpr& lhs, const PrimExpr& rhs);
/*!
* \brief Whether we can prove lhs is smaller than possibly symbolic shape.
*
* By calling this function, the caller gives an extra hint that shape > 0,
* because it appeared in buffer shape.
*
* This is useful to prove condition such as 32 <= 32 * n where the 32 * n
* is known to be a shape. Use this routine to reduce the symbolic comparisons
* in buffer compaction.
*
* The underlying analyzer will use the kSymbolicBound proof.
*
* \param lhs The input lhs.
* \param shape The symbolic shape.
* \return Whether we can prove lhs <= shape.
*/
bool CanProveLessEqualThanSymbolicShapeValue(const PrimExpr& lhs, const PrimExpr& shape);
/*!
* \brief Whether can we prove condition.
*
* \param cond The expression to be proved.
* \param strength the strength of the prove.
*
* \return The result.
*
* \note Analyzer will call into sub-analyzers to get the result.
* Do not use strength beyond default in sub-analyzers and
* only use it in top-level predicate analysis.
*/
bool CanProve(const PrimExpr& cond, ProofStrength strength = ProofStrength::kDefault);
/*!
* \brief Simplify expr.
*
* \param expr The expression to be simplified.
* \param steps The simplification runs in the order of
* rewrite_simplify (step 1) -> canonical_simplify (step 2) ->
* rewrite_simplify (step 3) -> canonical_simplify (step 4) -> ...
* param steps controls how many steps to run.
* Default is 2, i.e., rewrite_simplify + canonical_simplify.
* \return The result.
*
* \note Analyzer will call into sub-analyzers to get the result.
*/
PrimExpr Simplify(const PrimExpr& expr, int steps = 2);
/*!
* \brief Deep-copy this analyzer into a new, independent Analyzer.
*
* The returned analyzer carries the same accumulated facts (variable
* bounds, modular sets, rewrite/canonical bindings, integer-set domains,
* literal constraints and transitive comparisons) as this one, but owns
* its own state: binding or simplifying on either analyzer afterwards does
* not affect the other. This is the deep copy that handle-copying an
* Analyzer does not provide.
*
* \note Do not call this while a `With<ConstraintContext>` scope is active
* on this analyzer. The clone would inherit the scoped constraints
* but not the recovery functions that pop them on scope exit, so the
* constraints would leak as if they were global facts. Clone at a
* point where no constraint scope is in effect.
*
* \return A new Analyzer holding an independent copy of the facts.
*/
Analyzer Clone() const;
/*!
* \brief Analyzer methods update facts, constraints, caches, and stats.
*
* Marking the object mutable makes the `Analyzer` ObjectRef expose a
* non-const `operator->`, so APIs can take `const Analyzer&` while still
* allowing calls such as `analyzer->Bind(...)`.
* `const Analyzer&` keeps the handle itself from being rebound; it does
* not make the underlying AnalyzerObj immutable.
*/
static constexpr bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("arith.Analyzer", AnalyzerObj, ffi::Object);
};
/*!
* \brief Managed reference to AnalyzerObj.
*
* Analyzer is a lightweight, reference-counted handle around a heap-allocated
* AnalyzerObj. Because it is now a first-class FFI object, an Analyzer can be
* passed across the tvm-ffi boundary (e.g. handed from Python into a C++ pass)
* and shared, so that accumulated bindings/constraints persist across calls.
* Copying an Analyzer copies the handle, and both handles share the same
* mutable AnalyzerObj state.
* This is not a deep copy of analyzer facts or caches.
*
* \sa AnalyzerObj
*/
class Analyzer : public ffi::ObjectRef {
public:
/*! \brief Default-construct a fresh analyzer (allocates an AnalyzerObj). */
Analyzer() : Analyzer(ffi::make_object<AnalyzerObj>()) {}
explicit Analyzer(ffi::ObjectPtr<AnalyzerObj> n) : ffi::ObjectRef(std::move(n)) {
TVM_FFI_ICHECK(this->get() != nullptr);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Analyzer, ffi::ObjectRef, AnalyzerObj);
};
/*!
* \brief Constraint context.
*
* \code
*
* Var x("x");
* arith::Analyzer analyzer;
* {
* With<arith::ConstraintContext> scope(analyzer, tvm::floormod(x, 3) == 0);
* TVM_FFI_ICHECK_EQ(analyzer->modular_set(x)->coeff, 3);
* }
* // constraint no longer in effect.
* TVM_FFI_ICHECK_NE(analyzer->modular_set(x)->coeff, 3);
*
* \endcode
*/
class ConstraintContext {
private:
// declare friend to enable with.
friend class With<ConstraintContext>;
/*!
* \brief Construct a constraint context.
* \param analyzer The analyzer whose context is updated. The context
* keeps a reference to the analyzer while the scope is active.
* \param constraint The constraint to be applied.
*/
ConstraintContext(const Analyzer& analyzer, PrimExpr constraint)
: ConstraintContext(analyzer, std::move(constraint), false) {}
/*!
* \brief Construct a constraint context.
* \param analyzer The analyzer whose context is updated. The context
* keeps a reference to the analyzer while the scope is active.
* \param constraint The constraint to be applied.
* \param is_assume Whether the constraint comes from an assumption.
*/
ConstraintContext(const Analyzer& analyzer, PrimExpr constraint, bool is_assume)
: analyzer_(analyzer), constraint_(std::move(constraint)), is_assume_(is_assume) {}
/*!
* \brief Construct a constraint context from a borrowed analyzer object.
* \param analyzer The borrowed analyzer object.
* \param constraint The constraint to be applied.
*
* This overload is for internal callers that already operate on AnalyzerObj*.
*/
ConstraintContext(AnalyzerObj* analyzer, PrimExpr constraint)
: ConstraintContext(ffi::GetRef<Analyzer>(analyzer), std::move(constraint), false) {}
/*!
* \brief Construct a constraint context from a borrowed analyzer object.
* \param analyzer The borrowed analyzer object.
* \param constraint The constraint to be applied.
* \param is_assume Whether the constraint comes from an assumption.
*/
ConstraintContext(AnalyzerObj* analyzer, PrimExpr constraint, bool is_assume)
: ConstraintContext(ffi::GetRef<Analyzer>(analyzer), std::move(constraint), is_assume) {}
// enter the scope.
void EnterWithScope();
// exit the scope.
void ExitWithScope();
/*! \brief Analyzer kept alive while the context is active. */
Analyzer analyzer_;
/*! \brief The constraint */
PrimExpr constraint_;
/*! \brief functions to be called in recovery */
std::vector<std::function<void()>> recovery_functions_;
/*! \brief Whether the constraint comes from an assumption. */
bool is_assume_;
};
} // namespace arith
} // namespace tvm
#endif // TVM_ARITH_ANALYZER_H_
+85
View File
@@ -0,0 +1,85 @@
/*
* 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.
*/
/*!
* \file tvm/arith/bound.h
* \brief Bound deducers.
*/
#ifndef TVM_ARITH_BOUND_H_
#define TVM_ARITH_BOUND_H_
#include <tvm/arith/int_set.h>
#include <tvm/ir/expr.h>
#include <tvm/tirx/expr.h>
#include <tvm/tirx/stmt.h>
#include <unordered_map>
namespace tvm {
namespace arith {
using tirx::Region;
using tirx::Stmt;
using tirx::Var;
using tirx::VarNode;
/*!
* \brief Deduce the bound of the target variable in a expression,
* give the domain of each variables. Return undefined IntSet to
* represent failure.
*
* \note The returned set may be smaller than set that
* contains all possible values of v that satisfies the bound.
*
* \param v The target variable to be deduced.
* \param cond The conditional expression.
* \param hint_map The domain of variable, used to help deduce.
* \param relax_map The domain of each variable, used to relax the domain,
* The deduce bound must implies e for all value in relax_map
* \return An integer set that always satisfies the condition.
*/
IntSet DeduceBound(PrimExpr v, PrimExpr cond, const ffi::Map<Var, IntSet>& hint_map,
const ffi::Map<Var, IntSet>& relax_map);
/*!
* \brief Same as DeduceBound with unordered_map signature.
*
* \param v The target variable to be deduced.
* \param cond The conditional expression.
* \param hint_map The domain of variable, used to help deduce.
* \param relax_map The domain of each variable, used to relax the domain,
* The deduce bound mush implies e for all value in relax_map
* \return An integer set that always satisfies the condition.
*/
IntSet DeduceBound(PrimExpr v, PrimExpr cond,
const std::unordered_map<const VarNode*, IntSet>& hint_map,
const std::unordered_map<const VarNode*, IntSet>& relax_map);
/*!
* \brief Infer a regular domain that covers all the calls or provides within the given statement.
* \param body The given statement.
* \param buffer The buffer to check the access info.
* \param consider_loads If loads are considered.
* \param consider_stores If stores are considered.
* \return The domain that covers all the calls or provides within the given statement.
*/
Region DomainTouched(const Stmt& body, const tirx::Buffer& buffer, bool consider_loads,
bool consider_stores);
} // namespace arith
} // namespace tvm
#endif // TVM_ARITH_BOUND_H_
+339
View File
@@ -0,0 +1,339 @@
/*
* 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.
*/
/*!
* \file tvm/arith/int_set.h
* \brief Integer set
*/
#ifndef TVM_ARITH_INT_SET_H_
#define TVM_ARITH_INT_SET_H_
#include <tvm/ir/expr.h>
#include <tvm/tirx/expr.h>
#include <unordered_map>
namespace tvm {
namespace arith {
using tirx::IterVar;
using tirx::Var;
using tirx::VarNode;
class AnalyzerObj;
class Analyzer;
//-----------------------------------------------
// Integer set data structure.
//
// This is a API build on top of the base
// integer analysis API to provide set analysis.
//------------------------------------------------
/*!
* \brief Sign type of an integer expression.
*/
enum SignType { kPositive, kNegative, kZero, kUnknown };
/*!
* \brief Base class of all Integer set containers.
* represent a set of integers in one dimension.
* \sa IntSet
*/
class IntSetNode : public ffi::Object {
public:
TVM_FFI_DECLARE_OBJECT_INFO("ir.IntSet", IntSetNode, ffi::Object);
};
/*!
* \brief Managed reference to IntSetNode.
* \sa IntSetNode
*/
class IntSet : public ffi::ObjectRef {
public:
/*!
* \brief Find a range that covers the region.
* \param max_range The range to be covered.
* \return The covering range.
*/
Range CoverRange(Range max_range) const;
/*! \return Lower bound of the set */
PrimExpr min() const;
/*! \return upper bound of the set */
PrimExpr max() const;
/*! \return The sign of the elements in the integer set */
SignType GetSignType() const;
/*! \return Whether the set represent nothing */
bool IsNothing() const;
/*! \return Whether the set represent everything */
bool IsEverything() const;
/*! \return Whether the set is a single point */
bool IsSinglePoint() const;
/*!
* \brief Check if we can prove it is a single point.
*
* Unlike IsSinglePoint, which only checks ptr equality
* this function will invoke analyzer to do stonger proofs
* but also takes longer time.
*
* Use this function in some of the primitives but do not
* use it in the inner loop of simplification.
*
* \param ana Analyzer used in the proof.
* \return Whether we can prove it is a single point
*/
bool CanProveSinglePoint(const Analyzer& ana) const;
// TODO(tvm-team): update all CanProve to explicitly take
// analyzer to encourage more analyzer reuse
/*! \return Whether the set is proved to be bigger than 0 */
bool CanProvePositive() const;
/*! \return Whether the set is proved to be smaller than 0 */
bool CanProveNegative() const;
/*! \return Whether the set is proved to be smaller than or equal to 0 */
bool CanProveNonPositive() const;
/*! \return Whether the set is proved to be larger than or equal to 0 */
bool CanProveNonNegative() const;
/*! \return Whether the set has upper bound. */
bool HasUpperBound() const;
/*! \return Whether the set has lower bound. */
bool HasLowerBound() const;
/*!
* \brief The single point value, call only if IsSinglePoint is true
* \return The point value.
*/
PrimExpr PointValue() const;
/*!
* \brief Try to match IntSet with range r.
*
* \note It is guanrateed that IntSet::FromRange(r).MatchRange(r) == true
* \return true if we can prove they are the same.
*/
bool MatchRange(const tvm::Range& r) const;
/*! \return The set contains nothing */
static IntSet Nothing();
/*! \return The set contains everything */
static IntSet Everything();
/*!
* \brief construct a point set.
* \param point The point in the set.
* \return construct a single point set
*/
static IntSet SinglePoint(PrimExpr point);
/*!
* \brief construct a integer set from vector expression.
* \param vec The vector expression, can also be single point.
* \return The result set containing the indices in the vector.
*/
static IntSet Vector(PrimExpr vec);
/*!
* \brief Construct a set representing a range [min, min + extent).
* \param min The minimum of the range range
* \param extent The extent of the range.
* \return The constructed set.
*/
static IntSet FromMinExtent(PrimExpr min, PrimExpr extent);
/*!
* \brief Construct a set representing a range.
* \param r The range
* \return The constructed set.
*/
static IntSet FromRange(tvm::Range r);
/*!
* \brief Construct a set representing a interval.
* \param min The minimum value of the interval.
* \param max The maximum value of the interval.
* \return The constructed set.
*/
static IntSet Interval(PrimExpr min, PrimExpr max);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(IntSet, ffi::ObjectRef, IntSetNode);
};
//-----------------------------------------------
// Integer set legacy API.
//------------------------------------------------
/*!
* \brief Convert std::unordered_map<const VarNode*, IntSet> to ffi::Map<Var, IntSet>
*
* \param dom_map The domain map to convert.
* \return The converted map.
*/
ffi::Map<Var, IntSet> ConvertDomMap(const std::unordered_map<const VarNode*, IntSet>& dom_map);
/*!
* \brief Find an symbolic integer set that contains all possible values of
* e given the domain of each iteration variables.
*
* \param e The expression to be evaluated.
* \param dom_map The domain of each variable.
* \return An integer set that can cover all the possible values of e.
*/
IntSet EvalSet(PrimExpr e, const ffi::Map<IterVar, IntSet>& dom_map);
/*!
* \brief Find an symbolic integer set that contains all possible values of
* e given the domain of each variables.
*
* \param e The expression to be evaluated.
* \param dom_map The domain of each variable.
* \return An integer set that can cover all the possible values of e.
*/
IntSet EvalSet(PrimExpr e, const ffi::Map<Var, IntSet>& dom_map);
/*!
* \brief Same as EvalSet, but takes unordered_map
*
* \param e The expression to be evaluated.
* \param dom_map The domain of each variable.
* \return An integer set that can cover all the possible values of e.
*/
IntSet EvalSet(PrimExpr e, const std::unordered_map<const tirx::VarNode*, IntSet>& dom_map);
/*!
* \brief Find an symbolic integer set that contains is union over
* all the possible conditional values in dom_map.
*
* \param r The initial range.
* \param dom_map The domain of each variable.
* \return An integer set that can cover all the possible values.
*/
IntSet EvalSet(Range r, const ffi::Map<IterVar, IntSet>& dom_map);
/*!
* \brief Find an symbolic integer set that contains is union over
* all the possible conditional values in dom_map.
*
* \param s The initial set.
* \param dom_map The domain of each variable.
* \return An integer set that can cover all the possible values.
*/
IntSet EvalSet(IntSet s, const std::unordered_map<const VarNode*, IntSet>& dom_map);
/*!
* \brief Same as EvalSet, but takes unordered_map
*
* \param r The range to be evaluated.
* \param dom_map The domain of each variable.
* \return An integer set that can cover all the possible values of e.
*/
IntSet EvalSet(Range r, const std::unordered_map<const VarNode*, IntSet>& dom_map);
/*!
* \brief Same as EvalSet, but takes ffi::Array<Range>
*
* \param region The range to be evaluated.
* \param dom_map The domain of each variable.
* \return An array of integer sets that can cover all the possible values.
*/
ffi::Array<IntSet> EvalSet(const ffi::Array<Range>& region, const ffi::Map<Var, IntSet>& dom_map);
/*! \brief Map from Expr to IntSet */
using ExprIntSetMap = std::unordered_map<PrimExpr, IntSet, ffi::ObjectPtrHash, ffi::ObjectPtrEqual>;
/*!
* \brief Find the integer set of every sub-expression, given the
* domain of each iteration variables.
*
* \param e The expression to be evaluated.
* \param dom_map The domain of each variable.
* \return the map from the expression to its possible value.
*/
ExprIntSetMap EvalSetForEachSubExpr(PrimExpr e,
const std::unordered_map<const VarNode*, IntSet>& dom_map);
/*!
* \brief Create a union set of all sets, possibly relaxed
* \param sets The sets to be combined
* \return the set after union
*/
IntSet Union(const ffi::Array<IntSet>& sets);
/*!
* \brief The union of N-dimensional integer sets
* \param nd_int_sets A list of N-dimensional integer sets
* \return An N-dimensional integer set as the result of union
*/
ffi::Array<IntSet> UnionRegion(const ffi::Array<ffi::Array<IntSet>>& nd_int_sets);
/*!
* \brief Create a lower-bound of union set, where some of the segments may be dropped
* \param sets The sets to be combined
* \return the set after union
*/
IntSet UnionLowerBound(const ffi::Array<IntSet>& sets);
/*!
* \brief The union of N-dimensional integer sets
* \param nd_int_sets A list of N-dimensional integer sets
* \return An N-dimensional integer set as the result of union
*/
ffi::Array<IntSet> UnionRegionLowerBound(const ffi::Array<ffi::Array<IntSet>>& nd_int_sets);
/*!
* \brief Create an intersected set of all sets
* \param sets The sets to be intersected
* \return the set after intersected
*/
IntSet Intersect(const ffi::Array<IntSet>& sets);
/*!
* \brief Converts the Ranges to IntSets
* \param var_dom The ranges of variables
* \return The integer sets of the variables
*/
ffi::Map<Var, arith::IntSet> AsIntSet(const ffi::Map<Var, Range>& var_dom);
/*!
* \brief Analyze the region with affine map, given the domain of variables and their predicate.
* The result should be strict, i.e. no region is discarded or relaxed.
* \param region The region to be analyzed
* \param var_dom The ranges of the variables
* \param predicate The predicate for the affine map
* \param analyzer The analyzer used
* \return std::nullopt if the detection fails, or an array of arith::IntSet as the result of
* analysis
*/
TVM_DLL ffi::Optional<ffi::Array<IntSet>> EstimateRegionStrictBound(
const ffi::Array<Range>& region, const ffi::Map<Var, Range>& var_dom, const PrimExpr& predicate,
const arith::Analyzer& analyzer);
/*!
* \brief Analyze the region with affine map, given the domain of variables and their predicate.
* Some subregion may be discarded during the lower-bound analysis.
* \param region The region to be analyzed
* \param var_dom The ranges of the variables
* \param predicate The predicate for the affine map
* \param analyzer The analyzer used
* \return std::nullopt if the detection fails, or an array of arith::IntSet as the result of
* analysis
*/
TVM_DLL ffi::Optional<ffi::Array<IntSet>> EstimateRegionLowerBound(
const ffi::Array<Range>& region, const ffi::Map<Var, Range>& var_dom, const PrimExpr& predicate,
const arith::Analyzer& analyzer);
/*!
* \brief Analyze the region with affine map, given the domain of variables and their predicate
* Relaxation of the region may be used in upper-bound analysis, i.e. some extra region may be added
* to the result.
* \param region The region to be analyzed
* \param var_dom The ranges of the variables
* \param predicate The predicate for the affine map
* \param analyzer The analyzer used
* \return an array of arith::IntSet as the result of analysis
*/
TVM_DLL ffi::Array<IntSet> EstimateRegionUpperBound(const ffi::Array<Range>& region,
const ffi::Map<Var, Range>& var_dom,
const PrimExpr& predicate,
const arith::Analyzer& analyzer);
} // namespace arith
} // namespace tvm
#endif // TVM_ARITH_INT_SET_H_
+338
View File
@@ -0,0 +1,338 @@
/*
* 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.
*/
/*!
* \file tvm/arith/int_solver.h
* \brief integer constraints data structures and solvers
*/
#ifndef TVM_ARITH_INT_SOLVER_H_
#define TVM_ARITH_INT_SOLVER_H_
#include <tvm/ir/expr.h>
#include <tvm/tirx/expr.h>
#include <tvm/tirx/op.h>
#include <unordered_map>
#include <utility>
#include <vector>
#include "analyzer.h"
namespace tvm {
namespace arith {
using tirx::IterVar;
using tirx::Var;
using tirx::VarNode;
// According to experiments two best simplifications orders were can->rw and rw->can->rw,
// but rw->can->rw is better for a couple of cases.
// Also we should end with rw because it factors multipliers out.
constexpr int kSimplifyRewriteCanonicalRewrite = 3;
/*!
* \brief Represent integer grouped bounds which are classified into
* lower bounds (inclusive), upper bounds (inclusive) and equalities.
* It also contains coefficient as a multiplier for the bounds, i.e.,
* coef * var >= lower
* coef * var == equal
* coef * var <= upper
* \sa IntGroupBounds
*/
class IntGroupBoundsNode : public ffi::Object {
public:
PrimExpr coef;
ffi::Array<PrimExpr> lower;
ffi::Array<PrimExpr> equal;
ffi::Array<PrimExpr> upper;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<IntGroupBoundsNode>()
.def_ro("coef", &IntGroupBoundsNode::coef)
.def_ro("lower", &IntGroupBoundsNode::lower)
.def_ro("equal", &IntGroupBoundsNode::equal)
.def_ro("upper", &IntGroupBoundsNode::upper);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("arith.IntGroupBounds", IntGroupBoundsNode, ffi::Object);
};
/*!
* \brief Managed reference to IntGroupBoundsNode.
* \sa IntGroupBoundsNode
*/
class IntGroupBounds : public ffi::ObjectRef {
public:
/*!
* \brief Constructor by fields
* \param coef The coefficient. Must be integer.
* coef * var >= lower
* coef * var == equal
* coef * var >= upper
* \param lower the lower bounds (include)
* \param equal equalities
* \param upper the upper bounds (include)
*/
TVM_DLL IntGroupBounds(PrimExpr coef, ffi::Array<PrimExpr> lower, ffi::Array<PrimExpr> equal,
ffi::Array<PrimExpr> upper);
/*!
* \brief Construct bounds from a range.
* \param r The range
* \return constructed bounds.
*/
static IntGroupBounds FromRange(const Range& r);
/*!
* \brief Perform substitution on all components of the struct.
*/
IntGroupBounds Substitute(const ffi::Map<Var, PrimExpr>& subst) const;
/*!
* \brief Find the best range from the grouped bounds.
* \param vranges_addl additional variable ranges that help infer the best range.
* \return The best range (has the least difference between the lower bound and upper bound).
* undefined if (-inf, +inf).
*/
Range FindBestRange(const ffi::Map<Var, Range>& vranges_addl = {}) const;
/*!
* \brief Combine the bounds with another range.
* \param r range to be combined.
* \return combined bounds.
*/
IntGroupBounds operator+(const Range& r);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(IntGroupBounds, ffi::ObjectRef, IntGroupBoundsNode);
};
/*!
* \brief Represent integer constrains including (integer) variables, their ranges and
* the relations between them (either equations or inequalities).
* \sa LinearSystem
*/
class IntConstraintsNode : public ffi::Object {
public:
// e.g., \alpha, \beta, must be integers
ffi::Array<Var> variables;
// e.g., 1 <= \alpha <= N, etc.
// it is absolutely ok to include ranges for parameters
// (variables that are not in this->variables) in this map
ffi::Map<Var, Range> ranges;
// linear equalities or inequalities
// e.g., A \alpha = \beta or A \alpha <= \beta
ffi::Array<PrimExpr> relations;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<IntConstraintsNode>()
.def_ro("variables", &IntConstraintsNode::variables)
.def_ro("ranges", &IntConstraintsNode::ranges)
.def_ro("relations", &IntConstraintsNode::relations);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("arith.IntConstraints", IntConstraintsNode, ffi::Object);
};
/*!
* \brief Managed reference to IntConstraintsNode.
* \sa IntConstraintsNode
*/
class IntConstraints : public ffi::ObjectRef {
public:
/*!
* \brief Constructor by fields
* \param variables The variables in the constraints, must be integers.
* \param ranges The ranges of the variables.
* \param relations The linear relations between the variables
* (either equations or inequalities)
*/
TVM_DLL IntConstraints(ffi::Array<Var> variables, ffi::Map<Var, Range> ranges,
ffi::Array<PrimExpr> relations);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(IntConstraints, ffi::ObjectRef, IntConstraintsNode);
};
/*!
* \brief We can have different set of variables to represent the same constraints.
* For example, the following two systems are equivalent,
* {a + b = 0 | a >= 0, b >= 0} and
* {m - n = 0 | m >= 0, n <= 0}
* This data structure represents the transformation
* between two equivalent linear systems.
* In the above example,
* src : {a + b = 0 | a >= 0, b >= 0}
* dst : {m - n = 0 | m >= 0, n <= 0}
* src_to_dst : {a -> m, b -> -n}
* dst_to_src : {m -> a, n -> -b}
* \sa IntConstraintsTransform
*/
class IntConstraintsTransformNode : public ffi::Object {
public:
IntConstraints src;
IntConstraints dst;
ffi::Map<Var, PrimExpr> src_to_dst;
ffi::Map<Var, PrimExpr> dst_to_src;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<IntConstraintsTransformNode>()
.def_ro("src", &IntConstraintsTransformNode::src)
.def_ro("dst", &IntConstraintsTransformNode::dst)
.def_ro("src_to_dst", &IntConstraintsTransformNode::src_to_dst)
.def_ro("dst_to_src", &IntConstraintsTransformNode::dst_to_src);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("arith.IntConstraintsTransform", IntConstraintsTransformNode,
ffi::Object);
};
/*!
* \brief Managed reference to IntConstraintsTransformNode.
* \sa IntConstraintsTransformNode
*/
class IntConstraintsTransform : public ffi::ObjectRef {
public:
/*!
* \brief Constructor by fields
* \param src source integer constraints, e.g., {a + b = 0 | a >= 0, b >= 0}
* \param dst integer constraints equivalent to the source,
* e.g., {m - n = 0 | m >= 0, n <= 0}
* \param src_to_dst mapping from variables in the \p src to the variables in the \p dst,
* e.g., {a -> m, b -> -n}
* \param dst_to_src mapping from variables in the \p dst to the variables in the \p src,
* e.g., {m -> a, n -> -b}
*/
TVM_DLL IntConstraintsTransform(IntConstraints src, IntConstraints dst,
ffi::Map<Var, PrimExpr> src_to_dst,
ffi::Map<Var, PrimExpr> dst_to_src);
/*!
* \brief Chain-compose two IntConstraintsTransform together.
* this->dst must be the same as other->src.
* @param other another IntConstraintsTransform whose src is same as this->dst.
* @return composed IntConstraintsTransform(this->src, other->dst)
* with its variables and ranges are properly modified.
*/
IntConstraintsTransform operator+(const IntConstraintsTransform& other) const;
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(IntConstraintsTransform, ffi::ObjectRef,
IntConstraintsTransformNode);
};
typedef std::pair<ffi::Map<Var, IntGroupBounds>, ffi::Array<PrimExpr>> PartialSolvedInequalities;
/*!
* \brief Obtain Smith Normal Form of linear equation A x = y.
* Smith Normal Form of matrix A_{mxn} is S_{mxn} = U_{mxm} A_{mxn} V_{nxn},
* in which S_{mxn} is diag(s1, s2, ..., sr, 0, ..., 0) and r is the rank of A.
* NOTE: Although in standard Smith Normal Form the diagonal elements satisfy
* s_i | s_{i+1} (| means divides), the implement here does not guarantee it.
* TODO(yzhliu): From sergei-grechanik:
* computing the proper Smith normal form may improve stability of automatic
* differentiation (generating the same gradient code for slightly different but equivalent input
* code U_{mxm} and V_{nxn} are invertible matrices. This function modifies \p S to be S_{mxn}, \p V
* to be V_{nxn}, \p y to be U_{mxm} y_{mx1} and \p x to be V^{-1} x. \param S the original
* A_{mxn}, it will be modified to S_{mxn} \param V an identity matrix, it will be modified to
* V_{nxn} \param x the x in A x = y. it will be modified to V^{-1}_{nxn} x_{nx1} \param y the y
* in A x = y. it will be modified to U_{mxm} y_{mx1}
*/
void SmithNormalFormDiag(std::vector<std::vector<int64_t>>* S, std::vector<std::vector<int64_t>>* V,
std::vector<PrimExpr>* x, std::vector<PrimExpr>* y);
/*!
* \brief Solve linear equations.
* \param system_to_solve the variables to solve, their ranges, and a list of equations.
* \return A new linear system, with less variables (if \p system_to_solve is NOT of full rank),
* or no variable (if \p system_to_solve is of full rank),
* or an empty linear system (if \p system_to_solve is unsolvable).
* It also provides the ranges of the variables in the new system,
* as well as inequalities inferred from the \p system_to_solve.
* You can get the mapping from the original variables to the solution via ret->src_to_dst.
*/
IntConstraintsTransform SolveLinearEquations(const IntConstraints& system_to_solve);
/*!
* \brief Solve linear inequalities.
* \param system_to_solve the variables to solve, their ranges, and a list of inequalities.
* The inequalities are rewritten using Fourier-Motzkin elimination.
* This function takes an array of (in)equalities and an array of variables, and essentially
* rewrites the (in)equalities into an array of (in)equalities of the following form,
*
* x0 >= f0(x1, x2, ..., xn)
* x0 <= g0(x1, x2, ..., xn)
* x1 >= f1(x2, ..., xn)
* x1 <= g1(x2, ..., xn)
* ...
* xn >= fn() // just a constant
* xn <= gn() // just a constant
*
* \return A map of variables and their solved bounds,
* and constrains that cannot be solved to bounds.
*/
PartialSolvedInequalities SolveLinearInequalities(const IntConstraints& system_to_solve);
/*!
* \brief Combine the information into an array of (in)equalities.
* \param variables The variables in \p bounds.
* It is used to determine the iteration order to avoid indeterministic results.
* \param bounds grouped boundary of the variables.
* \param relations other relations.
*/
ffi::Array<PrimExpr> AsConditions(const ffi::Array<Var>& variables,
const ffi::Map<Var, IntGroupBounds>& bounds,
const ffi::Array<PrimExpr>& relations);
/*!
* \brief Solve linear inequalities and infer the range of each variable.
* \param system_to_solve the variables to solve, their ranges, and a list of inequalities.
* \return The result ranges for each variables.
* The returned IntConstraints(variables, ranges, relations) contains,
* 1. variables - the variables that have been solved.
* 2. ranges - the best range of each variable.
* 3. relations - constraints that cannot be transformed to
* Range will be stored in relations.
*/
IntConstraints SolveInequalitiesToRange(const IntConstraints& system_to_solve);
/*!
* \brief Solve linear inequalities and deskew the ranges towards zero.
* \param system_to_solve the variables to solve, their ranges, and a list of inequalities.
* \return A transform (src IntConstraints -> dst IntConstraints)
* from original variables to a set of new variables.
* The ranges of new variables always start from zero,
* their extents are solved from \p system_to_solve.
* src IntConstraints is the same as \p system_to_solve.
* dst IntConstraints(variables, ranges, relations) contains,
* 1. variables - the variables that have been solved.
* 2. ranges - the best range (start from zero) of each variable.
* 3. relations - constraints that cannot be transformed to
* Range will be stored in relations.
* Variable mapping can be obtained from
* IntConstraintsTransform.src_to_dst and IntConstraintsTransform.dst_to_src.
*/
IntConstraintsTransform SolveInequalitiesDeskewRange(const IntConstraints& system_to_solve);
} // namespace arith
} // namespace tvm
#endif // TVM_ARITH_INT_SOLVER_H_
+425
View File
@@ -0,0 +1,425 @@
/*
* 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.
*/
/*!
* \file tvm/arith/iter_affine_map.h
* \brief Iterator quasi-affine mapping patterns.
*
* This file defines a collection of mapping patterns
* maps a collection of independent iterators to another
* collection of independent iterators.
*
* There are two main kinds of mapping patterns:
*
* - Fuse: fuse a collection of iterators into a single one
*
* domain(x0) = [0, 4), domain(x1) = [0, 3), domain(x2) = [0, 2)
* fuse(x0, x1, x2): y = x2 * 12 + x1 * 4 + x0
* domain(y) = [0, 24)
*
* - Split: split an iterator into multiple ones
*
* domain(x) = [0, 24)
* split(x, 3, 12): [y0, y1, y2] = [x % 3, (x % 12) / 3, x / 12]
* domain(y0) = [0, 3), domain(y1) = [0, 4), domain(y2) = [0, 2)
*
* We use the name "(quasi)affine" to be consistent with
* the terminology used in the polyhedral compilation.
* Notably, fuse is an affine transformation,
* while split corresponds to additional floordiv/mod operations
* that can appear in quasi-affine transformations.
*/
#ifndef TVM_ARITH_ITER_AFFINE_MAP_H_
#define TVM_ARITH_ITER_AFFINE_MAP_H_
#include <tvm/arith/analyzer.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/cow.h>
#include <tvm/ir/expr.h>
#include <tvm/tirx/var.h>
namespace tvm {
namespace arith {
/*!
* \brief Base class of all iter map expressions.
*
* An IterMapExpr is a special expression to store
* the result of IterMapDetection.
* It should not appear in a legal TIR PrimFunc.
*/
class IterMapExprNode : public ExprNode {
public:
static constexpr const uint32_t _type_child_slots = 2;
TVM_FFI_DECLARE_OBJECT_INFO("arith.IterMapExpr", IterMapExprNode, ExprNode);
};
/*!
* \brief Managed reference to IterMapExprNode.
* \sa IterMapExprNode
*/
class IterMapExpr : public PrimExpr {
public:
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(IterMapExpr, PrimExpr, IterMapExprNode);
static constexpr bool _type_container_is_exact = true;
};
/*!
* \brief Mark the source as an iterator in [0, extent).
*
* IterMark is used to mark source expression as a valid
* iterator to make future analysis easy.
*/
class IterMarkNode : public ffi::Object {
public:
/*!
* \brief The source expression, can either be
* a IterSumExpr or a Var.
*/
PrimExpr source;
/*!
* \brief The extent of the iteration.
*/
PrimExpr extent;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<IterMarkNode>()
.def_ro("source", &IterMarkNode::source)
.def_ro("extent", &IterMarkNode::extent);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindDAGNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("arith.IterMark", IterMarkNode, ffi::Object);
};
/*!
* \brief Managed reference to IterMarkExprNode.
* \sa IterMarkExprNode
*/
class IterMark : public ffi::ObjectRef {
public:
/*!
* \brief constructor.
* \param source The source expression.
* \param extent The extent of the iterator.
*/
TVM_DLL IterMark(PrimExpr source, PrimExpr extent);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(IterMark, ffi::ObjectRef, IterMarkNode);
TVM_DEFINE_OBJECT_REF_COW_METHOD(IterMarkNode);
};
/*!
* \brief Split of an iterator.
*
* result = floormod(floordiv(source, lower_factor), extent) * scale
*/
class IterSplitExprNode : public IterMapExprNode {
public:
/*! \brief The source marked iterator. */
IterMark source;
/*! \brief The lower factor to split the source. */
PrimExpr lower_factor;
/*! \brief The extent of the split. */
PrimExpr extent;
/*! \brief Additional scale. */
PrimExpr scale;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<IterSplitExprNode>()
.def_ro("source", &IterSplitExprNode::source)
.def_ro("lower_factor", &IterSplitExprNode::lower_factor)
.def_ro("extent", &IterSplitExprNode::extent)
.def_ro("scale", &IterSplitExprNode::scale);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("arith.IterSplitExpr", IterSplitExprNode, IterMapExprNode);
};
/*!
* \brief Managed reference to IterSplitExprNode.
* \sa IterSplitExprNode
*/
class IterSplitExpr : public IterMapExpr {
public:
/*!
* \brief constructor from just source.
* \param source The source expression.
*/
TVM_DLL explicit IterSplitExpr(IterMark source);
/*!
* \brief constructor from just source.
* \param source The source expression.
* \param scale The additional scaling factor.
*/
TVM_DLL explicit IterSplitExpr(IterMark source, PrimExpr scale);
/*!
* \brief constructor
* \param source The source expression.
* \param lower_factor The lower factor to split the source.
* \param extent The extent of the split.
* \param scale The additional scaling factor.
*/
TVM_DLL explicit IterSplitExpr(IterMark source, PrimExpr lower_factor, PrimExpr extent,
PrimExpr scale);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(IterSplitExpr, IterMapExpr, IterSplitExprNode);
TVM_DEFINE_OBJECT_REF_COW_METHOD(IterSplitExprNode);
};
/*!
* \brief Fuse multiple iterators by summing them with scaling.
*
* result = sum(args) + base
*/
class IterSumExprNode : public IterMapExprNode {
public:
/*! \brief The args to the sum. */
ffi::Array<IterSplitExpr> args;
/*! \brief The base offset. */
PrimExpr base;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<IterSumExprNode>()
.def_ro("args", &IterSumExprNode::args)
.def_ro("base", &IterSumExprNode::base);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("arith.IterSumExpr", IterSumExprNode, IterMapExprNode);
};
/*!
* \brief Managed reference to IterSumExprNode.
* \sa IterSumExprNode
*/
class IterSumExpr : public IterMapExpr {
public:
/*!
* \brief constructor.
* \param args The args to the sum.
* \param base The base offset.
*/
TVM_DLL IterSumExpr(ffi::Array<IterSplitExpr> args, PrimExpr base);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(IterSumExpr, IterMapExpr, IterSumExprNode);
TVM_DEFINE_OBJECT_REF_COW_METHOD(IterSumExprNode);
};
} // namespace arith
namespace ffi {
template <>
inline constexpr bool object_ref_contains_v<PrimExpr, arith::IterSplitExprNode> = true;
template <>
inline constexpr bool object_ref_contains_v<PrimExpr, arith::IterSumExprNode> = true;
} // namespace ffi
namespace arith {
/*! \brief Mapping level for iterators. */
enum IterMapLevel {
// Require the mapping to be bijective.
Bijective = 0,
// Require the mapping to be surjective.
Surjective = 1,
// No mapping safety check.
NoCheck = 3
};
/*!
* \brief Result of DetectIterMap.
*/
class IterMapResultNode : public ffi::Object {
public:
// The detected pattern if a match exists.
ffi::Array<IterSumExpr> indices;
// Any errors that occurred while converting the input indices. If
// the array is empty, the conversion was successful.
ffi::Array<ffi::String> errors;
/*! \brief Boolean expression indicating if a specific value w
*
* `padding_predicate` evaluates to true for a set of indices that
* are outside the bounds of the provided index iterators, but
* inside the bounds of the returned index iterators. This
* expression is in terms of the variables provided in
* `input_iters`.
*/
PrimExpr padding_predicate;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<IterMapResultNode>()
.def_ro("indices", &IterMapResultNode::indices)
.def_ro("errors", &IterMapResultNode::errors)
.def_ro("padding_predicate", &IterMapResultNode::padding_predicate);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("arith.IterMapResult", IterMapResultNode, ffi::Object);
};
/*!
* \brief Managed reference to IterMapResultNode.
* \sa IterMapResultNode
*/
class IterMapResult : public ffi::ObjectRef {
public:
// constructor
IterMapResult() { data_ = ffi::make_object<IterMapResultNode>(); }
/*! \return mutable pointers to the node. */
IterMapResultNode* operator->() const { return static_cast<IterMapResultNode*>(get_mutable()); }
};
/*!
* \brief Detect if indices can be written as
* [y_0 + c_0, y_1 + c_1, ..., y_n + c_n]
*
* Here y = some-quasi-affine-iter-map(input_iters)
* and c are symbolic constants.
*
* We also requires that y_i and y_j to be independent for i != j.
*
* For returned value rv, the following is always true:
* - rv[i]->args.size() <=1: only one iterator per element.
*
* \param indices The indices to detect pattern for.
* \param input_iters Map from variable to iterator's range.
* \param predicate The predicate constraints on the input iterators
* \param check_level The iter mapping checking level.
* \param analyzer Analyzer used to get context information.
* \param simplify_trivial_iterators If true, iterators with extent of
* 1 will be replaced with a constant value.
*
* \return The detected iteration result.
* The return object's .indices is empty on failure.
*/
IterMapResult DetectIterMap(const ffi::Array<PrimExpr>& indices,
const ffi::Map<Var, Range>& input_iters, const PrimExpr& predicate,
IterMapLevel check_level, const arith::Analyzer& analyzer,
bool simplify_trivial_iterators = true);
/*!
* \brief Use IterVarMap detector to rewrite and simplify the indices
*
* \param indices The indices to detect pattern for.
* \param input_iters Map from variable to iterator's range.
* \param input_pred The predicate constraints on the input iterators
* \param check_level The iter mapping checking level.
* \param analyzer Analyzer used to get context information.
* \param simplify_trivial_iterators If true, iterators with unit extents are simplified
* \return The indices after rewrite
*/
ffi::Array<PrimExpr> IterMapSimplify(const ffi::Array<PrimExpr>& indices,
const ffi::Map<Var, Range>& input_iters,
const PrimExpr& input_pred, IterMapLevel check_level,
const arith::Analyzer& analyzer,
bool simplify_trivial_iterators = true);
/*!
* \brief Apply the inverse of the affine transformation to the outputs.
*
* Similar to the back-propagation, starting from the outputs, it visits the DAG of the expressions
* in reverse topology order and applies the inverse of the affine transformation until it reaches
* the input. The affine iter map is required to be bijective.
*
* For example, iter_map = [l0 // 16, l0 % 16], outputs = [output_0, output_1],
* the affine transformation specified by `iter_map` will be applied to `outputs` and the result
* will be {l0: ((output_0*16) + output_1)}.
*
* The range of `outputs` should be the same as the output range of the affine transmation.
*
* \sa DetectIterMap
*
* \param iter_map The bijective affine iter map.
* \param outputs The outputs of the affine transformation.
*
* \return The map from the input to the transformed result.
*/
ffi::Map<Var, PrimExpr> InverseAffineIterMap(const ffi::Array<IterSumExpr>& iter_map,
const ffi::Array<PrimExpr> outputs);
/*!
* \brief Detect if bindings can be written as
* [a_0*e_0 + b_0 + c_0, a_1*e_1 + b_1, ..., a_n*e_n + b_n]
*
* where a = some-quasi-affine-iter-map(input_iters set_minus sub_iters)
* b = some-quasi-affine-iter-map(sub_iters)
* c is constant symbols
* e is the extent of b
*
* For example, z*12 + y*3 + x + c = (z*4+y)*3 + x, if sub_iters={x}
*
* \param bindings The input bindings
* \param input_iters Map from variable to iterator's range.
* \param sub_iters Iterators of subspace.
* \param predicate The predicate constraints on the input iterators
* \param check_level The iter mapping checking level.
* \param analyzer Analyzer used to get context information.
* \param simplify_trivial_iterators If true, iterators with extent of
* 1 will be replaced with a constant value.
*
* \return The result list has length len(bindings) + 1
[0, len(bindings)): The iter map matching result. The inner list is of length 2.
The first expr is the basis of the quotient space.
The second expr is the basis of the subspace.
len(bindings): the predicate of outer space and inner space
Empty array if no match can be found.
*/
ffi::Array<ffi::Array<IterMark>> SubspaceDivide(const ffi::Array<PrimExpr>& bindings,
const ffi::Map<Var, Range>& input_iters,
const ffi::Array<Var>& sub_iters,
const PrimExpr& predicate, IterMapLevel check_level,
const arith::Analyzer& analyzer,
bool simplify_trivial_iterators = true);
/*!
* \brief Given an expression that may contain IterMapExpr, transform it to normal PrimExpr.
* \param expr The input expression, which may contain IterMapExpr.
* \return The corresponding normal PrimExpr.
*/
PrimExpr NormalizeIterMapToExpr(const PrimExpr& expr);
/*!
* \brief Rewrite index as IterSumExpr
*
* ((i0 // b0) % a0) * s0 + ((i0 // b1) % a1) * s1 ... + base
*
* The iterators are ordered such that s0 > s1 ...
* if we can prove the relation.
*
* Note that base may contain expressions that cannot be detected
* as the right pattern.
*
* \param index The input index
* \param input_iters The input iterators.
* \param analyzer The input analyzer.
* \note This function is useful to detect iterator stride patterns.
*/
IterSumExpr NormalizeToIterSum(PrimExpr index, const ffi::Map<Var, Range>& input_iters,
const arith::Analyzer& analyzer);
} // namespace arith
} // namespace tvm
#endif // TVM_ARITH_ITER_AFFINE_MAP_H_
+54
View File
@@ -0,0 +1,54 @@
/*
* 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.
*/
/*!
* \file tvm/arith/pattern.h
* \brief Expression pattern detectors.
*/
#ifndef TVM_ARITH_PATTERN_H_
#define TVM_ARITH_PATTERN_H_
#include <tvm/ir/expr.h>
#include <tvm/tirx/expr.h>
namespace tvm {
namespace arith {
/*!
* \brief Detect if e can be rewritten as e = sum_{i=0}^{n-1} var[i] * coeff[i] + coeff[n]
* Where coeff[i] and base are invariant of var[j] for all i and j.
*
* \param e The expression to be detected.
* \param vars List of variables to be used in detection.
* \return [coeff[i]] if it is possible, empty array if it is not.
*/
ffi::Array<PrimExpr> DetectLinearEquation(const PrimExpr& e, const ffi::Array<tirx::Var>& vars);
/*!
* \brief Detect if expression corresponds to clip bound of the vars
*
* \param e The expression to be detected.
* \param vars List of variables to be used in detection.
* \return concat([min_value[i], max_value[i]]), None is returned if there is no min or max value
* return empty if the e does not match the pattern.
*/
ffi::Array<PrimExpr> DetectClipBound(const PrimExpr& e, const ffi::Array<tirx::Var>& vars);
} // namespace arith
} // namespace tvm
#endif // TVM_ARITH_PATTERN_H_
+144
View File
@@ -0,0 +1,144 @@
/*
* 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.
*/
/*!
* \file tvm/ir/attr_registry_map.h
* \brief Attribute map used in registry.
*/
#ifndef TVM_IR_ATTR_REGISTRY_MAP_H_
#define TVM_IR_ATTR_REGISTRY_MAP_H_
#include <tvm/ffi/string.h>
#include <utility>
#include <vector>
namespace tvm {
/*!
* \brief Generic attribute map.
* \tparam KeyType the type of the key.
*/
template <typename KeyType>
class AttrRegistryMapContainerMap {
public:
/*!
* \brief Check if the map has key.
* \param key The key to the map
* \return 1 if key is contained in map, 0 otherwise.
*/
int count(const KeyType& key) const {
if (key.defined()) {
const uint32_t idx = key->AttrRegistryIndex();
return idx < data_.size() ? (data_[idx].second != 0) : 0;
} else {
return 0;
}
}
/*!
* \brief get the corresponding value element at key.
* \param key The key to the map
* \return the const reference to the content value.
*/
const ffi::Any& operator[](const KeyType& key) const {
TVM_FFI_ICHECK(key.defined());
const uint32_t idx = key->AttrRegistryIndex();
TVM_FFI_ICHECK(idx < data_.size() && data_[idx].second != 0)
<< "Attribute " << attr_name_ << " has not been registered for " << key->name;
return data_[idx].first;
}
/*!
* \brief get the corresponding value element at key with default value.
* \param key The key to the map
* \param def_value The default value when the key does not exist.
* \return the const reference to the content value.
* \tparam ValueType The content value type.
*/
template <typename ValueType>
ValueType get(const KeyType& key, ValueType def_value) const {
TVM_FFI_ICHECK(key.defined());
const uint32_t idx = key->AttrRegistryIndex();
if (idx < data_.size() && data_[idx].second != 0) {
if constexpr (std::is_same_v<ValueType, ffi::Any>) {
return data_[idx].first;
} else {
return data_[idx].first.template cast<ValueType>();
}
} else {
return def_value;
}
}
private:
/*! \brief The name of the attr field */
ffi::String attr_name_;
/*! \brief The internal data. */
std::vector<std::pair<ffi::Any, int>> data_;
/*! \brief The constructor */
AttrRegistryMapContainerMap() = default;
template <typename, typename>
friend class AttrRegistry;
friend class OpRegEntry;
};
/*!
* \brief ffi::Map<Key, ValueType> used to store meta-data.
* \tparam KeyType The type of the key
* \tparam ValueType The type of the value stored in map.
*/
template <typename KeyType, typename ValueType>
class AttrRegistryMap {
public:
/*!
* \brief constructor
* \param map The internal map.
*/
explicit AttrRegistryMap(const AttrRegistryMapContainerMap<KeyType>& map) : map_(map) {}
/*!
* \brief Check if the map has op as key.
* \param key The key to the map
* \return 1 if op is contained in map, 0 otherwise.
*/
int count(const KeyType& key) const { return map_.count(key); }
/*!
* \brief get the corresponding value element at key.
* \param key The key to the map
* \return the const reference to the content value.
*/
ValueType operator[](const KeyType& key) const {
if constexpr (std::is_same_v<ValueType, ffi::Any>) {
return map_[key];
} else {
return map_[key].template cast<ValueType>();
}
}
/*!
* \brief get the corresponding value element at key with default value.
* \param key The key to the map
* \param def_value The default value when the key does not exist.
* \return the const reference to the content value.
*/
ValueType get(const KeyType& key, ValueType def_value) const { return map_.get(key, def_value); }
protected:
/*! \brief The internal map field */
const AttrRegistryMapContainerMap<KeyType>& map_;
};
} // namespace tvm
#endif // TVM_IR_ATTR_REGISTRY_MAP_H_
+325
View File
@@ -0,0 +1,325 @@
/*
* 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.
*/
/*!
* \file tvm/ir/attrs.h
* \brief Helpers for attribute objects.
*
* This module enables declaration of named attributes
* which support default value setup and bound checking.
*
* \sa AttrsNode
*/
#ifndef TVM_IR_ATTRS_H_
#define TVM_IR_ATTRS_H_
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/extra/structural_equal.h>
#include <tvm/ffi/extra/structural_hash.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/cow.h>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <utility>
namespace tvm {
/*!
* \brief Base class of all attribute class
* \sa Attrs
*/
class AttrsNode : public ffi::Object {
public:
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO("ir.Attrs", AttrsNode, ffi::Object);
};
/*!
* \brief Managed reference to AttrsNode.
* \sa AttrsNode
*/
class Attrs : public ffi::ObjectRef {
public:
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Attrs, ffi::ObjectRef, AttrsNode);
};
/*!
* \brief Specialized attribute type that is backed by a map.
* The DictAttrsNode implements the Attrs behavior,
* its fields are directly accessible via object.field_name
* like other normal nodes.
*/
class DictAttrsNode : public AttrsNode {
public:
/*! \brief internal attrs map */
ffi::Map<ffi::String, ffi::Any> dict;
static void RegisterReflection() {
namespace rfl = ffi::reflection;
rfl::ObjectDef<DictAttrsNode>().def_ro("__dict__", &DictAttrsNode::dict);
}
// type info
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.DictAttrs", DictAttrsNode, AttrsNode);
};
/*!
* \brief Managed reference to DictAttrsNode
* \sa DictAttrsNode.
*
* \note DictAttrs is NOTNULLABLE: every instance must hold a backing
* DictAttrsNode. The class enforces this end-to-end by:
* - the default constructor (no args) allocating an empty backing,
* - the copy/move ctors and assignments leaving the moved-from
* instance in a defined-but-empty state rather than null,
* - the FFI type traits rejecting None at deserialization boundaries
* (since `_type_is_nullable == false`), and
* - the FFI lambda for ``ir.IRModule`` explicitly normalizing a
* missing/None attrs argument to ``DictAttrs()`` before forwarding
* to the C++ constructor.
* Callers (including third-party code via templates like ``WithAttr``)
* can therefore rely on ``attrs->dict`` being safe to dereference
* without a ``.defined()`` guard.
*/
class DictAttrs : public Attrs {
public:
/*!
* \brief Construct a DictAttrs backed by DictAttrsNode.
*
* The no-argument form constructs an empty (but always defined) DictAttrs.
* \param dict The attributes.
*/
explicit DictAttrs(ffi::Map<ffi::String, Any> dict = {}) {
ffi::ObjectPtr<DictAttrsNode> n = ffi::make_object<DictAttrsNode>();
n->dict = std::move(dict);
data_ = std::move(n);
}
/*!
* \brief Move constructor that leaves the source in a defined-but-empty
* state rather than null, preserving the NOTNULLABLE invariant
* even after `std::move`.
*/
DictAttrs(DictAttrs&& other) noexcept : Attrs(ffi::UnsafeInit{}) {
data_ = std::move(other.data_);
other.data_ = ffi::make_object<DictAttrsNode>();
}
/*!
* \brief Move assignment that leaves the source in a defined-but-empty
* state rather than null, preserving the NOTNULLABLE invariant
* even after `std::move`.
*/
DictAttrs& operator=(DictAttrs&& other) noexcept {
if (this != &other) {
data_ = std::move(other.data_);
other.data_ = ffi::make_object<DictAttrsNode>();
}
return *this;
}
// Explicit copy ctor/assign defaults. Declaring the move members above
// would otherwise suppress the implicit copy members.
DictAttrs(const DictAttrs& other) = default;
DictAttrs& operator=(const DictAttrs& other) = default;
// Utils for accessing attributes
/*!
* \brief Get a function attribute.
*
* \param attr_key The attribute key.
* \param default_value The default value if the key does not exist, defaults to nullptr.
*
* \return The result
*
* \tparam TOBjectRef the expected object type.
* \throw Error if the key exists but the value does not match TObjectRef
*
* \code
*
* void GetAttrExample(const BaseFunc& f) {
* auto value = f->attrs.GetAttr<int64_t>("AttrKey", 0);
* }
*
* \endcode
*/
template <typename TObjectRef>
ffi::Optional<TObjectRef> GetAttr(
const std::string& attr_key,
ffi::Optional<TObjectRef> default_value = ffi::Optional<TObjectRef>(std::nullopt)) const {
const DictAttrsNode* node = get();
auto it = node->dict.find(attr_key);
if (it != node->dict.end()) {
return (*it).second.cast<TObjectRef>();
} else {
return default_value;
}
}
// variant that uses TObjectRef to enable implicit conversion to default value.
template <typename TObjectRef>
ffi::Optional<TObjectRef> GetAttr(const std::string& attr_key, TObjectRef default_value) const {
return GetAttr<TObjectRef>(attr_key, ffi::Optional<TObjectRef>(default_value));
}
/*!
* \brief Check whether the function has an non-zero integer attr.
*
* This function can be used to check whether an optional
* attribute mark(e.g. inline) exists.
*
* \param attr_key The key to the attribute.
* \return The check result.
*
* \code
*
* void HasNonzeroAttrExample(const BaseFunc& f) {
* if (f->HasNonzeroAttr(attr::kInline)) {
* // inline the function.
* }
* }
*
* \endcode
*/
bool HasNonzeroAttr(const std::string& attr_key) const {
return GetAttr<int64_t>(attr_key, 0).value_or(0) != 0;
}
// Inline-expand TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE here, minus
// the default copy/move it normally injects (we define our own move members
// above so the moved-from instance stays defined-but-empty).
explicit DictAttrs(::tvm::ffi::UnsafeInit tag) : Attrs(tag) {}
using __PtrType =
std::conditional_t<DictAttrsNode::_type_mutable, DictAttrsNode*, const DictAttrsNode*>;
__PtrType operator->() const { return static_cast<__PtrType>(data_.get()); }
__PtrType get() const { return static_cast<__PtrType>(data_.get()); }
static constexpr bool _type_is_nullable = false;
using ContainerType = DictAttrsNode;
TVM_DEFINE_OBJECT_REF_COW_METHOD(DictAttrsNode);
};
/*!
* \brief Copy the function or module, but overrides
* the attribute value key with the value.
*
* \param input The thing to annotate (BaseFunc or IRModule)
* \param attr_key The attribute key.
* \param attr_value The value attribute value.
*
* \tparam TFunc The corresponding function or module type.
*
* \returns The new function or module with updated attributes.
*
* \note This function performs copy on write optimization for func and module.
* If we move a uniquely referenced func or module into WithAttr,
* then no additional copy will be performed.
*
* This is also why we make it as a function instead of a member function
* and why we pass by value in the first argument.
*
* \code
*
* // Recommended way to trigger copy on write
* func = WithAttr(std::move(func), "key1", value1);
* func = WithAttr(std::move(func), "key2", value2);
*
* \endcode
*/
template <typename TFunc>
inline TFunc WithAttr(TFunc input, const std::string& attr_key, Any attr_value) {
using TNode = typename TFunc::ContainerType;
static_assert(TNode::_type_final, "Can only operate on the leaf nodes");
TNode* node = input.CopyOnWrite();
// node->attrs is NOTNULLABLE by contract, but defend against a caller
// that left a moved-from DictAttrs in place by re-initializing here.
if (!node->attrs.defined()) node->attrs = DictAttrs();
node->attrs.CopyOnWrite()->dict.Set(attr_key, std::move(attr_value));
return input;
}
/*!
* \brief Copy the function or module, but overrides the attributes with the entries from \p attrs.
*
* \param input The thing to annotate (BaseFunc or IRModule)
* \param attrs Key/values attributes to add to \p input.
*
* \tparam TFunc The corresponding function or module type.
*
* \returns The new function or module with updated attributes.
*/
template <typename TFunc>
inline TFunc WithAttrs(TFunc input, ffi::Map<ffi::String, Any> attrs) {
using TNode = typename TFunc::ContainerType;
static_assert(TNode::_type_final, "Can only operate on the leaf nodes");
if (attrs.empty()) return input;
TNode* node = input.CopyOnWrite();
// node->attrs is NOTNULLABLE by contract, but defend against a caller
// that left a moved-from DictAttrs in place by re-initializing here.
if (!node->attrs.defined()) node->attrs = DictAttrs();
auto* dict_node = node->attrs.CopyOnWrite();
for (const auto& [k, v] : attrs) {
dict_node->dict.Set(k, v);
}
return input;
}
/*!
* \brief Copy the function or module, but removes the specified
* attribute.
*
* \param input The thing to annotate (BaseFunc or IRModule)
* \param attr_key The attribute key.
*
* \tparam TFunc The corresponding function or module type.
*
* \returns The new function or module with removed attribute.
*
* \note This function performs copy on write optimization for func and module.
* If we move a uniquely referenced func or module into WithoutAttr,
* then no additional copy will be performed.
*
* This is also why we make it as a function instead of a member function
* and why we pass by value in the first argument.
*
* \code
*
* // Recommended way to trigger copy on write
* func = WithoutAttr(std::move(func), "key1");
* func = WithoutAttr(std::move(func), "key2");
*
* \endcode
*/
template <typename TFunc>
inline TFunc WithoutAttr(TFunc input, const std::string& attr_key) {
using TNode = typename TFunc::ContainerType;
static_assert(TNode::_type_final, "Can only operate on the leaf nodes");
TNode* node = input.CopyOnWrite();
// node->attrs is NOTNULLABLE by contract, but defend against a caller
// that left a moved-from DictAttrs in place; nothing to erase from an
// empty dict.
if (!node->attrs.defined()) {
node->attrs = DictAttrs();
return input;
}
node->attrs.CopyOnWrite()->dict.erase(attr_key);
return input;
}
} // namespace tvm
#endif // TVM_IR_ATTRS_H_
+521
View File
@@ -0,0 +1,521 @@
/*
* 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.
*/
/*!
* \file tvm/ir/base_expr.h
* \brief Base expression and primitive type nodes.
*/
#ifndef TVM_IR_BASE_EXPR_H_
#define TVM_IR_BASE_EXPR_H_
#include <tvm/ffi/cast.h>
#include <tvm/ffi/dtype.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include <tvm/ir/source_map.h>
#include <cstddef>
#include <cstdint>
#include <optional>
#include <type_traits>
namespace tvm {
/*!
* \brief Type is the base type of all types.
*
* TVM's type system contains following subclasses:
*
* - PrimType: type of primitive type values used in the low-level IR.
* - FuncType: type of a function.
* - TensorType: type of certain Tensor values in the expression.
*
* There are also advanced types to support generic(polymorphic types).
* \sa Type
*/
class TypeNode : public ffi::Object {
public:
/*!
* \brief Span that points to the original source code.
* Reserved debug information.
*/
mutable Span span;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
// span do not participate in structural equal and hash.
refl::ObjectDef<TypeNode>().def_ro("span", &TypeNode::span, refl::DefaultValue(Span()),
refl::AttachFieldFlag::SEqHashIgnore());
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
static constexpr const uint32_t _type_child_slots = 14;
TVM_FFI_DECLARE_OBJECT_INFO("ir.Type", TypeNode, ffi::Object);
};
/*!
* \brief Managed reference to TypeNode.
* \sa TypeNode
*/
class Type : public ffi::ObjectRef {
public:
/*! \brief Sentinel for a type that has not been populated yet. */
TVM_DLL static Type Missing();
/*! \return whether this is the missing-type sentinel. */
TVM_DLL bool IsMissing() const;
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Type, ffi::ObjectRef, TypeNode);
};
/*!
* \brief Primitive data types used in the low-level IR.
*
* PrimType represents primitive POD values and the void sentinel.
*
* \sa PrimType
*/
class PrimTypeNode final : public TypeNode {
public:
/*!
* \brief The raw DLPack dtype represented by this primitive type.
*/
DLDataType dtype;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PrimTypeNode>().def_ro("dtype", &PrimTypeNode::dtype);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.PrimType", PrimTypeNode, TypeNode);
};
/*
* \brief Managed reference to PrimTypeNode.
* \sa PrimTypeNode
*/
class PrimType final : public Type {
public:
/*!
* \brief Construct from a raw DLPack dtype.
* \param dtype The corresponding DLPack dtype.
*/
TVM_DLL explicit PrimType(DLDataType dtype);
/*!
* \brief Construct from DLPack dtype fields.
* \param code The DLPack dtype code.
* \param bits The scalar bit width.
* \param lanes The fixed lane count.
*/
TVM_DLL PrimType(DLDataTypeCode code, int bits, int lanes = 1);
/*! \brief Construct a signed integer type with fixed lanes. */
TVM_DLL static PrimType Int(int bits, int lanes = 1);
/*! \brief Construct an unsigned integer type with fixed lanes. */
TVM_DLL static PrimType UInt(int bits, int lanes = 1);
/*! \brief Construct a floating-point type with fixed lanes. */
TVM_DLL static PrimType Float(int bits, int lanes = 1);
/*! \brief Construct a bfloat type with fixed lanes. */
TVM_DLL static PrimType BFloat(int bits, int lanes = 1);
/*! \brief Construct a boolean type with fixed lanes. */
TVM_DLL static PrimType Bool(int lanes = 1);
/*! \brief Construct the void sentinel type, encoded as handle(0, 0). */
TVM_DLL static PrimType Void();
/*!
* \brief Construct a scalable vector type.
* \param code The DLPack dtype code.
* \param bits The scalar bit width.
* \param lanes The positive vscale factor to encode in the DLPack lane field.
*/
TVM_DLL static PrimType ScalableVector(DLDataTypeCode code, int bits, int lanes);
/*! \return The DLPack dtype code. */
TVM_FFI_INLINE DLDataTypeCode code() const {
return static_cast<DLDataTypeCode>(static_cast<int>(get()->dtype.code));
}
/*! \return The scalar bit width. */
TVM_FFI_INLINE int32_t bits() const { return get()->dtype.bits; }
/*!
* \return The fixed lane count.
* \note Throws on scalable vector types, where the encoded lane field stores a vscale factor.
*/
TVM_FFI_INLINE int32_t lanes() const {
int16_t encoded_lanes = static_cast<int16_t>(get()->dtype.lanes);
if (TVM_FFI_PREDICT_FALSE(encoded_lanes < 0)) {
TVM_FFI_THROW(InternalError)
<< "Can't fetch the lanes of a scalable vector at a compile time.";
}
return encoded_lanes;
}
/*!
* \brief Check the scalar element code and bit width.
* \note Lane count and scalable-vector encoding are intentionally ignored.
*/
TVM_FFI_INLINE bool MatchesElementType(DLDataTypeCode code, int bits) const {
DLDataType dtype = get()->dtype;
return dtype.code == static_cast<uint8_t>(code) && dtype.bits == bits;
}
/*!
* \brief Check whether the dtype code matches any of the provided DLPack codes.
* \note Bit width and lanes are intentionally ignored.
*/
template <typename... Codes>
TVM_FFI_INLINE bool MatchesCode(Codes... codes) const {
uint8_t dtype_code = get()->dtype.code;
return ((dtype_code == static_cast<uint8_t>(codes)) || ...);
}
/*! \brief Whether this type is a scalar, excluding fixed and scalable vectors. */
TVM_FFI_INLINE bool IsScalar() const {
int16_t encoded_lanes = static_cast<int16_t>(get()->dtype.lanes);
return encoded_lanes == 1;
}
/*! \brief Whether this type is the void sentinel `handle(0, 0)`. */
TVM_FFI_INLINE bool IsVoid() const {
DLDataType dtype = get()->dtype;
return dtype.code == static_cast<uint8_t>(DLDataTypeCode::kDLOpaqueHandle) && dtype.bits == 0 &&
static_cast<int16_t>(dtype.lanes) == 0;
}
/*! \brief Whether this type is a scalable vector. */
TVM_FFI_INLINE bool IsScalableVector() const {
return static_cast<int16_t>(get()->dtype.lanes) < -1;
}
/*! \brief Whether this type is a fixed-length vector. */
TVM_FFI_INLINE bool IsFixedLengthVector() const {
return static_cast<int16_t>(get()->dtype.lanes) > 1;
}
/*!
* \brief Return the number of bytes needed to store one value of this type.
*
* This uses the same packed sub-byte dtype sizing rule as runtime tensors.
* Scalable vector types have no compile-time storage size and are rejected.
*/
TVM_FFI_INLINE size_t StorageBytes() const {
DLDataType dtype = get()->dtype;
int16_t encoded_lanes = static_cast<int16_t>(dtype.lanes);
if (TVM_FFI_PREDICT_FALSE(encoded_lanes < 0)) {
TVM_FFI_THROW(InternalError)
<< "Cannot compute compile-time storage bytes for non-fixed vector type " << dtype;
}
return static_cast<size_t>(
(static_cast<uint64_t>(dtype.bits) * static_cast<uint64_t>(dtype.lanes) + 7) / 8);
}
/*! \brief Return the same type with a different dtype code, preserving bits and lanes. */
TVM_FFI_INLINE PrimType WithCode(DLDataTypeCode code) const {
DLDataType dtype = get()->dtype;
int16_t encoded_lanes = static_cast<int16_t>(dtype.lanes);
if (encoded_lanes < -1) {
return ScalableVector(code, dtype.bits, -encoded_lanes);
}
return PrimType(code, dtype.bits, encoded_lanes);
}
/*! \brief Return the same type with a different scalar bit width, preserving code and lanes. */
TVM_FFI_INLINE PrimType WithBits(int bits) const {
DLDataType dtype = get()->dtype;
int16_t encoded_lanes = static_cast<int16_t>(dtype.lanes);
if (encoded_lanes < -1) {
return ScalableVector(this->code(), bits, -encoded_lanes);
}
return PrimType(this->code(), bits, encoded_lanes);
}
/*! \brief Return the same scalar element type with a fixed lane count. */
TVM_FFI_INLINE PrimType WithLanes(int lanes) const {
return PrimType(this->code(), this->bits(), lanes);
}
/*! \return The vscale factor encoded in a scalable vector type. */
TVM_FFI_INLINE int32_t VScaleFactor() const {
int16_t encoded_lanes = static_cast<int16_t>(get()->dtype.lanes);
if (encoded_lanes >= -1) {
TVM_FFI_THROW(InternalError) << "A fixed length vector doesn't have a vscale factor.";
}
return -encoded_lanes;
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(PrimType, Type, PrimTypeNode);
};
inline bool operator==(const PrimType& lhs, const PrimType& rhs) {
return lhs->dtype == rhs->dtype;
}
inline bool operator!=(const PrimType& lhs, const PrimType& rhs) { return !(lhs == rhs); }
/*!
* \brief Base type of all the expressions.
* \sa Expr
*/
class ExprNode : public ffi::Object {
public:
/*!
* \brief Span that points to the original source code.
* Reserved debug information.
*/
mutable Span span;
/*!
* \brief The deduced or annotated type of the expression.
*
* Type::Missing() denotes type information that will be populated by
* later analysis passes instead of expression constructors.
*/
mutable Type ty = Type::Missing();
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
// span does not participate in structural equal and hash.
refl::ObjectDef<ExprNode>()
.def_ro("span", &ExprNode::span, refl::DefaultValue(Span()),
refl::AttachFieldFlag::SEqHashIgnore())
.def_ro("ty", &ExprNode::ty, refl::DefaultValue(Type::Missing()));
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
static constexpr const uint32_t _type_child_slots = 64;
TVM_FFI_DECLARE_OBJECT_INFO("ir.Expr", ExprNode, ffi::Object);
};
/*!
* \brief Managed reference to ExprNode.
* \sa ExprNode
*/
class Expr : public ffi::ObjectRef {
public:
// Expressions do not implicitly compare by object identity or address. Callers must name
// whether they intend object identity, structural equality, or primitive symbolic comparison.
bool operator==(const Expr& other) const = delete;
bool operator!=(const Expr& other) const = delete;
bool operator<(const Expr& other) const = delete;
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Expr, ffi::ObjectRef, ExprNode);
};
class Call;
/*!
* \brief Typed reference/view over an expression whose result type is a
* specific Type subtype.
* \tparam ExpectedType The expected expression result type.
*/
template <typename ExpectedType>
class TypedExpr : public Expr {
public:
/*! \return the typed result of this expression. */
ExpectedType ty() const {
const auto* node = get();
TVM_FFI_DCHECK(node != nullptr);
const auto* ty_node = node->ExprNode::ty.template as<typename ExpectedType::ContainerType>();
TVM_FFI_DCHECK(ty_node != nullptr);
return ffi::GetRef<ExpectedType>(ty_node);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TypedExpr, Expr, ExprNode);
static constexpr bool _type_container_is_exact = false;
};
/*!
* \brief Typed reference/view over any Expr whose `ExprNode::ty` is PrimType.
*
* PrimExpr is a type category rather than a dedicated runtime node category.
* It can contain intrinsic primitive nodes such as IntImmNode and FloatImmNode,
* or a general ExprNode such as CallNode, when that expression's `ty` field is
* a PrimType. This keeps primitive-only APIs explicit while allowing shared
* Expr nodes for cross-dialect values with richer result types when needed.
*/
class PrimExpr : public TypedExpr<PrimType> {
public:
using TypedExpr<PrimType>::ty;
/*!
* \brief Construct from a call after checking that its result type is
* PrimType.
* \param call The call to view as a primitive expression.
*/
TVM_DLL PrimExpr(Call call); // NOLINT(*)
/*!
* \brief construct from integer.
* \param value The value to be constructed.
*/
TVM_DLL PrimExpr(int32_t value); // NOLINT(*)
/*!
* \brief construct from float.
* \param value The value to be constructed.
*/
TVM_DLL PrimExpr(float value); // NOLINT(*)
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(PrimExpr, TypedExpr<PrimType>, ExprNode);
static constexpr bool _type_container_is_exact = false;
/*!
* \brief construct from string to form a StringImm.
* \param value The value to be constructed.
*/
TVM_DLL static PrimExpr ConvertFallbackValue(ffi::String value); // NOLINT(*)
};
/*!
* \brief Base class for other IR constructs that can be converted to PrimExpr.
* This is useful for the FFI to convert the expressions to PrimExpr.
* \sa PrimExpr
*/
class PrimExprConvertibleNode : public ffi::Object {
public:
virtual ~PrimExprConvertibleNode() {}
virtual PrimExpr ToPrimExpr() const = 0;
TVM_FFI_DECLARE_OBJECT_INFO("ir.PrimExprConvertible", PrimExprConvertibleNode, ffi::Object);
};
/*!
* \brief Managed reference to PrimExprConvertibleNode.
* \sa PrimExprConvertibleNode
*/
class PrimExprConvertible : public ffi::ObjectRef {
public:
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(PrimExprConvertible, ffi::ObjectRef,
PrimExprConvertibleNode);
};
namespace ffi {
template <>
inline constexpr bool use_default_type_traits_v<PrimType> = false;
template <>
struct TypeTraits<PrimType> : public ObjectRefWithFallbackTraitsBase<PrimType, DLDataType> {
TVM_FFI_INLINE static PrimType ConvertFallbackValue(DLDataType dtype) { return PrimType(dtype); }
};
template <typename ExpectedType>
inline constexpr bool use_default_type_traits_v<TypedExpr<ExpectedType>> = false;
template <typename ExpectedType>
struct TypeTraits<TypedExpr<ExpectedType>>
: public ObjectRefTypeTraitsBase<TypedExpr<ExpectedType>> {
using Base = ObjectRefTypeTraitsBase<TypedExpr<ExpectedType>>;
using Base::CopyFromAnyViewAfterCheck;
using Base::CopyToAnyView;
using Base::GetMismatchTypeInfo;
using Base::MoveFromAnyAfterCheck;
using Base::MoveToAny;
using Base::TypeSchema;
using Base::TypeStr;
TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) {
if (src->type_index == TypeIndex::kTVMFFINone) {
return TypedExpr<ExpectedType>::_type_is_nullable;
}
if (src->type_index < TypeIndex::kTVMFFIStaticObjectBegin ||
!details::IsObjectInstance<ExprNode>(src->type_index)) {
return false;
}
const auto* expr = static_cast<const ExprNode*>(
details::ObjectUnsafe::ObjectPtrFromUnowned<Object>(src->v_obj).get());
return details::AnyUnsafe::CheckAnyStrict<ExpectedType>(expr->ty);
}
TVM_FFI_INLINE static std::optional<TypedExpr<ExpectedType>> TryCastFromAnyView(
const TVMFFIAny* src) {
if (CheckAnyStrict(src)) {
if (src->type_index == TypeIndex::kTVMFFINone) {
return details::ObjectUnsafe::ObjectRefFromObjectPtr<TypedExpr<ExpectedType>>(nullptr);
}
return details::ObjectUnsafe::ObjectRefFromObjectPtr<TypedExpr<ExpectedType>>(
details::ObjectUnsafe::ObjectPtrFromUnowned<ExprNode>(src->v_obj));
}
return std::nullopt;
}
};
template <>
inline constexpr bool use_default_type_traits_v<PrimExpr> = false;
template <typename ObjectRefType, typename ExpectedType, typename... FallbackTypes>
struct TypedExprWithFallbackTraitsBase
: public ObjectRefWithFallbackTraitsBase<ObjectRefType, FallbackTypes...> {
using Base = ObjectRefWithFallbackTraitsBase<ObjectRefType, FallbackTypes...>;
TVM_FFI_INLINE static bool CheckAnyStrict(const TVMFFIAny* src) {
return TypeTraits<TypedExpr<ExpectedType>>::CheckAnyStrict(src);
}
TVM_FFI_INLINE static std::optional<ObjectRefType> TryCastFromAnyView(const TVMFFIAny* src) {
if (TypeTraits<TypedExpr<ExpectedType>>::TryCastFromAnyView(src)) {
return details::ObjectUnsafe::ObjectRefFromObjectPtr<ObjectRefType>(
details::ObjectUnsafe::ObjectPtrFromUnowned<ExprNode>(src->v_obj));
}
return Base::template TryFallbackTypes<FallbackTypes...>(src);
}
};
// define automatic conversion from bool, int64_t, double, ffi::String to PrimExpr
// These functions are declared early to avoid circular dependency
template <>
struct TypeTraits<PrimExpr>
: public TypedExprWithFallbackTraitsBase<PrimExpr, PrimType, StrictBool, int64_t, double,
ffi::String, PrimExprConvertible> {
using Base = TypedExprWithFallbackTraitsBase<PrimExpr, PrimType, StrictBool, int64_t, double,
ffi::String, PrimExprConvertible>;
using Base::CheckAnyStrict;
using Base::CopyFromAnyViewAfterCheck;
using Base::CopyToAnyView;
using Base::GetMismatchTypeInfo;
using Base::MoveFromAnyAfterCheck;
using Base::MoveToAny;
using Base::TryCastFromAnyView;
using Base::TypeSchema;
using Base::TypeStr;
TVM_DLL static PrimExpr ConvertFallbackValue(StrictBool value);
TVM_DLL static PrimExpr ConvertFallbackValue(int64_t value);
TVM_DLL static PrimExpr ConvertFallbackValue(double value);
TVM_FFI_INLINE static PrimExpr ConvertFallbackValue(ffi::String value) {
return PrimExpr::ConvertFallbackValue(value);
}
TVM_FFI_INLINE static PrimExpr ConvertFallbackValue(PrimExprConvertible value) {
return value->ToPrimExpr();
}
};
template <>
inline constexpr bool use_default_type_traits_v<Expr> = false;
// Allow generic Expr arguments to use the primitive-literal conversions
// already defined by PrimExpr.
template <>
struct TypeTraits<Expr> : public ObjectRefWithFallbackTraitsBase<Expr, PrimExpr> {
TVM_FFI_INLINE static Expr ConvertFallbackValue(PrimExpr value) { return value; }
};
} // namespace ffi
} // namespace tvm
#endif // TVM_IR_BASE_EXPR_H_
+242
View File
@@ -0,0 +1,242 @@
/*
* 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.
*/
/*!
* \file tvm/ir/config_schema.h
* \brief Minimal schema for dynamic config canonicalization and validation.
*
* This utility is intended for dynamic map-like configs (e.g. Target options),
* where we still want type checking, optional defaulting, and canonicalization.
*/
#ifndef TVM_IR_CONFIG_SCHEMA_H_
#define TVM_IR_CONFIG_SCHEMA_H_
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
#include <sstream>
#include <string>
#include <type_traits>
#include <unordered_map>
#include <utility>
#include <vector>
namespace tvm {
namespace ir {
/*!
* \brief Dynamic config schema for map-like options.
*
* The schema supports:
* - Option declaration (`def_option<T>`)
* - Optional canonicalizer (`set_canonicalizer`)
* - Resolution (`Resolve`) that performs validation/defaulting, unknown-key policy,
* and canonicalization (last).
*/
class ConfigSchema {
public:
using ConfigMap = ffi::Map<ffi::String, ffi::Any>;
using Canonicalizer = ffi::TypedFunction<ConfigMap(ConfigMap)>;
/*! \brief Schema entry for one declared option. */
struct OptionEntry {
/*! \brief Option key. */
ffi::String key;
/*! \brief Type string for this option. */
ffi::String type_str;
/*! \brief Per-option validator/coercer (Any -> Any). */
ffi::TypedFunction<ffi::Any(ffi::Any)> validator;
/*! \brief Whether this option has a default value or factory. */
bool has_default = false;
/*! \brief Whether the default is produced by a factory function. */
bool default_from_factory = false;
/*! \brief Default value, or factory function () -> Any when default_from_factory is true. */
ffi::Any default_value;
};
/*!
* \brief Declare a typed option.
*
* Validation/coercion is implicitly generated from `T`.
* Additional optional traits may be supplied (e.g. `refl::DefaultValue`, `const char*` doc).
*
* \tparam T The canonical value type of this option.
* \tparam Traits Optional metadata/traits.
* \param key Option key.
* \param traits Optional traits.
* \return Reference to `*this` for chaining.
*/
template <typename T, typename... Traits>
ConfigSchema& def_option(const ffi::String& key, Traits&&... traits) {
std::string skey(key);
if (key_to_index_.count(skey)) {
TVM_FFI_THROW(ValueError) << "Duplicate config option key: '" << key << "'";
}
key_to_index_[skey] = options_.size();
options_.push_back(MakeEntry<T>(key, std::forward<Traits>(traits)...));
return *this;
}
/*! \brief Set whole-object canonicalizer. */
void set_canonicalizer(Canonicalizer f) { canonicalizer_ = std::move(f); }
/*! \brief Trait to set a custom validator for a config option. */
struct AttrValidator {
ffi::TypedFunction<ffi::Any(ffi::Any)> func;
explicit AttrValidator(ffi::TypedFunction<ffi::Any(ffi::Any)> f) : func(std::move(f)) {}
};
/*! \brief Set whether unknown keys trigger an error. */
void set_error_on_unknown(bool value) { error_on_unknown_ = value; }
/*!
* \brief Default/validate, then canonicalize a config object.
*
* Resolve flow:
* 1) Validate/coerce declared options in declaration order.
* 2) Materialize defaults and enforce required options.
* 3) Apply unknown-key policy.
* 4) Run canonicalizer as final step.
*
* \param config Input config object.
* \return Canonical validated config object.
* \throws ValueError/TypeError with option context.
*/
ConfigMap Resolve(ConfigMap config) const {
ConfigMap result;
// Step 1: validate/coerce and materialize options in declaration order
for (const auto& e : options_) {
auto it = config.find(e.key);
if (it != config.end()) {
result.Set(e.key, e.validator((*it).second));
} else if (e.has_default) {
if (e.default_from_factory) {
result.Set(e.key, e.default_value.cast<ffi::Function>()());
} else {
result.Set(e.key, e.default_value);
}
}
// else: missing non-required option, stays absent
}
// Step 2: unknown-key policy
if (error_on_unknown_) {
for (const auto& kv : config) {
if (!key_to_index_.count(std::string(kv.first))) {
std::ostringstream os;
os << "Unknown config option '" << kv.first << "'. Known options: ";
bool first = true;
for (const auto& e : options_) {
if (!first) os << ", ";
os << "'" << e.key << "'";
first = false;
}
TVM_FFI_THROW(ValueError) << os.str();
}
}
}
// Step 3: whole-object canonicalization (last)
if (canonicalizer_ != nullptr) {
result = canonicalizer_(result);
}
return result;
}
/*!
* \brief List declared options in declaration order.
* \return Const reference to the option entries vector.
*/
const std::vector<OptionEntry>& ListOptions() const { return options_; }
/*! \brief Check if an option with the given key exists. */
bool HasOption(const ffi::String& key) const { return key_to_index_.count(std::string(key)) > 0; }
private:
template <typename T>
static ffi::TypedFunction<ffi::Any(ffi::Any)> MakeValidator(const ffi::String& key) {
return ffi::TypedFunction<ffi::Any(ffi::Any)>([key](ffi::Any val) -> ffi::Any {
auto opt = val.try_cast<T>();
if (!opt.has_value()) {
TVM_FFI_THROW(TypeError) << "Option '" << key << "': expected type '"
<< ffi::TypeTraits<T>::TypeStr() << "' but got '"
<< val.GetTypeKey() << "'";
}
return ffi::Any(opt.value());
});
}
template <typename Trait>
static void ApplyTrait(OptionEntry* entry, ffi::reflection::FieldInfoBuilder* info,
Trait&& trait) {
using T = std::decay_t<Trait>;
if constexpr (std::is_same_v<T, AttrValidator>) {
entry->validator = std::move(trait.func);
} else if constexpr (std::is_base_of_v<ffi::reflection::InfoTrait, T>) {
trait.Apply(info);
} else if constexpr (std::is_same_v<T, const char*> || std::is_same_v<T, char*>) {
const char* doc = trait;
if (doc != nullptr && doc[0] != '\0') {
info->doc = TVMFFIByteArray{doc, std::char_traits<char>::length(doc)};
}
}
}
template <typename T, typename... Traits>
OptionEntry MakeEntry(const ffi::String& key, Traits&&... traits) {
OptionEntry e;
e.key = key;
e.type_str = ffi::String(ffi::TypeTraits<T>::TypeStr());
e.validator = MakeValidator<T>(key);
// Apply traits through a temporary FieldInfoBuilder so existing
// reflection traits (notably refl::DefaultValue) are reused unchanged.
ffi::reflection::FieldInfoBuilder info{};
info.flags = 0;
info.default_value_or_factory = ffi::AnyView(nullptr).CopyToTVMFFIAny();
info.doc = TVMFFIByteArray{nullptr, 0};
(ApplyTrait(&e, &info, std::forward<Traits>(traits)), ...);
if (info.flags & kTVMFFIFieldFlagBitMaskHasDefault) {
e.has_default = true;
e.default_from_factory = (info.flags & kTVMFFIFieldFlagBitMaskDefaultFromFactory) != 0;
e.default_value = ffi::AnyView::CopyFromTVMFFIAny(info.default_value_or_factory);
// Release the extra ref created by CopyToTVMFFIAny in Apply
if (info.default_value_or_factory.type_index >= TVMFFITypeIndex::kTVMFFIStaticObjectBegin) {
ffi::details::ObjectUnsafe::DecRefObjectHandle(info.default_value_or_factory.v_obj);
}
}
return e;
}
/*! \brief Declared options in declaration order. */
std::vector<OptionEntry> options_;
/*! \brief Map from key string to index in options_. */
std::unordered_map<std::string, size_t> key_to_index_;
/*! \brief Optional whole-config canonicalizer. */
Canonicalizer canonicalizer_{nullptr};
/*! \brief Whether unknown keys trigger an error. */
bool error_on_unknown_ = true;
};
} // namespace ir
} // namespace tvm
#endif // TVM_IR_CONFIG_SCHEMA_H_
+70
View File
@@ -0,0 +1,70 @@
/*
* 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.
*/
/*!
* \file tvm/ir/cow.h
* \brief Copy-on-write helper macro for IR ffi::ObjectRef types.
*/
#ifndef TVM_IR_COW_H_
#define TVM_IR_COW_H_
#include <tvm/ffi/object.h>
#include <utility>
namespace tvm {
/*!
* \brief Define CopyOnWrite function in an ffi::ObjectRef.
* \param ObjectName The Type of the Node.
*
* CopyOnWrite will generate a unique copy of the internal node.
* The node will be copied if it is referenced by multiple places.
* The function returns the raw pointer to the node to allow modification
* of the content.
*
* \code
*
* MyCOWObjectRef ref, ref2;
* ref2 = ref;
* ref.CopyOnWrite()->value = new_value;
* assert(ref2->value == old_value);
* assert(ref->value == new_value);
*
* \endcode
*/
#ifndef TVM_DEFINE_OBJECT_REF_COW_METHOD
#define TVM_DEFINE_OBJECT_REF_COW_METHOD(ObjectName) \
static_assert(ObjectName::_type_final, \
"TVM's CopyOnWrite may only be used for " \
"Object types that are declared as final, " \
"using the TVM_FFI_DECLARE_OBJECT_INFO_FINAL macro."); \
ObjectName* CopyOnWrite() { \
TVM_FFI_ICHECK(data_ != nullptr); \
if (!data_.unique()) { \
auto n = ::tvm::ffi::make_object<ObjectName>(*(operator->())); \
::tvm::ffi::ObjectPtr<::tvm::ffi::Object>(std::move(n)).swap(data_); \
} \
return static_cast<ObjectName*>(data_.get()); \
}
#endif // TVM_DEFINE_OBJECT_REF_COW_METHOD
} // namespace tvm
#endif // TVM_IR_COW_H_
+162
View File
@@ -0,0 +1,162 @@
/*
* 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.
*/
/*!
* \file tvm/ir/env_func.h
* \brief Serializable global function used in IR.
*/
#ifndef TVM_IR_ENV_FUNC_H_
#define TVM_IR_ENV_FUNC_H_
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/runtime/base.h>
#include <string>
#include <utility>
namespace tvm {
/*!
* \brief A serializable function backed by TVM's global environment.
*
* This is a wrapper to enable serializable global ffi::Function.
* An EnvFunc is saved by its name in the global registry
* under the assumption that the same function is registered during load.
* \sa EnvFunc
*/
class EnvFuncNode : public ffi::Object {
public:
/*! \brief Unique name of the global function */
ffi::String name;
/*! \brief The internal packed function */
ffi::Function func;
/*! \brief constructor */
EnvFuncNode() {}
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
// func do not participate in structural equal and hash.
refl::ObjectDef<EnvFuncNode>()
.def_ro("name", &EnvFuncNode::name)
.def_ro("func", &EnvFuncNode::func, refl::AttachFieldFlag::SEqHashIgnore());
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.EnvFunc", EnvFuncNode, ffi::Object);
};
/*!
* \brief Managed reference to EnvFuncNode.
* \sa EnvFuncNode
*/
class EnvFunc : public ffi::ObjectRef {
public:
EnvFunc() {}
explicit EnvFunc(ffi::ObjectPtr<ffi::Object> n) : ffi::ObjectRef(n) {}
/*!
* \brief constructor with UnsafeInit
*/
explicit EnvFunc(ffi::UnsafeInit tag) : ffi::ObjectRef(tag) {}
/*! \return The internal global function pointer */
const EnvFuncNode* operator->() const { return static_cast<const EnvFuncNode*>(get()); }
/*!
* \brief Invoke the function.
* \param args The arguments
* \returns The return value.
*/
template <typename... Args>
ffi::Any operator()(Args&&... args) const {
const EnvFuncNode* n = operator->();
TVM_FFI_ICHECK(n != nullptr);
return n->func(std::forward<Args>(args)...);
}
/*!
* \brief Get a global function based on the name.
* \param name The name of the global function.
* \return The created global function.
* \note The function can be unique
*/
TVM_DLL static EnvFunc Get(const ffi::String& name);
/*! \brief specify container node */
using ContainerType = EnvFuncNode;
};
/*!
* \brief Please refer to \ref TypedEnvFuncAnchor "TypedEnvFunc<R(Args..)>"
*/
template <typename FType>
class TypedEnvFunc;
/*!
* \anchor TypedEnvFuncAnchor
* \brief A typed version of EnvFunc.
* It is backed by a GlobalFuncNode internally.
*
* \tparam R The return value of the function.
* \tparam Args The argument signature of the function.
* \sa EnvFunc
*/
template <typename R, typename... Args>
class TypedEnvFunc<R(Args...)> : public ffi::ObjectRef {
public:
/*! \brief short hand for this function type */
using TSelf = TypedEnvFunc<R(Args...)>;
TypedEnvFunc() {}
explicit TypedEnvFunc(ffi::ObjectPtr<ffi::Object> n) : ffi::ObjectRef(n) {}
/*!
* \brief constructor with UnsafeInit
*/
explicit TypedEnvFunc(ffi::UnsafeInit tag) : ffi::ObjectRef(tag) {}
/*!
* \brief Assign global function to a TypedEnvFunc
* \param other Another global function.
* \return reference to self.
*/
TSelf& operator=(const EnvFunc& other) {
ffi::ObjectRef::operator=(other);
return *this;
}
/*! \return The internal global function pointer */
const EnvFuncNode* operator->() const { return static_cast<const EnvFuncNode*>(get()); }
/*!
* \brief Invoke the function.
* \param args The arguments
* \returns The return value.
*/
R operator()(Args... args) const {
const EnvFuncNode* n = operator->();
TVM_FFI_ICHECK(n != nullptr);
if constexpr (std::is_same_v<R, void>) {
n->func(std::forward<Args>(args)...);
} else {
ffi::Any res = n->func(std::forward<Args>(args)...);
if constexpr (std::is_same_v<R, ffi::Any>) {
return res;
} else {
return std::move(res).cast<R>();
}
}
}
/*! \brief specify container node */
using ContainerType = EnvFuncNode;
};
} // namespace tvm
#endif // TVM_IR_ENV_FUNC_H_
+568
View File
@@ -0,0 +1,568 @@
/*
* 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.
*/
/*!
* \file tvm/ir/expr.h
* \brief Base expr nodes in TVM.
*/
#ifndef TVM_IR_EXPR_H_
#define TVM_IR_EXPR_H_
#include <tvm/ffi/dtype.h>
#include <tvm/ffi/extra/dataclass.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include <tvm/ir/attrs.h>
#include <tvm/ir/base_expr.h>
#include <tvm/ir/cow.h>
#include <tvm/ir/source_map.h>
#include <algorithm>
#include <functional>
#include <limits>
#include <optional>
#include <string>
#include <type_traits>
namespace tvm {
// Forward-declare VirtualDevice to avoid circular imports.
class VirtualDevice;
/*!
* \brief add operator
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator+(PrimExpr a, PrimExpr b);
/*!
* \brief subtraction operator
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator-(PrimExpr a, PrimExpr b);
/*!
* \brief negation.
*
* \param a input.
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator-(PrimExpr a);
/*!
* \brief multiplication operator
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator*(PrimExpr a, PrimExpr b);
/*!
* \brief division operator
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator/(PrimExpr a, PrimExpr b);
/*!
* \brief left shift operator
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator<<(PrimExpr a, PrimExpr b);
/*!
* \brief right shift operator
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator>>(PrimExpr a, PrimExpr b);
/*!
* \brief greater
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator>(PrimExpr a, PrimExpr b);
/*!
* \brief greater_equal
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator>=(PrimExpr a, PrimExpr b);
/*!
* \brief less
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator<(PrimExpr a, PrimExpr b);
/*!
* \brief less_equal
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator<=(PrimExpr a, PrimExpr b);
/*!
* \brief equal
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator==(PrimExpr a, PrimExpr b);
/*!
* \brief not_equal
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator!=(PrimExpr a, PrimExpr b);
/*!
* \brief and
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note This operator does eager constant folding.
*/
TVM_DLL PrimExpr operator&&(PrimExpr a, PrimExpr b);
/*!
* \brief or
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note This operator does eager constant folding.
*/
TVM_DLL PrimExpr operator||(PrimExpr a, PrimExpr b);
/*!
* \brief not
*
* \param a left operand
* \return The result expression.
* \note This operator does eager constant folding.
*/
TVM_DLL PrimExpr operator!(PrimExpr a);
/*!
* \brief take bitwise and of two values
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator&(PrimExpr a, PrimExpr b);
/*!
* \brief take bitwise or of two values
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator|(PrimExpr a, PrimExpr b);
/*!
* \brief take bitwise xor of two values
*
* \param a left operand
* \param b right operand
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator^(PrimExpr a, PrimExpr b);
/*!
* \brief take bitwise negation of two values
*
* \param a the input expression.
* \return The result expression.
* \note this function does eager constant folding for
* index types(int32, int64) when possible.
*/
TVM_DLL PrimExpr operator~(PrimExpr a);
class GlobalVar;
/*!
* \brief Global variable that lives in the top-level module.
*
* A GlobalVar only refers to function definitions.
* This is used to enable recursive calls between function.
*
* \sa GlobalVarNode
*/
class GlobalVarNode : public ExprNode {
public:
/*! \brief The name of the variable, this only acts as a hint. */
ffi::String name_hint;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<GlobalVarNode>().def_ro("name_hint", &GlobalVarNode::name_hint);
// A GlobalVar identifies a module-level symbol. Its type is derived from the
// corresponding function definition and is not part of the symbol identity.
refl::TypeAttrDef<GlobalVarNode>()
.def("__s_equal__", &GlobalVarNode::SEqual)
.def("__s_hash__", &GlobalVarNode::SHash);
}
bool SEqual(const GlobalVarNode* other,
ffi::TypedFunction<bool(AnyView, AnyView, bool, AnyView)> equal) const {
return equal(name_hint, other->name_hint, false, "name_hint");
}
int64_t SHash(int64_t init_hash, ffi::TypedFunction<int64_t(AnyView, int64_t, bool)> hash) const {
return hash(name_hint, init_hash, false);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindFreeVar;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.GlobalVar", GlobalVarNode, ExprNode);
};
/*!
* \brief Managed reference to GlobalVarNode.
* \sa GlobalVarNode
*/
class GlobalVar : public Expr {
public:
TVM_DLL explicit GlobalVar(ffi::String name_hint, Span span = {});
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(GlobalVar, Expr, GlobalVarNode);
TVM_DEFINE_OBJECT_REF_COW_METHOD(GlobalVarNode);
};
/*!
* \brief Call corresponds to callable invocation.
*/
class CallNode : public ExprNode {
public:
/*!
* \brief The operator/function being invoked.
*
* It can be an Op, a GlobalVar, a local function value, or another callable
* expression.
*/
Expr op;
/*! \brief The arguments of the call. */
ffi::Array<Expr> args;
/*! \brief The additional attributes. */
Attrs attrs;
/*! \brief The type information arguments passed to the callee. */
ffi::Array<Type> ty_args;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<CallNode>()
.def_ro("op", &CallNode::op)
.def_ro("args", &CallNode::args)
.def_ro("attrs", &CallNode::attrs)
.def_ro("ty_args", &CallNode::ty_args);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.Call", CallNode, ExprNode);
};
/*!
* \brief Managed reference to CallNode.
*/
class Call : public Expr {
public:
TVM_DLL Call(Type ret_ty, Expr op, ffi::Array<Expr> args, Attrs attrs = Attrs(),
ffi::Array<Type> ty_args = ffi::Array<Type>(), Span span = Span());
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Call, Expr, CallNode);
TVM_DEFINE_OBJECT_REF_COW_METHOD(CallNode);
};
/*!
* \brief Constant integer literals in the program.
* \sa IntImm
*/
class IntImmNode : public ExprNode {
public:
/*! \brief the Internal value. */
int64_t value;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<IntImmNode>().def_ro("value", &IntImmNode::value);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.IntImm", IntImmNode, ExprNode);
};
/*!
* \brief Managed reference class to IntImmNode.
*
* \sa IntImmNode
*/
class IntImm : public PrimExpr {
public:
/*!
* \brief Constructor.
* \param value_ty The primitive type of the value.
* \param value The internal value.
* \param span The location of this object in the source code.
*/
TVM_DLL IntImm(PrimType value_ty, int64_t value, Span span = Span());
/*!
* \brief Construct a scalar boolean constant.
* \param value The boolean value.
* \param span The location of this object in the source code.
*/
static IntImm Bool(bool value, Span span = Span()) {
return IntImm(PrimType::Bool(), value, span);
}
/*!
* \brief Construct a scalar int32 constant.
* \param value The integer value.
* \param span The location of this object in the source code.
*/
static IntImm Int32(int64_t value, Span span = Span()) {
return IntImm(PrimType::Int(32), value, span);
}
/*!
* \brief Construct a scalar int64 constant.
* \param value The integer value.
* \param span The location of this object in the source code.
*/
static IntImm Int64(int64_t value, Span span = Span()) {
return IntImm(PrimType::Int(64), value, span);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(IntImm, PrimExpr, IntImmNode);
static constexpr bool _type_container_is_exact = true;
TVM_DEFINE_OBJECT_REF_COW_METHOD(IntImmNode);
};
/*!
* \brief Constant floating point literals in the program.
* \sa FloatImm
*/
class FloatImmNode : public ExprNode {
public:
/*! \brief The constant value content. */
double value;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<FloatImmNode>().def_ro("value", &FloatImmNode::value);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.FloatImm", FloatImmNode, ExprNode);
};
/*!
* \brief Managed reference class to FloatImmNode.
*
* \sa FloatImmNode
*/
class FloatImm : public PrimExpr {
public:
/*!
* \brief Constructor.
* \param value_ty The primitive type of the value.
* \param value The internal value.
* \param span The location in the source code.
*/
TVM_DLL FloatImm(PrimType value_ty, double value, Span span = Span());
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(FloatImm, PrimExpr, FloatImmNode);
static constexpr bool _type_container_is_exact = true;
TVM_DEFINE_OBJECT_REF_COW_METHOD(FloatImmNode);
};
/*! \brief range over one dimension */
class RangeNode : public ffi::Object {
public:
/*! \brief beginning of the node */
PrimExpr min;
/*! \brief the extend of range */
PrimExpr extent;
/*! \brief the location of this range in the source */
mutable Span span;
/*! \brief constructor */
RangeNode() {}
RangeNode(PrimExpr min, PrimExpr extent, Span span = Span())
: min(min), extent(extent), span(span) {}
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<RangeNode>()
.def_ro("min", &RangeNode::min)
.def_ro("extent", &RangeNode::extent)
.def_ro("span", &RangeNode::span, refl::AttachFieldFlag::SEqHashIgnore());
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.Range", RangeNode, ffi::Object);
};
/*! \brief Range container */
class Range : public ffi::ObjectRef {
public:
/*!
* \brief constructor by begin and end
* \param begin The begin of the range.
* \param end The end of the range.
* \param span The location of the Range in the source.
*/
TVM_DLL Range(PrimExpr begin, PrimExpr end, Span span = Span());
/*!
* \brief construct a new range with min and extent
* The corresponding constructor is removed,
* because that is counter convention of tradition meaning
* of range(begin, end)
*
* \param min The minimum range.
* \param extent The extent of the range.
* \param span The location of the Range in the source.
*/
TVM_DLL static Range FromMinExtent(PrimExpr min, PrimExpr extent, Span span = Span());
// declare range.
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Range, ffi::ObjectRef, RangeNode);
};
namespace ffi {
template <>
inline constexpr bool object_ref_contains_v<PrimExpr, IntImmNode> = true;
template <>
inline constexpr bool object_ref_contains_v<PrimExpr, FloatImmNode> = true;
// Type traits to enable automatic conversion into IntImm, Integer, and Bool
// when called through the FFI
template <>
inline constexpr bool use_default_type_traits_v<IntImm> = false;
// specialize to enable implicit conversion from const char*
template <>
struct TypeTraits<IntImm> : public ObjectRefWithFallbackTraitsBase<IntImm, int64_t> {
TVM_FFI_INLINE static IntImm ConvertFallbackValue(int64_t value) {
auto value_ty =
(value > std::numeric_limits<int>::max() || value < std::numeric_limits<int>::min())
? PrimType::Int(64)
: PrimType::Int(32);
return IntImm(value_ty, value);
}
};
template <>
inline constexpr bool use_default_type_traits_v<FloatImm> = false;
template <>
struct TypeTraits<FloatImm> : public ObjectRefWithFallbackTraitsBase<FloatImm, double> {
TVM_FFI_INLINE static FloatImm ConvertFallbackValue(double value) {
return FloatImm(PrimType::Float(32), value);
}
};
} // namespace ffi
} // namespace tvm
/* \brief Allow tvm.GLobalVar as key in STL tables
*
* For most IR 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
* `tvm::GlobalVar` allows it to be used as a key in STL tables. For
* other IR expressions, the user must specify the type of equality
* used (e.g. `std::unordered_set<T, StructuralHash, StructuralEqual>`
* or `std::unordered_set<T, ffi::ObjectPtrHash, ffi::ObjectPtrEqual>`).
*/
template <>
struct std::hash<tvm::GlobalVar> {
std::size_t operator()(const tvm::GlobalVar& var) const { return tvm::ffi::ObjectPtrHash()(var); }
};
template <>
struct std::equal_to<tvm::GlobalVar> {
bool operator()(const tvm::GlobalVar& var_a, const tvm::GlobalVar& var_b) const {
return tvm::ffi::ObjectPtrEqual()(var_a, var_b);
}
};
#endif // TVM_IR_EXPR_H_
+256
View File
@@ -0,0 +1,256 @@
/*
* 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.
*/
/*!
* \file tvm/ir/function.h
* \brief Function nodes.
*/
#ifndef TVM_IR_FUNCTION_H_
#define TVM_IR_FUNCTION_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/string.h>
#include <tvm/ir/attrs.h>
#include <tvm/ir/expr.h>
#include <string>
#include <type_traits>
namespace tvm {
/*!
* \brief Possible Calling conventions.
*
* NOTE: The calling convention also implies
* the way we implement the function during lowering.
*/
enum class CallingConv : int {
/*!
* \brief Default calling convention.
*
* - Uses the native calling convention of the target.
* - Implementation: specified by the native target.
*/
kDefault = 0,
/*!
* \brief ffi::Function that exposes a Cffi::Function signature.
*
* - Calling by ffi::Function calling convention.
* - Implementation: Expose a function with the Cffi::Function signature.
*/
kCPackedFunc = 1,
/*!
* \brief Device kernel launch
*
* - Call by ffi::Function calling convention.
* - Implementation: defined by device runtime(e.g. runtime/cuda)
*/
kDeviceKernelLaunch = 2,
};
/*!
* \brief Supported linkage types.
*/
enum class LinkageType : int {
/*!
* \brief Internal linkage.
*/
kInternal = 0,
/*!
* \brief External linkage.
- Function with external linkage should have a global symbol attached to it.
*/
kExternal = 1
};
/*!
* \brief Generic attribute names that can be attached to any function.
*
* \sa tvm::tirx::attr, tvm::relax::attr
*/
namespace attr {
/*!
* \brief Indicates the special calling convention.
*
* Type: IntImm
*
* \sa tvm::CallingConv
*/
constexpr const char* kCallingConv = "calling_conv";
/*!
* \brief Compilation target of the function.
*
* Type: Target
*
* \sa tvm::Target
*/
constexpr const char* kTarget = "target";
/*!
* \brief Global linker symbol of the function in generated code.
*
* This option forces the code generator to name the
* function with the given.
*
* For example, we could set a global_symbol of a function
* early to make sure that we can always refer to it by
* the symbol name in the generated DLL.
*
* We should not set the attribute for local functions,
* so that the compiler can freely rename them.
*
* A unique global symbol will be automatically assigned
* to each function in the module before the target code
* generation phase.
*
* Type: String
*/
constexpr const char* kGlobalSymbol = "global_symbol";
/*!
* \brief The function uses s_tir (apache-derived TIR) semantics:
* parser fills layout=None, ScriptComplete wraps body in a root SBlock,
* and printer emits `s_tir=True` on the decorator.
* Default (attr absent or False) is tirx semantics.
*
* Type: IntImm (bool dtype)
*/
constexpr const char* kSTir = "s_tir";
/*!
* \brief Number of inputs of the Primfunc
*
* Type: Int
*/
constexpr const char* kNumInputs = "num_inputs";
} // namespace attr
/*!
* \brief Base node of all functions.
*
* We support several variants of functions throughout the stack.
* All of the functions share the same type system
* to support cross variant calls.
*
* \sa BaseFunc
*/
class BaseFuncNode : public ExprNode {
public:
/*! \brief Additional attributes storing the meta-data */
DictAttrs attrs;
/*!
* \brief Get a function attribute.
*
* \param attr_key The attribute key.
* \param default_value The default value if the key does not exist, defaults to nullptr.
*
* \return The result
*
* \tparam TOBjectRef the expected object type.
* \throw Error if the key exists but the value does not match TObjectRef
*
* \code
*
* void GetAttrExample(const BaseFunc& f) {
* auto value = f->GetAttr<int64_t>("AttrKey", 0);
* }
*
* \endcode
*/
template <typename TObjectRef>
ffi::Optional<TObjectRef> GetAttr(const std::string& attr_key,
ffi::Optional<TObjectRef> default_value = std::nullopt) const {
return attrs.GetAttr(attr_key, default_value);
}
// variant that uses TObjectRef to enable implicit conversion to default value.
template <typename TObjectRef>
ffi::Optional<TObjectRef> GetAttr(const std::string& attr_key, TObjectRef default_value) const {
return GetAttr<TObjectRef>(attr_key, ffi::Optional<TObjectRef>(default_value));
}
/*!
* \brief Check whether the function has an non-zero integer attr.
*
* This function can be used to check whether an optional
* attribute mark(e.g. inline) exists.
*
* \param attr_key The key to the attribute.
* \return The check result.
*
* \code
*
* void HasNonzeroAttrExample(const BaseFunc& f) {
* if (f->HasNonzeroAttr(attr::kInline)) {
* // inline the function.
* }
* }
*
* \endcode
*/
bool HasNonzeroAttr(const std::string& attr_key) const { return attrs.HasNonzeroAttr(attr_key); }
/*!
* \brief Get the type of the linkage.
*
* Currently, we only consider external/internal linkage.
* This can be extended in the future when necessary.
*
* \return Linkage type.
*
* \code
*
* void Example(const BaseFunc& f) {
* if (f->GetLinkageType() == tvm::LinkageType::kExternal) {
* // Do not remove a function with external linkage
* }
* }
*
* \endcode
*/
LinkageType GetLinkageType() const {
if (GetAttr<ffi::String>(attr::kGlobalSymbol))
return LinkageType::kExternal;
else
return LinkageType::kInternal;
}
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<BaseFuncNode>().def_ro("attrs", &BaseFuncNode::attrs);
}
static constexpr const uint32_t _type_child_slots = 2;
TVM_FFI_DECLARE_OBJECT_INFO("ir.BaseFunc", BaseFuncNode, ExprNode);
};
/*!
* \brief Managed reference to BaseFuncNode.
* \sa BaseFuncNode
*/
class BaseFunc : public Expr {
public:
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(BaseFunc, Expr, BaseFuncNode);
};
} // namespace tvm
#endif // TVM_IR_FUNCTION_H_
+117
View File
@@ -0,0 +1,117 @@
/*
* 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.
*/
/*!
* \file tvm/ir/global_info.h
* \brief GlobalInfo are globally static object that are referred by the IR itself.
*/
#ifndef TVM_IR_GLOBAL_INFO_H_
#define TVM_IR_GLOBAL_INFO_H_
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/expr.h>
#include <tvm/target/target.h>
namespace tvm {
/*!
* \brief Abstract label for an area of memory.
*/
using MemoryScope = ffi::String;
/*!
* \brief GlobalInfo are globally static object that are referred by the IR itself.
* Base node for all global info that can appear in the IR
*/
class GlobalInfoNode : public ffi::Object {
public:
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO("ir.GlobalInfo", GlobalInfoNode, ffi::Object);
};
/*!
* \brief Managed reference to GlobalInfoNode.
* \sa GlobalInfoNode
*/
class GlobalInfo : public ffi::ObjectRef {
public:
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(GlobalInfo, ffi::ObjectRef, GlobalInfoNode);
};
/*!
* \brief A global info subclass for virtual devices.
*/
class VDeviceNode : public GlobalInfoNode {
public:
/*! \brief The \p Target describing how to compile for the virtual device. */
Target target;
/*! \brief The device identifier for the virtual device. This enables us to
* differentiate between distinct devices with same Target, such as multiple GPUs.
*/
int vdevice_id;
MemoryScope memory_scope;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<VDeviceNode>()
.def_ro("target", &VDeviceNode::target)
.def_ro("vdevice_id", &VDeviceNode::vdevice_id)
.def_ro("memory_scope", &VDeviceNode::memory_scope);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.VDevice", VDeviceNode, GlobalInfoNode);
};
/*!
* \brief Managed reference to VDeviceNode.
* \sa VDeviceNode
*/
class VDevice : public GlobalInfo {
public:
TVM_DLL explicit VDevice(Target tgt, int dev_id, MemoryScope mem_scope);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(VDevice, GlobalInfo, VDeviceNode);
};
/*!
* \brief A dummy global info sub-class for testing purpose.
*/
class DummyGlobalInfoNode : public GlobalInfoNode {
public:
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<DummyGlobalInfoNode>();
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.DummyGlobalInfo", DummyGlobalInfoNode, GlobalInfoNode);
};
/*!
* \brief Managed reference to DummyGlobalInfoNode.
* \sa DummyGlobalInfoNode
*/
class DummyGlobalInfo : public GlobalInfo {
public:
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(DummyGlobalInfo, GlobalInfo, DummyGlobalInfoNode);
};
} // namespace tvm
#endif // TVM_IR_GLOBAL_INFO_H_
+158
View File
@@ -0,0 +1,158 @@
/*
* 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.
*/
/*!
* \file tvm/ir/instrument.h
*
* This file introduces a pass instrument infrastructure, inspired by LLVM and MLIR.
* It inserts instrumentation points around passes.
*/
#ifndef TVM_IR_INSTRUMENT_H_
#define TVM_IR_INSTRUMENT_H_
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include <utility>
#include <vector>
namespace tvm {
class IRModule;
// Forward class for PassInstrumentNode methods
namespace transform {
class PassInfo;
} // namespace transform
namespace instrument {
/*!
* \brief PassInstrumentNode forms an instrument implementation.
* It provides API for users to register callbacks at different instrumentation points.
*
* Within a PassContext, call sequence of a PassInstrument implementation is like:
*
* with PassContext(instruments=[pi]): # pi = a PassInstrument implementation
* pi.EnterPassContext()
*
* if pi.ShouldRun(Pass1):
* pi.RunBeforePass()
* Pass1()
* pi.RunAfterPass()
*
* if pi.ShouldRun(Pass2):
* pi.RunBeforePass()
* Pass2()
* pi.RunAfterPass()
*
* pi.ExitPassContext()
*
* `EnterPassContext` and `ExitPassContext` are only called once when entering/exiting a
* PassContext. `ShouldRun`, `RunBeforePass` and `RunAfterPass` are called multiple times depending
* on how many passes.
*
* If there are multiple pass instrumentations provided, the instrument points are the same.
* PassInstrument implementations' callbacks are called in order:
*
* with PassContext(instruments=[pi1, pi2]): # pi1, pi2 = two distinct PassInstrument impls
* pi.EnterPassContext() for pi in instruments
*
* should_run = all([pi.ShoudRun(Pass1) for pi in instruments)])
* if (should_run)
* pi.RunBeforePass() for pi in instruments
* Pass1()
* pi.RunAfterPass() for pi in instruments
*
* should_run = all([pi.ShouldRun(Pass2) for pi in instruments)])
* if (should_run)
* pi.RunBeforePass() for pi in instruments
* Pass2()
* pi.RunAfterPass() for pi in instruments
*
* pi.ExitPassContext() for pi in instruments
*
* Note:
* 1. Assume there is no dependency between PassInstrument implementations in `instruments` .
* 2. `EnterPassContext` and `ExitPassContext` have `with` behavior (see PassContext and its FFI):
* If there is any exception raised in `ShouldRun()`, `RunBeforePass()`, `RunAfterPass()` and
* `Pass()`, `ExitPassContext()` is still called.
* 3. In mutiple PassInstrument instances scenario, callbacks are called in order:
* If one throws exceptions, remainings will not be called.
*
* \sa PassInstrument
* \sa src/ir/transform.cc
*/
class PassInstrumentNode : public ffi::Object {
public:
/*! \brief Name of this pass instrument object. */
ffi::String name;
virtual ~PassInstrumentNode() {}
/*! \brief Instrument when entering PassContext. Called once within a PassContext. */
virtual void EnterPassContext() const = 0;
/*! \brief Instrument when exiting PassContext. Called once within a PassContext. */
virtual void ExitPassContext() const = 0;
/*!
* \brief Determine whether to run the pass or not. Called multiple times depend on number of
* passes.
* \param mod The module that an optimization pass runs on.
* \param info The pass information.
*
* \return true to run the pass; false to skip the pass.
*/
virtual bool ShouldRun(const IRModule& mod, const transform::PassInfo& info) const = 0;
/*!
* \brief Instrument before pass run. Called multiple times depend on number of passes.
* \param mod The module that an optimization pass runs on.
* \param info The pass information.
*/
virtual void RunBeforePass(const IRModule& mod, const transform::PassInfo& info) const = 0;
/*!
* \brief Instrument after pass run. Called multiple time depend on number of passes.
* \param mod The module that an optimization pass runs on.
* \param info The pass information.
*/
virtual void RunAfterPass(const IRModule& mod, const transform::PassInfo& info) const = 0;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PassInstrumentNode>().def_ro("name", &PassInstrumentNode::name);
}
TVM_FFI_DECLARE_OBJECT_INFO("instrument.PassInstrument", PassInstrumentNode, ffi::Object);
};
/*!
* \brief Managed reference class for PassInstrumentNode
* \sa PassInstrumentNode
*/
class PassInstrument : public ffi::ObjectRef {
public:
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(PassInstrument, ffi::ObjectRef, PassInstrumentNode);
};
} // namespace instrument
} // namespace tvm
#endif // TVM_IR_INSTRUMENT_H_
+366
View File
@@ -0,0 +1,366 @@
/*
* 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.
*/
/*!
* \file tvm/ir/module.h
* \brief IRModule that holds the functions and type definitions.
*/
#ifndef TVM_IR_MODULE_H_
#define TVM_IR_MODULE_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include <tvm/ir/cow.h>
#include <tvm/ir/expr.h>
#include <tvm/ir/function.h>
#include <tvm/ir/global_info.h>
#include <tvm/ir/source_map.h>
#include <tvm/ir/type.h>
#include <string>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
namespace tvm {
class IRModule;
/*!
* \brief IRModule that holds functions and type definitions.
*
* IRModule is the basic unit for all IR transformations across the stack.
*
* Many operations require access to the global IRModule.
* We pass the IRModule by value in a functional style as an explicit argument,
* but we mutate the Module while optimizing programs.
* \sa IRModule
*/
class IRModuleNode : public ffi::Object {
public:
/*! \brief A map from ids to all global functions. */
ffi::Map<GlobalVar, BaseFunc> functions;
/*! \brief The source map for the module. */
SourceMap source_map;
/* \brief Additional attributes storing meta-data about the module. */
DictAttrs attrs;
/*! \brief Globally static object that are referred by the IR itself */
ffi::Map<ffi::String, ffi::Array<GlobalInfo>> global_infos;
/*!
* \brief A map from string names to global variables that
* ensures global uniqueness.
*/
ffi::Map<ffi::String, GlobalVar> global_var_map_;
/*!
* \brief Get a module attribute.
*
* \param attr_key The attribute key.
* \param default_value The default value if the key does not exist, defaults to nullptr.
*
* \return The result
*
* \tparam TObjectRef the expected object type.
* \throw Error if the key exists but the value does not match TObjectRef
*
* \code
*
* void GetAttrExample(const IRModule& mod) {
* auto value = f->GetAttr<int64_t>("AttrKey", 0);
* }
*
* \endcode
*/
template <typename TObjectRef>
ffi::Optional<TObjectRef> GetAttr(
const std::string& attr_key,
ffi::Optional<TObjectRef> default_value = ffi::Optional<TObjectRef>(std::nullopt)) const {
return attrs.GetAttr(attr_key, default_value);
}
// variant that uses TObjectRef to enable implicit conversion to default value.
template <typename TObjectRef>
ffi::Optional<TObjectRef> GetAttr(const std::string& attr_key, TObjectRef default_value) const {
return GetAttr<TObjectRef>(attr_key, ffi::Optional<TObjectRef>(default_value));
}
/*!
* \brief Get the metadata attributes.
* \returns The additional meta-data attributes
*/
DictAttrs GetAttrs() const { return attrs; }
/*!
* \brief Check whether the module has an non-zero integer attr.
*
* This function can be used to check whether an optional
* attribute mark(e.g. inline) exists.
*
* \param attr_key The key to the attribute.
* \return The check result.
*
* \code
*
* void HasNonzeroAttrExample(const IRModule& mod) {
* if (mod->HasNonzeroAttr(attr::kInline)) {
* // inline the function.
* }
* }
*
* \endcode
*/
bool HasNonzeroAttr(const std::string& attr_key) const { return attrs.HasNonzeroAttr(attr_key); }
IRModuleNode() : source_map() {}
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<IRModuleNode>()
.def_ro("functions", &IRModuleNode::functions)
.def_ro("global_var_map_", &IRModuleNode::global_var_map_)
.def_ro("source_map", &IRModuleNode::source_map)
.def_ro("attrs", &IRModuleNode::attrs)
.def_ro("global_infos", &IRModuleNode::global_infos);
// register custom structural equal and hash.
refl::TypeAttrDef<IRModuleNode>()
.def("__s_equal__", &IRModuleNode::SEqual)
.def("__s_hash__", &IRModuleNode::SHash);
}
TVM_DLL bool SEqual(const IRModuleNode* other,
ffi::TypedFunction<bool(AnyView, AnyView, bool, AnyView)> equal) const;
TVM_DLL int64_t SHash(int64_t init_hash,
ffi::TypedFunction<int64_t(AnyView, int64_t, bool)> hash) const;
/*!
* \brief Add a function to the global environment.
* \param var The var of the global function.
* \param func The function.
* \param update Controls whether you can replace a definition in the
* environment.
*/
TVM_DLL void Add(const GlobalVar& var, const BaseFunc& func, bool update = false);
/*!
* \brief Add a function to the global environment.
* \param var The name of the global function.
* \param func The function.
*
* It does not do type inference as Add does.
*/
TVM_DLL void AddUnchecked(const GlobalVar& var, const BaseFunc& func);
/*!
* \brief Update a function in the global environment.
* \param var The name of the global function to update.
* \param func The new function.
*/
TVM_DLL void Update(const GlobalVar& var, const BaseFunc& func);
/*!
* \brief Update an array of global infos in the global environment.
* \param name The name of the global info.
* \param info The new array of global infos.
*/
TVM_DLL void UpdateGlobalInfo(const ffi::String& name, const ffi::Array<GlobalInfo>& info);
/*!
* \brief Remove a function from the global environment.
* \param var The name of the global function to update.
*/
TVM_DLL void Remove(const GlobalVar& var);
/*!
* \brief Check if the global_var_map_ contains a global variable.
* \param name The variable name.
* \returns true if contains, otherise false.
*/
TVM_DLL bool ContainGlobalVar(const ffi::String& name) const;
/*!
* \brief Lookup a global function by its variable.
* \param str The unique string specifying the global variable.
* \returns The global variable.
*/
TVM_DLL GlobalVar GetGlobalVar(const ffi::String& str) const;
/*!
* \brief Collect all global vars defined in this module, ordered by
* the global variable name.
* \returns An array of global vars
*/
TVM_DLL ffi::Array<GlobalVar> GetGlobalVars() const;
/*!
* \brief Look up a global function by its variable.
* \param var The global var to lookup.
* \returns The function named by the variable argument.
*/
TVM_DLL BaseFunc Lookup(const GlobalVar& var) const;
/*!
* \brief Look up a global function by its string name
* \param name The name of the function.
* \returns The function named by the argument.
*/
TVM_DLL BaseFunc Lookup(const ffi::String& name) const;
/*!
* \brief Update the functions inside this environment by
* functions in another environment.
* \param other The other environment.
*/
TVM_DLL void Update(const IRModule& other);
/*!
* \brief Create a shallow copy of this IRModule.
* \returns The shallow copy of the IRModule.
*/
TVM_DLL IRModule ShallowCopy();
/*!
* \brief The set of imported files.
*/
TVM_DLL std::unordered_set<ffi::String> Imports() const;
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.IRModule", IRModuleNode, ffi::Object);
private:
friend class IRModule;
};
/*!
* \brief Managed reference class to IRModuleNode.
* \sa IRModuleNode
*/
class IRModule : public ffi::ObjectRef {
public:
/*!
* \brief constructor
* \param functions Functions in the module.
* \param map The module source map.
* \param attrs The module meta-data attributes.
* \param global_infos Global infos in the module.
*/
TVM_DLL explicit IRModule(ffi::Map<GlobalVar, BaseFunc> functions, SourceMap map = {},
DictAttrs attrs = DictAttrs(),
ffi::Map<ffi::String, ffi::Array<GlobalInfo>> global_infos = {});
/*! \brief default constructor */
IRModule() : IRModule(ffi::Map<GlobalVar, BaseFunc>({})) {}
/*!
* \brief constructor
* \param n The object pointer.
*/
explicit IRModule(ffi::ObjectPtr<IRModuleNode> n) : ffi::ObjectRef(n) {}
/*!
* \brief constructor with UnsafeInit
*/
explicit IRModule(ffi::UnsafeInit tag) : ffi::ObjectRef(tag) {}
/*! \return mutable pointers to the node. */
IRModuleNode* operator->() const {
auto* ptr = get_mutable();
TVM_FFI_ICHECK(ptr != nullptr);
return static_cast<IRModuleNode*>(ptr);
}
/*!
* \brief As for \p FromExprInContext, but assuming \p expr is bound to 'main' and no
* imports.
*/
TVM_DLL static IRModule FromExpr(const Expr& expr,
const ffi::Map<GlobalVar, BaseFunc>& global_funcs = {});
/*!
* \brief Create a shallow copy of an IRModule.
* \param mod The module to copy.
* \return The copied module.
*/
IRModule ShallowCopyIRModule(IRModule mod);
/*! \brief Declare the container type. */
using ContainerType = IRModuleNode;
// allow copy on write.
TVM_DEFINE_OBJECT_REF_COW_METHOD(IRModuleNode);
};
namespace attr {
// Following are attributes for IRModule only.
/*!
* \brief Name of the module
*
* Type: String
*/
constexpr const char* kModuleName = "mod_name";
/*!
* \brief All the runtime::Modules accumulated during compilation by external codegen. These
* modules must be either directly linked or captured in the final compilation artifact.
*
* Type: ffi::Array<runtime::Module>
*/
constexpr const char* kExternalMods = "external_mods";
/*!
* \brief A prefix for generating C symbols system lib creation.
*
* This prefix guides passes that creates global_symbol for internal functions
* that may have c linkage (e.g. TIR functions and some BYOC functions). It also affects
* the symbol of the fat bin blob during module export.
*
* This attribute is used to avoid symbol conflict when we
* generate and combine multiple system libs that get linked into one.
*
* Rationale: mechanisms like BYOC rely on the common global symbol
* and each external compiler also has its own mechanism of mangling.
* As a result, we cannot rely on other mechanisms on setting a global_symbol and then renaming,
* because the external compiler already agreed on the name.
*
* system_lib_prefix provides a way to hint at the passes to allow names to
* avoid name conflict at the beginning.
*
* Note that users can still directly specify global symbols that may conflict.
* It is up to the downstream toolchain to manage those external-facing functions.
*
* This does not affect non-C linkage functions it is less of an issue because
* they will be embedded into fatbin that in different symbols,
* The system lib loader can pick the right prefix for a given prefix.
*
* Having this attribute implies system lib generation linkage.
*/
constexpr const char* kSystemLibPrefix = "system_lib_prefix";
/*!
* \brief All the named runtime::Tensors accumulated during compilation by external codegen.
* Generally the associated runtime::Module will indicate it requires bindings for these names,
* and during module initialization these bindings will be recovered from a ConstLoaderModule.
*
* Type: ffi::Map<ffi::String, runtime::Tensor>
*/
constexpr const char* kConstNameToConstant = "const_name_to_constant";
} // namespace attr
} // namespace tvm
#endif // TVM_IR_MODULE_H_
+194
View File
@@ -0,0 +1,194 @@
/*
* 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.
*/
/*!
* \file tvm/ir/node_functor.h
* \brief Defines the Functor data structures.
*/
#ifndef TVM_IR_NODE_FUNCTOR_H_
#define TVM_IR_NODE_FUNCTOR_H_
#include <tvm/ffi/error.h>
#include <cstring>
#include <type_traits>
#include <utility>
#include <vector>
namespace tvm {
/*!
* \brief A dynamically dispatched functor on the type of the first argument.
*
* This is a class that is useful to construct polymorphic dispatching
* base on the AST/IR node's type.
*
* \code
* NodeFunctor<std::string (const ffi::ObjectRef& n, std::string prefix)> tostr;
* tostr.set_dispatch<Add>([](const ffi::ObjectRef& op, std::string prefix) {
* return prefix + "Add";
* });
* tostr.set_dispatch<IntImm>([](const ffi::ObjectRef& op, std::string prefix) {
* return prefix + "IntImm"
* });
*
* Expr x = MakeConst(1);
* Expr y = x + x;
* // dispatch to IntImm, outputs "MyIntImm"
* LOG(INFO) << tostr(x, "My");
* // dispatch to IntImm, outputs "MyAdd"
* LOG(INFO) << tostr(y, "My");
* \endcode
*
* \tparam FType function signiture
* This type if only defined for FType with function signature
*/
template <typename FType>
class NodeFunctor;
template <typename R, typename... Args>
class NodeFunctor<R(const ffi::ObjectRef& n, Args...)> {
private:
/*! \brief internal function pointer type */
typedef R (*FPointer)(const ffi::ObjectRef& n, Args...);
/*! \brief refer to itself. */
using TSelf = NodeFunctor<R(const ffi::ObjectRef& n, Args...)>;
/*! \brief internal function table */
std::vector<FPointer> func_;
/*! \brief start range of func index */
uint32_t begin_type_index_{0};
public:
/*! \brief the result type of this functor */
using result_type = R;
/*!
* \brief Whether the functor can dispatch the corresponding Node
* \param n The node to be dispatched
* \return Whether dispatching function is registered for n's type.
*/
bool can_dispatch(const ffi::ObjectRef& n) const {
uint32_t type_index = n->type_index();
if (type_index < begin_type_index_) return false;
type_index -= begin_type_index_;
return type_index < func_.size() && func_[type_index] != nullptr;
}
/*!
* \brief invoke the functor, dispatch on type of n
* \param n The Node argument
* \param args The additional arguments
* \return The result.
*/
R operator()(const ffi::ObjectRef& n, Args... args) const {
TVM_FFI_ICHECK(can_dispatch(n))
<< "NodeFunctor calls un-registered function on type " << n->GetTypeKey();
return (*func_[n->type_index() - begin_type_index_])(n, std::forward<Args>(args)...);
}
/*!
* \brief set the dispatcher for type TNode
* \param f The function to be set.
* \tparam TNode the type of Node to be dispatched.
* \return reference to self.
*/
template <typename TNode>
TSelf& set_dispatch(FPointer f) { // NOLINT(*)
uint32_t tindex = TNode::RuntimeTypeIndex();
if (func_.size() <= tindex) {
func_.resize(tindex + 1, nullptr);
}
TVM_FFI_ICHECK(func_[tindex] == nullptr)
<< "Dispatch for " << TNode::_type_key << " is already set";
TVM_FFI_ICHECK_EQ(begin_type_index_, 0) << " Cannot call set_dispatch after calling Finalize";
func_[tindex] = f;
return *this;
}
/*!
* \brief unset the dispatcher for type TNode
*
* \tparam TNode the type of Node to be dispatched.
* \return reference to self.
*/
template <typename TNode>
TSelf& clear_dispatch() { // NOLINT(*)
uint32_t tindex = TNode::RuntimeTypeIndex();
TVM_FFI_ICHECK_LT(tindex, func_.size()) << "clear_dispatch: index out of range";
TVM_FFI_ICHECK_EQ(begin_type_index_, 0) << " Cannot call clear_dispatch after calling Finalize";
func_[tindex] = nullptr;
return *this;
}
/*!
* \brief Finalize the functor after calling sequence of set_dispatch
* This function will attempt to find the min type index that is not null
* and optimize the space of the func table so it is more compact
*/
void Finalize() {
TVM_FFI_ICHECK_EQ(begin_type_index_, 0) << "Can only call Finalize once";
while (begin_type_index_ < func_.size() && func_[begin_type_index_] == nullptr) {
++begin_type_index_;
}
// shift up the function value
size_t new_ftable_size = func_.size() - begin_type_index_;
if (begin_type_index_ != 0) {
std::memmove(func_.data(), func_.data() + begin_type_index_,
new_ftable_size * sizeof(FPointer));
}
func_.resize(new_ftable_size);
func_.shrink_to_fit();
}
};
#define TVM_REG_FUNC_VAR_DEF(ClsName) [[maybe_unused]] static auto& __make_functor##_##ClsName
/*!
* \brief Useful macro to set NodeFunctor dispatch in a global static field.
*
* \code
* // Use NodeFunctor to implement TVMScriptPrinter similar to Visitor Pattern.
* // vtable allows easy patch of new Node types, without changing
* // the interface of TVMScriptPrinter.
*
* class TVMScriptPrinter {
* public:
* // the dispatch function.
* static std::string Script(const ffi::ObjectRef& node, const PrinterConfig& cfg) {
* return vtable()(node, cfg);
* }
* using FType = NodeFunctor<std::string(const ffi::ObjectRef&, const PrinterConfig&)>;
* // function to return global function table
* static FType& vtable();
* };
*
* // in cpp/cc file
* TVMScriptPrinter::FType& TVMScriptPrinter::vtable() {
* static FType inst; return inst;
* }
*
* TVM_STATIC_IR_FUNCTOR(TVMScriptPrinter, vtable)
* .set_dispatch<AddNode>([](const ffi::ObjectRef& ref, const PrinterConfig& cfg) {
* auto* n = static_cast<const AddNode*>(ref.get());
* return Script(n->a, cfg) + " + " + Script(n->b, cfg);
* });
*
* \endcode
*
* \param ClsName The name of the class
* \param FField The static function that returns a singleton of NodeFunctor.
*/
#define TVM_STATIC_IR_FUNCTOR(ClsName, FField) \
TVM_FFI_STR_CONCAT(TVM_REG_FUNC_VAR_DEF(ClsName), __COUNTER__) = ClsName::FField()
} // namespace tvm
#endif // TVM_IR_NODE_FUNCTOR_H_
+423
View File
@@ -0,0 +1,423 @@
/*
* 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.
*/
/*!
* \file tvm/ir/op.h
* \brief Primitive operators(builtin intrinsics)
* and registry for them.
*/
#ifndef TVM_IR_OP_H_
#define TVM_IR_OP_H_
#include <tvm/ffi/error.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/attr_registry_map.h>
#include <tvm/ir/attrs.h>
#include <tvm/ir/env_func.h>
#include <tvm/ir/expr.h>
#include <tvm/ir/type.h>
#include <string>
#include <utility>
#include <vector>
namespace tvm {
// forward declare name.
template <typename>
class OpAttrMap;
/*!
* \brief Information about an input field of an Op (name, type, description).
*
* Populated via OpRegEntry::add_argument and consumed both by
* internal sanity checks / error messages and by external tooling
* that wants to introspect an Op's argument schema.
*/
class ArgumentInfoNode : public ffi::Object {
public:
/*! \brief name of the field */
ffi::String name;
/*! \brief type docstring information in str. */
ffi::String type_info;
/*! \brief detailed description of the type */
ffi::String description;
static void RegisterReflection() {
namespace rfl = ffi::reflection;
rfl::ObjectDef<ArgumentInfoNode>()
.def_ro("name", &ArgumentInfoNode::name)
.def_ro("type_info", &ArgumentInfoNode::type_info)
.def_ro("description", &ArgumentInfoNode::description);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.ArgumentInfo", ArgumentInfoNode, ffi::Object);
};
/*! \brief Managed reference to ArgumentInfoNode. */
class ArgumentInfo : public ffi::ObjectRef {
public:
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(ArgumentInfo, ffi::ObjectRef, ArgumentInfoNode);
};
// TODO(tvm-team): migrate low-level intrinsics to use Op
/*!
* \brief Primitive Op(builtin intrinsics)
*
* This data structure stores the meta-data
* about primitive operators that can be invoked via Call.
*
* Low-level IR intrinsics(such as libc.expf) are also
* implemented via Op.
*
* \sa Op
*/
class OpNode : public ExprNode {
public:
/*! \brief name of the operator */
ffi::String name;
/*!
* \brief detailed description of the operator
* This can be used to generate docstring automatically for the operator.
*/
ffi::String description;
/* \brief Information of input arguments to the operator */
ffi::Array<ArgumentInfo> arguments;
/*!
* \brief The type key of the attribute field
* This can be empty, in which case it defaults to anything.
*/
ffi::String attrs_type_key;
/*!
* \brief attribute type index,
* this field varies in each run and is not exposed to frontend.
*/
uint32_t attrs_type_index{0};
/*!
* \brief number of input arguments to the operator,
* -1 means it is variable length
*/
int32_t num_inputs = -1;
/*!
* \brief support level of the operator,
* The lower the more priority it contains.
* This is in analogies to BLAS levels.
*/
int32_t support_level = 10;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<OpNode>()
.def_ro("name", &OpNode::name)
.def_ro("description", &OpNode::description, refl::AttachFieldFlag::SEqHashIgnore())
.def_ro("arguments", &OpNode::arguments, refl::AttachFieldFlag::SEqHashIgnore())
.def_ro("attrs_type_key", &OpNode::attrs_type_key, refl::AttachFieldFlag::SEqHashIgnore())
.def_ro("num_inputs", &OpNode::num_inputs, refl::AttachFieldFlag::SEqHashIgnore())
.def_ro("support_level", &OpNode::support_level, refl::AttachFieldFlag::SEqHashIgnore());
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindUniqueInstance;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.Op", OpNode, ExprNode);
private:
/*! \return the internal attr registry index. */
uint32_t AttrRegistryIndex() const { return index_; }
/*! \brief repr to be printed in registry*/
std::string AttrRegistryName() const { return name; }
// friend class
template <typename>
friend class AttrRegistryMapContainerMap;
template <typename, typename>
friend class AttrRegistry;
friend class OpRegEntry;
// Program internal unique index of operator.
// Used to help index the program.
uint32_t index_{0};
};
/*!
* \brief Managed reference class to OpNode.
* \sa OpNode
*/
class Op : public Expr {
public:
explicit Op(ffi::ObjectPtr<OpNode> n) : Expr(std::move(n)) {
TVM_FFI_CHECK(defined(), ValueError) << "Op expects a defined OpNode";
}
/*!
* \brief Get additional registered attribute about operators.
* If nothing has been registered, an empty OpAttrMap will be returned.
* \param attr_name The name of the attribute.
* \return An OpAttrMap of specified attr_name.
* \tparam ValueType The type of the attribute.
*/
template <typename ValueType>
inline static OpAttrMap<ValueType> GetAttrMap(const ffi::String& attr_name);
/*!
* \brief Checks if an attr map is present in the registry.
* \param attr_name The name of the attribute.
* \return bool True if the attr is present.
*/
TVM_DLL static bool HasAttrMap(const ffi::String& attr_name);
/*!
* \brief Get an Op for a given operator name.
* Will raise an error if the op has not been registered.
* \param op_name Name of the operator.
* \return Pointer to a Op, valid throughout program lifetime.
*/
TVM_DLL static const Op& Get(const ffi::String& op_name);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Op, Expr, OpNode);
private:
/*!
* \brief Get generic attrmap given attr name
* \param key The attribute key
* \return The attr map.
*/
TVM_DLL static const AttrRegistryMapContainerMap<Op>& GetAttrMapContainer(const ffi::String& key);
};
/*!
* \brief Helper structure to register operators
* \sa TVM_REGISTER_OP
*/
class OpRegEntry {
public:
/*! \return the operator */
const Op& op() const { return op_; }
/*!
* \brief setter function during registration
* Set the description of operator
* \param descr the description string.
* \return reference to self.
*/
inline OpRegEntry& describe(const std::string& descr); // NOLINT(*)
/*!
* \brief Add argument information to the function.
* \param name Name of the argument.
* \param type Type of the argument.
* \param description Description of the argument.
* \return reference to self.
*/
inline OpRegEntry& add_argument(const std::string& name, const std::string& type,
const std::string& description);
/*!
* \brief Set the attrs type key and index to be AttrsType.
* \tparam AttrsType the attribute type to b set.
* \return reference to self.
*/
template <typename AttrsType>
inline OpRegEntry& set_attrs_type();
/*!
* \brief Set the attrs type key and index to be AttrsType.
* \param key The attribute type key to be set.
* \return reference to self.
*/
inline OpRegEntry& set_attrs_type_key(const ffi::String& key);
/*!
* \brief Set the num_inputs
* \param n The number of inputs to be set.
* \return reference to self.
*/
inline OpRegEntry& set_num_inputs(int32_t n); // NOLINT(*)
/*!
* \brief Set the support level of op.
* \param level The support level.
* \return reference to self.
*/
inline OpRegEntry& set_support_level(int32_t level); // NOLINT(*)
/*!
* \brief Register additional attributes to operator.
* \param attr_name The name of the attribute.
* \param value The value to be set.
* \param plevel The priority level of this set,
* an higher priority level attribute
* will replace lower priority level attribute.
* Must be bigger than 0.
*
* Cannot set with same plevel twice in the code.
*
* \tparam ValueType The type of the value to be set.
*/
template <typename ValueType>
inline OpRegEntry& set_attr(const std::string& attr_name, // NOLINT(*)
const ValueType& value, int plevel = 10);
/*!
* \brief Resets an attr of the registry.
* \param attr_name The name of the attribute.
*/
inline void reset_attr(const std::string& attr_name);
// set the name of the op to be the same as registry
inline OpRegEntry& set_name() { // NOLINT(*)
if (get()->name.length() == 0) {
get()->name = name;
}
return *this;
}
/*!
* \brief Register or get a new entry.
* \param name The name of the operator.
* \return the corresponding entry.
*/
TVM_DLL static OpRegEntry& RegisterOrGet(const ffi::String& name);
private:
template <typename, typename>
friend class AttrRegistry;
// the name
std::string name;
/*! \brief The operator */
Op op_;
/*! \brief Construct the non-null Op for this registry entry. */
static Op MakeOp(uint32_t reg_index);
// private constructor
TVM_DLL OpRegEntry(uint32_t reg_index);
// return internal pointer to op.
inline OpNode* get();
// update the attribute OpAttrMap
TVM_DLL void UpdateAttr(const ffi::String& key, ffi::Any value, int plevel);
};
/*!
* \brief ffi::Map<Op,ValueType> used to store meta-information about Op.
* \tparam ValueType The type of the value stored in map.
*/
template <typename ValueType>
class OpAttrMap : public AttrRegistryMap<Op, ValueType> {
public:
/*!
* \brief get the corresponding value element at op with default value.
* \param expr The key to the map
* \param def_value The default value when the key does not exist
* or if expr is not an Op.
* \return the const reference to the content value.
*/
inline ValueType get(const Expr& expr, ValueType def_value) const;
using TParent = AttrRegistryMap<Op, ValueType>;
using TParent::count;
using TParent::get;
using TParent::operator[];
private:
friend class Op;
// constructor
explicit OpAttrMap(const AttrRegistryMapContainerMap<Op>& map) : TParent(map) {}
};
// internal macros to make
#define TVM_OP_REGISTER_VAR_DEF [[maybe_unused]] static ::tvm::OpRegEntry& __make_##Op
/*!
* \def TVM_REGISTER_OP
* \brief Register a new operator, or set attribute of the corresponding op.
*
* \param OpName The name of registry
*
* \code
*
* TVM_REGISTER_OP("add")
* .describe("add two inputs together")
* .set_num_inputs(2)
* .set_attr<OpKernel>("gpu_kernel", AddKernel);
*
* \endcode
*/
#define TVM_REGISTER_OP(OpName) \
TVM_FFI_STR_CONCAT(TVM_OP_REGISTER_VAR_DEF, __COUNTER__) = \
::tvm::OpRegEntry::RegisterOrGet(OpName).set_name()
// implementations
template <typename ValueType>
inline OpAttrMap<ValueType> Op::GetAttrMap(const ffi::String& key) {
return OpAttrMap<ValueType>(Op::GetAttrMapContainer(key));
}
inline OpNode* OpRegEntry::get() { return const_cast<OpNode*>(op_.operator->()); }
inline OpRegEntry& OpRegEntry::describe(const std::string& descr) { // NOLINT(*)
get()->description = descr;
return *this;
}
inline OpRegEntry& OpRegEntry::add_argument(const std::string& name, const std::string& type,
const std::string& description) {
auto n = ffi::make_object<ArgumentInfoNode>();
n->name = name;
n->type_info = type;
n->description = description;
get()->arguments.push_back(ArgumentInfo(n));
return *this;
}
inline OpRegEntry& OpRegEntry::set_num_inputs(int32_t n) { // NOLINT(*)
get()->num_inputs = n;
return *this;
}
template <typename AttrsType>
inline OpRegEntry& OpRegEntry::set_attrs_type() { // NOLINT(*)
get()->attrs_type_key = AttrsType::_type_key;
get()->attrs_type_index = AttrsType::RuntimeTypeIndex();
return *this;
}
inline OpRegEntry& OpRegEntry::set_attrs_type_key(const ffi::String& key) { // NOLINT(*)
get()->attrs_type_key = key;
get()->attrs_type_index = tvm::ffi::TypeKeyToIndex(key.c_str());
return *this;
}
inline OpRegEntry& OpRegEntry::set_support_level(int32_t n) { // NOLINT(*)
get()->support_level = n;
return *this;
}
template <typename ValueType>
inline OpRegEntry& OpRegEntry::set_attr( // NOLINT(*)
const std::string& attr_name, const ValueType& value, int plevel) {
TVM_FFI_ICHECK_GT(plevel, 0) << "plevel in set_attr must be greater than 0";
UpdateAttr(attr_name, Any(value), plevel);
return *this;
}
// member functions of OpAttrMap
template <typename ValueType>
inline ValueType OpAttrMap<ValueType>::get(const Expr& expr, ValueType def_value) const {
TVM_FFI_ICHECK(expr.defined());
if (const OpNode* op = expr.as<OpNode>()) {
return this->map_.get(ffi::GetRef<Op>(op), def_value);
} else {
return def_value;
}
}
} // namespace tvm
#endif // TVM_IR_OP_H_
+123
View File
@@ -0,0 +1,123 @@
/*
* 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.
*/
/*!
* \file tvm/ir/scope_stack.h
* \brief A generic scope stack for managing hierarchical state during IR visiting.
*/
#ifndef TVM_IR_SCOPE_STACK_H_
#define TVM_IR_SCOPE_STACK_H_
#include <tvm/ffi/error.h>
#include <deque>
#include <type_traits>
namespace tvm {
/*!
* \brief A scope stack for maintaining hierarchical state during IR visiting.
*
* During IR tree traversal, visitors often need to track scope-local state
* (e.g., active constraints, variable bindings) that should be automatically
* cleaned up when leaving a scope. ScopeStack provides this via WithNewScope,
* which pushes a new element on entry and pops it on exit.
*
* \code
* ScopeStack<WithGroup<ConstraintContext>> constraints;
*
* // In VisitStmt_(ForNode):
* return constraints.WithNewScope([&]() -> Stmt {
* constraints.Current().Emplace(analyzer, condition);
* return StmtExprMutator::VisitStmt_(op);
* });
* \endcode
*
* \tparam T The element type stored on the stack. Must be default-constructible.
*/
template <typename T>
class ScopeStack {
public:
/*! \brief Construct with one initial scope level. */
ScopeStack() { stack_.emplace_back(); }
/*! \brief Return the number of active scopes. */
size_t size() const { return stack_.size(); }
/*! \brief Return true if no scopes are active. */
bool empty() const { return stack_.empty(); }
/*!
* \brief Access the current (innermost) scope element.
*
* The returned reference is stable across push_back/pop_back because
* std::deque guarantees pointer stability for these operations.
*
* \return Mutable reference to the top element.
*/
T& Current() {
TVM_FFI_ICHECK(!stack_.empty());
return stack_.back();
}
/*! \brief Const access to the current (innermost) scope element. */
const T& Current() const {
TVM_FFI_ICHECK(!stack_.empty());
return stack_.back();
}
/*!
* \brief Execute body within a new scope.
*
* Pushes a new T onto the stack, executes the body, then pops it.
*
* \param body A callable to execute within the scope.
* \return The return value of body(), if non-void.
*/
template <typename F>
auto WithNewScope(F&& body) -> decltype(body()) {
stack_.emplace_back();
struct Guard {
std::deque<T>* stack;
~Guard() noexcept(false) { stack->pop_back(); }
} guard{&stack_};
if constexpr (std::is_void_v<decltype(body())>) {
body();
} else {
return body();
}
}
private:
/*!
* \brief The scope stack.
*
* We use std::deque rather than std::vector for pointer stability:
* references returned by Current() remain valid across push/pop operations.
* This is critical because methods called on Current() (e.g., Emplace on
* a WithGroup) may trigger re-entrant code that pushes new scopes onto
* the same stack. With std::vector the internal buffer reallocation would
* invalidate the reference, causing use-after-free.
*/
std::deque<T> stack_;
};
} // namespace tvm
#endif // TVM_IR_SCOPE_STACK_H_
+226
View File
@@ -0,0 +1,226 @@
/*
* 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.
*/
/*!
* \file source_map.h
* \brief A map from source names to source code.
*/
#ifndef TVM_IR_SOURCE_MAP_H_
#define TVM_IR_SOURCE_MAP_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/runtime/base.h>
#include <fstream>
#include <string>
#include <utility>
#include <vector>
namespace tvm {
/*!
* \brief The source name in the Span
* \sa SourceNameNode, Span
*/
class SourceName;
/*!
* \brief The name of a source fragment.
*/
class SourceNameNode : public ffi::Object {
public:
/*! \brief The source name. */
ffi::String name;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SourceNameNode>().def_ro("name", &SourceNameNode::name);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.SourceName", SourceNameNode, ffi::Object);
};
/*!
* \brief The source name of a file span.
* \sa SourceNameNode, Span
*/
class SourceName : public ffi::ObjectRef {
public:
/*!
* \brief Get an SourceName for a given operator name.
* Will raise an error if the source name has not been registered.
* \param name Name of the operator.
* \return SourceName valid throughout program lifetime.
*/
TVM_DLL static SourceName Get(const ffi::String& name);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(SourceName, ffi::ObjectRef, SourceNameNode);
};
/*!
* \brief Span information for debugging purposes
*/
class Span;
/*!
* \brief Stores locations in frontend source that generated a node.
*/
class SpanNode : public ffi::Object {
public:
/*! \brief The source name. */
SourceName source_name;
/*! \brief The line number. */
int line;
/*! \brief The column offset. */
int column;
/*! \brief The end line number. */
int end_line;
/*! \brief The end column number. */
int end_column;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SpanNode>()
.def_ro("source_name", &SpanNode::source_name)
.def_ro("line", &SpanNode::line)
.def_ro("column", &SpanNode::column)
.def_ro("end_line", &SpanNode::end_line)
.def_ro("end_column", &SpanNode::end_column);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO("ir.Span", SpanNode, ffi::Object);
};
class Span : public ffi::ObjectRef {
public:
TVM_DLL Span(SourceName source_name, int line, int end_line, int column, int end_column);
/*! \brief Merge two spans into one which captures the combined regions. */
TVM_DLL Span Merge(const Span& other) const;
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Span, ffi::ObjectRef, SpanNode);
};
/*!
* \brief Store a list of spans for an expr generated from mulitple source exprs
*/
class SequentialSpanNode : public SpanNode {
public:
/*! \brief The original source list of spans to construct a sequential span. */
ffi::Array<Span> spans;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SequentialSpanNode>().def_ro("spans", &SequentialSpanNode::spans);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.SequentialSpan", SequentialSpanNode, SpanNode);
};
/*!
* \brief Reference class of SequentialSpanNode.
* \sa SequentialSpanNode
*/
class SequentialSpan : public Span {
public:
TVM_DLL SequentialSpan(ffi::Array<Span> spans);
TVM_DLL SequentialSpan(std::initializer_list<Span> init);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(SequentialSpan, Span, SequentialSpanNode);
};
/*! \brief A program source in any language.
*
* Could represent the source from an ML framework or a source
* representing a tvm::IRModule.
*/
class Source;
class SourceNode : public ffi::Object {
public:
/*! \brief The source name. */
SourceName source_name;
/*! \brief The raw source. */
ffi::String source;
/*! \brief A mapping of line breaks into the raw source. */
std::vector<std::pair<int, int>> line_map;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SourceNode>()
.def_ro("source_name", &SourceNode::source_name)
.def_ro("source", &SourceNode::source);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.Source", SourceNode, ffi::Object);
};
class Source : public ffi::ObjectRef {
public:
TVM_DLL Source(SourceName src_name, std::string source);
TVM_DLL tvm::ffi::String GetLine(int line);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Source, ffi::ObjectRef, SourceNode);
};
/*!
* \brief A mapping from a unique source name to source fragment.
*/
class SourceMap;
/*!
* \brief Stores locations in frontend source that generated a node.
*/
class SourceMapObj : public ffi::Object {
public:
/*! \brief The source mapping. */
ffi::Map<SourceName, Source> source_map;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SourceMapObj>().def_ro("source_map", &SourceMapObj::source_map);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.SourceMap", SourceMapObj, ffi::Object);
};
class SourceMap : public ffi::ObjectRef {
public:
explicit SourceMap(ffi::Map<SourceName, Source> source_map);
explicit SourceMap(std::initializer_list<std::pair<SourceName, Source>> source_map)
: SourceMap(ffi::Map<SourceName, Source>(source_map)) {}
SourceMap() : SourceMap(ffi::Map<SourceName, Source>()) {}
void Add(const Source& source);
SourceMapObj* operator->() {
TVM_FFI_ICHECK(get() != nullptr);
return static_cast<SourceMapObj*>(get_mutable());
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(SourceMap, ffi::ObjectRef, SourceMapObj);
};
} // namespace tvm
#endif // TVM_IR_SOURCE_MAP_H_
+584
View File
@@ -0,0 +1,584 @@
/*
* 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.
*/
/*!
* \file tvm/ir/transform.h
*
* This file implements a pass manager. The pass manager manages a sequence
* of IRModule -> IRModule transformation passes over a particlar unit of AST. The
* design is largely inspired from LLVM's pass manager and modern deep learning
* frameworks that perform tensor->tensor transformations.
*
* The responsibilities of a traditional compiler pass manager usually involves:
* - Organizing the execution order of optimization passes though not
* necessarily in the optimal sequence.
* - Collecting required analysis information and keep them up-to-date.
* - Reducing the effort required to implement new passes for compiler
* developers, etc.
*
* Similar to LLVM's pass manager, we designed the Relax pass manager to work
* different granularity, i.e. module level, function level, and even sequential
* passe that contains a host of passes.
*
* However, we also extend the functionality of the traditional pass manager
* with the consideration of requirements/convention from deep learning
* frameworks, such as Pytorch and Gluon, etc. Each pass in the Relax pass
* manager performs the IRModule -> IRModule transformation. All
* different types of passes, including the sequential-level pass object, are
* essentially pass objects. This design, therefore, effectively provides users
* a consistent and convenient interface, i.e. Pass, to play with. It offers a
* means to ease the development and testing of Relax passes. For example, with
* the pass manager, external users will be able to have custom passes correctly
* scheduled without having to modify a single handcrafted pass order.
*
* In the future we need to describe constraints between passes. For example,
* we may want to preserve dependencies between different passes and validate
* them on the completion of a certain pass.
*
* We also need to store side information and import the error reporting system.
*/
#ifndef TVM_IR_TRANSFORM_H_
#define TVM_IR_TRANSFORM_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/creator.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include <tvm/ir/instrument.h>
#include <tvm/ir/module.h>
#include <tvm/ir/with_context.h>
#include <string>
#include <type_traits>
#include <utility>
namespace tvm {
namespace transform {
/*!
* \brief PassContextNode contains the information that a pass can rely on,
* such as analysis results.
* \sa PassContext
*/
class PassContextNode : public ffi::Object {
public:
/*! \brief The default optimization level. */
int opt_level{2};
/*! \brief The list of required passes. */
ffi::Array<ffi::String> required_pass;
/*! \brief The list of disabled passes. */
ffi::Array<ffi::String> disabled_pass;
/*! \brief Pass specific configurations. */
ffi::Map<ffi::String, Any> config;
/*! \brief A list of pass instrument implementations. */
ffi::Array<instrument::PassInstrument> instruments;
PassContextNode() = default;
/*!
* \brief Get a config value from the pass context.
*
* \param key The config key.
* \param default_value The default value if the key does not exist, defaults to nullptr.
*
* \return The result
*
* \tparam TOBjectRef the expected object type.
* \throw Error if the key exists but the value does not match TObjectRef.
*/
template <typename TObjectRef>
ffi::Optional<TObjectRef> GetConfig(
const std::string& key,
ffi::Optional<TObjectRef> default_value = ffi::Optional<TObjectRef>(std::nullopt)) const {
if (!config.defined()) return default_value;
auto it = config.find(key);
if (it != config.end()) {
return (*it).second.as_or_throw<ffi::Optional<TObjectRef>>();
} else {
return default_value;
}
}
// variant that uses TObjectRef to enable implicit conversion to default value.
template <typename TObjectRef>
ffi::Optional<TObjectRef> GetConfig(const std::string& key, TObjectRef default_value) const {
return GetConfig<TObjectRef>(key, ffi::Optional<TObjectRef>(default_value));
}
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PassContextNode>()
.def_ro("opt_level", &PassContextNode::opt_level)
.def_ro("required_pass", &PassContextNode::required_pass)
.def_ro("disabled_pass", &PassContextNode::disabled_pass)
.def_ro("instruments", &PassContextNode::instruments)
.def_ro("config", &PassContextNode::config);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("transform.PassContext", PassContextNode, ffi::Object);
};
/*!
* \brief PassContext that is used to configure the pass behavior.
*
* \code
*
* auto new_ctx = PassContext::Create();
* ctx->opt_level = 2;
* With<PassContext> scope(ctx);
* // pass context in effect.
*
* \endcode
* \sa PassContextNode
*/
class PassContext : public ffi::ObjectRef {
public:
PassContext() {}
/*!
* \brief constructor with UnsafeInit
*/
explicit PassContext(ffi::UnsafeInit tag) : ffi::ObjectRef(tag) {}
/*!
* \brief constructor with ffi::ObjectPtr
*/
explicit PassContext(ffi::ObjectPtr<PassContextNode> n) : ffi::ObjectRef(n) {}
/*!
* \brief const accessor.
* \return const access pointer.
*/
const PassContextNode* operator->() const {
TVM_FFI_ICHECK(get() != nullptr);
return static_cast<const PassContextNode*>(get());
}
/*!
* \brief mutable accessor.
* \return mutable access pointer.
*/
PassContextNode* operator->() {
TVM_FFI_ICHECK(get() != nullptr);
return static_cast<PassContextNode*>(get_mutable());
}
/*!
* \brief Construct a PassContext containing the default configurations.
* \return The new PassContext.
*/
TVM_DLL static PassContext Create();
/*!
* \brief Get the default pass context in the current scope.
* \return The pass context.
*/
TVM_DLL static PassContext Current();
/*!
* \brief Get all supported configuration names and metadata, registered within the PassContext.
* \return Map indexed by the config name, pointing to the metadata map as key-value
*/
TVM_DLL static ffi::Map<ffi::String, ffi::Map<ffi::String, ffi::String>> ListConfigs();
/*!
* \brief Call instrument implementations' callbacks when entering PassContext.
* The callbacks are called in order, and if one raises an exception, the rest will not be
* called.
*/
TVM_DLL void InstrumentEnterPassContext();
/*!
* \brief Call instrument implementations' callbacks when exiting PassContext.
* The callbacks are called in order, and if one raises an exception, the rest will not be
* called.
*/
TVM_DLL void InstrumentExitPassContext();
/*!
* \brief Call instrument implementations' callbacks before a pass run.
* The callbacks are called in order, and if one raises an exception, the rest will not be
* called.
*
* \param mod The module that an optimization pass runs on.
* \param info The pass information.
*
* \return false: the pass is skipped; true: the pass runs.
*/
TVM_DLL bool InstrumentBeforePass(const IRModule& mod, const PassInfo& info) const;
/*!
* \brief Call instrument implementations callbacks after a pass run.
* The callbacks are called in order, and if one raises an exception, the rest will not be
* called.
*
* \param mod The module that an optimization pass runs on.
* \param info The pass information.
*/
TVM_DLL void InstrumentAfterPass(const IRModule& mod, const PassInfo& info) const;
/*!
* \brief Check whether a pass is enabled.
* \param info The pass information.
* \return true if the pass is enabled. Otherwise, false.
*/
TVM_DLL bool PassEnabled(const PassInfo& info) const;
/*!
* \brief Register a valid configuration option and its ValueType for validation.
*
* \param key The configuration key.
* \tparam ValueType The value type to be registered
*/
template <typename ValueType>
static int32_t RegisterConfigOption(const char* key) {
// NOTE: we could further update the function later.
if constexpr (std::is_base_of_v<ffi::ObjectRef, ValueType>) {
int32_t tindex = ffi::TypeToRuntimeTypeIndex<ValueType>::v();
auto type_key = ffi::TypeIndexToTypeKey(tindex);
auto legalization = [=](ffi::Any value) -> ffi::Any {
if (auto opt_map = value.try_cast<ffi::Map<ffi::String, ffi::Any>>()) {
return ffi::reflection::ObjectCreator(type_key)(opt_map.value());
} else {
auto opt_val = value.try_cast<ValueType>();
if (!opt_val.has_value()) {
TVM_FFI_THROW(AttributeError)
<< "Expect config " << key << " to have type " << type_key << ", but instead get "
<< ffi::details::AnyUnsafe::GetMismatchTypeInfo<ValueType>(value);
}
return *opt_val;
}
};
RegisterConfigOption(key, type_key, legalization);
} else {
// non-object type, do not support implicit conversion from map
std::string type_str = ffi::TypeTraits<ValueType>::TypeStr();
auto legalization = [=](ffi::Any value) -> ffi::Any {
auto opt_val = value.try_cast<ValueType>();
if (!opt_val.has_value()) {
TVM_FFI_THROW(AttributeError)
<< "Expect config " << key << " to have type " << type_str << ", but instead get "
<< ffi::details::AnyUnsafe::GetMismatchTypeInfo<ValueType>(value);
} else {
return *opt_val;
}
};
RegisterConfigOption(key, type_str, legalization);
}
return 0;
}
// accessor.
using ContainerType = PassContextNode;
class Internal;
private:
// The entry of a pass context scope.
TVM_DLL void EnterWithScope();
// The exit of a pass context scope.
TVM_DLL void ExitWithScope();
// Register configuration key value type.
TVM_DLL static void RegisterConfigOption(const char* key, ffi::String value_type_str,
std::function<ffi::Any(ffi::Any)> legalization);
// Classes to get the Python `with` like syntax.
friend class Internal;
friend class With<PassContext>;
};
/*!
* \brief Create a pass-config object with all default values, using the
* reflection defaults.
* \tparam TConfig the ObjectRef type to be created.
* \return An instance with all reflection-defined default values applied.
*/
template <typename TConfig>
inline TConfig PassConfigWithDefaults() {
static_assert(std::is_base_of_v<ffi::ObjectRef, TConfig>,
"Can only create ObjectRef-derived types");
using ContainerType = typename TConfig::ContainerType;
static auto finit_object = ffi::Function::GetGlobalRequired("ffi.MakeObjectFromPackedArgs");
ffi::AnyView packed_args[1];
packed_args[0] = ContainerType::RuntimeTypeIndex();
ffi::Any rv;
finit_object.CallPacked(ffi::PackedArgs(packed_args, 1), &rv);
return rv.cast<TConfig>();
}
#define TVM_PASS_CTX_CONFIG_VAR_DEF [[maybe_unused]] static uint32_t __make_PassContext_tid
/*!
* \brief Helper macro to register the object type to runtime.
* Makes sure that the runtime type table is correctly populated.
*
* Use this macro in the cc file for each terminal class.
*/
#define TVM_REGISTER_PASS_CONFIG_OPTION(Key, ValueType) \
TVM_FFI_STR_CONCAT(TVM_PASS_CTX_CONFIG_VAR_DEF, __COUNTER__) = \
::tvm::transform::PassContext::RegisterConfigOption<ValueType>(Key)
/*!
* \brief Meta data that will be used to help optimization and analysis.
* \sa PassInfo
*/
class PassInfoNode : public ffi::Object {
public:
/*! \brief The minimal optimization level that this pass will be enabled. */
int opt_level;
/*! \brief The name of an optimization/analysis pass. */
ffi::String name;
/*! \brief Boolean that tells whether this pass will be traced or not. */
bool traceable;
/*! \brief The passes that are required to perform the current pass. */
ffi::Array<ffi::String> required;
PassInfoNode() = default;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PassInfoNode>()
.def_ro("opt_level", &PassInfoNode::opt_level)
.def_ro("name", &PassInfoNode::name)
.def_ro("required", &PassInfoNode::required)
.def_ro("traceable", &PassInfoNode::traceable);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("transform.PassInfo", PassInfoNode, ffi::Object);
};
/*!
* \brief Managed reference class for PassInfoNode
* \sa PassInfoNode
*/
class PassInfo : public ffi::ObjectRef {
public:
/*!
* \brief Constructor
* \param opt_level The optimization level
* \param name Name of the pass.
* \param required The passes that are required to perform the current pass.
* \param traceable Boolean that tells whether the pass is traceable.
*/
TVM_DLL PassInfo(int opt_level, ffi::String name, ffi::Array<ffi::String> required,
bool traceable);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(PassInfo, ffi::ObjectRef, PassInfoNode);
};
/*!
* \brief PassNode is the base type of differnt types of optimization passes.
* It is designed as a pure class and implemented by different pass subclasses
* at different granularity of Relax nodes.
*/
class PassNode : public ffi::Object {
public:
virtual ~PassNode() {}
/*!
* \brief Get the pass information/meta data. */
virtual PassInfo Info() const = 0;
/*!
* \brief Transform mod using the default PassContext in the current scope.
*
* \param mod The module that an optimization pass runs on.
*
* \return The transformed module.
*/
IRModule operator()(IRModule mod) const {
return this->operator()(std::move(mod), PassContext::Current());
}
/*!
* \brief Transform mod using a functor under a given pass context.
*
* \param mod The module that an optimization pass runs on.
* \param pass_ctx The pass context that can provide information for the optimization.
*
* \return The transformed module.
*/
virtual IRModule operator()(IRModule mod, const PassContext& pass_ctx) const = 0;
TVM_FFI_DECLARE_OBJECT_INFO("transform.Pass", PassNode, ffi::Object);
};
class Pass : public ffi::ObjectRef {
public:
/*!
* \brief Transform mod using the default PassContext in the current scope.
*
* \code
*
* // If you do no longer need the input module
* // it is recommended to use std::move to move your input module.
* mod = pass(std::move(mod));
*
* \endcode
*
* \param mod The module that an optimization pass runs on.
*
* \return The transformed module.
*/
IRModule operator()(IRModule mod) const;
/*!
* \brief Transform mod using a functor under a given pass context.
*
* \param mod The module that an optimization pass runs on.
* \param pass_ctx The pass context that can provide information for the optimization.
*
* \return The transformed module.
*/
IRModule operator()(IRModule mod, const PassContext& pass_ctx) const;
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Pass, ffi::ObjectRef, PassNode);
private:
IRModule static AssertImmutableModule(const IRModule& mod, const PassNode* node,
const PassContext& pass_ctx);
};
/*!
* \brief The SequentialNode contains a set of passes that transform Relax
* programs from one AST to another semantically equivalent one.
*
* One example of this level of pass is that the pass manager needs to correctly
* perform a host of optimizations with a given optimization level and disabled
* passes.
*/
class SequentialNode : public PassNode {
public:
/* \brief The pass meta data.*/
PassInfo pass_info;
/*! \brief A list of passes that used to compose a sequential pass. */
tvm::ffi::Array<Pass> passes;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SequentialNode>()
.def_ro("pass_info", &SequentialNode::pass_info)
.def_ro("passes", &SequentialNode::passes);
}
/*!
* \brief Get the pass information/meta data.
*/
PassInfo Info() const override { return pass_info; }
/*!
* \brief Resolve the pass dependency. It globs all required passes by
* a given pass and executes them.
*
* \param mod The module that an optimization pass runs on.
*
* TODO(zhiics) Build a dependency graph among the passes using provided
* metadata, i.e. required_passes. Likely, we can have a data structure, i.e.
* PassInfo, to store the relevant information including the parent passes.
*/
void ResolveDependency(const IRModule& mod);
/*!
* \brief Perform optimizations on a series of passes. The aforementioned
* typical pass manager jobs could be done by it. This function could
* be overloaded to focus on different metrics, i.e. performance,
* memory footprint, etc.
*
* \param mod The module that these passes are applied on.
* \param pass_ctx The context that these passes execute on.
*
* \return Return the updated module.
*/
IRModule operator()(IRModule mod, const PassContext& pass_ctx) const final;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("transform.Sequential", SequentialNode, PassNode);
};
class Sequential : public Pass {
public:
/*!
* \brief The constructor of `Sequential`.
*
* \param passes The passes to apply.
* \param pass_info The pass metadata.
*/
TVM_DLL Sequential(ffi::Array<Pass> passes, PassInfo pass_info);
/*!
* \brief The constructor of `Sequential`.
*
* \param passes The passes to apply.
* \param name The name of a sequential pass. It's defaulted to "sequential".
* This allows users to only provide a list of passes and execute them
* under a given context.
*/
TVM_DLL Sequential(ffi::Array<Pass> passes, ffi::String name = "sequential");
Sequential() = default;
explicit Sequential(ffi::ObjectPtr<SequentialNode> n) : Pass(n) {}
const SequentialNode* operator->() const;
using ContainerType = SequentialNode;
};
/*
* \brief Create a module pass.
*
* \param pass_func The packed function that contains the optimization.
* \param opt_level The optimization level of the module pass.
* \param name The name of the module pass.
* \param required The list of the passes that the module pass is dependent on.
*
* \return The created module pass.
*/
TVM_DLL Pass CreateModulePass(std::function<IRModule(IRModule, PassContext)> pass_func,
int opt_level, ffi::String name, ffi::Array<ffi::String> required,
bool traceable = false);
/*!
* \brief A special trace pass that prints the header and IR to LOG(INFO).
* \param header The header to be attached to the output.
* \return The pass.
*/
TVM_DLL Pass PrintIR(ffi::String header = "");
/*!
* \brief Enrich a pass-time error with a TVMScript-rendered, underlined source
* location derived from the error's embedded VisitErrorContext.
*
* Returns an ffi::Error that preserves err's kind, original message, and
* backtrace, and appends the failing pass name plus the offending location
* rendered as TVMScript (the whole \p mod, or local to \p func when provided).
* The returned error drops the VisitErrorContext payload, so an outer catch
* that re-enriches finds no context and returns the error unchanged.
*
* Pure and total: never throws; returns \p err unchanged when there is no
* context, the path is unresolvable, or rendering fails.
*
* \param err The error thrown by the pass body.
* \param mod The IRModule the pass ran on (the access-path root, or the
* container of \p func when \p func is provided).
* \param pass_name The name of the failing pass, shown in the message.
* \param func When set, resolve and render the location local to
* \p mod->functions[func]; otherwise use the whole module.
* \return The enriched (or, on any fallback, the original) error.
*/
TVM_DLL ffi::Error EnrichPassErrorWithContext(
const ffi::Error& err, const IRModule& mod, ffi::String pass_name,
ffi::Optional<GlobalVar> func = ffi::Optional<GlobalVar>(std::nullopt));
} // namespace transform
} // namespace tvm
#endif // TVM_IR_TRANSFORM_H_
+207
View File
@@ -0,0 +1,207 @@
/*
* 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.
*/
/*!
* \file tvm/ir/type.h
* \brief IR/AST nodes for TVM types shared across IR variants.
*/
#ifndef TVM_IR_TYPE_H_
#define TVM_IR_TYPE_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/dtype.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/base_expr.h>
#include <tvm/ir/source_map.h>
#include <string>
namespace tvm {
/*!
* \brief Low-level raw pointer type.
*
* PointerType represents type hints in the TIR to be
* passed to the final code generator.
*
* PointerType should not occur in the high-level analysis.
*
* \sa PointerType
*/
class PointerTypeNode : public TypeNode {
public:
/*!
* \brief The type of the element which the pointer points to.
*/
Type element_type = PrimType::Void();
/*!
* \brief The storage scope of the pointer
*/
ffi::String storage_scope;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PointerTypeNode>()
.def_ro("element_type", &PointerTypeNode::element_type)
.def_ro("storage_scope", &PointerTypeNode::storage_scope);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.PointerType", PointerTypeNode, TypeNode);
};
/*
* \brief Managed reference to PointerTypeNode.
* \sa PointerTypeNode
*/
class PointerType : public Type {
public:
/*!
* \brief Constructor
* \param element_type The type of the element which the pointer points to.
* \param storage_scope The storage scope into which the pointer addresses
*/
TVM_DLL explicit PointerType(Type element_type, ffi::String storage_scope = "");
/*! \brief Construct an opaque pointer with void element type. */
TVM_DLL static PointerType VoidPointerTy(ffi::String storage_scope = "");
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(PointerType, Type, PointerTypeNode);
};
/*!
* \brief The type of tuple values.
* \sa TupleType
*/
class TupleTypeNode : public TypeNode {
public:
/*! \brief The type of each field in the tuple. */
ffi::Array<Type> fields;
TupleTypeNode() {}
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TupleTypeNode>().def_ro("fields", &TupleTypeNode::fields);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.TupleType", TupleTypeNode, TypeNode);
};
/*!
* \brief Managed reference to TupleTypeNode.
* \sa TupleTypeNode.
*/
class TupleType : public Type {
public:
/*!
* \brief Constructor
* \param fields Fields in the tuple.
* \param span The span of the type.
*/
TVM_DLL explicit TupleType(ffi::Array<Type> fields, Span span = Span());
/*!
* \brief Create an empty tuple type that constains nothing.
* \return A empty tuple type.
*/
TVM_DLL TupleType static Empty();
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(TupleType, Type, TupleTypeNode);
};
/*!
* \return a type that represents void.
*/
inline Type VoidType() { return TupleType::Empty(); }
/*!
* \brief Check whether the tyep represents void.
* \return The check result.
*/
inline bool IsVoidType(const Type& type) {
auto* n = type.as<TupleTypeNode>();
return n && n->fields.size() == 0;
}
/*!
* \brief Function type.
*
* We support polymorphic function type.
* This can be roughly viewed as template function in C++.
*
* \sa FuncType, TypeConstraint
*/
class FuncTypeNode : public TypeNode {
public:
/*! \brief type type of arguments */
ffi::Array<Type> arg_types;
/*! \brief The type of return value. */
Type ret_type = VoidType();
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<FuncTypeNode>()
.def_ro("arg_types", &FuncTypeNode::arg_types)
.def_ro("ret_type", &FuncTypeNode::ret_type);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.FuncType", FuncTypeNode, TypeNode);
};
/*!
* \brief Managed reference to FuncTypeNode.
* \sa FuncTypeNode
*/
class FuncType : public Type {
public:
/*!
* \brief Constructor
* \param arg_types The types of the arguments.
* \param ret_type The type of the return value.
* \param span The span information.
* \sa FuncTypeNode for more docs about these fields.
*/
TVM_DLL FuncType(ffi::Array<Type> arg_types, Type ret_type, Span span = Span());
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(FuncType, Type, FuncTypeNode);
};
/*!
* \brief The type of tensor map.
* \sa TensorMapType
*/
class TensorMapTypeNode : public TypeNode {
public:
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TensorMapTypeNode>();
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.TensorMapType", TensorMapTypeNode, TypeNode);
};
/*!
* \brief Managed reference to TensorMapTypeNode.
* \sa TensorMapTypeNode
*/
class TensorMapType : public Type {
public:
TVM_DLL TensorMapType(Span span = Span());
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(TensorMapType, Type, TensorMapTypeNode);
};
} // namespace tvm
#endif // TVM_IR_TYPE_H_
+143
View File
@@ -0,0 +1,143 @@
/*
* 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.
*/
/*!
* \file tvm/ir/unique_name_supply.h
* \brief UniqueNameSupply that can be used to generate unique variable names.
*/
#ifndef TVM_IR_UNIQUE_NAME_SUPPLY_H_
#define TVM_IR_UNIQUE_NAME_SUPPLY_H_
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/expr.h>
#include <cstdint>
#include <string>
#include <utility>
namespace tvm {
/*!
* \brief UniqueNameSupply can be used to generate unique names.
*/
class UniqueNameSupplyNode : public ffi::Object {
public:
/*!
* \brief Empty constructor. Needed by the TVM_REGISTER_NODE_TYPE macro.
*/
UniqueNameSupplyNode() = default;
/*!
* \brief Constructor.
* \param prefix The prefix to be used with this UniqueNameSupply.
* \param name_map The map used to guarantee uniqueness.
*/
UniqueNameSupplyNode(const ffi::String& prefix, ffi::Map<ffi::String, int64_t> name_map)
: prefix_(prefix), name_map(std::move(name_map)) {}
/*!
* \brief Generates a unique name from this UniqueNameSupply.
* \param name The name from which the generated name is derived.
* \param add_prefix If set to true, then the prefix of this UniqueNameSupply will be prepended to
* the name.
* \param add_underscore If set to true, add '_' between prefix and a digit.
* \return A unique name.
*/
ffi::String FreshName(const ffi::String& name, bool add_prefix = true,
bool add_underscore = true);
/*!
* \brief Reserves an existing name with this UniqueNameSupply.
* \param name The name to be reserved.
* \param add_prefix If set to true, then the prefix of this UniqueNameSupply will be prepended to
* the name before reserving it. \return The name that was reserved with the UniqueNameSupply. It
* can be different if a prefix is added.
*/
ffi::String ReserveName(const ffi::String& name, bool add_prefix = true);
/*!
* \brief Checks if this UniqueNameSupply already generated a name.
* \param name The name to check.
* \param add_prefix If set to true, then the prefix of this UniqueNameSupply will be prepended to
* the name before checking for it. \return True if the name has already been generated. False
* otherwise.
*/
bool ContainsName(const ffi::String& name, bool add_prefix = true);
// Prefix for all GlobalVar names. It can be empty.
std::string prefix_;
static constexpr const bool _type_mutable = true;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<UniqueNameSupplyNode>();
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("ir.UniqueNameSupply", UniqueNameSupplyNode, ffi::Object);
private:
/*! \brief Helper function to add the UniqueNameSupply prefix to the name. */
ffi::String AddPrefixToName(const ffi::String& name);
/*!
* \brief Function that will generate a unique name.
* \param name The name to be used as a base.
* \param add_underscore If set to true, add '_' between prefix and a digit.
* \return A unique name.
*/
std::string GetUniqueName(std::string name, bool add_underscore = true);
/*! \brief A map that is used to generate unique names. */
ffi::Map<ffi::String, int64_t> name_map;
};
/*!
* \brief Managed reference class to UniqueNameSupplyNode.
* \sa UniqueNameSupplyNode
*/
class UniqueNameSupply : public ffi::ObjectRef {
public:
/*!
* \brief Constructor.
* \param prefix The prefix to be used with this UniqueNameSupply.
* \param name_map An optional map.
*/
TVM_DLL explicit UniqueNameSupply(const ffi::String& prefix = "",
ffi::Map<ffi::String, int64_t> name_map = {});
/*!
* \brief Construct UniqueNameSupply by reserving names from the given iterator range.
*
* The functor should return the name of the dereferenced object.
*/
template <typename Iter, typename Lambda>
TVM_DLL UniqueNameSupply(Iter begin, Iter end, Lambda f) : UniqueNameSupply("") {
for (auto it = begin; it != end; ++it) {
this->operator->()->ReserveName(f(*it), false);
}
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(UniqueNameSupply, ffi::ObjectRef,
UniqueNameSupplyNode);
};
} // namespace tvm
#endif // TVM_IR_UNIQUE_NAME_SUPPLY_H_
+168
View File
@@ -0,0 +1,168 @@
/*
* 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.
*/
/*!
* \file tvm/ir/with_context.h
* \brief RAII wrapper function to enter and exit a context object
* similar to python's with syntax.
*/
#ifndef TVM_IR_WITH_CONTEXT_H_
#define TVM_IR_WITH_CONTEXT_H_
#include <exception>
#include <functional>
#include <memory>
#include <utility>
#include <vector>
namespace tvm {
/*!
* \brief RAII wrapper function to enter and exit a context object
* similar to python's with syntax.
*
* \code
* // context class
* class MyContext {
* private:
* friend class With<MyContext>;
MyContext(arguments);
* void EnterWithScope();
* void ExitWithScope();
* };
*
* {
* With<MyContext> scope(arguments);
* // effect take place.
* }
* \endcode
*
* \tparam ContextType Type of the context object.
*/
template <typename ContextType>
class With {
public:
/*!
* \brief constructor.
* Enter the scope of the context.
*/
template <typename... Args>
explicit With(Args&&... args) : ctx_(std::forward<Args>(args)...) {
ctx_.EnterWithScope();
}
/*! \brief destructor, leaves the scope of the context. */
~With() noexcept(false) { ctx_.ExitWithScope(); }
// Disable copy and move construction. `With` is intended only for
// use in nested contexts that are exited in the reverse order of
// entry. Allowing context to be copied or moved would break this
// expectation.
With(const With& other) = delete;
With& operator=(const With& other) = delete;
With(With&& other) = delete;
With& operator=(With&& other) = delete;
ContextType* get() { return &ctx_; }
const ContextType* get() const { return &ctx_; }
ContextType* operator->() { return get(); }
const ContextType* operator->() const { return get(); }
ContextType& operator*() { return *get(); }
const ContextType* operator*() const { return *get(); }
ContextType operator()() { return ctx_; }
private:
/*! \brief internal context type. */
ContextType ctx_;
};
/*!
* \brief A group of RAII contexts managed together.
*
* Allows dynamically emplacing multiple context objects that are
* all exited (in reverse order) when the group is destroyed.
* ContextType must declare `friend class With<ContextType>`
* and provide EnterWithScope() / ExitWithScope() methods.
*
* \code
* WithGroup<ConstraintContext> group;
* group.Emplace(analyzer, cond1); // constructs and enters
* group.Emplace(analyzer, cond2); // constructs and enters
* // destructor: exits cond2, then cond1
* \endcode
*
* \tparam ContextType The context type with EnterWithScope/ExitWithScope.
*/
template <typename ContextType>
class WithGroup {
public:
WithGroup() = default;
WithGroup(WithGroup&&) = default;
WithGroup& operator=(WithGroup&&) = default;
WithGroup(const WithGroup&) = delete;
WithGroup& operator=(const WithGroup&) = delete;
/*!
* \brief Construct a context and enter its scope.
* \param args Arguments forwarded to ContextType constructor.
*/
template <typename... Args>
void Emplace(Args&&... args) {
entries_.push_back(std::make_unique<With<ContextType>>(std::forward<Args>(args)...));
}
/*! \brief Number of active contexts in this group. */
size_t size() const { return entries_.size(); }
/*!
* \brief Destructor — exits all contexts in reverse order.
*
* On normal exit: if any ExitWithScope throws, the remaining
* contexts are still cleaned up, then the first exception
* is re-thrown.
*
* During stack unwinding: all exceptions are swallowed
* to avoid std::terminate.
*/
~WithGroup() noexcept(false) {
bool unwinding = std::uncaught_exceptions() > 0;
std::exception_ptr first_exc;
while (!entries_.empty()) {
// Move the last entry out of the vector first, then destroy it.
// This ensures entries_ shrinks even if ~With() throws.
auto entry = std::move(entries_.back());
entries_.pop_back();
try {
entry.reset(); // calls ~With<ContextType>() -> ExitWithScope()
} catch (...) {
if (!unwinding && !first_exc) {
first_exc = std::current_exception();
}
}
}
if (first_exc) std::rethrow_exception(first_exc);
}
private:
std::vector<std::unique_ptr<With<ContextType>>> entries_;
};
} // namespace tvm
#endif // TVM_IR_WITH_CONTEXT_H_
+685
View File
@@ -0,0 +1,685 @@
/*
* 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.
*/
/*!
* \file tvm/relax/analysis.h
* \brief The set of Relax specific analysis on IR.
*/
#ifndef TVM_RELAX_ANALYSIS_H_
#define TVM_RELAX_ANALYSIS_H_
#include <tvm/arith/analyzer.h>
#include <tvm/ir/module.h>
#include <tvm/relax/expr.h>
#include <tvm/relax/op_attr_types.h>
#include <tvm/relax/type.h>
#include <tvm/tirx/function.h>
#include <tvm/tirx/index_map.h>
#include <functional>
#include <set>
#include <utility>
namespace tvm {
namespace relax {
//-----------------------------------
// Shape expression analysis
//----------------------------------
/*!
* \brief Can prove the two symbolic shape arrays equals to each other.
*
* \param lhs The left operand.
* \param rhs The right operand.
* \param ana The analyzer used for integer analysis.
* \return The prove result.
*
* \note This function does best effort prove, which means
* if result is false, there is still possibility that
* two shapes equals to each other during runtime.
*/
TVM_DLL bool CanProveShapeEqual(const ffi::Array<PrimExpr>& lhs, const ffi::Array<PrimExpr>& rhs,
const arith::Analyzer& ana);
/*!
* \brief Can prove the two symbolic shape expressions equals to each other.
*
* \param lhs The left operand.
* \param rhs The right operand.
* \param ana The analyzer used for integer analysis.
*
* \note This function does best effort prove, which means
* if result is false, there is still possibility that
* two shapes equals to each other during runtime.
*/
TVM_DLL bool CanProveShapeEqual(const Expr& lhs, const Expr& rhs, const arith::Analyzer& ana);
//-----------------------------------
// Foundational Type analysis
//-----------------------------------
/*!
* \brief Get the corresponding static type from a given type.
* \param info The type.
* \return the corresponding static type.
*/
TVM_DLL Type GetStaticType(const Type& info);
/*!
* \brief Get the corresponding type from static type.
* \param type The input type
* \return the corresponding type.
*/
TVM_DLL Type TypeFromStaticType(const Type& type);
/*!
* \return Derive the call's ret value type from inputs.
* \param finfo The function type.
* \param call The call expression to be derived.
* \param ctx The builder context.
* \return The derived type of the call.
* \note call->op field is ignored during derivation and we only rely on information
* presented by func_ty.
*/
TVM_DLL Type DeriveCallRetType(const FuncType& finfo, const Call& call, const BlockBuilder& ctx);
/*!
* \brief Derive the call's ret value type using a caller-provided analyzer.
* \param finfo The function type.
* \param call The call expression to be derived.
* \param ctx The builder context.
* \param ana Context analyzer to prove symbolic expression equality.
* \return The derived type of the call.
*/
TVM_DLL Type DeriveCallRetType(const FuncType& finfo, const Call& call, const BlockBuilder& ctx,
const arith::Analyzer& ana);
/*!
* \brief Erase the info to a corresponding more coarse grained
* type that is still well-defined(with all the vars in scope).
*
* When we are returning a Type to another scope,
* it is important to remember that Type may carry
* dependencies on var that is not defined the other scope.
*
* In such cases, it is important to call EraseToWellDefined to get
* another Type that **only** contains the vars that are defined
* in the target scope.
*
* For example, consider the following function
*
* \code
*
* @R.function
* def f(x: R.Tensor[(n, m)]):
* k = tirx.Var("k", "int64")
* v0 = opaque_fn(x)
* v1 = match_cast(v0, R.Tensor[(n, k)])
* v2 : R.Tensor[(n + 1, k + 2)] = pad(v1)
* return v2
*
* \endcode
*
* In the above code, the return value y have shape `(n + 1, k + 2)`,
* However, at the level of function signature, only n, m are defined,
* k is undefined here.
*
* When we call EraseToWellDefined(R.Tensor[(n + 1, k + 2)], fshape_var_map={n: n, m: m}),
* we will obtain R.Tensor(ndim=2), which is an erased info that does not depend
* on k(which is undefined from parameter signature).
*
* However, if we call EraseToWellDefined(R.Tensor[(n + 1, m)], fshape_var_map={n: n, m: m}),
* Then the return value will be R.Tensor[(n + 1, m)], because both n and m are defined.
*
* We can also make these var map to return a different expression.
* For example, EraseToWellDefined(R.Tensor[(n + 1, m)], fshape_var_map={n: 2, m: m})
* will give us R.Tensor[(3, m)], where n get replaced by 2.
*
* Use this function in the following scenarios:
* - Decide the ty of expr with sub-scopes, such as If, SeqExpr
* - Decide the deduced return ty of a function that can be fully decided by params.
*
* \param info The type.
* \param f_shape_var_map callback function to specify
* whether a symbolic shape var is defined and the value it maps to,
* return nullopt if var is undefined.
* \param f_var_map callback function to specify
* whether a var is defined in the target scope and the value it maps to,
* return nullopt if var is undefined.
*
* \return the corresponding erased type.
*/
TVM_DLL Type EraseToWellDefined(
const Type& info,
std::function<ffi::Optional<PrimExpr>(const tirx::Var& var)> f_shape_var_map = nullptr,
std::function<ffi::Optional<Expr>(const Var& var)> f_var_map = nullptr);
/*!
* \brief EraseToWellDefined overload using a caller-provided analyzer.
* \param info The type.
* \param f_shape_var_map callback function to specify
* whether a symbolic shape var is defined and the value it maps to,
* return nullopt if var is undefined.
* \param f_var_map callback function to specify
* whether a var is defined in the target scope and the value it maps to,
* return nullopt if var is undefined.
* \param ana Context analyzer to prove symbolic expression equality.
* \return the corresponding erased type.
*/
TVM_DLL Type EraseToWellDefined(
const Type& info, std::function<ffi::Optional<PrimExpr>(const tirx::Var& var)> f_shape_var_map,
std::function<ffi::Optional<Expr>(const Var& var)> f_var_map, const arith::Analyzer& ana);
/*!
* \brief EraseToWellDefined variant with map.
* \param info The type.
* \param shape_var_map map to specify
* whether a symbolic shape var is defined and the value it maps to,
* return nullopt if var is undefined.
* \param var_map map to specify
* whether a var is defined in the target scope and the value it maps to,
* return nullopt if var is undefined.
*
* \return the corresponding erased type.
*/
TVM_DLL Type EraseToWellDefined(const Type& info, ffi::Map<tirx::Var, PrimExpr> shape_var_map,
ffi::Map<Var, Expr> var_map);
/*!
* \brief EraseToWellDefined map overload using a caller-provided analyzer.
* \param info The type.
* \param shape_var_map map to specify
* whether a symbolic shape var is defined and the value it maps to,
* return nullopt if var is undefined.
* \param var_map map to specify
* whether a var is defined in the target scope and the value it maps to,
* return nullopt if var is undefined.
* \param ana Context analyzer to prove symbolic expression equality.
* \return the corresponding erased type.
*/
TVM_DLL Type EraseToWellDefined(const Type& info, ffi::Map<tirx::Var, PrimExpr> shape_var_map,
ffi::Map<Var, Expr> var_map, const arith::Analyzer& ana);
/*!
* \brief Fine grained result of base check.
*
* This analysis comes with different levels of checking failures
* that can help to customize the compilation decisions.
*
* For a given pair of lhs_ty, rhs_ty. We adopt
* the following terminology:
* - LSet = {value | value matches lhs_ty}
* - RSet = {value | value matches rhs_ty}
*
* See the definition of each level below.
*/
enum class BaseCheckResult {
/*!
* \brief The two value sets have no intersection at all: Interset(LSet, RSet) = empty
*/
kFailL0 = 0,
/*!
* \brief LSet is not superset of RSet by only looking at static information.
*
* \note This level will trigger static type checking error when lhs is param and rhs is arg.
*/
kFailL1 = 1,
/*!
* \brief WLSet is not superset of RSet because of mismatch in value information.
*
* L1-level mismatches in params of FuncType is categorized as
* If lhs is FuncType, then L1-level mismatch in its params
* is categorized as L2-level mismatch for lhs.
*
* Design considerations for functions:
* - (a) We want to be able to erase type/value in function signature
* when we unify function type and preserve simpler representations.
* - (b) We automatically insert match_cast at function boundary, so
* we can erase (int)->int argument as (object)->int.
* The input shape/type mismatch will be detected by runtime checks at function boundary.
* This behavior is also consistent with the ffi::Function behavior.
*
* \note This level means there is no problem about static known information.
* It is OK for the checker to do best effort and return this value.
*/
kFailL2 = 2,
/*! \brief LSet is superset of RSet. */
kPass = 3
};
/*!
* \brief Run a base check to see if base subsumes derived.
*
* This function returns fine-grained base-check result on reasons of failure.
*
* \param base The base type.
* \param derived The derived type.
* \return Whether the relation holds.
*
* \sa BaseCheckResult
*/
TVM_DLL BaseCheckResult TypeBaseCheck(const Type& base, const Type& derived);
/*!
* \brief Run a base check using a caller-provided analyzer.
* \param base The base type.
* \param derived The derived type.
* \param ana Context analyzer to prove symbolic expression equality.
* \return Whether the relation holds.
*
* \sa BaseCheckResult
*/
TVM_DLL BaseCheckResult TypeBaseCheck(const Type& base, const Type& derived,
const arith::Analyzer& ana);
/*!
* \brief Check the relation of two type to see if one subsumes another one.
*
* \param base The base type.
* \param derived The derived type.
* \return Whether the relation holds.
*/
TVM_DLL bool IsBaseOf(const Type& base, const Type& derived);
/*!
* \brief Check whether one type subsumes another using a caller-provided analyzer.
* \param base The base type.
* \param derived The derived type.
* \param ana Context analyzer to prove symbolic expression equality.
* \return Whether the relation holds.
*/
TVM_DLL bool IsBaseOf(const Type& base, const Type& derived, const arith::Analyzer& ana);
/*!
* \brief Return the condition for which base is a superset of derived
*
* This function returns finer-grained conditions for kFailL2 than TypeBaseCheck
*
* If the returned expression is true, or simplifies to true, then
* base is a superset of derived. If the returned expression is
* false, or simplifies to false, then base is not a superset of
* derived.
*
* If the returned expression is neither true nor false, it is an
* expression in terms of the symbolic variables available in `base`
* and `derived`.
*
* \param base The base type.
* \param derived The derived type.
* \return Whether base is a base of derived.
*
* \sa BaseCheckResult
*/
TVM_DLL PrimExpr TypeBaseCheckPrecondition(const Type& base, const Type& derived);
/*!
* \brief Unify the two type to their least common ancestor.
*
* \param lhs The left operand.
* \param rhs The right operand.
* \return The unified information.
*/
TVM_DLL Type TypeLCA(const Type& lhs, const Type& rhs);
/*!
* \brief Unify two types using a caller-provided analyzer.
* \param lhs The left operand.
* \param rhs The right operand.
* \param ana Context analyzer to prove symbolic expression equality.
* \return The unified information.
*/
TVM_DLL Type TypeLCA(const Type& lhs, const Type& rhs, const arith::Analyzer& ana);
/*!
* \brief Get the TIR variables that appear in the input type.
* The returned list is deduplicated - each TIR variable will appear at most once.
* \param ty The type object to be analyzed.
* \return The list of TIR variables that appear in the input type.
*/
TVM_DLL ffi::Array<tirx::Var> TIRVarsInType(const Type& ty);
/*!
* \brief Get the TIR variables that appear in the input type.
*
* Returns all symbolic variables that are definable based on, and
* used within, the Type.
*
* \param ty The type object to be analyzed.
*
* \return A tuple of (definable,used) TIR variables. Both lists are
* deduplicated, each TIR variable will appear at most once, and in
* order of occurrence.
*/
TVM_DLL ffi::Array<tirx::Var> DefinableTIRVarsInType(const Type& ty);
/*! \brief Collect expressions whose usage requires them to be non-negative
*
* Any PrimExpr that is used as a tensor shape, or as an element in a
* ShapeExpr, may not be negative. This utility function can be used
* to generate assertions prior to calling a kernel, or to provide
* assumptions within a kernel that may be useful for simplification.
*
* \param ty The type to be analyzed
*
* \return A list of non-negative expressions.
*/
TVM_DLL ffi::Array<PrimExpr> CollectNonNegativeExpressions(const Type& ty);
/*!
* \brief Get the TIR variables that defined in the input function.
* The returned list is deduplicated - each TIR variable will appear at most once.
* \param expr The relax expression (e.g. a Function) to be analyzed.
* \return The list of TIR variables that are defined in the input function.
*/
TVM_DLL ffi::Array<tirx::Var> DefinedSymbolicVars(const Expr& expr);
/*!
* \brief Get the TIR variables that are used but not defined in the input function.
* The returned list is deduplicated - each TIR variable will appear at most once.
* \param expr The relax expression (e.g. a Function) to be analyzed.
* \return The list of TIR variables that are used but not defined in the input function.
*/
TVM_DLL ffi::Array<tirx::Var> FreeSymbolicVars(const Expr& expr);
//-----------------------------------
// General IR analysis
//-----------------------------------
/*!
* \brief Get all bound variables from expression expr.
*
* Bound variables are all variables that are declared in the expr.
* They only have meaning inside that expr, and can only be used in it.
*
* \param expr the expression.
*
* \return List of bound vars, in the PostDFS order in the expression.
*/
TVM_DLL tvm::ffi::Array<Var> BoundVars(const Expr& expr);
/*!
* \brief Get free type parameters from expression expr.
*
* Free variables are variables that are not bound by a
* varbinding or a function parameter in the context.
*
* \param expr the expression.
*
* \return List of free vars, in the PostDFS order in the expression.
*/
TVM_DLL tvm::ffi::Array<Var> FreeVars(const Expr& expr);
/*!
* \brief Get all variables from expression expr.
*
* \param expr the expression.
*
* \return List of all vars, in the PostDFS order in the expression.
*/
TVM_DLL tvm::ffi::Array<Var> AllVars(const Expr& expr);
/*!
* \brief Get all global variables from expression expr.
*
* AllVars is a superset of BoundVars and FreeVars.
* The union of BoundVars and FreeVars is Allvars.
*
* \param expr the expression.
*
* \return List of all global variables, in the PostDFS order in the expression.
*/
TVM_DLL tvm::ffi::Array<GlobalVar> AllGlobalVars(const Expr& expr);
/*!
* \brief Find all sets of recursive or mutually recursive functions in the module.
*
* Two or more functions are mutually recursive if there is some cycle of references
* among them. For example, if there are two functions A and B, they are
* mutually recursive if A calls B and B calls A. Another case would be with
* three functions A, B, and C, where A calls B, B calls C, and C calls A.
*
* (Note that functions do not have to call each other to reference each other.
* For example, if a function returns another function, that is still a reference
* that could potentially be recursive, even without a call.)
*
* If a function is simply recursive and not mutually recursive with any other,
* it will be reported as a group by itself.
*
* \param m The module
*
* \return List of all groups of mutually recursive functions.
* Each member of the result is a list of functions in the module
* that are all mutually recursive.
* If a function is simply recursive and not mutually recursive with any other,
* then it will be listed as a group by itself.
*/
TVM_DLL tvm::ffi::Array<tvm::ffi::Array<GlobalVar>> DetectRecursion(const IRModule& m);
/*!
* \brief Analyze var -> value mapping from VarBindings.
*
* \param m The IRModule to check.
* \return Var -> Value (Expr)
*/
TVM_DLL ffi::Map<Var, Expr> AnalyzeVar2Value(const IRModule& m);
/*!
* \brief Analyze var -> value mapping from VarBindings.
*
* \param expr The expression to check.
* \return Var -> Value (Expr)
*/
TVM_DLL ffi::Map<Var, Expr> AnalyzeVar2Value(const Expr& expr);
/*!
* \brief Analyze var -> value mapping from VarBindings.
*
* \param dfb The dataflow block to check.
* \return Var -> Value (Expr)
*/
TVM_DLL ffi::Map<Var, Expr> AnalyzeVar2Value(const DataflowBlock& dfb);
/*!
* \brief Return a mapping from variable name to its Bindings.
*
* \param fn The function to be analyzed.
* \return A mapping from variable name to its Bindings.
*/
TVM_DLL ffi::Map<ffi::String, ffi::Array<Binding>> NameToBinding(const Function& fn);
/*!
* \brief Get the use-def chain of variables inside a dataflow block.
*
* \param dfb The dataflow block to be analyzed.
* \return A map mapping variable definitions to a set of uses.
*/
TVM_DLL ffi::Map<Var, ffi::Array<Var>> DataflowBlockUseDef(const DataflowBlock& dfb);
/*!
* \brief Get the use-def chain of variables inside a function.
*
* \param expr The expression to be analyzed.
*
* \return A tuple of variable usage and variable outputs. The first
* element is a map from variable definitions to the set of downstream
* users of that definition. The second element is a list of
* variables whose usage occurs outside of any variable binding,
* typically the output body of a relax::Function or a relax::SeqExpr.
*/
std::pair<ffi::Map<Var, ffi::Array<Var>>, ffi::Array<Var>> FunctionUseDef(const Expr& expr);
/*! \brief A utility struct returned by CollectVarUsage
*/
struct VarUsageInfo {
/* \brief A map from variables to the bound expression.
*
* This is equivalent to the output of AnalyzeVar2Value
*/
ffi::Map<Var, Expr> bound_values;
/* \brief The map from variables to downstream usages of the variable
*
* This is equivalent to the first output of FunctionUseDef.
*/
ffi::Map<Var, ffi::Array<Var>> downstream_usage;
/* \brief A list of variables produced as output
*
* This is equivalent to the second output of FunctionUseDef
*/
ffi::Array<Var> outputs;
};
/*! \brief Collect variable bindings and usage
*
* This function is equivalent to calling both FunctionUseDef and
* AnalyzeVar2Value, but requires only a single traversal of the
* expression.
*
* \param expr The expression to analyze
*
* \return The collected information
*/
VarUsageInfo CollectVarUsage(const Expr& expr);
/*!
* \brief Get the used variables in an expression.
*
* This function collects all variables that are referenced within the given expression.
*
* \param expr The expression to analyze
*
* \return A set of variable nodes that are used in the expression
*/
TVM_DLL std::set<const VarNode*> GetUsedVars(const Expr& expr);
/*!
* \brief Remove unused statements inside DataflowBlocks.
*
* \param expr The expression (typically a relax::Function) from which
* to remove unused statements.
*
* \return The updated function with no unused statements in DataflowBlock.
*/
TVM_DLL Expr RemoveAllUnused(Expr expr);
/*!
* \brief Annotate Op Pattern Kind for PrimFunc, which is used in relax FuseOps.
*
* \param func The PrimFunc to be analyzed.
* \return The Op Pattern Kind.
*
* \note This analysis applies on TIR function but is primarily used by relax passes.
* As a result we place it under the relax namespace.
*/
TVM_DLL OpPatternKind AnalyzeOpPatternKind(const tirx::PrimFunc& func);
/*!
* \brief Check if the given PrimFunc is essentially doing a reshape operation.
* The reshape operation also includes expand_dims, squeeze, flatten, etc.
* \details Here the allowed reshape pattern is: for example, assume the operation is
* `B[l_0, l_1, ..., l_b] = A[r_0, r_1, ..., r_a]`, we check if we can prove that the flattened
* index of l_0, ..., l_b under buffer B equals to the flattened index of r_0, ..., r_a under
* buffer A.
* \param func The function to be examined.
* \return A boolean indicating if the given PrimFunc is doing a reshape.
* \note According to the description above, the returned result can only be false-negative and
* cannot be false-positive, since whenever we cannot prove the equality, we return false. This
* property guarantees the safety of this function.
*/
TVM_DLL bool HasReshapePattern(const tirx::PrimFunc& func);
/*!
* \brief Check if the given expression (likely a function body) contains any impure calls.
* \param expr The expression to be examined. If expr is a function, we check the body.
* \param own_name (Optional.) If we are checking a recursive function body,
* the caller can pass the function's name so recursive calls
* can be ignored in the check (must be a Var or GlobalVar).
* \return The impure expression, if one exists within the given
* expression. Otherwise, std::nullopt.
* \note Relies on Type annotations, so ensure that the module has been normalized first.
* Also, an impure call in a *nested* function does *not* mean that the outer expression contains
* an impure call--it only does if the nested function is *later called*.
*/
TVM_DLL ffi::Optional<Expr> FindImpureCall(
const Expr& expr, const ffi::Optional<Expr>& own_name = ffi::Optional<Expr>(std::nullopt));
/*!
* \brief Check if the given expression (likely a function body) contains any impure calls.
* \param expr The expression to be examined. If expr is a function, we check the body.
* \param own_name (Optional.) If we are checking a recursive function body,
* the caller can pass the function's name so recursive calls
* can be ignored in the check (must be a Var or GlobalVar).
* \return A boolean indicating if the expression contains any impure calls.
* \note Relies on Type annotations, so ensure that the module has been normalized first.
* Also, an impure call in a *nested* function does *not* mean that the outer expression contains
* an impure call--it only does if the nested function is *later called*.
*/
TVM_DLL bool ContainsImpureCall(
const Expr& expr, const ffi::Optional<Expr>& own_name = ffi::Optional<Expr>(std::nullopt));
/*!
* \brief Check if an IRModule or Function is well-formed.
*
* Throws an ffi::Error on the first well-formedness violation. The error is
* seeded with the offending node so a pass runner can resolve a precise access
* path. Use \ref CheckWellFormed for a boolean answer.
*
* \param obj The IRModule or relax::Function to check.
* \param check_ty If true, verify that every Expr has ty populated.
* \note By default the type information is always checked. It is only in test cases
* where `check_ty` might be false, so that other well-formed requirements
* will be well tested and will not be blocked by not having type information.
*/
TVM_DLL void WellFormed(ffi::Variant<IRModule, Function> obj, bool check_ty = true);
/*!
* \brief Return whether an IRModule or Function is well-formed.
*
* Wraps \ref WellFormed, returning false instead of throwing on the first
* violation.
*
* \param obj The IRModule or relax::Function to check.
* \param check_ty If true, verify that every Expr has ty populated.
* \return true if the object is well-formed, false otherwise.
*/
TVM_DLL bool CheckWellFormed(ffi::Variant<IRModule, Function> obj, bool check_ty = true);
/*!
* \brief Using the layout transforms on the outputs, suggest layout transformation on the blocks
* and buffers for the PrimFunc.
*
* \param fn The PrimFunc to be analyzed.
* \param write_buffer_transformations Array of IndexMap transformations on PrimFunc outputs.
* \return Suggested transforms per block in `fn`. For each block the returned value is a map
* from the object (block or buffer) to it's index map transformation.
*/
TVM_DLL ffi::Map<tirx::SBlock, ffi::Map<ffi::ObjectRef, tirx::IndexMap>> SuggestLayoutTransforms(
const Function& fn, ffi::Array<tirx::IndexMap> write_buffer_transformations);
/* \brief Collect variables whose value can be computed at compile-time
*
* If a function has the `kNumInput` attribute, then the first
* `kNumInput` parameters are provided at run-time, while all
* remaining parameters may be known at compile-time. This utility
* collects all variable bindings that only depend, directly or
* indirectly, on the parameters known at compile-time.
*
* \param func The relax::Function to analyze
*
* \return The set of variables that can be computed at compile-time,
* in order of their occurrence within the function.
*/
TVM_DLL ffi::Array<Var> ComputableAtCompileTime(const Function& func);
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ANALYSIS_H_
+91
View File
@@ -0,0 +1,91 @@
/*
* 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.
*/
/*!
* \file tvm/relax/attrs/ccl.h
* \brief Attributes for ccl operators.
*/
#ifndef TVM_RELAX_ATTRS_CCL_H_
#define TVM_RELAX_ATTRS_CCL_H_
#include <tvm/ffi/reflection/registry.h>
#include <tvm/relax/expr.h>
namespace tvm {
namespace relax {
/*! \brief Attributes used in allreduce operators */
struct AllReduceAttrs : public tvm::AttrsNode {
ffi::String op_type;
bool in_group;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<AllReduceAttrs>()
.def_ro("op_type", &AllReduceAttrs::op_type,
"The type of reduction operation to be applied to the input data. Now only sum is "
"supported.")
.def_ro("in_group", &AllReduceAttrs::in_group,
"Whether the reduction operation performs in group or globally or in group as "
"default.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.AllReduceAttrs", AllReduceAttrs, AttrsNode);
}; // struct AllReduceAttrs
/*! \brief Attributes used in allgather operators */
struct AllGatherAttrs : public tvm::AttrsNode {
int num_workers;
bool in_group;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<AllGatherAttrs>()
.def_ro("num_workers", &AllGatherAttrs::num_workers,
"The number of workers, also the number of parts the given buffer should be "
"chunked into.")
.def_ro("in_group", &AllGatherAttrs::in_group,
"Whether the allgather operation performs in group or globally or in group as "
"default.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.AllGatherAttrs", AllGatherAttrs, AttrsNode);
}; // struct AllGatherAttrs
/*! \brief Attributes used in scatter operators */
struct ScatterCollectiveAttrs : public tvm::AttrsNode {
int num_workers;
int axis;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ScatterCollectiveAttrs>()
.def_ro("num_workers", &ScatterCollectiveAttrs::num_workers,
"The number of workers, also the number of parts the given buffer should be "
"chunked into.")
.def_ro("axis", &ScatterCollectiveAttrs::axis,
"The axis of the tensor to be scattered. The tensor will be chunked along "
"this axis.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.ScatterCollectiveAttrs", ScatterCollectiveAttrs,
AttrsNode);
}; // struct ScatterCollectiveAttrs
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ATTRS_CCL_H_
+60
View File
@@ -0,0 +1,60 @@
/*
* 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.
*/
/*!
* \file tvm/relax/attrs/create.h
* \brief Attributes for tensor creation operators.
*/
#ifndef TVM_RELAX_ATTRS_CREATE_H_
#define TVM_RELAX_ATTRS_CREATE_H_
#include <tvm/relax/expr.h>
namespace tvm {
namespace relax {
/*! \brief Attributes used in full/full_like, ones/ones_like, and zeros/zeros_like operators */
struct InitAttrs : public AttrsNode {
ffi::Optional<DLDataType> dtype;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<InitAttrs>().def_ro("dtype", &InitAttrs::dtype,
"The data type of the created tensor.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.InitAttrs", InitAttrs, AttrsNode);
}; // struct InitAttrs
/*! \brief Attributes used in tril and triu operator */
struct TriluAttrs : public AttrsNode {
int k;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TriluAttrs>().def_ro(
"k", &TriluAttrs::k,
"The number of diagonals above or below the main diagonal to exclude or include.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.TriluAttrs", TriluAttrs, AttrsNode);
}; // struct TriluAttrs
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ATTRS_CREATE_H_
+57
View File
@@ -0,0 +1,57 @@
/*
* 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.
*/
/*!
* \file tvm/relax/attrs/datatype.h
* \brief Attributes for datatype operators.
*/
#ifndef TVM_RELAX_ATTRS_DATATYPE_H_
#define TVM_RELAX_ATTRS_DATATYPE_H_
#include <tvm/relax/expr.h>
namespace tvm {
namespace relax {
/*! \brief Attributes used in astype operator */
struct AstypeAttrs : public AttrsNode {
DLDataType dtype;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<AstypeAttrs>().def_ro("dtype", &AstypeAttrs::dtype, "Target data type");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.AstypeAttrs", AstypeAttrs, AttrsNode);
}; // struct AstypeAttrs.
/*! \brief Attributes used in wrap_param operator */
struct WrapParamAttrs : public AttrsNode {
DLDataType dtype;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<WrapParamAttrs>().def_ro("dtype", &WrapParamAttrs::dtype, "Target data type");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.WrapParamAttrs", WrapParamAttrs, AttrsNode);
}; // struct WrapParamAttrs.
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ATTRS_DATATYPE_H_
+53
View File
@@ -0,0 +1,53 @@
/*
* 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.
*/
/*!
* \file tvm/relax/attrs/distributed.h
* \brief Attributes for redistribute and annotate_sharding operators.
*/
#ifndef TVM_RELAX_ATTRS_DISTRIBUTED_H_
#define TVM_RELAX_ATTRS_DISTRIBUTED_H_
#include <tvm/relax/distributed/global_info.h>
#include <tvm/relax/distributed/type.h>
#include <tvm/relax/expr.h>
namespace tvm {
namespace relax {
/*! \brief Attributes for redistribute and annotate_sharding operator */
struct DistributionAttrs : public AttrsNode {
distributed::DeviceMesh device_mesh;
distributed::Placement placement;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<DistributionAttrs>()
.def_ro("device_mesh", &DistributionAttrs::device_mesh,
"The device mesh of a tensor's distribution plan")
.def_ro("placement", &DistributionAttrs::placement,
"The placement of a tensor's distribution plan");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.DistributionAttrs", DistributionAttrs, AttrsNode);
}; // struct DistributionAttrs
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ATTRS_DISTRIBUTED_H_
+168
View File
@@ -0,0 +1,168 @@
/*
* 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.
*/
/*!
* \file tvm/relax/attrs/image.h
* \brief Attributes for image operators.
*/
#ifndef TVM_RELAX_ATTRS_IMAGE_H_
#define TVM_RELAX_ATTRS_IMAGE_H_
#include <tvm/relax/expr.h>
namespace tvm {
namespace relax {
/*! \brief Attributes used in image resize2d operator */
struct Resize2DAttrs : public AttrsNode {
ffi::Array<FloatImm> roi;
ffi::String layout;
ffi::String method;
ffi::String coordinate_transformation_mode;
ffi::String rounding_method;
double cubic_alpha;
int cubic_exclude;
double extrapolation_value;
ffi::Optional<DLDataType> out_dtype;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<Resize2DAttrs>()
.def_ro("roi", &Resize2DAttrs::roi,
"Region of Interest for coordinate transformation mode 'tf_crop_and_resize'")
.def_ro("layout", &Resize2DAttrs::layout,
"Dimension ordering of input data. Can be 'NCHW', 'NHWC', etc."
"'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
"dimensions respectively. Resize is applied on the 'H' and"
"'W' dimensions.")
.def_ro("method", &Resize2DAttrs::method,
"Specify the mode to use for scaling."
"nearest_neighbor - Nearest Neighbor"
"linear - Bilinear Interpolation"
"cubic - Bicubic Interpolation")
.def_ro("coordinate_transformation_mode", &Resize2DAttrs::coordinate_transformation_mode,
"Describes how to transform the coordinate in the resized tensor"
"to the coordinate in the original tensor."
"Refer to the ONNX Resize operator specification for details"
"Available options are half_pixel, align_corners and asymmetric")
.def_ro("rounding_method", &Resize2DAttrs::rounding_method,
"indicates how to find the \"nearest\" pixel in nearest_neighbor method"
"Available options are round, floor, and ceil.")
.def_ro("cubic_alpha", &Resize2DAttrs::cubic_alpha,
"Spline Coefficient for Bicubic Interpolation")
.def_ro("cubic_exclude", &Resize2DAttrs::cubic_exclude,
"Flag to exclude exterior of the image during bicubic interpolation")
.def_ro("extrapolation_value", &Resize2DAttrs::extrapolation_value,
"Value to return when roi is outside of the image")
.def_ro(
"out_dtype", &Resize2DAttrs::out_dtype,
"The dtype of the output tensor. It it is not specified, the output will have the same "
"dtype as input if not specified.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.Resize2DAttrs", Resize2DAttrs, AttrsNode);
}; // struct Resize2dAttrs
/*! \brief Attributes used in image resize3d operator */
struct Resize3DAttrs : public AttrsNode {
ffi::Array<FloatImm> roi;
ffi::String layout;
ffi::String method;
ffi::String coordinate_transformation_mode;
ffi::String rounding_method;
double cubic_alpha;
int cubic_exclude;
double extrapolation_value;
ffi::Optional<DLDataType> out_dtype;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<Resize3DAttrs>()
.def_ro("roi", &Resize3DAttrs::roi,
"Region of Interest for coordinate transformation mode 'tf_crop_and_resize'")
.def_ro("layout", &Resize3DAttrs::layout,
"Dimension ordering of input data. Can be 'NCDHW', 'NDHWC', etc."
"'N', 'C', 'D', 'H', 'W' stands for batch, channel, depth, height, and width"
"dimensions respectively. Resize is applied on the 'D', 'H' and"
"'W' dimensions.")
.def_ro("method", &Resize3DAttrs::method,
"Specify the mode to use for scaling."
"nearest_neighbor - Nearest Neighbor"
"linear - Trilinear Interpolation"
"cubic - Tricubic Interpolation")
.def_ro("coordinate_transformation_mode", &Resize3DAttrs::coordinate_transformation_mode,
"Describes how to transform the coordinate in the resized tensor"
"to the coordinate in the original tensor."
"Refer to the ONNX Resize operator specification for details"
"Available options are half_pixel, align_corners and asymmetric")
.def_ro("rounding_method", &Resize3DAttrs::rounding_method,
"indicates how to find the \"nearest\" pixel in nearest_neighbor method"
"Available options are round, floor, and ceil.")
.def_ro("cubic_alpha", &Resize3DAttrs::cubic_alpha,
"Spline Coefficient for Tricubic Interpolation")
.def_ro("cubic_exclude", &Resize3DAttrs::cubic_exclude,
"Flag to exclude exterior of the image during tricubic interpolation")
.def_ro("extrapolation_value", &Resize3DAttrs::extrapolation_value,
"Value to return when roi is outside of the image")
.def_ro(
"out_dtype", &Resize3DAttrs::out_dtype,
"The dtype of the output tensor. It it is not specified, the output will have the same "
"dtype as input if not specified.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.Resize3DAttrs", Resize3DAttrs, AttrsNode);
}; // struct Resize3DAttrs
/*! \brief Attributes used in image grid_sample operator */
struct GridSampleAttrs : public AttrsNode {
ffi::String method;
ffi::String layout;
ffi::String padding_mode;
bool align_corners;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<GridSampleAttrs>()
.def_ro("method", &GridSampleAttrs::method,
"Interpolation method. Can be 'nearest', 'bilinear', or 'bicubic'.")
.def_ro("layout", &GridSampleAttrs::layout,
"Dimension ordering of input data. Can be 'NCHW', 'NHWC', etc.")
.def_ro("padding_mode", &GridSampleAttrs::padding_mode,
"Padding mode for outside grid values. Can be 'zeros', 'border', or 'reflection'.")
.def_ro("align_corners", &GridSampleAttrs::align_corners,
"If True, the corner pixels of the input and output tensors are aligned.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.GridSampleAttrs", GridSampleAttrs, AttrsNode);
}; // struct GridSampleAttrs
/*! \brief Attributes used in image affine_grid operator */
struct AffineGridAttrs : public AttrsNode {
bool align_corners;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<AffineGridAttrs>().def_ro(
"align_corners", &AffineGridAttrs::align_corners,
"If True, normalized grid coordinates map to corner pixels; otherwise to pixel centers.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.AffineGridAttrs", AffineGridAttrs, AttrsNode);
}; // struct AffineGridAttrs
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ATTRS_IMAGE_H_
+65
View File
@@ -0,0 +1,65 @@
/*
* 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.
*/
/*!
* \file tvm/relax/attrs/index.h
* \brief Attributes for indexing operators.
*/
#ifndef TVM_RELAX_ATTRS_INDEX_H_
#define TVM_RELAX_ATTRS_INDEX_H_
#include <tvm/relax/expr.h>
namespace tvm {
namespace relax {
/*! \brief Attributes used in take operator */
struct TakeAttrs : public AttrsNode {
ffi::Optional<int64_t> axis;
ffi::String mode;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TakeAttrs>()
.def_ro("axis", &TakeAttrs::axis, "The axis over which to select values.")
.def_ro("mode", &TakeAttrs::mode, "The mode for handling out-of-bounds indices.",
refl::DefaultValue("fast"));
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.TakeAttrs", TakeAttrs, AttrsNode);
}; // struct TakeAttrs
/*! \brief Attributes used in strided_slice operator */
struct StridedSliceAttrs : public AttrsNode {
bool assume_inbound;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<StridedSliceAttrs>().def_ro(
"assume_inbound", &StridedSliceAttrs::assume_inbound,
"Whether to assume the indices are in bound. If it is set to false, "
"out of bound indices will be clipped to the bound.",
refl::DefaultValue(true));
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.StridedSliceAttrs", StridedSliceAttrs, AttrsNode);
}; // struct StridedSliceAttrs
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ATTRS_INDEX_H_
+59
View File
@@ -0,0 +1,59 @@
/*
* 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.
*/
/*!
* \file tvm/relax/attrs/linear_algebra.h
* \brief Attributes for linear algebra operators.
*/
#ifndef TVM_RELAX_ATTRS_LINEAR_ALGEBRA_H_
#define TVM_RELAX_ATTRS_LINEAR_ALGEBRA_H_
#include <tvm/relax/expr.h>
namespace tvm {
namespace relax {
/*! \brief Attributes for matmul operator */
struct MatmulAttrs : public AttrsNode {
ffi::Optional<DLDataType> out_dtype;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<MatmulAttrs>().def_ro("out_dtype", &MatmulAttrs::out_dtype,
"The data type of the output tensor");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.MatmulAttrs", MatmulAttrs, AttrsNode);
}; // struct MatmulAttrs
/*! \brief Attributes used in einsum operator */
struct EinsumAttrs : public AttrsNode {
ffi::String subscripts;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<EinsumAttrs>().def_ro("subscripts", &EinsumAttrs::subscripts,
"The einsum expression string");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.EinsumAttrs", EinsumAttrs, AttrsNode);
}; // struct EinsumAttrs
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ATTRS_LINEAR_ALGEBRA_H_
+333
View File
@@ -0,0 +1,333 @@
/*
* 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.
*/
/*!
* \file tvm/relax/attrs/manipulate.h
* \brief Attributes for tensor manipulation operators.
*/
#ifndef TVM_RELAX_ATTRS_MANIPULATE_H_
#define TVM_RELAX_ATTRS_MANIPULATE_H_
#include <tvm/relax/expr.h>
#include <tvm/tirx/index_map.h>
namespace tvm {
namespace relax {
/*! \brief Attributes used in concat operators */
struct ConcatAttrs : public AttrsNode {
ffi::Optional<int64_t> axis;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ConcatAttrs>().def_ro("axis", &ConcatAttrs::axis,
"The axis at which the input arrays are concatenated."
"Should lie in range `[-ndim, ndim)`.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.ConcatAttrs", ConcatAttrs, AttrsNode);
}; // struct ConcatAttrs
/*! \brief Attributes used in expand_dims operators */
struct ExpandDimsAttrs : public AttrsNode {
ffi::Array<int64_t> axis;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ExpandDimsAttrs>().def_ro(
"axis", &ExpandDimsAttrs::axis,
"The axes at which the input array are expanded. "
"All values are required to lie in range `[-data.ndim - 1, data.ndim]`, "
"with the convention of negative indexing.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.ExpandDimsAttrs", ExpandDimsAttrs, AttrsNode);
}; // struct ExpandDimsAttrs
/*! \brief Attributes used in layout_transform operator */
struct LayoutTransformAttrs : public AttrsNode {
tirx::IndexMap index_map;
// pad_value is chosen to be of PrimExpr type, as it represents constant TIR POD expression. This
// needs to be revisited in case PrimExpr is evolved to represent symbolic expression in future.
ffi::Optional<PrimExpr> pad_value;
/*!
* axis_separators between input axes when generating flattened output axes. For buffers
* representing flat 1-d memory (e.g. any buffer in RAM), this should be an empty array.
* For buffers representing non-flat memory, each entry in axis_separators should be the
* first input axis that is part of a new flattened axis.
*/
ffi::Optional<ffi::Array<IntImm>> axis_separators;
/*!
* axis_separators for input buffers.
* Needed to identify if the input buffer to layout_transform
* contains axis separator.
*/
ffi::Optional<ffi::Array<IntImm>> input_axis_separators;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<LayoutTransformAttrs>()
.def_ro("index_map", &LayoutTransformAttrs::index_map,
"The layout transformation to apply.")
.def_ro(
"pad_value", &LayoutTransformAttrs::pad_value,
"The specific value to be used to pad if the layout transform would result in implicit "
"padding. If not specified, the compiler is free to choose any value.")
.def_ro("axis_separators", &LayoutTransformAttrs::axis_separators,
"The separators between input axes when generating flat output axes")
.def_ro("input_axis_separators", &LayoutTransformAttrs::input_axis_separators,
"The separators between axes to regenerate output");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.LayoutTransformAttrs", LayoutTransformAttrs,
AttrsNode);
}; // struct LayoutTransformAttrs
/*! \brief Attributes used in permute_dims operator */
struct PermuteDimsAttrs : public AttrsNode {
ffi::Optional<ffi::Array<int64_t>> axes;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PermuteDimsAttrs>().def_ro(
"axes", &PermuteDimsAttrs::axes, "The target axes order, reverse order if not specified.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.PermuteDimsAttrs", PermuteDimsAttrs, AttrsNode);
}; // struct PermuteDimsAttrs
/*! \brief Attributes used in split operator */
struct SplitAttrs : public AttrsNode {
ffi::ObjectRef indices_or_sections;
int axis;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SplitAttrs>()
.def_ro("indices_or_sections", &SplitAttrs::indices_or_sections,
"The input array of indices or the number of split sections.")
.def_ro("axis", &SplitAttrs::axis, "The axis to be splitted");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.SplitAttrs", SplitAttrs, AttrsNode);
}; // struct SplitAttrs
/*! \brief Attributes used in squeeze operators */
struct SqueezeAttrs : public AttrsNode {
ffi::Optional<ffi::Array<int64_t>> axis;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SqueezeAttrs>().def_ro("axis", &SqueezeAttrs::axis,
"The axis to squeeze in the input tensor."
"If `axis = None`, all axis of dimension 1 get squeezed;"
"Else, the dimension in axes get squeezed."
"It is an error if an axis does not has dimension 1.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.SqueezeAttrs", SqueezeAttrs, AttrsNode);
}; // struct SqueezeAttrs
/*! \brief Attributes used in stack operators */
struct StackAttrs : public AttrsNode {
ffi::Optional<int64_t> axis;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<StackAttrs>().def_ro(
"axis", &StackAttrs::axis,
"The axis along which to stack the input tensors. "
"The axis will be inserted at this position in the output, "
"so it must be in range [-ndim-1, ndim] where ndim is the "
"number of dimensions of the input tensors.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.StackAttrs", StackAttrs, AttrsNode);
}; // struct StackAttrs
/*! \brief Attributes used in repeat operators */
struct RepeatAttrs : public AttrsNode {
int repeats;
ffi::Optional<int64_t> axis;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<RepeatAttrs>()
.def_ro("repeats", &RepeatAttrs::repeats, "The number of repetitions.")
.def_ro("axis", &RepeatAttrs::axis,
"The axis along which to repeat values. The negative numbers are interpreted "
"counting from the backward. By default, use the flattened input array, and "
"return a flat output array.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.RepeatAttrs", RepeatAttrs, AttrsNode);
}; // struct RepeatAttrs
/*! \brief Attributes used in tile operators */
struct TileAttrs : public AttrsNode {
ffi::Array<int64_t> repeats;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TileAttrs>().def_ro("repeats", &TileAttrs::repeats,
"The number of repetitions of data along each axis.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.TileAttrs", TileAttrs, AttrsNode);
}; // struct TileAttrs
/*! \brief Attributes used in flip operators */
struct FlipAttrs : public AttrsNode {
int64_t axis;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<FlipAttrs>().def_ro("axis", &FlipAttrs::axis,
"The axis along which to flip over.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.FlipAttrs", FlipAttrs, AttrsNode);
}; // struct FlipAttrs
/*! \brief Attributes used in reverse_sequence operators */
struct ReverseSequenceAttrs : public AttrsNode {
int64_t seq_axis;
int64_t batch_axis;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ReverseSequenceAttrs>()
.def_ro("seq_axis", &ReverseSequenceAttrs::seq_axis,
"The axis along which to reverse variable length slices.")
.def_ro("batch_axis", &ReverseSequenceAttrs::batch_axis,
"The axis that indexes the batch.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.ReverseSequenceAttrs", ReverseSequenceAttrs,
AttrsNode);
}; // struct ReverseSequenceAttrs
/*! \brief Attributes used in gather_elements operators */
struct GatherElementsAttrs : public AttrsNode {
int64_t axis;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<GatherElementsAttrs>().def_ro("axis", &GatherElementsAttrs::axis,
"The axis along which to index.",
refl::DefaultValue(0));
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.GatherElementsAttrs", GatherElementsAttrs,
AttrsNode);
}; // struct GatherElementsAttrs
/*! \brief Attributes used in gather_nd operators */
struct GatherNDAttrs : public AttrsNode {
int64_t batch_dims;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<GatherNDAttrs>().def_ro("batch_dims", &GatherNDAttrs::batch_dims,
"The number of batch dims.", refl::DefaultValue(0));
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.GatherNDAttrs", GatherNDAttrs, AttrsNode);
}; // struct GatherNDAttrs
/*! \brief Attributes used in index_put operator */
struct IndexPutAttrs : public AttrsNode {
bool accumulate;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<IndexPutAttrs>().def_ro(
"accumulate", &IndexPutAttrs::accumulate,
"Whether to accumulate (add) values rather than replace. "
"If true, performs tensor[indices] += values, "
"otherwise performs tensor[indices] = values.",
refl::DefaultValue(false));
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.IndexPutAttrs", IndexPutAttrs, AttrsNode);
}; // struct IndexPutAttrs
/*! \brief Attribute used in meshgrid operator */
struct MeshgridAttrs : public AttrsNode {
ffi::Optional<ffi::String> indexing;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<MeshgridAttrs>().def_ro("indexing", &MeshgridAttrs::indexing,
"Specifies how the grid dimensions are ordered.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.MeshgridAttrs", MeshgridAttrs, AttrsNode);
};
/*! \brief Attributes used in scatter_elements operators */
struct ScatterElementsAttrs : public AttrsNode {
int64_t axis;
ffi::String reduction;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ScatterElementsAttrs>()
.def_ro("axis", &ScatterElementsAttrs::axis, "The axis over which to select values.",
refl::DefaultValue(0))
.def_ro("reduction", &ScatterElementsAttrs::reduction,
"Reduction mode of the scatter elements, "
"either \"update\", \"add\", \"mul\", \"mean\", \"min\" or \"max\".",
refl::DefaultValue("update"));
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.ScatterElementsAttrs", ScatterElementsAttrs,
AttrsNode);
}; // struct ScatterElementsAttrs
/*! \brief Attributes used in scatter_nd operators */
struct ScatterNDAttrs : public AttrsNode {
ffi::String reduction;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ScatterNDAttrs>().def_ro(
"reduction", &ScatterNDAttrs::reduction,
"Accumulation mode of the ScatterND, "
"either \"update\", \"add\", \"mul\", \"min\" or \"max\".",
refl::DefaultValue("update"));
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.ScatterNDAttrs", ScatterNDAttrs, AttrsNode);
}; // struct ScatterNDAttrs
/*! \brief Attributes used in slice_scatter operator */
struct SliceScatterAttrs : public AttrsNode {
int axis;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SliceScatterAttrs>().def_ro("axis", &SliceScatterAttrs::axis,
"the dimension to insert the slice into ",
refl::DefaultValue(0));
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.SliceScatterAttrs", SliceScatterAttrs, AttrsNode);
}; // struct SliceScatterAttrs
/*! \brief Attributes used in one_hot operator */
struct OneHotAttrs : public AttrsNode {
int depth;
int axis;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<OneHotAttrs>()
.def_ro("depth", &OneHotAttrs::depth, "Depth of the one hot dimension.")
.def_ro("axis", &OneHotAttrs::axis, "Axis to fill.", refl::DefaultValue(-1));
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.OneHotAttrs", OneHotAttrs, AttrsNode);
}; // struct OneHotAttrs
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ATTRS_MANIPULATE_H_
+785
View File
@@ -0,0 +1,785 @@
/*
* 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.
*/
/*!
* \file tvm/relax/attrs/nn.h
* \brief Attributes for neural network operators.
*/
#ifndef TVM_RELAX_ATTRS_NN_H_
#define TVM_RELAX_ATTRS_NN_H_
#include <tvm/relax/expr.h>
namespace tvm {
namespace relax {
/*! \brief Attributes used in Conv1d operator */
struct Conv1DAttrs : public AttrsNode {
ffi::Array<int64_t> strides;
ffi::Array<int64_t> padding;
ffi::Array<int64_t> dilation;
int groups;
ffi::String data_layout;
ffi::String kernel_layout;
ffi::String out_layout;
ffi::Optional<DLDataType> out_dtype;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<Conv1DAttrs>()
.def_ro("strides", &Conv1DAttrs::strides, "Specifies the strides of the convolution.")
.def_ro("padding", &Conv1DAttrs::padding,
"If padding is non-zero, then the input is implicitly zero-padded"
"Padding support both symmetric and asymmetric as"
"one int : same padding used on both sides"
"two int : padding width in the order of (left, right)")
.def_ro("dilation", &Conv1DAttrs::dilation,
"Specifies the dilation rate to use for dilated convolution.")
.def_ro("groups", &Conv1DAttrs::groups,
"Number of groups to split the input into for grouped convolution. The number of "
"input and "
"output channels should be divisible by the number of groups.")
.def_ro("data_layout", &Conv1DAttrs::data_layout,
"Dimension ordering of input data. Can be 'NCW', 'NWC', etc."
"'N', 'C', 'W' stands for batch, channel, width"
"dimensions respectively. Convolution is applied on the 'W' dimensions.")
.def_ro("kernel_layout", &Conv1DAttrs::kernel_layout,
"Dimension ordering of weight. Can be 'OIW', 'IOW', etc."
"'O', 'I', 'W' stands for num_filter, input_channel, and width"
"dimensions respectively.")
.def_ro("out_layout", &Conv1DAttrs::out_layout,
"Dimension ordering of output. Can be 'NCW', 'NWC', etc."
"'N', 'C', 'W' stands for batch, channel, and width"
"dimensions respectively. Default to be same as input layout.")
.def_ro("out_dtype", &Conv1DAttrs::out_dtype,
"Output data type, set to explicit type under mixed precision setting");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.Conv1DAttrs", Conv1DAttrs, AttrsNode);
}; // struct Conv1dAttrs
/*! \brief Attributes used in Conv2d operator */
struct Conv2DAttrs : public AttrsNode {
ffi::Array<int64_t> strides;
ffi::Array<int64_t> padding;
ffi::Array<int64_t> dilation;
int groups;
ffi::String data_layout;
ffi::String kernel_layout;
ffi::String out_layout;
ffi::Optional<DLDataType> out_dtype;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<Conv2DAttrs>()
.def_ro("strides", &Conv2DAttrs::strides, "Specifies the strides of the convolution.")
.def_ro("padding", &Conv2DAttrs::padding,
"If padding is non-zero, then the input is implicitly zero-padded"
"Padding support both symmetric and asymmetric as"
"one int : same padding used on all sides"
"two int : bottom, right will use same padding as top, left"
"four int : padding width in the order of (top, left, bottom, right)")
.def_ro("dilation", &Conv2DAttrs::dilation,
"Specifies the dilation rate to use for dilated convolution.")
.def_ro("groups", &Conv2DAttrs::groups,
"Number of groups to split the input into for grouped convolution. The number of "
"input and "
"output channels should be divisible by the number of groups.")
.def_ro("data_layout", &Conv2DAttrs::data_layout,
"Dimension ordering of input data. Can be 'NCHW', 'NHWC', etc."
"'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
"dimensions respectively. Convolution is applied on the 'H' and"
"'W' dimensions.")
.def_ro("kernel_layout", &Conv2DAttrs::kernel_layout,
"Dimension ordering of weight. Can be 'OIHW', 'OIHW16o16i', etc."
"'O', 'I', 'H', 'W' stands for num_filter, input_channel, height, and width"
"dimensions respectively.")
.def_ro("out_layout", &Conv2DAttrs::out_layout,
"Dimension ordering of output. Can be 'NCHW', 'NHWC', etc."
"'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
"dimensions respectively. Default to be same as input layout.")
.def_ro("out_dtype", &Conv2DAttrs::out_dtype,
"Output data type, set to explicit type under mixed precision setting");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.Conv2DAttrs", Conv2DAttrs, AttrsNode);
}; // struct Conv2dAttrs
/*! \brief Attributes used in Conv3d operator */
struct Conv3DAttrs : public AttrsNode {
ffi::Array<int64_t> strides;
ffi::Array<int64_t> padding;
ffi::Array<int64_t> dilation;
int groups;
ffi::String data_layout;
ffi::String kernel_layout;
ffi::String out_layout;
ffi::Optional<DLDataType> out_dtype;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<Conv3DAttrs>()
.def_ro("strides", &Conv3DAttrs::strides, "Specifies the strides of the convolution.")
.def_ro(
"padding", &Conv3DAttrs::padding,
"If padding is non-zero, then the input is implicitly zero-padded"
"Padding support both symmetric and asymmetric as"
"one int : same padding used on all sides"
"two int : bottom, right will use same padding as top, left"
"four int : padding width in the order of (forward, back, top, left, bottom, right)")
.def_ro("dilation", &Conv3DAttrs::dilation,
"Specifies the dilation rate to use for dilated convolution.")
.def_ro("groups", &Conv3DAttrs::groups,
"Number of groups to split the input into for grouped convolution. The number of "
"input and "
"output channels should be divisible by the number of groups.")
.def_ro("data_layout", &Conv3DAttrs::data_layout,
"Dimension ordering of input data. Can be 'NCDHW', 'NDHWC', etc."
"'N', 'C', 'D', 'H', 'W' stands for batch, channel, depth, height, and width"
"dimensions respectively. Convolution is applied on the 'D', 'H', and"
"'W' dimensions.")
.def_ro(
"kernel_layout", &Conv3DAttrs::kernel_layout,
"Dimension ordering of weight. Can be 'OIDHW', 'OIDHW16o16i', etc."
"'O', 'I', 'D', 'H', 'W' stands for num_filter, input_channel, depth, height, and width"
"dimensions respectively.")
.def_ro("out_layout", &Conv3DAttrs::out_layout,
"Dimension ordering of output. Can be 'NCDHW', 'NDHWC', etc."
"'N', 'C', 'D', 'H', 'W' stands for batch, channel, depth, height, and width"
"dimensions respectively. Default to be same as input layout.")
.def_ro("out_dtype", &Conv3DAttrs::out_dtype,
"Output data type, set to explicit type under mixed precision setting");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.Conv3DAttrs", Conv3DAttrs, AttrsNode);
}; // struct Conv3dAttrs
/*! \brief Attributes used in Conv1DTranspose operator */
struct Conv1DTransposeAttrs : public AttrsNode {
ffi::Array<int64_t> strides;
ffi::Array<int64_t> padding;
ffi::Array<int64_t> output_padding;
ffi::Array<int64_t> dilation;
int groups;
ffi::String data_layout;
ffi::String kernel_layout;
ffi::String out_layout;
ffi::Optional<DLDataType> out_dtype;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<Conv1DTransposeAttrs>()
.def_ro("strides", &Conv1DTransposeAttrs::strides,
"Specifies the strides of the convolution.")
.def_ro("padding", &Conv1DTransposeAttrs::padding,
"If padding is non-zero, then the input is implicitly zero-padded"
"Padding support both symmetric and asymmetric as"
"one int : same padding used on both sides"
"two int : padding width in the order of (left, right)")
.def_ro("output_padding", &Conv1DTransposeAttrs::output_padding,
"Used to disambiguate the output shape.")
.def_ro("dilation", &Conv1DTransposeAttrs::dilation,
"Specifies the dilation rate to use for dilated convolution.")
.def_ro("groups", &Conv1DTransposeAttrs::groups,
"Number of groups to split the input into for grouped convolution. The number of "
"input and "
"output channels should be divisible by the number of groups.")
.def_ro("data_layout", &Conv1DTransposeAttrs::data_layout,
"Dimension ordering of input data. Can be 'NCW', 'NWC', etc."
"'N', 'C', 'W' stands for batch, channel, width"
"dimensions respectively. Convolution is applied on the 'W' dimensions.")
.def_ro("kernel_layout", &Conv1DTransposeAttrs::kernel_layout,
"Dimension ordering of weight. Can be 'OIW', 'IOW', etc."
"'O', 'I', 'W' stands for num_filter, input_channel, and width"
"dimensions respectively.")
.def_ro("out_layout", &Conv1DTransposeAttrs::out_layout,
"Dimension ordering of output. Can be 'NCW', 'NWC', etc."
"'N', 'C', 'W' stands for batch, channel, and width"
"dimensions respectively. Default to be same as input layout.")
.def_ro("out_dtype", &Conv1DTransposeAttrs::out_dtype,
"Output data type, set to explicit type under mixed precision setting");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.Conv1DTransposeAttrs", Conv1DTransposeAttrs,
AttrsNode);
}; // struct Conv1DTransposeAttrs
/*! \brief Attributes used in Conv2d operator */
struct Conv2DTransposeAttrs : public AttrsNode {
ffi::Array<int64_t> strides;
ffi::Array<int64_t> padding;
ffi::Array<int64_t> output_padding;
ffi::Array<int64_t> dilation;
int groups;
ffi::String data_layout;
ffi::String kernel_layout;
ffi::String out_layout;
ffi::Optional<DLDataType> out_dtype;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<Conv2DTransposeAttrs>()
.def_ro("strides", &Conv2DTransposeAttrs::strides,
"Specifies the strides of the convolution.")
.def_ro("padding", &Conv2DTransposeAttrs::padding,
"If padding is non-zero, then the input is implicitly zero-padded"
"Padding support both symmetric and asymmetric as"
"one int : same padding used on all sides"
"two int : bottom, right will use same padding as top, left"
"four int : padding width in the order of (top, left, bottom, right)")
.def_ro("output_padding", &Conv2DTransposeAttrs::output_padding,
"Used to disambiguate the output shape.")
.def_ro("dilation", &Conv2DTransposeAttrs::dilation,
"Specifies the dilation rate to use for dilated convolution.")
.def_ro("groups", &Conv2DTransposeAttrs::groups,
"Number of groups to split the input into for grouped convolution. The number of "
"input and "
"output channels should be divisible by the number of groups.")
.def_ro("data_layout", &Conv2DTransposeAttrs::data_layout,
"Dimension ordering of input data. Can be 'NCHW', 'NHWC', etc."
"'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
"dimensions respectively. Convolution is applied on the 'H' and"
"'W' dimensions.")
.def_ro("kernel_layout", &Conv2DTransposeAttrs::kernel_layout,
"Dimension ordering of weight. Can be 'OIHW', 'OIHW16o16i', etc."
"'O', 'I', 'H', 'W' stands for num_filter, input_channel, height, and width"
"dimensions respectively.")
.def_ro("out_layout", &Conv2DTransposeAttrs::out_layout,
"Dimension ordering of output. Can be 'NCHW', 'NHWC', etc."
"'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
"dimensions respectively. Default to be same as input layout.")
.def_ro("out_dtype", &Conv2DTransposeAttrs::out_dtype,
"Output data type, set to explicit type under mixed precision setting");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.Conv2DTransposeAttrs", Conv2DTransposeAttrs,
AttrsNode);
}; // struct Conv2DTransposeAttrs
/*! \brief Attributes used in Conv3dTranspose operator */
struct Conv3DTransposeAttrs : public AttrsNode {
ffi::Array<int64_t> strides;
ffi::Array<int64_t> padding;
ffi::Array<int64_t> output_padding;
ffi::Array<int64_t> dilation;
int groups;
ffi::String data_layout;
ffi::String kernel_layout;
ffi::String out_layout;
ffi::Optional<DLDataType> out_dtype;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<Conv3DTransposeAttrs>()
.def_ro("strides", &Conv3DTransposeAttrs::strides,
"Specifies the strides of the convolution.")
.def_ro("padding", &Conv3DTransposeAttrs::padding,
"If padding is non-zero, then the input is implicitly zero-padded"
"Padding support both symmetric and asymmetric as"
"one int : same padding used on all sides"
"three int : back/bottom/right will use same padding as front/top/left"
"six int : padding width in the order of (front, top, left, back, bottom, right)")
.def_ro("output_padding", &Conv3DTransposeAttrs::output_padding,
"Used to disambiguate the output shape.")
.def_ro("dilation", &Conv3DTransposeAttrs::dilation,
"Specifies the dilation rate to use for dilated convolution.")
.def_ro("groups", &Conv3DTransposeAttrs::groups,
"Number of groups to split the input into for grouped convolution. The number of "
"input and "
"output channels should be divisible by the number of groups.")
.def_ro("data_layout", &Conv3DTransposeAttrs::data_layout,
"Dimension ordering of input data. Can be 'NCDHW', 'NDHWC', etc."
"'N', 'C', 'D', 'H', 'W' stands for batch, channel, depth, height, and width"
"dimensions respectively. Convolution is applied on the 'D', 'H', and"
"'W' dimensions.")
.def_ro(
"kernel_layout", &Conv3DTransposeAttrs::kernel_layout,
"Dimension ordering of weight. Can be 'IODHW', etc."
"'I', 'O', 'D', 'H', 'W' stands for input_channel, output_channel, depth, height, and "
"width"
"dimensions respectively.")
.def_ro("out_layout", &Conv3DTransposeAttrs::out_layout,
"Dimension ordering of output. Can be 'NCDHW', 'NDHWC', etc."
"'N', 'C', 'D', 'H', 'W' stands for batch, channel, depth, height, and width"
"dimensions respectively. Default to be same as input layout.")
.def_ro("out_dtype", &Conv3DTransposeAttrs::out_dtype,
"Output data type, set to explicit type under mixed precision setting");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.Conv3DTransposeAttrs", Conv3DTransposeAttrs,
AttrsNode);
}; // struct Conv3DTransposeAttrs
/*! \brief Attributes used in max_pool1d and avg_pool1d operator */
struct Pool1DAttrs : public AttrsNode {
ffi::Array<int64_t> pool_size;
ffi::Array<int64_t> strides;
ffi::Array<int64_t> padding;
ffi::Array<int64_t> dilation;
bool ceil_mode;
bool count_include_pad;
ffi::String layout;
ffi::String out_layout;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<Pool1DAttrs>()
.def_ro("pool_size", &Pool1DAttrs::pool_size, "Size of the pooling windows.")
.def_ro("strides", &Pool1DAttrs::strides, "Specifies the strides of the convolution.")
.def_ro("dilation", &Pool1DAttrs::dilation, "Specifies the dilation of the convolution.")
.def_ro("padding", &Pool1DAttrs::padding,
"If padding is non-zero, then the input is implicitly zero-padded"
"Padding support both symmetric and asymmetric as"
"one int : same padding used on all sides"
"two int : padding width in the order of (left, right)")
.def_ro(
"ceil_mode", &Pool1DAttrs::ceil_mode,
"A boolean indicating if use ceil or floor to compute the output shape. By using ceil, "
"every element in the input tensor will be covered by a sliding window.")
.def_ro("count_include_pad", &Pool1DAttrs::count_include_pad,
"When true, will include padding to compute the average")
.def_ro("layout", &Pool1DAttrs::layout,
"Dimension ordering of input data. Can be 'NCW', 'NWC', etc."
"'N', 'C', 'W' stands for batch, channel, and width"
"dimensions respectively. Pooling is applied on the 'W' dimensions.",
refl::DefaultValue("NCW"))
.def_ro("out_layout", &Pool1DAttrs::out_layout,
"Dimension ordering of output data. Can be 'NCW', 'NWC', etc."
"'N', 'C', 'W' stands for batch, channel, and width"
"dimensions respectively. Pooling is applied on the 'W' dimensions.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.Pool1DAttrs", Pool1DAttrs, AttrsNode);
}; // struct Pool1dAttrs
/*! \brief Attributes used in max_pool2d and avg_pool2d operator */
struct Pool2DAttrs : public AttrsNode {
ffi::Array<int64_t> pool_size;
ffi::Array<int64_t> strides;
ffi::Array<int64_t> padding;
ffi::Array<int64_t> dilation;
bool ceil_mode;
bool count_include_pad;
ffi::String layout;
ffi::String out_layout;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<Pool2DAttrs>()
.def_ro("pool_size", &Pool2DAttrs::pool_size, "Size of the pooling windows.")
.def_ro("strides", &Pool2DAttrs::strides, "Specifies the strides of the convolution.")
.def_ro("dilation", &Pool2DAttrs::dilation, "Specifies the dilation of the convolution.")
.def_ro("padding", &Pool2DAttrs::padding,
"If padding is non-zero, then the input is implicitly zero-padded"
"Padding support both symmetric and asymmetric as"
"one int : same padding used on all sides"
"two int : bottom, right will use same padding as top, left"
"four int : padding width in the order of (top, left, bottom, right)")
.def_ro(
"ceil_mode", &Pool2DAttrs::ceil_mode,
"A boolean indicating if use ceil or floor to compute the output shape. By using ceil, "
"every element in the input tensor will be covered by a sliding window.")
.def_ro("count_include_pad", &Pool2DAttrs::count_include_pad,
"When true, will include padding to compute the average")
.def_ro("layout", &Pool2DAttrs::layout,
"Dimension ordering of input data. Can be 'NCHW', 'NHWC', etc."
"'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
"dimensions respectively. Pooling is applied on the 'H' and"
"'W' dimensions.")
.def_ro("out_layout", &Pool2DAttrs::out_layout,
"Dimension ordering of output data. Can be 'NCHW', 'NHWC', etc."
"'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
"dimensions respectively. Pooling is applied on the 'H' and"
"'W' dimensions.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.Pool2DAttrs", Pool2DAttrs, AttrsNode);
}; // struct Pool2dAttrs
/*! \brief Attributes used in max_pool3d and avg_pool3d operator */
struct Pool3DAttrs : public AttrsNode {
ffi::Array<int64_t> pool_size;
ffi::Array<int64_t> strides;
ffi::Array<int64_t> padding;
ffi::Array<int64_t> dilation;
bool ceil_mode;
bool count_include_pad;
ffi::String layout;
ffi::String out_layout;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<Pool3DAttrs>()
.def_ro("pool_size", &Pool3DAttrs::pool_size, "Size of the pooling windows.")
.def_ro("strides", &Pool3DAttrs::strides, "Specifies the strides of the convolution.")
.def_ro("dilation", &Pool3DAttrs::dilation, "Specifies the dilation of the convolution.")
.def_ro("padding", &Pool3DAttrs::padding,
"If padding is non-zero, then the input is implicitly zero-padded"
"Padding support both symmetric and asymmetric as"
"one int : same padding used on all sides"
"three int : back, bottom, right will use same padding as front, top, left"
"four int : padding width in the order of (front, top, left, back, bottom, right)")
.def_ro(
"ceil_mode", &Pool3DAttrs::ceil_mode,
"A boolean indicating if use ceil or floor to compute the output shape. By using ceil, "
"every element in the input tensor will be covered by a sliding window.")
.def_ro("count_include_pad", &Pool3DAttrs::count_include_pad,
"When true, will include padding to compute the average")
.def_ro("layout", &Pool3DAttrs::layout,
"Dimension ordering of input data. Can be 'NCDHW', 'NDHWC', etc."
"'N', 'C', 'D', 'H', 'W' stands for batch, channel, depth, height, and width"
"dimensions respectively. Pooling is applied on the 'D', 'H' and"
"'W' dimensions.")
.def_ro("out_layout", &Pool3DAttrs::out_layout,
"Dimension ordering of output data. Can be 'NCDHW', 'NDHWC', etc."
"'N', 'C', 'D', 'H', 'W' stands for batch, channel, depth, height, and width"
"dimensions respectively. Pooling is applied on the 'D', 'H' and"
"'W' dimensions.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.Pool3DAttrs", Pool3DAttrs, AttrsNode);
}; // struct Pool3dAttrs
/*! \brief Attributes for 1d adaptive pool operator */
struct AdaptivePool1DAttrs : public AttrsNode {
ffi::Optional<ffi::Array<int64_t>> output_size;
ffi::String layout;
ffi::String out_layout;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<AdaptivePool1DAttrs>()
.def_ro("output_size", &AdaptivePool1DAttrs::output_size, "Output width.")
.def_ro("layout", &AdaptivePool1DAttrs::layout,
"Dimension ordering of input data. Can be 'NCW', 'NWC', etc."
"'N', 'C', 'W' stands for batch, channel and width"
"dimensions respectively. Pooling is applied on the"
"'W' dimensions.")
.def_ro("out_layout", &AdaptivePool1DAttrs::out_layout,
"Dimension ordering of output data. Can be 'NCW', 'NWC', etc."
"'N', 'C', 'W' stands for batch, channel and width"
"dimensions respectively. Pooling is applied on the"
"'W' dimensions.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.AdaptivePool1DAttrs", AdaptivePool1DAttrs,
AttrsNode);
}; // struct AdaptivePool1DAttrs
/*! \brief Attributes for 2d adaptive pool operator */
struct AdaptivePool2DAttrs : public AttrsNode {
ffi::Optional<ffi::Array<int64_t>> output_size;
ffi::String layout;
ffi::String out_layout;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<AdaptivePool2DAttrs>()
.def_ro("output_size", &AdaptivePool2DAttrs::output_size, "Output height and width.")
.def_ro("layout", &AdaptivePool2DAttrs::layout,
"Dimension ordering of input data. Can be 'NCHW', 'NHWC', etc."
"'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
"dimensions respectively. Pooling is applied on the 'H' and"
"'W' dimensions.")
.def_ro("out_layout", &AdaptivePool2DAttrs::out_layout,
"Dimension ordering of output data. Can be 'NCHW', 'NHWC', etc."
"'N', 'C', 'H', 'W' stands for batch, channel, height, and width"
"dimensions respectively. Pooling is applied on the 'H' and"
"'W' dimensions.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.AdaptivePool2DAttrs", AdaptivePool2DAttrs,
AttrsNode);
}; // struct AdaptivePool2DAttrs
/*! \brief Attributes for 3d adaptive pool operator */
struct AdaptivePool3DAttrs : public AttrsNode {
ffi::Optional<ffi::Array<int64_t>> output_size;
ffi::String layout;
ffi::String out_layout;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<AdaptivePool3DAttrs>()
.def_ro("output_size", &AdaptivePool3DAttrs::output_size, "Output depth, height and width.")
.def_ro("layout", &AdaptivePool3DAttrs::layout,
"Dimension ordering of input data. Can be 'NCDHW', 'NDHWC', etc."
"'N', 'C', 'D', 'H', 'W' stands for batch, channel, depth, height, and width"
"dimensions respectively. Pooling is applied on 'D', 'H' and"
"'W' dimensions.")
.def_ro("out_layout", &AdaptivePool3DAttrs::out_layout,
"Dimension ordering of output data. Can be 'NCDHW', 'NDHWC', etc."
"'N', 'C', 'D', 'H', 'W' stands for batch, channel, depth, height, and width"
"dimensions respectively. Pooling is applied on 'D', 'H' and"
"'W' dimensions.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.AdaptivePool3DAttrs", AdaptivePool3DAttrs,
AttrsNode);
}; // struct AdaptivePool3DAttrs
/*! \brief Attributes used in softmax operators */
struct SoftmaxAttrs : public AttrsNode {
int axis;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SoftmaxAttrs>().def_ro("axis", &SoftmaxAttrs::axis,
"The axis to sum over when computing softmax.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.SoftmaxAttrs", SoftmaxAttrs, AttrsNode);
};
/*! \brief Attributes used in softmax operators */
struct LeakyReluAttrs : public AttrsNode {
double alpha;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<LeakyReluAttrs>().def_ro("alpha", &LeakyReluAttrs::alpha,
"The slope of the negative part.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.LeakyReluAttrs", LeakyReluAttrs, AttrsNode);
};
/*! \brief Attributes used in softplus operators */
struct SoftplusAttrs : public AttrsNode {
double beta;
double threshold;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SoftplusAttrs>()
.def_ro("beta", &SoftplusAttrs::beta,
"Scaling factor controlling the sharpness of the Softplus transition.")
.def_ro("threshold", &SoftplusAttrs::threshold,
"Value determining when to use linear approximation for numerical stability.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.SoftplusAttrs", SoftplusAttrs, AttrsNode);
};
/*! \brief Attributes used in PReLU operator */
struct PReluAttrs : public AttrsNode {
int axis;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PReluAttrs>().def_ro("axis", &PReluAttrs::axis,
"The axis along which the alpha values are applied.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.PReluAttrs", PReluAttrs, AttrsNode);
};
/*! \brief Attributes used in batch_norm operator */
struct BatchNormAttrs : public AttrsNode {
int axis;
double epsilon;
bool center;
bool scale;
double momentum;
bool training;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<BatchNormAttrs>()
.def_ro("axis", &BatchNormAttrs::axis, "The axis along which the normalization is applied.")
.def_ro("epsilon", &BatchNormAttrs::epsilon,
"Small float added to variance to avoid dividing by zero")
.def_ro("center", &BatchNormAttrs::center,
"Indicating if the beta offset will be added to the normalized tensor.")
.def_ro("scale", &BatchNormAttrs::scale,
"Indicating if the gamma scale will be multiplied.")
.def_ro("momentum", &BatchNormAttrs::momentum,
"The value used for the moving_mean and moving_var update.")
.def_ro("training", &BatchNormAttrs::training,
"Whether we are training (i.e., not in eval mode).");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.BatchNormAttrs", BatchNormAttrs, AttrsNode);
}; // struct BatchNormAttrs
/*! \brief Attributes used in layer_norm operator */
struct LayerNormAttrs : public AttrsNode {
ffi::Array<int64_t> axes;
double epsilon;
bool center;
bool scale;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<LayerNormAttrs>()
.def_ro("axes", &LayerNormAttrs::axes,
"The axes that along which the normalization is applied.")
.def_ro("epsilon", &LayerNormAttrs::epsilon,
"Small float added to variance to avoid dividing by zero")
.def_ro("center", &LayerNormAttrs::center,
"Indicating if the beta offset will be added to the normalized tensor.")
.def_ro("scale", &LayerNormAttrs::scale,
"Indicating if the gamma scale will be multiplied.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.LayerNormAttrs", LayerNormAttrs, AttrsNode);
}; // struct LayerNormAttrs
/*! \brief Attributes used in group_norm operator */
struct GroupNormAttrs : public AttrsNode {
int num_groups;
int channel_axis;
ffi::Array<int64_t> axes;
double epsilon;
bool center;
bool scale;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<GroupNormAttrs>()
.def_ro("num_groups", &GroupNormAttrs::num_groups,
"The number of groups to separate the channels into.")
.def_ro("channel_axis", &GroupNormAttrs::channel_axis,
"The axis that represents the channel.")
.def_ro(
"axes", &GroupNormAttrs::axes,
"The axes that along which the normalization is applied (excluding the channel axis).")
.def_ro("epsilon", &GroupNormAttrs::epsilon,
"Small float added to variance to avoid dividing by zero")
.def_ro("center", &GroupNormAttrs::center,
"Indicating if the beta offset will be added to the normalized tensor.")
.def_ro("scale", &GroupNormAttrs::scale,
"Indicating if the gamma scale will be multiplied.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.GroupNormAttrs", GroupNormAttrs, AttrsNode);
}; // struct GroupNormAttrs
/*! \brief Attributes used in instance_norm operator */
struct InstanceNormAttrs : public AttrsNode {
int channel_axis;
ffi::Array<int64_t> axes;
double epsilon;
bool center;
bool scale;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<InstanceNormAttrs>()
.def_ro("channel_axis", &InstanceNormAttrs::channel_axis,
"The axis that represents the channel.")
.def_ro("axes", &InstanceNormAttrs::axes,
"The axes that along which the normalization is applied.")
.def_ro("epsilon", &InstanceNormAttrs::epsilon,
"Small float added to variance to avoid dividing by zero")
.def_ro("center", &InstanceNormAttrs::center,
"Indicating if the beta offset will be added to the normalized tensor.")
.def_ro("scale", &InstanceNormAttrs::scale,
"Indicating if the gamma scale will be multiplied.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.InstanceNormAttrs", InstanceNormAttrs, AttrsNode);
}; // struct InstanceNormAttrs
/*! \brief Attributes used in rms_norm operator */
struct RMSNormAttrs : public AttrsNode {
ffi::Array<int64_t> axes;
double epsilon;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<RMSNormAttrs>()
.def_ro("axes", &RMSNormAttrs::axes,
"The axes that along which the normalization is applied.")
.def_ro("epsilon", &RMSNormAttrs::epsilon,
"Small float added to variance to avoid dividing by zero");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.RMSNormAttrs", RMSNormAttrs, AttrsNode);
}; // struct RMSNormAttrs
/*! \brief Attributes used in nll_loss operator */
struct NLLLossAttrs : public AttrsNode {
ffi::String reduction;
int ignore_index;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<NLLLossAttrs>()
.def_ro("reduction", &NLLLossAttrs::reduction,
"The reduction method to apply to the output. Can be"
"'none', 'mean' or 'sum'.",
refl::DefaultValue("mean"))
.def_ro("ignore_index", &NLLLossAttrs::ignore_index, "The target value to ignore.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.NLLLossAttrs", NLLLossAttrs, AttrsNode);
}; // struct NLLLossAttrs
/*! \brief Attributes used in dropout operator */
struct DropoutAttrs : public AttrsNode {
double rate;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<DropoutAttrs>().def_ro(
"rate", &DropoutAttrs::rate,
"Fraction of the input that gets dropped out during training time");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.DropoutAttrs", DropoutAttrs, AttrsNode);
}; // struct DropoutAttrs
/*! \brief Attributes used in Attention operator */
struct AttentionAttrs : public AttrsNode {
ffi::Optional<FloatImm> scale;
ffi::Optional<ffi::String> causal_mask;
ffi::Optional<IntImm> window_size;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<AttentionAttrs>()
.def_ro(
"scale", &AttentionAttrs::scale,
"The custom scale applied before the softmax. The default value is 1 / sqrt(head_dim).")
.def_ro("causal_mask", &AttentionAttrs::causal_mask,
"The type of the causal mask, i.e. 'TopLeft' and 'BottomRight'.")
.def_ro("window_size", &AttentionAttrs::window_size,
"The size of the window for sliding-window attention.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.AttentionAttrs", AttentionAttrs, AttrsNode);
}; // struct AttentionAttrs
/*! \brief Attributes used for the padding operator */
struct PadAttrs : public AttrsNode {
ffi::Array<int64_t> pad_width;
double pad_value = 0.0;
tvm::ffi::String pad_mode;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PadAttrs>()
.def_ro("pad_width", &PadAttrs::pad_width,
"Number of values padded to the edges of each axis, "
"in the format of (before_1, after_1, ..., before_N, after_N)")
.def_ro("pad_value", &PadAttrs::pad_value, "The value to fill in padded area with",
refl::DefaultValue(0.0))
.def_ro("pad_mode", &PadAttrs::pad_mode,
"Padding type to use. \"constant\" pads with constant_value, "
"\"edge\" pads using the edge values of the input array, "
"\"reflect\" pads by reflecting values with respect to the edges.",
refl::DefaultValue("constant"));
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.PadAttrs", PadAttrs, AttrsNode);
};
/*! \brief Attributes used for the pixel shuffle operator */
struct PixelShuffleAttrs : public AttrsNode {
int upscale_factor;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PixelShuffleAttrs>().def_ro("upscale_factor",
&PixelShuffleAttrs::upscale_factor,
"Scale factor for spatial upsampling.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.PixelShuffleAttrs", PixelShuffleAttrs, AttrsNode);
};
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ATTRS_NN_H_
+123
View File
@@ -0,0 +1,123 @@
/*
* 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.
*/
/*!
* \file tvm/relax/attrs/op.h
* \brief Attributes for relax specific operators.
*/
#ifndef TVM_RELAX_ATTRS_OP_H_
#define TVM_RELAX_ATTRS_OP_H_
#include <tvm/ir/global_info.h>
#include <tvm/relax/expr.h>
namespace tvm {
namespace relax {
/*! \brief Attributes used in call_tir_with_grad */
struct CallTIRWithGradAttrs : public AttrsNode {
ffi::String te_grad_name;
ffi::Map<ffi::String, Any> te_grad_kwargs;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<CallTIRWithGradAttrs>()
.def_ro(
"te_grad_name", &CallTIRWithGradAttrs::te_grad_name,
"The name of the te gradient function associated with this call_tir_with_grad node.")
.def_ro("te_grad_kwargs", &CallTIRWithGradAttrs::te_grad_kwargs,
"The keyword arguments passed to the te gradient function.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.CallTIRWithGradAttrs", CallTIRWithGradAttrs,
AttrsNode);
}; // struct CallTIRAttrs
/*! \brief Attributes used in call_tir_inplace */
struct CallTIRInplaceAttrs : public AttrsNode {
/*!
* \brief Indices that describe which input corresponds to which output.
*
* If the `i`th member has the value `k` >= 0, then that means that input `k` should be used to
* store the `i`th output. If an element has the value -1, that means a new tensor should be
* allocated for that output.
*/
ffi::Array<int64_t> inplace_indices;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<CallTIRInplaceAttrs>().def_ro("inplace_indices",
&CallTIRInplaceAttrs::inplace_indices);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.CallTIRInplaceAttrs", CallTIRInplaceAttrs,
AttrsNode);
}; // struct CallTIRInplaceAttrs
/*! \brief Attributes used in call_inplace_packed */
struct CallInplacePackedAttrs : public AttrsNode {
/*!
* \brief Indices that describe which input corresponds to which output.
*
* If the `i`th member has the value `k` >= 0, then that means that input `k` should be used to
* store the `i`th output. If an element has the value -1, that means the output will be newly
* allocated.
*/
ffi::Array<int64_t> inplace_indices;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<CallInplacePackedAttrs>().def_ro("inplace_indices",
&CallInplacePackedAttrs::inplace_indices);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.CallInplacePackedAttrs", CallInplacePackedAttrs,
AttrsNode);
}; // struct CallInplacePackedAttrs
/*! \brief Attributes used in to_vdevice */
struct ToVDeviceAttrs : public AttrsNode {
VDevice dst_vdevice;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ToVDeviceAttrs>().def_ro("dst_vdevice", &ToVDeviceAttrs::dst_vdevice,
"The destination device where the data is copied to.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.ToVDeviceAttrs", ToVDeviceAttrs, AttrsNode);
}; // struct ToVDeviceAttrs
/*! \brief Attributes used in hint_on_device */
struct HintOnDeviceAttrs : public AttrsNode {
int32_t device_type;
int32_t index;
MemoryScope memory_scope;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<HintOnDeviceAttrs>()
.def_ro("device_type", &HintOnDeviceAttrs::device_type,
"The device type where the data is supposed to be executed.")
.def_ro("index", &HintOnDeviceAttrs::index, "The device id.")
.def_ro("memory_scope", &HintOnDeviceAttrs::memory_scope, "The device memory scope.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.HintOnDeviceAttrs", HintOnDeviceAttrs, AttrsNode);
}; // struct HintOnDeviceAttrs
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ATTRS_OP_H_
+52
View File
@@ -0,0 +1,52 @@
/*
* 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.
*/
/*!
* \file include/tvm/relax/attrs/qdq.h
* \brief Attributes for quantize/dequantize operators.
*/
#ifndef TVM_RELAX_ATTRS_QDQ_H_
#define TVM_RELAX_ATTRS_QDQ_H_
#include <tvm/relax/expr.h>
namespace tvm {
namespace relax {
/*! \brief Attributes for relax.quantize/relax.dequantize operator */
struct QuantizeAttrs : public AttrsNode {
DLDataType out_dtype;
int axis;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<QuantizeAttrs>()
.def_ro("out_dtype", &QuantizeAttrs::out_dtype, "Output data type.")
.def_ro("axis", &QuantizeAttrs::axis,
"The output channel axis for channel wise quantization/dequantization. "
"Default value is -1, which corresponds to the last axis.",
refl::DefaultValue(-1));
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.QuantizeAttrs", QuantizeAttrs, AttrsNode);
}; // QuantizeAttrs
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ATTRS_QDQ_H_
+49
View File
@@ -0,0 +1,49 @@
/*
* 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.
*/
/*!
* \file tvm/relax/attrs/sampling.h
* \brief Attributes for sampling operators.
*/
#ifndef TVM_RELAX_ATTRS_SAMPLING_H_
#define TVM_RELAX_ATTRS_SAMPLING_H_
#include <tvm/relax/expr.h>
namespace tvm {
namespace relax {
/*! \brief Attributes used in multinomial_from_uniform operator */
struct MultinomialFromUniformAttrs : public AttrsNode {
DLDataType dtype;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<MultinomialFromUniformAttrs>().def_ro(
"dtype", &MultinomialFromUniformAttrs::dtype, "Data type of the output indices.",
refl::DefaultValue((DLDataType{kDLInt, 64, 1})));
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.MultinomialFromUniformAttrs",
MultinomialFromUniformAttrs, AttrsNode);
}; // struct MultinomialFromUniformAttrs
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ATTRS_SAMPLING_H_
+69
View File
@@ -0,0 +1,69 @@
/*
* 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.
*/
/*!
* \file tvm/relax/attrs/search.h
* \brief Attributes for search operators.
*/
#ifndef TVM_RELAX_ATTRS_SEARCH_H_
#define TVM_RELAX_ATTRS_SEARCH_H_
#include <tvm/relax/expr.h>
namespace tvm {
namespace relax {
/*! \brief Attributes for search operators */
struct ArgmaxArgminAttrs : public AttrsNode {
ffi::Optional<int64_t> axis;
bool keepdims;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ArgmaxArgminAttrs>()
.def_ro("axis", &ArgmaxArgminAttrs::axis,
"The axis along which to perform the argmin/argmax.")
.def_ro("keepdims", &ArgmaxArgminAttrs::keepdims,
"If this is set to `True`, the reduced axis is left in the result as dimension "
"with size "
"one.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.ArgmaxArgminAttrs", ArgmaxArgminAttrs, AttrsNode);
}; // struct ArgmaxArgminAttrs
/*! \brief Attributes for bucketize operator */
struct BucketizeAttrs : public tvm::AttrsNode {
bool out_int32;
bool right;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<BucketizeAttrs>()
.def_ro("out_int32", &BucketizeAttrs::out_int32,
"Indicate the output datatype, int32 if True, int64 otherwise.")
.def_ro("right", &BucketizeAttrs::right,
"Determines the behavior for values in boundaries");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.BucketizeAttrs", BucketizeAttrs, AttrsNode);
}; // struct BucketizeAttrs
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ATTRS_SEARCH_H_
+107
View File
@@ -0,0 +1,107 @@
/*
* 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.
*/
/*!
* \file tvm/relax/attrs/sorting.h
* \brief Attributes for sorting operators.
*/
#ifndef TVM_RELAX_ATTRS_SORTING_H_
#define TVM_RELAX_ATTRS_SORTING_H_
#include <tvm/relax/expr.h>
#include <tvm/tirx/index_map.h>
namespace tvm {
namespace relax {
/*! \brief Attributes used in sort operator */
struct SortAttrs : public AttrsNode {
int axis;
bool descending;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SortAttrs>()
.def_ro("axis", &SortAttrs::axis,
"Axis along which the sort is computed."
"The default the last axis is used.",
refl::DefaultValue(-1))
.def_ro("descending", &SortAttrs::descending,
"Whether to sort in descending order."
"If it is not specified, it defaults to the ascending order.",
refl::DefaultValue(false));
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.SortAttrs", SortAttrs, AttrsNode);
}; // struct SortAttrs
/*! \brief Attributes used in argsort operator */
struct ArgsortAttrs : public AttrsNode {
int axis;
bool descending;
ffi::Optional<DLDataType> dtype;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ArgsortAttrs>()
.def_ro("axis", &ArgsortAttrs::axis,
"Axis along which the argsort is computed."
"The default the last axis is used.",
refl::DefaultValue(-1))
.def_ro("descending", &ArgsortAttrs::descending,
"Whether to argsort in descending order."
"If it is not specified, it defaults to the ascending order.",
refl::DefaultValue(false))
.def_ro("dtype", &ArgsortAttrs::dtype, "DType of the output indices.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.ArgsortAttrs", ArgsortAttrs, AttrsNode);
}; // struct ArgsortAttrs
/*! \brief Attributes used in topk operator */
struct TopKAttrs : public AttrsNode {
int k;
int axis;
bool largest;
ffi::String ret_type;
ffi::Optional<DLDataType> dtype;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TopKAttrs>()
.def_ro("k", &TopKAttrs::k, "Number of top elements to select")
.def_ro("axis", &TopKAttrs::axis, "Axis along which to sort the input tensor.",
refl::DefaultValue(-1))
.def_ro("ret_type", &TopKAttrs::ret_type,
"The return type [both, values, indices]."
"both - return both top k data and indices."
"values - return top k data only."
"indices - return top k indices only.",
refl::DefaultValue("both"))
.def_ro("largest", &TopKAttrs::largest,
"Whether to return largest or smallest elements."
"By default, return the largest k elements.",
refl::DefaultValue(true))
.def_ro("dtype", &TopKAttrs::dtype, "Data type of the output indices.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.TopKAttrs", TopKAttrs, AttrsNode);
}; // struct TopKAttrs
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ATTRS_SORTING_H_
+74
View File
@@ -0,0 +1,74 @@
/*
* 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.
*/
/*!
* \file tvm/relax/attrs/statistical.h
* \brief Attributes for statistical operators.
*/
#ifndef TVM_RELAX_ATTRS_STATISTICAL_H_
#define TVM_RELAX_ATTRS_STATISTICAL_H_
#include <tvm/relax/expr.h>
namespace tvm {
namespace relax {
/*! \brief Attributes for statistical operators */
struct StatisticalAttrs : public AttrsNode {
ffi::Optional<ffi::Array<int64_t>> axis;
bool keepdims;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<StatisticalAttrs>()
.def_ro("axis", &StatisticalAttrs::axis,
"The axis or axes along which to perform the reduction.")
.def_ro("keepdims", &StatisticalAttrs::keepdims,
"If this is set to `True`, the reduced axes are left in the result as dimension "
"with size "
"one.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.StatisticalAttrs", StatisticalAttrs, AttrsNode);
}; // struct StatisticalAttrs
/*! \brief Attributes used in scan operators like cumsum, cumprod */
struct ScanopAttrs : public AttrsNode {
ffi::Optional<int64_t> axis;
ffi::Optional<DLDataType> dtype;
bool exclusive = false;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ScanopAttrs>()
.def_ro("axis", &ScanopAttrs::axis,
"The axis along which to perform the scan computation."
"The default (None) is to compute over the flattened array.")
.def_ro("dtype", &ScanopAttrs::dtype,
"The output data type."
"If dtype is not specified, it defaults to the dtype of input data.")
.def_ro("exclusive", &ScanopAttrs::exclusive, "The first element is not included",
refl::DefaultValue(false));
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.ScanopAttrs", ScanopAttrs, AttrsNode);
}; // struct ScanopAttrs
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ATTRS_STATISTICAL_H_
+182
View File
@@ -0,0 +1,182 @@
/*
* 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.
*/
/*!
* \file tvm/relax/attrs/vision.h
* \brief Auxiliary attributes for vision operators.
*/
#ifndef TVM_RELAX_ATTRS_VISION_H_
#define TVM_RELAX_ATTRS_VISION_H_
#include <tvm/ffi/string.h>
#include <tvm/ir/attrs.h>
#include <tvm/ir/type.h>
#include <tvm/relax/expr.h>
namespace tvm {
namespace relax {
/*! \brief Attributes used in AllClassNonMaximumSuppression operator */
struct AllClassNonMaximumSuppressionAttrs : public AttrsNode {
ffi::String output_format;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<AllClassNonMaximumSuppressionAttrs>().def_ro(
"output_format", &AllClassNonMaximumSuppressionAttrs::output_format,
"Output format, onnx or tensorflow. Returns outputs in a way that can be easily "
"consumed by each frontend.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.AllClassNonMaximumSuppressionAttrs",
AllClassNonMaximumSuppressionAttrs, AttrsNode);
}; // struct AllClassNonMaximumSuppressionAttrs
/*! \brief Attributes used in ROIAlign operator */
struct ROIAlignAttrs : public AttrsNode {
ffi::Array<int64_t> pooled_size;
double spatial_scale;
int sample_ratio;
bool aligned;
ffi::String layout;
ffi::String mode;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ROIAlignAttrs>()
.def_ro("pooled_size", &ROIAlignAttrs::pooled_size, "Output size of roi align.")
.def_ro("spatial_scale", &ROIAlignAttrs::spatial_scale,
"Ratio of input feature map height (or width) to raw image height (or width).")
.def_ro("sample_ratio", &ROIAlignAttrs::sample_ratio,
"Optional sampling ratio of ROI align, using adaptive size by default.")
.def_ro("aligned", &ROIAlignAttrs::aligned,
"Whether to use the aligned ROIAlign semantics without the legacy 1-pixel clamp.")
.def_ro("layout", &ROIAlignAttrs::layout, "Dimension ordering of the input data.")
.def_ro("mode", &ROIAlignAttrs::mode, "Mode for ROI Align. Can be 'avg' or 'max'.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.ROIAlignAttrs", ROIAlignAttrs, AttrsNode);
}; // struct ROIAlignAttrs
/*! \brief Attributes used in ROIPool operator */
struct ROIPoolAttrs : public AttrsNode {
ffi::Array<int64_t> pooled_size;
double spatial_scale;
ffi::String layout;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ROIPoolAttrs>()
.def_ro("pooled_size", &ROIPoolAttrs::pooled_size, "Output size of roi pool.")
.def_ro("spatial_scale", &ROIPoolAttrs::spatial_scale,
"Ratio of input feature map height (or width) to raw image height (or width).")
.def_ro("layout", &ROIPoolAttrs::layout, "Dimension ordering of the input data.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.ROIPoolAttrs", ROIPoolAttrs, AttrsNode);
}; // struct ROIPoolAttrs
/*! \brief Attributes used in GetValidCounts operator */
struct GetValidCountsAttrs : public AttrsNode {
double score_threshold;
int id_index;
int score_index;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<GetValidCountsAttrs>()
.def_ro("score_threshold", &GetValidCountsAttrs::score_threshold,
"Lower limit of score for valid bounding boxes.")
.def_ro("id_index", &GetValidCountsAttrs::id_index,
"Index of the class categories, -1 to disable.")
.def_ro("score_index", &GetValidCountsAttrs::score_index,
"Index of the scores/confidence of boxes.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.GetValidCountsAttrs", GetValidCountsAttrs,
AttrsNode);
}; // struct GetValidCountsAttrs
/*! \brief Attributes used in NonMaximumSuppression operator */
struct NonMaximumSuppressionAttrs : public AttrsNode {
int max_output_size;
double iou_threshold;
bool force_suppress;
int top_k;
int coord_start;
int score_index;
int id_index;
bool return_indices;
bool invalid_to_bottom;
double soft_nms_sigma;
double score_threshold;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<NonMaximumSuppressionAttrs>()
.def_ro("max_output_size", &NonMaximumSuppressionAttrs::max_output_size,
"Max number of output valid boxes, -1 for no limit.")
.def_ro("iou_threshold", &NonMaximumSuppressionAttrs::iou_threshold,
"Non-maximum suppression IoU threshold.")
.def_ro("force_suppress", &NonMaximumSuppressionAttrs::force_suppress,
"Whether to suppress all detections regardless of class_id.")
.def_ro("top_k", &NonMaximumSuppressionAttrs::top_k,
"Keep maximum top k detections before nms, -1 for no limit.")
.def_ro("coord_start", &NonMaximumSuppressionAttrs::coord_start,
"Start index of the consecutive 4 coordinates.")
.def_ro("score_index", &NonMaximumSuppressionAttrs::score_index,
"Index of the scores/confidence of boxes.")
.def_ro("id_index", &NonMaximumSuppressionAttrs::id_index,
"Index of the class categories, -1 to disable.")
.def_ro("return_indices", &NonMaximumSuppressionAttrs::return_indices,
"Whether to return box indices in input data.")
.def_ro("invalid_to_bottom", &NonMaximumSuppressionAttrs::invalid_to_bottom,
"Whether to move all valid bounding boxes to the top.")
.def_ro("soft_nms_sigma", &NonMaximumSuppressionAttrs::soft_nms_sigma,
"Sigma for soft-NMS; 0.0 means standard hard NMS.")
.def_ro("score_threshold", &NonMaximumSuppressionAttrs::score_threshold,
"Score threshold for soft-NMS validity check; 0.0 when unused.");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.NonMaximumSuppressionAttrs",
NonMaximumSuppressionAttrs, AttrsNode);
}; // struct NonMaximumSuppressionAttrs
/*! \brief Attributes for multibox_transform_loc (SSD / TFLite-style box decode). */
struct MultiboxTransformLocAttrs : public AttrsNode {
bool clip;
double threshold;
ffi::Array<double> variances;
bool keep_background;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<MultiboxTransformLocAttrs>()
.def_ro("clip", &MultiboxTransformLocAttrs::clip,
"Clip decoded ymin,xmin,ymax,xmax to [0,1].")
.def_ro("threshold", &MultiboxTransformLocAttrs::threshold,
"After softmax, zero scores strictly below this value.")
.def_ro("variances", &MultiboxTransformLocAttrs::variances,
"(x,y,w,h) scales = TFLite 1/x_scale,1/y_scale,1/w_scale,1/h_scale on "
"encodings. Very large w/h scales can overflow exp in decode.")
.def_ro("keep_background", &MultiboxTransformLocAttrs::keep_background,
"If false, force output scores[:,0,:] to 0 (background class).");
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.attrs.MultiboxTransformLocAttrs",
MultiboxTransformLocAttrs, AttrsNode);
}; // struct MultiboxTransformLocAttrs
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_ATTRS_VISION_H_
+51
View File
@@ -0,0 +1,51 @@
/*
* 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.
*/
/*!
* \file tvm/relax/backend.h
* \brief Relax backend specific transformation passes.
*/
#ifndef TVM_RELAX_BACKEND_H_
#define TVM_RELAX_BACKEND_H_
#include <tvm/relax/transform.h>
namespace tvm {
namespace relax {
namespace transform {
/*!
* \brief Perform builtin lowering to map most of the op to VM builtin functions.
*
* \return The Pass.
*/
TVM_DLL Pass LowerRuntimeBuiltin();
/*!
* \brief Lower the shape expression in relax to VM shape heap and TIR functions.
*
* \return The Pass.
*/
TVM_DLL Pass VMShapeLower();
} // namespace transform
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_BACKEND_H_
@@ -0,0 +1,67 @@
/*
* 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.
*/
/*!
* \file tvm/relax/backend/adreno/transform.h
* \brief Adreno GPU specific transformation passes.
*/
#ifndef TVM_RELAX_BACKEND_ADRENO_TRANSFORM_H_
#define TVM_RELAX_BACKEND_ADRENO_TRANSFORM_H_
#include <tvm/relax/expr.h>
#include <tvm/relax/transform.h>
namespace tvm {
namespace relax {
namespace backend {
namespace adreno {
namespace transform {
using Pass = tvm::transform::Pass;
using PassInfo = tvm::transform::PassInfo;
using PassContext = tvm::transform::PassContext;
using Function = tvm::relax::Function;
using DataflowBlock = tvm::relax::DataflowBlock;
using tvm::relax::transform::CreateFunctionPass;
using tvm::transform::CreateModulePass;
/*!
* \brief This pass is designed to annotate the memory scope information via VDevice attribute.
* This pass need operator attrbutes which in general vanish aftre legalization.
* FuseOps and FuseTIR are modified to pass on the operator specific attributes and also
* op_pattern details as part of the PrimFunc. This pass is Adreno specific and annotates each
* BindingVar with appropriate HintInDevice. RealizeVDevice pass followed by handles these hints.
* Followed by this pass we also invoke SpecializePrimFuncBasedOnCallSite which updates the
* var_buffer_map based on this new VDevice information.
*/
TVM_DLL Pass AnnotateCustomMemoryScope(Target target);
/*
* \brief This is a texture specific pass that can optimize unnecessary to_device copies.
* Like texture_scope -> ToVDevice -> global scope. In this case the producer can directly
* store into global scope avoiding unnecessary device copy.
*/
TVM_DLL Pass FoldVDeviceScopeChange();
} // namespace transform
} // namespace adreno
} // namespace backend
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_BACKEND_ADRENO_TRANSFORM_H_
+118
View File
@@ -0,0 +1,118 @@
/*
* 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.
*/
/*!
* \file tvm/relax/binding_rewrite.h
* \brief An IR rewriter to easily add/remove/replace bindings (statements).
*/
#ifndef TVM_RELAX_BINDING_REWRITE_H_
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/unique_name_supply.h>
#include <tvm/relax/analysis.h>
#include <tvm/relax/expr.h>
#include <map>
#include <set>
#include <type_traits>
#include <utility>
#include <vector>
namespace tvm {
namespace relax {
/*! \brief Statement rewriter for relax.DataflowBlock. */
class DataflowBlockRewriteNode : public ffi::Object {
public:
/*! \brief Replace all uses of old_var with new_var. */
void ReplaceAllUses(Var old_var, Var new_var);
/*! \brief Insert a Binding statement. */
void Add(Binding binding);
/*! \brief Insert an expression as VarBinding with variable name. */
void Add(ffi::String var_name, Expr expr, bool is_dfvar = false) {
auto var = is_dfvar ? DataflowVar(var_name, GetType(expr)) //
: Var(var_name, GetType(expr));
Add(VarBinding(std::move(var), std::move(expr)));
}
/*! \brief Insert an expression as VarBinding with automatic variable name. */
void Add(Expr expr, bool is_dfvar = false) {
Add(name_supply_->FreshName("tmp"), expr, is_dfvar);
}
/*! \brief Remove the definition statement of an unused variable. */
void RemoveUnused(Var unused, bool allow_undef = false);
/*! \brief Remove the definition statements of all unused variables. */
void RemoveAllUnused();
/*! \brief The rewritten dataflow block. */
DataflowBlock MutatedDataflowBlock() { return dfb_; }
/*! \brief The rewritten function. */
Function MutatedFunc() { return root_fn_.value(); }
/*! \brief The rewritten IRModule. */
IRModule MutateIRModule(IRModule irmod);
/*! \brief Visit attributes. */
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<DataflowBlockRewriteNode>()
.def_ro("dfb", &DataflowBlockRewriteNode::dfb_)
.def_ro("root_fn", &DataflowBlockRewriteNode::root_fn_);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.DataflowBlockRewrite", DataflowBlockRewriteNode,
ffi::Object);
protected:
friend class DataflowBlockRewrite;
DataflowBlock dfb_; //!< The rewritten dataflow block.
ffi::Optional<Function> root_fn_; //!< The rewritten function.
const FunctionNode* original_fn_ptr_; //!< Pointer to the original function.
ffi::Map<Var, ffi::Array<Var>> to_users_; //!< Map from variable to its users.
ffi::Array<Var> fn_outputs_; //!< Variables required by function outputs.
private:
UniqueNameSupply name_supply_; //!< Unique name supply for tracking and generating unique names.
};
/*!
* \brief A statement rewriter for relax.DataflowBlock.
* \sa DataflowBlockRewriteNode
*/
class DataflowBlockRewrite : public ffi::ObjectRef {
public:
TVM_DLL explicit DataflowBlockRewrite(DataflowBlock dfb, Function root_fn);
/*!
* \brief mutable accessor.
* \return mutable access pointer.
*/
DataflowBlockRewriteNode* operator->() {
TVM_FFI_ICHECK(get() != nullptr);
return static_cast<DataflowBlockRewriteNode*>(get_mutable());
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(DataflowBlockRewrite, ffi::ObjectRef,
DataflowBlockRewriteNode);
};
} // namespace relax
} // namespace tvm
#define TVM_RELAX_BINDING_REWRITE_H_
#endif // TVM_RELAX_BINDING_REWRITE_H_
+320
View File
@@ -0,0 +1,320 @@
/*
* 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.
*/
/*!
* \file tvm/relax/block_builder.h
* \brief The utility for constructing Relax binding blocks.
*/
#ifndef TVM_RELAX_BLOCK_BUILDER_H_
#define TVM_RELAX_BLOCK_BUILDER_H_
#include <tvm/arith/analyzer.h>
#include <tvm/ir/unique_name_supply.h>
#include <tvm/relax/expr.h>
#include <tvm/relax/utils.h>
#include <tvm/runtime/base.h>
namespace tvm {
namespace relax {
/*!
* \brief A builder to build Relax binding blocks.
*
* BlockBuilder provides the following three categories
* of main functionalities for IR building and transformations:
*
* - Global context management: manages the IRModule,
* allowing query, update the surrounding global context.
* Provide context tools for analysis.
* - Scope management:
* - Manages block scopes for bulding nested blocks.
* - Emit bindings to the current scope.
* - Construct blocks by calling EndScope.
* - Normalization: Take an Expr, normalize it
* to deduce shape/type, turn things into normal forms.
*
* Importantly, these three categories of features can be dependent
* on each other. For example, when we emit into scope we will call
* normalize to ensure the code is in normal form. Similarly, when we
* normalize we could choose to emit into the current context.
*
* We would encourage the developers to keep these three category
* in mind when using and developing BlockBuilder, we can group
* the code in a logically clean way.
*
* BlockBuilderNode is implemented as a virtual interface to
* allow logically grouped implementation and internal data
* structures that are hidden from the users.
*/
class BlockBuilderNode : public ffi::Object {
public:
//-------------------------------
// Global Context management
//-------------------------------
/*!
* \brief Get the unique name supply for generating unique names.
*
* \return The unique name supply.
*/
virtual UniqueNameSupply name_supply() = 0;
/*!
* \brief Get the context IRModule in this builder.
*
* \note The context
* \return The IRModule in this BlockBuilder.
*/
virtual IRModule GetContextIRModule() const = 0;
/*!
* \brief Finalize the building process and return the result IRModule. Possibly rename
* GlobalVars in the IRModule to ensure name uniqueness and the invariant:
* every public function has the same name as its "global_symbol" attribute.
*
* \note this method should be called only once at the end of the building process, since it may
* invalidate global vars previously returned by this builder. See also
* transform::NormalizeGlobalVar.
*
* \return The result IRModule.
*/
virtual IRModule Finalize() = 0;
/*!
* \brief Add a Relax function or a TIR PrimFunc to internal context module.
* \param func The function to be added.
* \param func_name_hint The name hint of the function to be added.
* \note If the function to be added already exists, return its
* GlobalVar directly.
* \return The global var bound to the added function.
*/
virtual GlobalVar AddFunction(const BaseFunc& func, ffi::String func_name_hint) = 0;
/*!
* \brief Update a Relax function or a TIR PrimFunc in the internal context module.
* \param gv The global var referring the function to be updated.
* \param function The updated function.
*/
virtual void UpdateFunction(const GlobalVar& gv, BaseFunc function) = 0;
//-------------------------------
// Scope management
//-------------------------------
/*!
* \brief Lookup the binding value that var binds to in the current emitted sequences.
* \param var The input var.
* \return The Expr bound to the input \p var.
* \note For function parameters, this function returns std::nullopt.
*/
virtual ffi::Optional<Expr> LookupBinding(const Var& var) = 0;
/*!
* \brief Begin a new scope, with optional parameters that
* are visible within the scope.
*
* Symbolic variables from the parent scope are not available.
*
* \param params Parameters that are visible within the scope.
*
* \note This function should be called when new scope is introduced
* (e.g. function bodies) to properly track the variable
* availability and help the best effort deduction.
*
* \sa EndScope
*/
virtual void BeginScope(ffi::Optional<ffi::Array<Var>> params) = 0;
/*!
* \brief Begin a new scope, which inherits visible parameters from
* its parent scope.
*
* Symbolic variables from the parent scope are available.
*
* \note This function should be called when an inner scope is
* introduced (e.g. conditional branches) to properly track
* the variable availability and help the best effort
* deduction.
*
* \sa EndScope
*/
virtual void BeginInnerScope() = 0;
/*!
* \brief Append a definition to the current scope.
*
* \param var A variable within the current scope.
*
* \note This function should be called when a new variable is
* defined that may impact struct inference (e.g. MatchCast)
* to properly track the variable availability and help the
* best effort deduction.
*
* \sa EndScope
*/
virtual void AddDefinitionToScope(Var var) = 0;
/*! \brief End the previously defined scope. */
virtual void EndScope() = 0;
/*! \brief Begin to build a DataflowBlock. */
virtual void BeginDataflowBlock() = 0;
/*! \brief Begin to build a BindingBlock. */
virtual void BeginBindingBlock() = 0;
/*!
* \brief End building a BindingBlock.
* \return The BindingBlock being built.
*/
virtual BindingBlock EndBlock() = 0;
/*!
* \brief Check if the block being built is DataflowBlock or not.
* \return A boolean that indicates if the block being built is DataflowBlock or not.
*/
virtual bool CurrentBlockIsDataFlow() = 0;
/*!
* \brief Emits an Expr, and returns the variable it is bound to.
* \param expr The Expr to be emitted.
* \param name_hint Name hint for the bound variable.
* \return The new variable that \p expr is bound to.
*
* \note This Emit function normalizes the \p expr, and
* performs shape and type deductions by calling Normalize.
*/
virtual Var Emit(Expr expr, ffi::String name_hint = "") = 0;
/*!
* \brief Emit a MatchCast.
* \param value The input value.
* \param ty The type to be matched.
* \param name_hint Name hint for the bound variable.
* \return The variable bound to the MatchCast.
*/
virtual Var EmitMatchCast(Expr value, Type ty, ffi::String name_hint = "") = 0;
/*!
* \brief Generate an output for the current dataflow block.
* \param output The output variable of the block.
* \param name_hint Name hint for the bound variable.
* \return The variable bound to \p output.
*/
virtual Var EmitOutput(Expr output, ffi::String name_hint = "") = 0;
/*!
* \brief Emit a binding that is already normalized.
*
* \param normalized_binding A binding whose value is already normalized.
*
* \note This function requires binding to be pre-normalized.
*/
virtual void EmitNormalized(Binding normalized_binding) = 0;
/*!
* \brief Convert an expression to normal form, and try to eagerly infer types and shapes.
* \param expr The input expression.
* \return The normalized expression.
*
* \note Invariant: If any of the sub expr have ty field.
* they must have already been normalized.
*/
virtual Expr Normalize(const Expr& expr) = 0;
/*!
* \brief Normalize argument to a call or another IRNode.
* \param expr The input expression.
* \return The normalized expression.
*
* \note This function will create a binding var for non-leaf expressions such as Call.
*/
virtual Expr NormalizeArgument(const Expr& expr) = 0;
/*!
* \brief Get the analyzer of the BlockBuilder.
* \return The BlockBuilder's arithmetic analyzer.
*/
virtual arith::Analyzer GetAnalyzer() = 0;
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO("relax.BlockBuilder", BlockBuilderNode, ffi::Object);
};
class BlockBuilder : public ffi::ObjectRef {
public:
/*!
* \brief Create a BlockBuilder.
*
* \param ctx_mod Optional before-transformation context module for rewriting.
*
* \return The created BlockBuilder.
*
* \note When rewriting an existing IRModule, it is important to pass it in as
* ctx_mod so you can lookup the context functions for cross function
* call analysis.
*/
TVM_DLL static BlockBuilder Create(ffi::Optional<IRModule> ctx_mod);
/*! \brief A marker struct to disable FNormalize
*
* This struct is used as a marker to disable the use of FNormalize
* by this block builder. This should only be used for TVMScript
* parsing, which may require producing un-normalized Relax IR for
* testing purposes, and to ensure that round-trips are unchanged.
*
* The name is deliberately verbose to draw attention during a code
* review. The explicit default constructor prevents aggregate
* initialization, ensuring that the full name of the marker struct
* appears at the callsite.
*
* This constructor is marked as no-lint to allow a zero-parameter
* constructor to be marked as explicit. The constructor must be
* explicit in order to disable aggregate initialization in C++17.
* While C++20 disables aggregate initialization when a
* user-declared constructor is present, C++17 only disables
* aggregate initialization when a user-defined constructor is
* present. Therefore, we need to mark the zero-parameter
* constructor as explicit in order to prevent aggregate
* initialization, and to ensure that the name appears at all
* callsites.
*/
struct DisableOperatorSpecificNormalizationForTVMScript {
explicit DisableOperatorSpecificNormalizationForTVMScript() = default; // NOLINT(*)
};
/*!
* \brief Create a BlockBuilder.
*
* \param ctx_mod Optional before-transformation context module for rewriting.
*
* \param tag An instance of DisableOperatorSpecificNormalizationForTVMScript
*
* \return The created BlockBuilder.
*
* \note When rewriting an existing IRModule, it is important to pass it in as
* ctx_mod so you can lookup the context functions for cross function
* call analysis.
*/
TVM_DLL static BlockBuilder Create(ffi::Optional<IRModule> ctx_mod,
DisableOperatorSpecificNormalizationForTVMScript tag);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(BlockBuilder, ffi::ObjectRef, BlockBuilderNode);
};
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_BLOCK_BUILDER_H_
+107
View File
@@ -0,0 +1,107 @@
/*
* 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.
*/
/*!
* \file tvm/relax/dataflow_matcher.h
* \brief A pattern matcher for matching dataflow properties.
*/
#ifndef TVM_RELAX_DATAFLOW_MATCHER_H_
#define TVM_RELAX_DATAFLOW_MATCHER_H_
#include <tvm/ffi/function.h>
#include <tvm/ffi/optional.h>
#include <tvm/relax/dataflow_pattern.h>
#include <memory>
namespace tvm {
namespace relax {
/**
* \brief Determine if a pattern matches an expression.
* \note The behavior of MatchExpr is to match a relax.Expr (`expr`) syntactically through
* one given pattern (`pattern`).
*
* \param pattern The pattern to match
* \param expr The expression to match
* \param bindings The mapping from relax.Var to relax.Expr
* \return true if matched
* \return false if unmatched
*/
bool MatchExpr(DFPattern pattern, Expr expr,
ffi::Optional<ffi::Map<Var, Expr>> bindings = std::nullopt);
/* \brief Similar to above, but return pairs of a matching pattern and an expression. */
ffi::Optional<ffi::Map<DFPattern, Expr>> ExtractMatchedExpr(
DFPattern pattern, Expr expr, ffi::Optional<ffi::Map<Var, Expr>> bindings = std::nullopt);
/**
* \brief Match a sub-graph in a DataflowBlock with a graph of patterns and return the mapping.
* \param ctx The graph-wise patterns.
* \param dfb The function to match.
* \return Matched patterns and corresponding bound variables
*/
TVM_DLL ffi::Optional<ffi::Map<DFPattern, Var>> MatchGraph(const PatternContext& ctx,
const DataflowBlock& dfb);
/**
* \brief Rewrite a function with the given pattern and the rewriter function.
* \param ctx The pattern constraint context under which rewriting takes place.
* \param rewriter The function to be called on a successful matching for rewriting.
Given the map of patterns and corresponding variables (bound variables or parameters),
it should return a map that specifies new values for matched bound variables.
* \param f The function to rewrite
* \return The rewritten or the input function, depending on the pattern matching result.
*/
TVM_DLL Function RewriteBindings(
const PatternContext& ctx,
ffi::TypedFunction<ffi::Map<Var, Expr>(ffi::Map<DFPattern, Var>, ffi::Map<Var, Expr>)> rewriter,
Function f);
/**
* \brief Rewrite a function with the given pattern and the rewriter function.
*
* Pattern match and replace at an expression level. This level of
* granularity does not allow simultaneous replacement cannot be
* performed. In addition, removal of bindings cannot be performed
* explicitly, and is only done implicitly through RemoveAllUnused.
* See also `RewriteBindings`, which performs replacement on a
* block-level, and does not have these restrictions.
*
* \param pattern The pattern to be replaced
*
* \param rewriter The function to be called on a successful pattern
* matching. Given the matched expression and a map of sub-matches,
* it should return the replacement expression. If the expression
* doesn't require updating (e.g. replacement required checks beyond
* those expressed in the pattern), it should return the expression
* unmodified.
*
* \param func The function to rewrite
*
* \return The updated function, if any updates were applied.
*/
TVM_DLL Function RewriteCall(const DFPattern& pattern,
ffi::TypedFunction<Expr(Expr, ffi::Map<DFPattern, Expr>)> rewriter,
Function func);
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_DATAFLOW_MATCHER_H_
+978
View File
@@ -0,0 +1,978 @@
/*
* 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.
*/
/*!
* \file tvm/relax/dataflow_pattern.h
* \brief A pattern language for matching dataflow properties.
*/
#ifndef TVM_RELAX_DATAFLOW_PATTERN_H_
#define TVM_RELAX_DATAFLOW_PATTERN_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/optional.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/expr.h>
#include <tvm/ir/with_context.h>
#include <tvm/relax/expr.h>
#include <tvm/relax/type.h>
#include <cstdint>
#include <functional>
#include <map>
#include <memory>
#include <string>
#include <tuple>
#include <utility>
#include <vector>
namespace tvm {
namespace arith {
class AnalyzerObj;
class Analyzer;
} // namespace arith
namespace relax {
class PatternSeq;
class CallPattern;
class OrPattern;
class AndPattern;
class NotPattern;
class ShapePattern;
class TypePattern;
class DataTypePattern;
class AttrPattern;
class SameShapeConstraint;
/*!
* \brief Create used-by relationship between lhs[-1] and rhs[0], with [*lhs, *rhs] returned.
*
* \param lhs Left hand side of the used-by relationship.
* \param rhs Right hand side of the used-by relationship.
* \param index lhs[-1] is used as the index'th argument of rhs[0].
* \return PatternSeq The concatenated sequence of [*lhs, *rhs].
*/
TVM_DLL PatternSeq UsedBy(const PatternSeq& lhs, const PatternSeq& rhs, int index = -1);
/*! \brief Syntax sugar of UsedBy(lhs, rhs, -1). */
TVM_DLL PatternSeq operator^(const PatternSeq& lhs, const PatternSeq& rhs);
/*!
* \brief Create only-used-by relationship between lhs[-1] and rhs[0], with [*lhs, *rhs] returned.
*
* \param lhs Left hand side of the used-by relationship.
* \param rhs Right hand side of the used-by relationship.
* \param index lhs[-1] is used as the index'th argument of rhs[0].
* \return PatternSeq The concatenated sequence of [*lhs, *rhs].
*/
TVM_DLL PatternSeq OnlyUsedBy(const PatternSeq& lhs, const PatternSeq& rhs, int index = -1);
/*! \brief Syntax sugar of OnlyUsedBy(lhs, rhs, -1). */
TVM_DLL PatternSeq operator>>(const PatternSeq& lhs, const PatternSeq& rhs);
/*!
* \brief Base type of all dataflow patterns.
* \sa DFPattern
*/
class DFPatternNode : public ffi::Object {
public:
static constexpr const uint32_t _type_child_slots = 21;
TVM_FFI_DECLARE_OBJECT_INFO("relax.dpl.DFPattern", DFPatternNode, ffi::Object);
};
/*!
* \brief Managed reference to dataflow patterns.
* \sa DFPatternNode
*/
class DFPattern : public ffi::ObjectRef {
public:
/*! \brief Syntatic Sugar for creating a CallPattern */
template <typename... Args>
CallPattern operator()(Args&&... args) const;
/*! \brief Syntatic Sugar for creating a CallPattern */
TVM_DLL CallPattern operator()(const std::vector<DFPattern>& args) const;
/*! \brief Syntatic Sugar for creating an OrPattern */
TVM_DLL OrPattern operator|(const DFPattern& other) const;
/*! \brief Syntatic Sugar for creating an AndPattern */
TVM_DLL AndPattern operator&(const DFPattern& other) const;
/*! \brief Syntatic Sugar for creating a NotPattern */
TVM_DLL NotPattern operator~() const;
/*! \brief Syntatic Sugar for creating an AttrPattern */
TVM_DLL AttrPattern HasAttr(const ffi::Map<ffi::String, Any>& attrs) const;
/*! \brief Syntatic Sugar for creating a TypePattern */
TVM_DLL TypePattern HasType(const Type& ty) const;
/*! \brief Syntatic Sugar for creating a DataTypePattern with a dtype */
TVM_DLL DataTypePattern HasDtype(DLDataType dtype) const;
/*! \brief Syntatic Sugar for creating a DataTypePattern with a data type's name */
TVM_DLL DataTypePattern HasDtype(const std::string& dtype) const;
/*! \brief Syntatic Sugar for creating a ShapePattern */
TVM_DLL ShapePattern HasShape(const ffi::Array<PrimExpr>& shape) const;
/*! \brief Syntatic Sugar for creating a ShapePattern */
TVM_DLL SameShapeConstraint HasSameShapeAs(const DFPattern& other) const;
/*! \brief Syntatic Sugar for duplicating the current pattern */
TVM_DLL DFPattern dup() const;
/*! \brief Implicit conversion from DFPattern to PatternSeq */
TVM_DLL operator PatternSeq() const;
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(DFPattern, ffi::ObjectRef, DFPatternNode);
};
/*! \brief Constraint of a DFPattern edge (producer -> consumer) in graph-level matching */
struct PairCons {
/*! \brief Constraint types of the edge */
enum Type {
kUsedBy, /*!< producer ^ consumer */
kOnlyUsedBy, /*!< producer >> consumer */
} type = kUsedBy;
int index = -1; /*!< The argument index of the producer in the consumer caller site */
/*!
* \brief Construct a new PairCons object
*
* \param t The constraint type
* \param index The producer is called as the index'th argument of the consumer function.
*/
TVM_DLL explicit PairCons(Type t, int index = -1) : type(t), index(index) {}
bool operator==(const PairCons& other) const {
return type == other.type && index == other.index;
}
};
/*! \brief Additional constraints on the graph
*
* Unlike PairCons, these may relate nodes that are not directly
* connected by a DFPattern edge from producer to consumer. For
* example, constraining the two branches of an elementwise operation
* to have the same shape.
*/
class DFConstraintNode : public ffi::Object {
public:
/*! \brief Return the patterns on which the constraint depends */
virtual ffi::Array<DFPattern> GetDependentPatterns() const = 0;
/*! \brief Convert the constraint to a PrimExpr
*
* If the returned boolean parameter is true, then the returned
* expression is a necessary-and-sufficient condition for evaluating
* the constraint. In this case, the matcher may either mark the
* constraint as satisfied (no need to re-check later), or as failed
* (need to back-track).
*
* If the returned boolean parameter is false, then the returned
* expression is a necessary-but-not-sufficient condition for
* evaluating the constraint. In this case, the matcher may start
* backtracking as a result of a failed condition, but may not mark
* the constraint as satisfied. This typically occurs when the
* constraint involves a parameter that the matcher has not yet
* filled.
*
* \param match_state A function that can be called to check the
* current state of the match. The function takes as argument a
* pattern on which the constraint depends, and returns the relax
* variable matched by that pattern, or std::nullopt if the pattern
* has not yet been matched.
*
* \return A tuple of `PrimExpr` and `bool`. The first element is a
* necessary condition for the constraint to be satisfied. The
* second tuple element indicates whether the condition is also
* sufficient for the constraint to be satisfied.
*/
virtual std::tuple<PrimExpr, bool> AsCondition(
std::function<ffi::Optional<Var>(const DFPatternNode*)> match_state) const = 0;
static constexpr const uint32_t _type_child_slots = 1;
TVM_FFI_DECLARE_OBJECT_INFO("relax.dpl.DFConstraint", DFConstraintNode, ffi::Object);
};
class DFConstraint : public ffi::ObjectRef {
public:
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(DFConstraint, ffi::ObjectRef, DFConstraintNode);
};
/*!
* \brief A sequence of DFPatterns that the previous DFPattern is connected to the next one.
* \sa PatternSeq
*/
class PatternSeqNode final : public ffi::Object {
public:
tvm::ffi::Array<DFPattern> patterns; /*!< The sequence of DFPatterns */
std::vector<PairCons> pair_constraints; /*!< Constraints between the previous and next patterns */
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PatternSeqNode>().def_ro("patterns", &PatternSeqNode::patterns);
}
TVM_FFI_DECLARE_OBJECT_INFO("relax.dpl.PatternSeq", PatternSeqNode, ffi::Object);
};
/*!
* \brief Managed reference to pattern sequences.
* \sa PatternSeqNode
*/
class PatternSeq final : public ffi::ObjectRef {
public:
TVM_DLL explicit PatternSeq(DFPattern init_pattern);
TVM_DLL explicit PatternSeq(tvm::ffi::Array<DFPattern> patterns, bool only_used_by = false);
PatternSeq UsedBy(PatternSeq other, int index = -1) const;
PatternSeq OnlyUsedBy(PatternSeq other, int index = -1) const;
/*! \brief Syntatic Sugar for duplicating the current pattern sequence */
PatternSeq dup() const;
// friend functions
friend PatternSeq UsedBy(const PatternSeq& lhs, const PatternSeq& rhs, int index);
friend PatternSeq OnlyUsedBy(const PatternSeq& lhs, const PatternSeq& rhs, int index);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(PatternSeq, ffi::ObjectRef, PatternSeqNode);
};
/*!
* \brief A context to manage the graph-level pattern matching.
* \sa PatternContext
*/
class PatternContextNode : public ffi::Object {
public:
/*! \brief Constrainting matched graph with assertion to external uses */
enum ExternUse {
kMay, /*!< No constraints */
kMustNot, /*!< All nodes except outputs only have internal depedencies in the matched graph. */
} allow_extern_use = kMay;
// src node -> <dst node, constraint type> constraints.
// Dst nodes are kept in a vector to keep them ordered.
std::map<DFPattern, std::vector<std::pair<DFPattern, std::vector<PairCons>>>> edge_constraints;
// Underlying DFPattern nodes which the edge constraints may reference
// Kept as a separate vector of patterns to process constraints in a fixed order.
std::vector<DFPattern> src_ordered;
// Non-edge constraints
std::vector<DFConstraint> validation_constraints;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.PatternContext", PatternContextNode, ffi::Object);
};
/*!
* \brief Managed reference to a pattern context.
* \sa PatternContextNode
*/
class PatternContext : public ffi::ObjectRef {
public:
explicit PatternContext(ffi::UnsafeInit tag) : ffi::ObjectRef(tag) {}
TVM_DLL explicit PatternContext(ffi::ObjectPtr<ffi::Object> n) : ffi::ObjectRef(n) {}
TVM_DLL explicit PatternContext(bool incremental = false);
const PatternContextNode* operator->() const {
TVM_FFI_ICHECK(get() != nullptr);
return static_cast<const PatternContextNode*>(get());
}
PatternContextNode* operator->() {
TVM_FFI_ICHECK(get() != nullptr);
return static_cast<PatternContextNode*>(get_mutable());
}
/*!
* \brief Build an edge constraint between two patterns (producer and consumer).
*
* \param producer The pattern corresponding to the producer node.
* \param consumer The pattern corresponding to the consumer node.
* \param cons The constraint type. \sa PairCons
*/
void add_constraint(DFPattern producer, DFPattern consumer, PairCons cons) {
auto& pairs = (*this)->edge_constraints[producer];
auto it = std::find_if(pairs.begin(), pairs.end(),
[consumer](auto p) { return p.first == consumer; });
if (it == pairs.end()) {
pairs.emplace_back(consumer, std::vector{cons});
} else {
auto& vec = it->second;
TVM_FFI_ICHECK(std::find(vec.cbegin(), vec.cend(), cons) == vec.cend())
<< "Constraint already exists";
vec.push_back(cons);
}
auto& patterns = (*this)->src_ordered;
if (std::find(patterns.begin(), patterns.end(), producer) == patterns.end()) {
patterns.push_back(producer);
}
}
/*!
* \brief Add a validation constraint
*
* \param constraint The new constraint
*/
void add_constraint(DFConstraint constraint) {
(*this)->validation_constraints.push_back(constraint);
}
/*! \brief Get the constraint context object on the top of the stack */
TVM_DLL static ffi::Optional<PatternContext> Current();
/*! \brief The RAII-like entry of a constraint context scope */
TVM_DLL void EnterWithScope() const;
/*! \brief The RAII-like exit of a constraint context scope */
TVM_DLL void ExitWithScope() const;
private:
friend class With<PatternContext>;
};
/*!
* \brief Pattern for Relax Expression.
* \sa ExprPattern
*/
class ExprPatternNode : public DFPatternNode {
public:
Expr expr; /*!< The expression to match */
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ExprPatternNode>().def_ro("expr", &ExprPatternNode::expr);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.ExprPattern", ExprPatternNode, DFPatternNode);
};
/*!
* \brief Managed reference to an ExprPattern.
* \sa ExprPatternNode
*/
class ExprPattern : public DFPattern {
public:
TVM_DLL explicit ExprPattern(Expr expr);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(ExprPattern, DFPattern, ExprPatternNode);
};
/*!
* \brief A Pattern to Match a Relax Variable.
* \note The name field matches any string if it is empty.
* \sa VarPattern
*/
class VarPatternNode : public DFPatternNode {
public:
ffi::String name;
const ffi::String& name_hint() const { return name; }
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<VarPatternNode>().def_ro("name", &VarPatternNode::name);
}
static constexpr const uint32_t _type_child_slots = 1;
TVM_FFI_DECLARE_OBJECT_INFO("relax.dpl.VarPattern", VarPatternNode, DFPatternNode);
};
/*!
* \brief Managed reference to a VarPattern.
* \sa VarPatternNode
*/
class VarPattern : public DFPattern {
public:
/*!
* \brief Create a pattern matching by variable name.
*
* \param name_hint Variable name to match. Any if empty ("").
*/
TVM_DLL VarPattern(ffi::String name_hint);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(VarPattern, DFPattern, VarPatternNode);
};
/*!
* \brief A Pattern to Match a Relax Dataflow Variable
* \sa DataflowVarPattern
*/
class DataflowVarPatternNode : public VarPatternNode {
public:
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<DataflowVarPatternNode>();
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.DataflowVarPattern", DataflowVarPatternNode,
VarPatternNode);
};
/*!
* \brief Managed reference to a DataflowVarPattern.
* \sa DataflowVarPatternNode
*/
class DataflowVarPattern : public DFPattern {
public:
/*! \sa VarPattern::VarPattern */
TVM_DLL DataflowVarPattern(ffi::String name_hint);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(DataflowVarPattern, DFPattern, DataflowVarPatternNode);
};
/*!
* \brief A Pattern to Match a Relax Global Variable
* \sa GlobalVarPattern
*/
class GlobalVarPatternNode : public VarPatternNode {
public:
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.GlobalVarPattern", GlobalVarPatternNode,
DFPatternNode);
};
/*!
* \brief Managed reference to a GlobalVarPattern.
* \sa GlobalVarPatternNode
*/
class GlobalVarPattern : public DFPattern {
public:
TVM_DLL GlobalVarPattern(ffi::String name_hint);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(GlobalVarPattern, DFPattern, GlobalVarPatternNode);
};
/*!
* \brief A Pattern to Match a Relax Constant.
* \sa ConstantPattern
*/
class ConstantPatternNode : public DFPatternNode {
public:
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ConstantPatternNode>();
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.ConstantPattern", ConstantPatternNode,
DFPatternNode);
};
/*!
* \brief Managed reference to a ConstantPattern.
* \sa ConstantPatternNode
*/
class ConstantPattern : public DFPattern {
public:
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(ConstantPattern, DFPattern, ConstantPatternNode);
};
/*!
* \brief A pattern to match a callable node in Relax.
* \sa CallPattern
*/
class CallPatternNode : public DFPatternNode {
public:
/*!
* \note The op field can be:
* - relax::Op which corresponds to the primitive operators.
* - user defined functions (Function, GlobalVar, Var).
*/
DFPattern op; /*!< The operator (function) being invoked */
tvm::ffi::Array<DFPattern> args; /*!< The arguments of the function call */
/*!
* \note If varg_default_wildcard is true. Given args of [pA, pB], when matching a call whose
* arguments are [A, B, ...], the pattern will still match despite N(args) < N(call.args). That
* said, with varg_default_wildcard set to true, we match the args in the order we have, and
* regard the rest of the arguments as wildcards.
*/
bool varg_default_wildcard; /*!< N(args) can be < N(real args) by the padding of Wildcard */
// Todo(relax-team): Dataflow pattern for Type, and match ty_args
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<CallPatternNode>()
.def_ro("op", &CallPatternNode::op)
.def_ro("args", &CallPatternNode::args);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.CallPattern", CallPatternNode, DFPatternNode);
};
class CallPattern : public DFPattern {
public:
TVM_DLL CallPattern(DFPattern op, ffi::Array<DFPattern> args, bool varg_default_wildcard = false);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(CallPattern, DFPattern, CallPatternNode);
};
/*!
* \brief A pattern to match an array of PrimExpr.
* \sa PrimArrPattern
* \note This is often used to match shapes specified as arguments to a function.
*/
class PrimArrPatternNode : public DFPatternNode {
public:
ffi::Array<PrimExpr> fields; /*!< The array to match */
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PrimArrPatternNode>().def_ro("fields", &PrimArrPatternNode::fields);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.PrimArrPattern", PrimArrPatternNode, DFPatternNode);
};
/*!
* \brief Managed reference to a PrimArrPattern.
* \sa PrimArrPatternNode
*/
class PrimArrPattern : public DFPattern {
public:
TVM_DLL PrimArrPattern(ffi::Array<PrimExpr> arr);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(PrimArrPattern, DFPattern, PrimArrPatternNode);
};
/*!
* \brief A pattern to match a Relax Function
* \sa Function
* \sa FunctionPattern
*/
class FunctionPatternNode : public DFPatternNode {
public:
tvm::ffi::Array<DFPattern> params; /*!< The parameters of the function */
/*!
* \note Note that in Relax, the function body is a SeqExpr which contains
* 1) SeqExprNode::blocks, which is a list of blocks of statements; and 2)
* SeqExprNode::body, which is an Expr that can be anything. FunctionPattern
* only matches the body of the function (writing patterns to statements is tricky).
*/
DFPattern body; /*!< The body of the function */
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<FunctionPatternNode>()
.def_ro("params", &FunctionPatternNode::params)
.def_ro("body", &FunctionPatternNode::body);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.FunctionPattern", FunctionPatternNode,
DFPatternNode);
};
/*!
* \brief Managed reference to FunctionPatternNode.
* \sa FunctionPatternNode
*/
class FunctionPattern : public DFPattern {
public:
/*!
* \brief Constructor
* \param params The parameters of the function.
* \param body The body of the function.
*/
TVM_DLL FunctionPattern(tvm::ffi::Array<DFPattern> params, DFPattern body);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(FunctionPattern, DFPattern, FunctionPatternNode);
};
/*!
* \brief Pattern to match a tuple of ordered expressions.
* \sa TuplePattern
*/
class TuplePatternNode : public DFPatternNode {
public:
tvm::ffi::Array<DFPattern> fields; /*!< The fields of the tuple */
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TuplePatternNode>().def_ro("fields", &TuplePatternNode::fields);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.TuplePattern", TuplePatternNode, DFPatternNode);
};
/*!
* \brief Managed reference to TuplePatternNode.
* \sa TuplePatternNode
*/
class TuplePattern : public DFPattern {
public:
TVM_DLL explicit TuplePattern(tvm::ffi::Array<DFPattern> fields);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TuplePattern, DFPattern, TuplePatternNode);
};
/*!
* \brief A pattern to match multiple expressions unorderedly.
* \sa UnorderedTuplePattern
*/
class UnorderedTuplePatternNode : public DFPatternNode {
public:
tvm::ffi::Array<DFPattern> fields; /*!< The fields of the tuple */
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<UnorderedTuplePatternNode>().def_ro("fields",
&UnorderedTuplePatternNode::fields);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.UnorderedTuplePattern", UnorderedTuplePatternNode,
DFPatternNode);
};
/*!
* \brief Managed reference to UnorderedTuplePatternNode.
* \sa UnorderedTuplePatternNode
*/
class UnorderedTuplePattern : public DFPattern {
public:
TVM_DLL explicit UnorderedTuplePattern(tvm::ffi::Array<DFPattern> fields);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(UnorderedTuplePattern, DFPattern,
UnorderedTuplePatternNode);
};
/*!
* \brief A pattern to match n'th indexing to a tuple.
* \sa TupleGetItem
* \sa TupleGetItemPattern
*/
class TupleGetItemPatternNode : public DFPatternNode {
public:
DFPattern tuple; /*!< The tuple Expression */
int index; /*!< The index of the tuple with -1 meaning arbitrary */
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TupleGetItemPatternNode>()
.def_ro("tuple", &TupleGetItemPatternNode::tuple)
.def_ro("index", &TupleGetItemPatternNode::index);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.TupleGetItemPattern", TupleGetItemPatternNode,
DFPatternNode);
};
/*!
* \brief Managed reference to TupleGetItemPatternNode.
* \sa TupleGetItemPatternNode
*/
class TupleGetItemPattern : public DFPattern {
public:
TVM_DLL TupleGetItemPattern(DFPattern tuple, int index);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TupleGetItemPattern, DFPattern,
TupleGetItemPatternNode);
};
/*!
* \brief Match a conjunction of other patterns.
* \sa AndPattern
*/
class AndPatternNode : public DFPatternNode {
public:
DFPattern left; /*!< The left hand side of the conjunction */
DFPattern right; /*!< The right hand side of the conjunction */
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<AndPatternNode>()
.def_ro("left", &AndPatternNode::left)
.def_ro("right", &AndPatternNode::right);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.AndPattern", AndPatternNode, DFPatternNode);
};
/*!
* \brief Managed reference to AndPatternNode.
* \sa AndPatternNode
*/
class AndPattern : public DFPattern {
public:
TVM_DLL AndPattern(DFPattern lhs, DFPattern rhs);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(AndPattern, DFPattern, AndPatternNode);
};
/*!
* \brief Match a disjunction of other patterns.
* \sa OrPattern
*/
class OrPatternNode : public DFPatternNode {
public:
DFPattern left; /*!< The left hand side of the disjunction */
DFPattern right; /*!< The right hand side of the disjunction */
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<OrPatternNode>()
.def_ro("left", &OrPatternNode::left)
.def_ro("right", &OrPatternNode::right);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.OrPattern", OrPatternNode, DFPatternNode);
};
/*!
* \brief Managed reference to OrPatternNode.
* \sa OrPatternNode
*/
class OrPattern : public DFPattern {
public:
TVM_DLL OrPattern(DFPattern left, DFPattern right);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(OrPattern, DFPattern, OrPatternNode);
};
/*!
* \brief Pattern for rejecting a certain pattern.
* \sa NotPattern
*/
class NotPatternNode : public DFPatternNode {
public:
DFPattern reject; /*!< The pattern to reject */
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<NotPatternNode>().def_ro("reject", &NotPatternNode::reject);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.NotPattern", NotPatternNode, DFPatternNode);
};
/*!
* \brief Managed reference to NotPatternNode.
* \sa NotPatternNode
*/
class NotPattern : public DFPattern {
public:
TVM_DLL NotPattern(DFPattern reject);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(NotPattern, DFPattern, NotPatternNode);
};
/*!
* \brief Wildcard Pattern is a pattern that can match anything.
* \sa WildcardPattern
*/
class WildcardPatternNode : public DFPatternNode {
public:
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<WildcardPatternNode>();
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.WildcardPattern", WildcardPatternNode,
DFPatternNode);
};
/*!
* \brief Managed reference to WildcardPatternNode.
* \sa WildcardPatternNode
*/
class WildcardPattern : public DFPattern {
public:
WildcardPattern();
explicit WildcardPattern(ffi::ObjectPtr<WildcardPatternNode> data)
: DFPattern(ffi::UnsafeInit{}) {
TVM_FFI_ICHECK(data != nullptr);
data_ = std::move(data);
}
// Declaring WildcardPattern declared as non-nullable avoids the
// default zero-parameter constructor for ffi::ObjectRef with `data_ =
// nullptr`. This allows a zero-parameter constructor to be
// declared here, to create a valid wildcard instance.
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(WildcardPattern, DFPattern, WildcardPatternNode);
};
/*!
* \brief Pattern for matching a certain type.
* \sa TypePattern
*/
class TypePatternNode : public DFPatternNode {
public:
DFPattern pattern; /*!< The pattern to match */
Type ty = Type::Missing(); /*!< The type to match */
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TypePatternNode>()
.def_ro("pattern", &TypePatternNode::pattern)
.def_ro("ty", &TypePatternNode::ty);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.TypePattern", TypePatternNode, DFPatternNode);
};
class TypePattern : public DFPattern {
public:
TVM_DLL TypePattern(DFPattern pattern, Type ty);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(TypePattern, DFPattern, TypePatternNode);
};
/*!
* \brief A pattern that asserting a root pattern has a certain shape.
* \sa ShapePattern
*/
class ShapePatternNode : public DFPatternNode {
public:
DFPattern pattern; /*!< The root pattern to match */
ffi::Array<PrimExpr> shape; /*!< The shape to match */
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ShapePatternNode>()
.def_ro("pattern", &ShapePatternNode::pattern)
.def_ro("shape", &ShapePatternNode::shape);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.ShapePattern", ShapePatternNode, DFPatternNode);
};
/*!
* \brief Managed reference to ShapePatternNode.
* \sa ShapePatternNode
*/
class ShapePattern : public DFPattern {
public:
TVM_DLL ShapePattern(DFPattern pattern, ffi::Array<PrimExpr> type);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(ShapePattern, DFPattern, ShapePatternNode);
};
/*!
* \brief A pattern that asserting multiple root patterns have the same shape
* \sa SameShapePattern
*/
class SameShapeConstraintNode : public DFConstraintNode {
public:
ffi::Array<DFPattern> args; /*!< The patterns with matching shapes */
ffi::Array<DFPattern> GetDependentPatterns() const override { return args; }
std::tuple<PrimExpr, bool> AsCondition(
std::function<ffi::Optional<Var>(const DFPatternNode*)> match_state) const override;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SameShapeConstraintNode>().def_ro("args", &SameShapeConstraintNode::args);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.SameShapeConstraint", SameShapeConstraintNode,
DFConstraintNode);
};
/*!
* \brief Managed reference to SameShapePatternNode.
* \sa SameShapePatternNode
*/
class SameShapeConstraint : public DFConstraint {
public:
TVM_DLL SameShapeConstraint(ffi::Array<DFPattern> args);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(SameShapeConstraint, DFConstraint,
SameShapeConstraintNode);
};
/*!
* \brief A pattern that asserting a root pattern has a certain data type.
* \sa DataTypePattern
*/
class DataTypePatternNode : public DFPatternNode {
public:
DFPattern pattern; /*!< The root pattern to match */
DLDataType dtype; /*!< The data type to match */
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<DataTypePatternNode>()
.def_ro("pattern", &DataTypePatternNode::pattern)
.def_ro("dtype", &DataTypePatternNode::dtype);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.DataTypePattern", DataTypePatternNode,
DFPatternNode);
};
/*!
* \brief Managed reference to DataTypePatternNode.
* \sa DataTypePatternNode
*/
class DataTypePattern : public DFPattern {
public:
TVM_DLL DataTypePattern(DFPattern pattern, DLDataType dtype);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(DataTypePattern, DFPattern, DataTypePatternNode);
};
/*!
* \brief A pattern that asserting a root pattern has certain attributes.
* \sa AttrPattern
*/
class AttrPatternNode : public DFPatternNode {
public:
DFPattern pattern; /*!< The root pattern to match */
DictAttrs attrs; /*!< The attributes (a map/dictionary) to match */
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<AttrPatternNode>()
.def_ro("pattern", &AttrPatternNode::pattern)
.def_ro("attrs", &AttrPatternNode::attrs);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.AttrPattern", AttrPatternNode, DFPatternNode);
};
/*!
* \brief Managed reference to AttrPatternNode.
* \sa AttrPatternNode
*/
class AttrPattern : public DFPattern {
public:
TVM_DLL AttrPattern(DFPattern pattern, DictAttrs attrs);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(AttrPattern, DFPattern, AttrPatternNode);
};
/*!
* \brief A pattern of external function.
* \sa ExternFunc
* \sa ExternFuncPattern
*/
class ExternFuncPatternNode : public DFPatternNode {
public:
ffi::String global_symbol_; /*!< The global symbol name of the external function */
/*! \brief The external function name */
const ffi::String& global_symbol() const { return global_symbol_; }
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ExternFuncPatternNode>().def_ro("global_symbol",
&ExternFuncPatternNode::global_symbol_);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.dpl.ExternFuncPattern", ExternFuncPatternNode,
DFPatternNode);
};
/*!
* \brief Managed reference to ExternFuncPatternNode.
* \sa ExternFuncPatternNode
*/
class ExternFuncPattern : public DFPattern {
public:
TVM_DLL ExternFuncPattern(ffi::String global_symbol);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(ExternFuncPattern, DFPattern, ExternFuncPatternNode);
};
/*! \brief Syntatic Sugar for creating a VarPattern with a name */
VarPattern IsVar(const ffi::String& name);
/*! \brief Syntatic Sugar for creating a ConstantPattern */
ConstantPattern IsConst();
/*! \brief Syntatic Sugar for creating a WildcardPattern */
WildcardPattern Wildcard();
/*! \brief Syntatic Sugar for creating a ExprPattern */
ExprPattern IsExpr(const Expr& expr);
/*! \brief Syntatic Sugar for creating a ExprPattern base on an Op */
ExprPattern IsOp(const ffi::String& op_name);
/*! \brief Syntatic Sugar for call_tir (return a tensor) */
// Todo(relax-team): Dataflow pattern for Type, and match out_ty
CallPattern IsCallTIR(const ffi::String& name, ffi::Optional<TuplePattern> args = std::nullopt);
/*! \brief Syntatic Sugar for call_tir (return a tuple of tensor) */
CallPattern IsCallTIR(const ffi::String& name, TuplePattern var_args);
/*! \brief Syntatic Sugar for call_dps_packed (return a tensor) */
CallPattern IsCallDPSPacked(const ffi::String& name,
ffi::Optional<TuplePattern> args = std::nullopt);
/*! \brief Syntatic Sugar for call_dps_packed (return a tuple of tensor) */
CallPattern IsCallDPSPacked(const ffi::String& name, TuplePattern var_args);
/*! \brief Syntatic Sugar for creating TuplePattern or UnorderedTuplePattern (unordered=true) */
DFPattern IsTuple(const ffi::Array<DFPattern>& fields, bool unordered = false);
/*! \brief Syntatic Sugar for creating a TupleGetItemPattern */
TupleGetItemPattern IsTupleGetItem(const DFPattern tuple, int index = -1);
/*! \brief Implementation of the templated CallPattern syntax sugar */
template <typename... Args>
CallPattern DFPattern::operator()(Args&&... args) const {
return CallPattern(ffi::GetRef<DFPattern>(this->get()),
ffi::Array<DFPattern>({std::forward<Args>(args)...}));
}
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_DATAFLOW_PATTERN_H_
@@ -0,0 +1,185 @@
/*
* 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.
*/
/*!
* \file tvm/relax/dataflow_pattern_functor.h
* \brief Functors and visitors for dataflow patterns.
*/
#ifndef TVM_RELAX_DATAFLOW_PATTERN_FUNCTOR_H_
#define TVM_RELAX_DATAFLOW_PATTERN_FUNCTOR_H_
#include <tvm/relax/dataflow_pattern.h>
#include <unordered_set>
#include <utility>
namespace tvm {
namespace relax {
/*!
* \brief A dynamical functor that dispatches on in the first DFPattern argument.
*
* \tparam FType function signature
* This type is only defined for FType with function signature R(const DFPattern&,
* Args...)
*/
template <typename FType>
class DFPatternFunctor;
// functions to be overriden.
#define DFPATTERN_FUNCTOR_DEFAULT \
{ \
return VisitDFPatternDefault_(op, std::forward<Args>(args)...); \
}
#define RELAX_DFPATTERN_FUNCTOR_DISPATCH(OP) \
vtable.template set_dispatch<OP>([](const ffi::ObjectRef& n, TSelf* self, Args... args) { \
return self->VisitDFPattern_(static_cast<const OP*>(n.get()), std::forward<Args>(args)...); \
});
template <typename R, typename... Args>
class DFPatternFunctor<R(const DFPattern& n, Args...)> {
private:
using TSelf = DFPatternFunctor<R(const DFPattern& n, Args...)>;
using FType = tvm::NodeFunctor<R(const ffi::ObjectRef& n, TSelf* self, Args...)>;
public:
/*! \brief virtual destructor */
virtual ~DFPatternFunctor() {}
/*!
* \brief Same as call.
* \param n The expression node.
* \param args Additional arguments.
* \return The result of the call
*/
R operator()(const DFPattern& n, Args... args) {
return VisitDFPattern(n, std::forward<Args>(args)...);
}
/*!
* \brief The functor call.
* \param n The expression node.
* \param args Additional arguments.
* \return The result of the call
*/
virtual R VisitDFPattern(const DFPattern& n, Args... args) {
TVM_FFI_ICHECK(n.defined());
static FType vtable = InitVTable();
return vtable(n, this, std::forward<Args>(args)...);
}
// Functions that can be overriden by subclass
virtual R VisitDFPattern_(const OrPatternNode* op, Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const AndPatternNode* op, Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const NotPatternNode* op, Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const AttrPatternNode* op, Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const CallPatternNode* op, Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const ConstantPatternNode* op, Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const DataTypePatternNode* op, Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const ExprPatternNode* op, Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const FunctionPatternNode* op, Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const ShapePatternNode* op, Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const TupleGetItemPatternNode* op,
Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const TuplePatternNode* op, Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const TypePatternNode* op, Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const WildcardPatternNode* op, Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const VarPatternNode* op, Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const DataflowVarPatternNode* op,
Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const GlobalVarPatternNode* op, Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const ExternFuncPatternNode* op,
Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const PrimArrPatternNode* op, Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPattern_(const UnorderedTuplePatternNode* op,
Args... args) DFPATTERN_FUNCTOR_DEFAULT;
virtual R VisitDFPatternDefault_(const ffi::Object* op, Args...) {
TVM_FFI_THROW(InternalError) << "Do not have a default for " << op->GetTypeKey();
throw;
}
private:
// initialize the vtable.
static FType InitVTable() {
FType vtable;
// Set dispatch
RELAX_DFPATTERN_FUNCTOR_DISPATCH(OrPatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(AndPatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(NotPatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(AttrPatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(CallPatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(ConstantPatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(DataTypePatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(ExprPatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(FunctionPatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(ShapePatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(TupleGetItemPatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(TuplePatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(TypePatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(WildcardPatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(VarPatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(DataflowVarPatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(GlobalVarPatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(ExternFuncPatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(PrimArrPatternNode);
RELAX_DFPATTERN_FUNCTOR_DISPATCH(UnorderedTuplePatternNode);
vtable.Finalize();
return vtable;
}
};
/*!
* \brief A simple visitor wrapper around DFPatternFunctor.
* Recursively visit the content.
*
* DFPatternVisitor treats the Pattern as dataflow graph,and only visit each Expr node once.
*/
class DFPatternVisitor : public DFPatternFunctor<void(const DFPattern&)> {
public:
void VisitDFPattern(const DFPattern& pattern) override;
void VisitDFPattern_(const OrPatternNode* op) override;
void VisitDFPattern_(const AndPatternNode* op) override;
void VisitDFPattern_(const NotPatternNode* op) override;
void VisitDFPattern_(const AttrPatternNode* op) override;
void VisitDFPattern_(const CallPatternNode* op) override;
void VisitDFPattern_(const ConstantPatternNode* op) override;
void VisitDFPattern_(const DataTypePatternNode* op) override;
void VisitDFPattern_(const ExprPatternNode* op) override;
void VisitDFPattern_(const FunctionPatternNode* op) override;
void VisitDFPattern_(const ShapePatternNode* op) override;
void VisitDFPattern_(const TupleGetItemPatternNode* op) override;
void VisitDFPattern_(const TuplePatternNode* op) override;
void VisitDFPattern_(const TypePatternNode* op) override;
void VisitDFPattern_(const WildcardPatternNode* op) override;
void VisitDFPattern_(const VarPatternNode* op) override;
void VisitDFPattern_(const DataflowVarPatternNode* op) override;
void VisitDFPattern_(const GlobalVarPatternNode* op) override;
void VisitDFPattern_(const ExternFuncPatternNode* op) override;
void VisitDFPattern_(const PrimArrPatternNode* op) override;
void VisitDFPattern_(const UnorderedTuplePatternNode* op) override;
protected:
// set of already-visited nodes
std::unordered_set<const ffi::Object*> visited_;
};
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_DATAFLOW_PATTERN_FUNCTOR_H_
@@ -0,0 +1,480 @@
/*
* 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_DISTRIBUTED_AXIS_GROUP_GRAPH_H_
#define TVM_RELAX_DISTRIBUTED_AXIS_GROUP_GRAPH_H_
#include <tvm/arith/iter_affine_map.h>
#include <tvm/relax/distributed/type.h>
#include <tvm/relax/expr.h>
#include <tvm/tirx/function.h>
#include <tvm/tirx/stmt_functor.h>
#include <algorithm>
#include <limits>
#include <string>
#include <tuple>
#include <unordered_map>
#include <unordered_set>
#include <utility>
#include <vector>
namespace tvm {
namespace tirx {
// (var, axis)
using TIRVarAxis = std::pair<Var, int>;
// (buffer, axis)
using BufferAxis = std::pair<Buffer, int>;
class BufferAxisHash {
public:
size_t operator()(const BufferAxis& buffer_axis) const {
size_t const h1(ffi::ObjectPtrHash()(buffer_axis.first));
size_t const h2(std::hash<int>()(buffer_axis.second));
return h1 ^ (h2 << 1);
}
};
/*!
* \brief Suppose we want to shard a buffer along a specific dimension, we need to know how
* to rewrite the access index of the buffer. To make it simple, we only support the case that
* the access can be rewritten by changing the extent of an iter var.
* \param index The access index
* \param var_range The range of each iter var
* \param analyzer The analyzer
* \return The iter var whose extent to be changed
*/
Var GetShardingVarFromIndex(PrimExpr index, ffi::Map<Var, Range> var_range,
const arith::Analyzer& analyzer);
/*!
* \brief Construct an axis group graph from a PrimFunc. Two buffer axis are connected if they
* are accessed by the same index.
*/
class BufferAxisGraphExtractor : public StmtExprVisitor {
public:
static std::vector<std::vector<TIRVarAxis>> GetTIRVarAxisGraph(const PrimFunc& prim_func) {
BufferAxisGraphExtractor extractor;
extractor(prim_func->body);
ffi::Map<Buffer, Var> inverse_buffer_map;
for (const auto& pr : prim_func->buffer_map) {
inverse_buffer_map.Set(pr.second, pr.first);
}
std::vector<std::vector<TIRVarAxis>> tir_var_axis_group_list;
std::unordered_set<BufferAxis, BufferAxisHash> visited;
for (const auto& pr : prim_func->buffer_map) {
Var param = pr.first;
Buffer buffer = pr.second;
for (int i = 0; i < static_cast<int>(buffer->shape.size()); i++) {
if (extractor.buffer_axis_graph_.count({buffer, i})) {
std::vector<BufferAxis> buffer_axis_group;
extractor.DFSGraph({buffer, i}, &visited, &buffer_axis_group);
if (buffer_axis_group.size() <= 1) {
continue;
}
std::vector<TIRVarAxis> tir_var_axis_group;
for (const auto& buffer_axis : buffer_axis_group) {
if (!inverse_buffer_map.count(buffer_axis.first)) {
continue;
}
tir_var_axis_group.push_back(
{inverse_buffer_map[buffer_axis.first], buffer_axis.second});
}
tir_var_axis_group_list.push_back(tir_var_axis_group);
}
}
}
return tir_var_axis_group_list;
}
void DFSGraph(BufferAxis cur, std::unordered_set<BufferAxis, BufferAxisHash>* visited,
std::vector<BufferAxis>* buffer_axis_group) {
if (visited->count(cur)) {
return;
}
visited->insert(cur);
buffer_axis_group->push_back(cur);
for (const auto& next : buffer_axis_graph_[cur]) {
DFSGraph(next, visited, buffer_axis_group);
}
}
private:
void VisitStmt_(const BufferStoreNode* op) final {
StmtExprVisitor::VisitStmt_(op);
buffer_access_indices_.push_back({op->buffer, op->indices});
}
void VisitExpr_(const BufferLoadNode* op) final {
StmtExprVisitor::VisitExpr_(op);
buffer_access_indices_.push_back({op->buffer, op->indices});
}
bool Match(PrimExpr a, PrimExpr buffer_shape_a, PrimExpr b, PrimExpr buffer_shape_b,
const arith::Analyzer& analyzer) {
if (b.as<VarNode>()) {
std::swap(a, b);
std::swap(buffer_shape_a, buffer_shape_b);
}
if (!a.as<VarNode>()) {
return false;
}
Var var = a.as_or_throw<Var>();
analyzer->Bind(iter_var_range_);
b = analyzer->Simplify(b);
// index var `a` must access whole range of a specific buffer dimension
arith::IntSet intset_b = arith::EvalSet(b, arith::AsIntSet(iter_var_range_));
if (!analyzer->CanProveEqual(buffer_shape_a, iter_var_range_[var]->extent) ||
!intset_b.MatchRange(Range::FromMinExtent(0, buffer_shape_b))) {
return false;
}
Var matched_var = GetShardingVarFromIndex(b, iter_var_range_, analyzer);
if (!matched_var.same_as(var)) {
return false;
}
return true;
}
void VisitStmt_(const SBlockNode* op) final {
if (op->name_hint == "root") {
StmtExprVisitor::VisitStmt_(op);
return;
}
buffer_access_indices_.clear();
StmtExprVisitor::VisitStmt_(op);
iter_var_range_.clear();
for (const auto& iter_var : op->iter_vars) {
iter_var_range_.Set(iter_var->var, iter_var->dom);
}
arith::Analyzer analyzer;
for (const auto& access_pr : buffer_access_indices_) {
Buffer buffer = access_pr.first;
ffi::Array<PrimExpr> indices = access_pr.second;
for (int i = 0; i < static_cast<int>(indices.size()); i++) {
for (const auto& another_access_pr : buffer_access_indices_) {
if (another_access_pr.first.same_as(buffer)) {
continue;
}
Buffer another_buffer = another_access_pr.first;
ffi::Array<PrimExpr> another_indices = another_access_pr.second;
for (int j = 0; j < static_cast<int>(another_indices.size()); j++) {
if (Match(indices[i], buffer->shape[i], another_indices[j], another_buffer->shape[j],
analyzer)) {
JoinBufferAxis({buffer, i}, {another_buffer, j});
}
}
}
}
}
}
void JoinBufferAxis(BufferAxis axis1, BufferAxis axis2) {
if (!buffer_axis_graph_.count(axis1)) {
buffer_axis_graph_[axis1] = {};
}
if (!buffer_axis_graph_.count(axis2)) {
buffer_axis_graph_[axis2] = {};
}
buffer_axis_graph_[axis1].push_back(axis2);
buffer_axis_graph_[axis2].push_back(axis1);
}
std::vector<std::pair<Buffer, ffi::Array<PrimExpr>>> buffer_access_indices_;
std::unordered_map<BufferAxis, std::vector<BufferAxis>, BufferAxisHash> buffer_axis_graph_;
ffi::Map<Var, Range> iter_var_range_;
std::string func_name;
};
} // namespace tirx
} // namespace tvm
namespace tvm {
namespace relax {
namespace distributed {
/*! \brief tensor axis*/
struct Axis {
const ExprNode* tensor;
int dim = 0;
int tuple_index = 0;
Axis(const ExprNode* tensor, int dim, int tuple_index = 0)
: tensor(tensor), dim(dim), tuple_index(tuple_index) {
TVM_FFI_ICHECK(tensor->IsInstance<ConstantNode>() || tensor->IsInstance<VarNode>());
}
bool operator==(const Axis& other) const {
return tensor == other.tensor && dim == other.dim && tuple_index == other.tuple_index;
}
};
class AxisHash {
public:
size_t operator()(const Axis& axis) const {
size_t const h1(std::hash<const ExprNode*>()(axis.tensor));
size_t const h2(std::hash<int>()(axis.dim));
size_t const h3(std::hash<int>()(axis.tuple_index));
return h1 ^ (h2 << 1) ^ (h3 << 2);
}
};
using AxisGroup = std::unordered_set<Axis, AxisHash>;
class AxisGroupHash {
public:
size_t operator()(const AxisGroup& axis_group) const {
size_t seed = 0;
for (auto axis : axis_group) {
seed ^= AxisHash()(axis) + 0x9e3779b9 + (seed << 6) + (seed >> 2);
}
return seed;
}
};
using ShardingSpec = std::pair<DeviceMesh, Placement>;
// device mesh and the device mesh axis that the tensor axis maps to
using AxisShardingSpec = std::pair<DeviceMesh, int>;
class AxisShardingSpecEqual {
public:
bool operator()(const AxisShardingSpec& lhs, const AxisShardingSpec& rhs) const {
return ffi::StructuralEqual()(lhs.first, rhs.first) && lhs.second == rhs.second;
}
};
class AxisShardingSpecHash {
public:
size_t operator()(const AxisShardingSpec& sharding_spec) const {
size_t seed = 0;
seed ^= ffi::StructuralHash()(sharding_spec.first);
seed ^= std::hash<int>()(sharding_spec.second) << 1;
return seed;
}
};
/*!
* \brief A graph whose nodes are tensor axes, and the edge means some information can be propagated
* through the two axes. Although it only does sharding propagation, this data structure can be
* extended to perform all kinds of propagation that happens on tensor axes.
*/
class AxisGroupGraph {
public:
enum class EdgeType { kAscend, kDescend, kSimbling };
private:
static EdgeType ReverseEdgeType(EdgeType type) {
switch (type) {
case EdgeType::kAscend:
return EdgeType::kDescend;
case EdgeType::kDescend:
return EdgeType::kAscend;
case EdgeType::kSimbling:
return EdgeType::kSimbling;
}
TVM_FFI_THROW(InternalError) << "Unreachable code";
throw;
}
static int GetEdgePriority(EdgeType type) {
switch (type) {
case EdgeType::kAscend:
return 0;
case EdgeType::kDescend:
return 2;
case EdgeType::kSimbling:
return 1;
}
TVM_FFI_THROW(InternalError) << "Unreachable code";
throw;
}
struct AxisGraphEdge {
Axis src;
Axis dst;
// the producer-consumer relationship between src tensor and dst tensor
// kAscend means consumer->producer
// kDescend means producer->consumer
// kSimbling means other cases
EdgeType type;
bool operator==(const AxisGraphEdge& other) const {
return src == other.src && dst == other.dst && type == other.type;
}
};
struct Path {
int direction = 0;
Path AddEdge(EdgeType type) { return {direction |= (1 << GetEdgePriority(type))}; }
int GetPriority() const {
switch (direction) {
case 1: // ascend only
return 0;
case 4: // descend only
return 2;
case 0: // empty path (source node)
return 3; // source node must have max priority
default: // mixed path
return 1;
}
}
};
public:
AxisGroupGraph() = default;
/*!
* \brief add edge between two axes
* \param axis1 The src axis
* \param axis2 The dst axis
* \param type The producer-consumer relationship between src tensor and dst tensor
* kAscend means consumer->producer
* kDescend means producer->consumer
* kSimbling means other cases
*/
void JoinAxis(Axis axis1, Axis axis2, EdgeType type) {
AddEdge(axis1, axis2, type);
AddEdge(axis2, axis1, ReverseEdgeType(type));
}
/*!
* \brief add a source shardingspec to propagate
* \param axis The source axis
* \param spec The axis's sharding spec
*/
void AddSrcShardingPoint(Axis axis, AxisShardingSpec spec) {
src_axis_sharding_spec_[axis] = spec;
}
/*!
* \brief propagate sharding specs from source axes
*/
void PropagateShardingSpec() {
axis_sharding_specs_priority_.clear();
for (const auto& pr : src_axis_sharding_spec_) {
std::unordered_set<Axis, AxisHash> visited;
PropagateShardingSpec(pr.first, pr.second, Path(), &visited);
}
ChooseAxisShardingSpec();
}
/*!
* \brief add a cut point that stops the propagation of a certain sharding spec
*
* \param axis The cut point
* \param spec The spec to stop propagation
*/
void AddPropagationCutPoint(Axis axis, AxisShardingSpec spec) {
cutpoint_axis_sharding_spec_[axis] = spec;
}
/*!
* \brief Get the Sharding Spec of an axis after propagation
*
* \param axis the specified axis
* \return if a sharding spec is found, return (axis_sharding_spec, true)
* otherwise, return (null axis_sharding_spec, false)
*/
std::tuple<AxisShardingSpec, bool> GetAxisShardingSpec(Axis axis) {
if (axis_sharding_specs_priority_.count(axis)) {
return {axis_sharding_specs_priority_[axis].begin()->first, true};
} else {
return {{DeviceMesh(), -1}, false};
}
}
private:
void AddEdge(Axis src, Axis dst, EdgeType type) {
if (!graph_.count(src)) {
graph_[src] = {};
}
graph_[src].push_back({src, dst, type});
}
void PropagateShardingSpec(Axis axis, AxisShardingSpec spec, Path path,
std::unordered_set<Axis, AxisHash>* visited) {
if (cutpoint_axis_sharding_spec_.count(axis) ||
(src_axis_sharding_spec_.count(axis) &&
!AxisShardingSpecEqual()(src_axis_sharding_spec_[axis], spec)) ||
visited->count(axis)) {
return;
}
visited->insert(axis);
if (!axis_sharding_specs_priority_.count(axis)) {
axis_sharding_specs_priority_[axis] = {};
}
axis_sharding_specs_priority_[axis][spec] = path.GetPriority();
for (auto edge : graph_[axis]) {
PropagateShardingSpec(edge.dst, spec, path.AddEdge(edge.type), visited);
}
}
void ChooseAxisShardingSpec() {
for (auto& pr : axis_sharding_specs_priority_) {
auto& axis = pr.first;
auto& specs = pr.second;
int max_priority = std::numeric_limits<int>::min();
for (auto& pr2 : specs) {
max_priority = std::max(max_priority, pr2.second);
}
for (auto it = specs.begin(); it != specs.end();) {
if (it->second != max_priority) {
it = specs.erase(it);
} else {
it++;
}
}
TVM_FFI_ICHECK(specs.size() == 1)
<< "multiple possible sharding for axis: (" << ffi::GetRef<Expr>(axis.tensor) << ", "
<< axis.dim << ")";
}
}
// union set
std::unordered_map<Axis, std::vector<AxisGraphEdge>, AxisHash> graph_;
std::unordered_map<Axis, AxisShardingSpec, AxisHash> src_axis_sharding_spec_;
std::unordered_map<Axis, AxisShardingSpec, AxisHash> cutpoint_axis_sharding_spec_;
std::unordered_map<
Axis, std::unordered_map<AxisShardingSpec, int, AxisShardingSpecHash, AxisShardingSpecEqual>,
AxisHash>
axis_sharding_specs_priority_;
};
using FBuildAxisGraph = std::function<void(const Var& output_var, const Call& call,
distributed::AxisGroupGraph* axis_group_graph)>;
void BuildAxisGraphUnary(const Var& output_var, const Call& call,
distributed::AxisGroupGraph* axis_group_graph);
void BuildAxisGraphBinary(const Var& output_var, const Call& call,
distributed::AxisGroupGraph* axis_group_graph);
void BuildAxisGraphReduce(const Var& output_var, const Call& call,
distributed::AxisGroupGraph* axis_group_graph);
void BuildAxisGraphMatmul(const Var& output_var, const Call& call,
distributed::AxisGroupGraph* axis_group_graph);
void BuildAxisGraphPermuteDims(const Var& output_var, const Call& call,
distributed::AxisGroupGraph* axis_group_graph);
void BuildAxisGraphReshape(const Var& output_var, const Call& call,
distributed::AxisGroupGraph* axis_group_graph);
void BuildAxisGraphCallTIR(const Var& output_var, const Call& call, const tirx::PrimFunc& func,
distributed::AxisGroupGraph* axis_group_graph);
} // namespace distributed
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_DISTRIBUTED_AXIS_GROUP_GRAPH_H_
@@ -0,0 +1,74 @@
/*
* 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.
*/
/*!
* \file tvm/relax/distributed/global_info.h
* \brief Data structure for distributed inference
*/
#ifndef TVM_RELAX_DISTRIBUTED_GLOBAL_INFO_H_
#define TVM_RELAX_DISTRIBUTED_GLOBAL_INFO_H_
#include <tvm/ffi/container/shape.h>
#include <tvm/ir/expr.h>
#include <tvm/ir/module.h>
namespace tvm {
namespace relax {
namespace distributed {
/*
* \brief Device mesh express a view of topology of devices, represented by an n-d matrix of
* device ids
*/
class DeviceMeshNode : public GlobalInfoNode {
public:
/*! \brief logical shape of the mesh*/
ffi::Shape shape;
/*! \brief device ids in the mesh*/
ffi::Array<int64_t> device_ids;
/*! \brief Optionally use range to represent device_ids*/
ffi::Optional<Range> device_range;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<DeviceMeshNode>()
.def_ro("shape", &DeviceMeshNode::shape)
.def_ro("device_ids", &DeviceMeshNode::device_ids)
.def_ro("device_range", &DeviceMeshNode::device_range);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.distributed.DeviceMesh", DeviceMeshNode, GlobalInfoNode);
};
/*!
* \brief Managed reference to a DeviceMesh.
* \sa DeviceMeshNode
*/
class DeviceMesh : public GlobalInfo {
public:
TVM_DLL DeviceMesh(ffi::Shape shape, ffi::Array<int64_t> device_ids);
TVM_DLL DeviceMesh(ffi::Shape shape, Range device_range);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(DeviceMesh, GlobalInfo, DeviceMeshNode);
};
} // namespace distributed
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_DISTRIBUTED_GLOBAL_INFO_H_
+78
View File
@@ -0,0 +1,78 @@
/*
* 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.
*/
/*!
* \file tvm/relax/distributed/transform.h
* \brief Relax distributed specific transformation passes.
*/
#ifndef TVM_RELAX_DISTRIBUTED_TRANSFORM_H_
#define TVM_RELAX_DISTRIBUTED_TRANSFORM_H_
#include <tvm/ir/transform.h>
#include <tvm/relax/dataflow_pattern.h>
#include <tvm/relax/expr.h>
#include <tvm/relax/transform.h>
#include <tvm/tirx/function.h>
#include <tvm/tirx/index_map.h>
namespace tvm {
namespace relax {
namespace distributed {
namespace transform {
using Pass = tvm::transform::Pass;
using PassInfo = tvm::transform::PassInfo;
using PassContext = tvm::transform::PassContext;
using Function = tvm::relax::Function;
using DataflowBlock = tvm::relax::DataflowBlock;
using tvm::transform::CreateModulePass;
/*!
* \brief Propagate sharding information.
*
* \return The Pass.
*/
TVM_DLL Pass PropagateSharding();
/*!
* \brief Lower global view TensorIR into local view.
*
* \return The Pass.
*/
TVM_DLL Pass LowerGlobalViewToLocalView();
/*!
* \brief Legalize redistribute op to ccl op.
*
* \return The Pass.
*/
TVM_DLL Pass LegalizeRedistribute();
/*!
* \brief Lower DistIR to Relax
*
* \return The Pass.
*/
TVM_DLL Pass LowerDistIR();
} // namespace transform
} // namespace distributed
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_DISTRIBUTED_TRANSFORM_H_
+175
View File
@@ -0,0 +1,175 @@
/*
* 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.
*/
/*!
* \file tvm/relax/distributed/type.h
* \brief Type definitions for DTensor (Distributed Tensor)
*/
#ifndef TVM_RELAX_DISTRIBUTED_TYPE_H_
#define TVM_RELAX_DISTRIBUTED_TYPE_H_
#include <tvm/relax/distributed/global_info.h>
#include <tvm/relax/type.h>
#include <utility>
namespace tvm {
namespace relax {
namespace distributed {
enum class PlacementSpecKind : int { kSharding = 0, kReplica = 1 };
/*! \brief Describes how data is distributed in one dimension of the device mesh*/
class PlacementSpecNode : public ffi::Object {
public:
/*! \brief If the kind is sharding, this value represents the tensor dimension to shard.
* otherwise, axis is -1.
*/
int axis;
/*! \brief The kind of placement spec. Possible values: kSharding and kReplica. */
PlacementSpecKind kind;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PlacementSpecNode>()
.def_ro("axis", &PlacementSpecNode::axis)
.def_ro("kind", &PlacementSpecNode::kind);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindConstTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO("relax.distributed.PlacementSpec", PlacementSpecNode, ffi::Object);
};
/*!
* \brief Managed reference to PlacementSpecNode.
* \sa PlacementSpecNode
*/
class PlacementSpec : public ffi::ObjectRef {
public:
TVM_DLL static PlacementSpec Sharding(int axis);
TVM_DLL static PlacementSpec Replica();
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(PlacementSpec, ffi::ObjectRef, PlacementSpecNode);
};
class ShardingNode : public PlacementSpecNode {
public:
/*! \brief The dimension of tensor we shard*/
int64_t sharding_dim;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ShardingNode>().def_ro("sharding_dim", &ShardingNode::sharding_dim);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.distributed.Sharding", ShardingNode, PlacementSpecNode);
};
/*! \brief Describes how data is distributed in each dimension of the device mesh*/
class PlacementNode : public ffi::Object {
public:
/*! \brief specs for each dim of device mesh.*/
ffi::Array<PlacementSpec> dim_specs;
ffi::String ToString() const;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PlacementNode>().def_ro("dim_specs", &PlacementNode::dim_specs);
}
static constexpr TVMFFISEqHashKind _type_s_eq_hash_kind = kTVMFFISEqHashKindConstTreeNode;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.distributed.Placement", PlacementNode, ffi::Object);
};
/*!
* \brief Managed reference to a Placement.
* \sa PlacementNode
*/
class Placement : public ffi::ObjectRef {
public:
TVM_DLL explicit Placement(ffi::Array<PlacementSpec> dim_specs);
/*! \brief replica dim is printed as "R" and sharding dim is printed as "S[i]".]*/
static Placement FromText(ffi::String text_repr);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Placement, ffi::ObjectRef, PlacementNode);
};
/*!
* \brief Type of DTensor (Distributed Tensor).
*/
class DTensorTypeNode : public TypeNode {
public:
explicit DTensorTypeNode(ffi::UnsafeInit)
: tensor_ty(ffi::UnsafeInit{}), device_mesh(), placement() {}
DTensorTypeNode(TensorType tensor_ty, DeviceMesh device_mesh, Placement placement)
: tensor_ty(std::move(tensor_ty)),
device_mesh(std::move(device_mesh)),
placement(std::move(placement)) {}
/*!
* \brief The tensor type carried by the DTensor type.
*/
TensorType tensor_ty;
/*!
* \brief The device mesh of the tensor.
*/
DeviceMesh device_mesh;
/*!
* \brief The placement of the tensor among the device mesh.
*/
Placement placement;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<DTensorTypeNode>()
.def_ro("device_mesh", &DTensorTypeNode::device_mesh)
.def_ro("placement", &DTensorTypeNode::placement)
.def_ro("tensor_ty", &DTensorTypeNode::tensor_ty);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.DTensorType", DTensorTypeNode, TypeNode);
};
/*!
* \brief Managed reference to DTensorTypeNode.
* \sa DTensorTypeNode
*/
class DTensorType : public Type {
public:
/*!
* \brief Construction with device mesh and placement.
* \param tensor_ty The tensor type carried by the DTensor type.
* \param device_mesh The device mesh of the tensor.
* \param placement The placement of the tensor among the device mesh.
* \param span The span of the AST.
*/
TVM_DLL DTensorType(TensorType tensor_ty, DeviceMesh device_mesh, Placement placement,
Span span = Span());
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(DTensorType, Type, DTensorTypeNode);
};
} // namespace distributed
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_DISTRIBUTED_TYPE_H_
+194
View File
@@ -0,0 +1,194 @@
/*
* 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.
*/
/*!
* \file tvm/relax/exec_builder.h
*/
#ifndef TVM_RELAX_EXEC_BUILDER_H_
#define TVM_RELAX_EXEC_BUILDER_H_
#include <tvm/ffi/extra/dataclass.h>
#include <tvm/ffi/extra/structural_equal.h>
#include <tvm/ffi/extra/structural_hash.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/expr.h>
#include <tvm/runtime/vm/bytecode.h>
#include <tvm/runtime/vm/executable.h>
#include <string>
#include <unordered_map>
#include <vector>
namespace tvm {
namespace relax {
namespace vm = tvm::runtime::vm;
class ExecBuilder;
/*!
* \brief A builder provides api to build VM executable with instructions.
*/
class ExecBuilderNode : public ffi::Object {
public:
/*!
* \brief Declare a function, it is OK to have multiple declarations.
* \param func The function name.
* \param kind The kind of the function.
*/
void DeclareFunction(const std::string& func, vm::VMFuncInfo::FuncKind kind);
/*!
* \brief To annotate the start of a vm function.
* \param func The function name.
* \param num_inputs The number of inputs.
* \param param_names The function parameter names.
* \param kind The kind of the function.
* \param init_register_size Initial setting of register file size.
*/
void EmitFunction(const std::string& func, int64_t num_inputs,
ffi::Optional<ffi::Array<ffi::String>> param_names,
vm::VMFuncInfo::FuncKind kind = vm::VMFuncInfo::FuncKind::kVMFunc,
int64_t init_register_size = 0);
/*!
* \brief Annotate the end of a vm function.
* \param func The function name.
*/
void EndFunction(const std::string& func);
/*!
* \brief Emit a call instruction for a packed function.
* \param func The packed function name.
* \param args The arguments of the function.
* \param ret The return register.
*/
void EmitCall(const std::string& func, std::vector<vm::Instruction::Arg> args, vm::RegName ret);
/*!
* \brief Emit a call instruction with func as argument.
* \param func The packed function index.
* \param args The arguments of the function.
* \param ret The return register.
*/
void EmitCall(vm::Instruction::Arg func, std::vector<vm::Instruction::Arg> args, vm::RegName ret);
/*!
* \brief Emit a ret instruction.
* \param result The return result.
* \note result must be a register.
*/
void EmitRet(vm::Instruction::Arg result);
/*!
* \brief Emit a goto instruction.
* \param pc_offset The program counter offset as the jump offset.
*/
void EmitGoto(vm::Index pc_offset);
/*!
* \brief Emit an If instruction.
* \param cond The register containing the cond value.
* \param false_offset The program counter offset for the false branch.
* \note result must be a register.
*/
void EmitIf(vm::Instruction::Arg cond, vm::Index false_offset);
/*!
* \brief Get function index by its name.
* \param name The name of the function.
* \return The argument corresponding to the function index.
*/
vm::Instruction::Arg GetFunction(const std::string& name);
/*!
* \brief Convert a constant value something that exec builder can understand.
*
* This function may update the constant pool to include the obj value.
*
* \param value The input constant value
* \return An Arg that represents the result of constant argument.
*/
template <typename T>
vm::Instruction::Arg ConvertConstant(T value) {
ffi::Any rv;
rv = value;
return ConvertConstant_(rv);
}
/*!
* \brief update memory scopes.
*
* This function builds the memory scopes for constants.
*
* \param idx Index of the constant
* \param scope The memory scope.
*/
void SaveMemoryScope(vm::Instruction::Arg idx, ffi::String scope);
/*!
* \brief Raw access to underlying executable build in progress.
*/
vm::VMExecutable* exec() const;
/*!
* \brief Finalize the build, run formalize and get the final result.
* \note This function should not be called during construction.
*/
ffi::ObjectPtr<vm::VMExecutable> Get();
/*!
* \brief Create an ExecBuilder.
* \return The ExecBuilder.
*/
TVM_DLL static ExecBuilder Create();
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ExecBuilderNode>();
}
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.ExecBuilder", ExecBuilderNode, ffi::Object);
private:
/*!
* \brief Convert a constant value something that exec builder can understand.
*
* This function may update the constant pool to include the obj value.
*
* \param obj The constant value to be emitted
* \return An Arg that represents the result of constant argument.
*/
vm::Instruction::Arg ConvertConstant_(ffi::Any obj);
/*!
* \brief A helper function to check if an executable is legal by checking if registers are used
* properly
*/
void CheckExecutable();
/*!
* \brief Formalize the executable.
*/
void Formalize();
/*! \brief The mutable internal executable. */
ffi::ObjectPtr<vm::VMExecutable> exec_; // mutable
/*! \brief internal dedup map when creating index for a new constant */
std::unordered_map<ffi::Any, vm::Index, ffi::StructuralHash, ffi::StructuralEqual>
const_dedup_map_;
};
class ExecBuilder : public ffi::ObjectRef {
public:
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(ExecBuilder, ffi::ObjectRef, ExecBuilderNode);
};
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_EXEC_BUILDER_H_
+701
View File
@@ -0,0 +1,701 @@
/*
* 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 <tvm/ffi/container/array.h>
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/cow.h>
#include <tvm/ir/expr.h>
#include <tvm/ir/function.h>
#include <tvm/ir/source_map.h>
#include <tvm/relax/type.h>
#include <tvm/runtime/tensor.h>
#include <tvm/tirx/expr.h>
#include <tvm/tirx/op.h>
#include <functional>
namespace tvm {
namespace relax {
/*! \brief Tuple container */
class TupleNode : public ExprNode {
public:
/*! \brief the fields of the tuple */
tvm::ffi::Array<Expr> fields;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TupleNode>().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<Expr> 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<relax::Var>`), it must be converted
* into an array of base type. This constructor handles the
* conversion to the base `ffi::Array<relax::Expr>`.
*
* \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 <typename ExprType, typename = std::enable_if_t<std::is_base_of_v<Expr, ExprType>>>
TVM_DLL explicit Tuple(tvm::ffi::Array<ExprType> 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<TupleGetItemNode>()
.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<PrimExpr> values;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ShapeExprNode>().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<PrimExpr> 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<VarNode>().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<Type> 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<DataflowVarNode>();
}
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<Type> 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<ConstantNode>().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<Type> 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<StringImmNode>().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<DataTypeImmNode>().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<BindingNode>()
.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<BindingNode> 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<const BindingNode*>(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<MatchCastNode>()
.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<VarBindingNode>().def_ro("value", &VarBindingNode::value);
// customize the SEqual and SHash methods for better error messages
refl::TypeAttrDef<VarBindingNode>()
.def("__s_equal__", &VarBindingNode::SEqual)
.def("__s_hash__", &VarBindingNode::SHash);
}
bool SEqual(const VarBindingNode* other,
ffi::TypedFunction<bool(AnyView, AnyView, bool, AnyView)> equal) const;
int64_t SHash(int64_t init_hash, ffi::TypedFunction<int64_t(AnyView, int64_t, bool)> 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<Binding> bindings;
mutable Span span;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<BindingBlockNode>()
.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<Binding> 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<DataflowBlockNode>();
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.expr.DataflowBlock", DataflowBlockNode,
BindingBlockNode);
};
class DataflowBlock : public BindingBlock {
public:
TVM_DLL explicit DataflowBlock(ffi::Array<Binding> 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<BindingBlock> blocks;
Expr body;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SeqExprNode>()
.def_ro("blocks", &SeqExprNode::blocks)
.def_ro("body", &SeqExprNode::body);
refl::TypeAttrDef<SeqExprNode>()
.def("__s_equal__", &SeqExprNode::SEqual)
.def("__s_hash__", &SeqExprNode::SHash);
}
bool SEqual(const SeqExprNode* other,
ffi::TypedFunction<bool(AnyView, AnyView, bool, AnyView)> 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<int64_t(AnyView, int64_t, bool)> 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<BindingBlock> 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<IfNode>()
.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<Var> 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<FunctionNode>()
.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<Var> params, Expr body, ffi::Optional<Type> 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<Var> 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<ExternFuncNode>().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<T, StructuralHash, StructuralEqual>` or
* `std::unordered_set<T, ffi::ObjectPtrHash, ffi::ObjectPtrEqual>`).
*/
template <>
struct std::hash<tvm::relax::Var> {
std::size_t operator()(const tvm::relax::Var& var) const {
return tvm::ffi::ObjectPtrHash()(var);
}
};
template <>
struct std::equal_to<tvm::relax::Var> {
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_
+574
View File
@@ -0,0 +1,574 @@
/*
* 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.
*/
/*!
* \file tvm/relax/expr_functor.h
* \brief A more powerful visitor which enables defining arbitrary function
* signatures with type based dispatch on first argument.
*/
#ifndef TVM_RELAX_EXPR_FUNCTOR_H_
#define TVM_RELAX_EXPR_FUNCTOR_H_
#include <tvm/ir/node_functor.h>
#include <tvm/relax/block_builder.h>
#include <tvm/relax/expr.h>
#include <tvm/relax/type.h>
#include <tvm/relax/type_functor.h>
#include <tvm/tirx/function.h>
#include <unordered_map>
#include <utility>
namespace tvm {
namespace relax {
/*!
* \brief A dynamical functor that dispatches on in the first Expr argument.
* You can use this as a more powerful Visitor, since it allows you to
* define function signatures of Visit Function.
*
* \sa tvm/ir_functor.h
*
* \tparam FType function signature
* This type is only defined for FType with function signature R(const Expr&,
* Args...)
*/
template <typename FType>
class ExprFunctor;
// functions to be overriden.
#define EXPR_FUNCTOR_DEFAULT \
{ \
return VisitExprDefault_(op, std::forward<Args>(args)...); \
}
#define RELAX_EXPR_FUNCTOR_DISPATCH(OP) \
vtable.template set_dispatch<OP>([](const ffi::ObjectRef& n, TSelf* self, Args... args) { \
return self->VisitExpr_(static_cast<const OP*>(n.get()), std::forward<Args>(args)...); \
});
#define PY_EXPR_VISITOR_DEFAULT(N, PY_FUNC, DEFAULT_FUNC) \
{ \
if (PY_FUNC != nullptr) \
PY_FUNC(N); \
else \
DEFAULT_FUNC; \
}
#define PY_EXPR_MUTATOR_DEFAULT(N, PY_FUNC, DEFAULT_FUNC, RET_TYPE) \
{ \
if (PY_FUNC != nullptr) { \
RET_TYPE ret = PY_FUNC(N).cast<RET_TYPE>(); \
return ret; \
} else { \
return DEFAULT_FUNC; \
} \
}
#define PY_EXPR_VISITOR_DISPATCH(OP, PY_FUNC) \
vtable.template set_dispatch<OP>([](const ffi::ObjectRef& n, TSelf* self) { \
if (self->PY_FUNC != nullptr) \
self->PY_FUNC(n); \
else \
self->VisitExpr_(static_cast<const OP*>(n.get())); \
});
#define PY_EXPR_MUTATOR_DISPATCH(OP, PY_FUNC) \
vtable.template set_dispatch<OP>([](const ffi::ObjectRef& n, TSelf* self) { \
if (self->PY_FUNC != nullptr) { \
Expr expr = self->PY_FUNC(n).cast<Expr>(); \
return expr; \
} else { \
return self->VisitExpr_(static_cast<const OP*>(n.get())); \
} \
});
#define PY_EXPR_MUTATOR_VISIT_EXPR_POST_ORDER_DISPATCH(OP) \
post_order_vtable.template set_dispatch<OP>([](const ffi::ObjectRef& n, TSelf* self) { \
return self->VisitExprPostOrder_(static_cast<const OP*>(n.get())); \
});
template <typename R, typename... Args>
class ExprFunctor<R(const Expr& n, Args...)> {
private:
using TSelf = ExprFunctor<R(const Expr& n, Args...)>;
using FType = tvm::NodeFunctor<R(const ffi::ObjectRef& n, TSelf* self, Args...)>;
public:
/*! \brief the result type of this functor */
using result_type = R;
/*! \brief virtual destructor */
virtual ~ExprFunctor() {}
/*!
* \brief Same as call.
* \param n The expression node.
* \param args Additional arguments.
* \return The result of the call
*/
R operator()(const Expr& n, Args... args) { return VisitExpr(n, std::forward<Args>(args)...); }
/*!
* \brief The functor call.
* \param n The expression node.
* \param args Additional arguments.
* \return The result of the call
*/
virtual R VisitExpr(const Expr& n, Args... args) {
TVM_FFI_ICHECK(n.defined())
<< "Found null pointer node while traversing AST. The previous pass may "
"have generated invalid data.";
static FType vtable = InitVTable();
if (vtable.can_dispatch(n)) {
return vtable(n, this, std::forward<Args>(args)...);
}
return VisitExprFallback_(n.get(), std::forward<Args>(args)...);
}
// Functions that can be overriden by subclass
// NOTE: cross dialect calls are invoked through global var
// We do not expect inline PrimFunc to appear in relax IR.
virtual R VisitExpr_(const ConstantNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
virtual R VisitExpr_(const TupleNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
virtual R VisitExpr_(const VarNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
virtual R VisitExpr_(const DataflowVarNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
virtual R VisitExpr_(const ShapeExprNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
virtual R VisitExpr_(const ExternFuncNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
virtual R VisitExpr_(const GlobalVarNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
virtual R VisitExpr_(const FunctionNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
virtual R VisitExpr_(const CallNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
virtual R VisitExpr_(const SeqExprNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
virtual R VisitExpr_(const IfNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
virtual R VisitExpr_(const OpNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
virtual R VisitExpr_(const TupleGetItemNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
virtual R VisitExprFallback_(const ExprNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
virtual R VisitExpr_(const StringImmNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
virtual R VisitExpr_(const DataTypeImmNode* op, Args... args) EXPR_FUNCTOR_DEFAULT;
virtual R VisitExprDefault_(const ffi::Object* op, Args...) {
TVM_FFI_THROW(InternalError) << "Do not have a default for " << op->GetTypeKey();
throw;
}
private:
// initialize the vtable.
static FType InitVTable() {
FType vtable;
// Set dispatch
RELAX_EXPR_FUNCTOR_DISPATCH(ConstantNode);
RELAX_EXPR_FUNCTOR_DISPATCH(TupleNode);
RELAX_EXPR_FUNCTOR_DISPATCH(VarNode);
RELAX_EXPR_FUNCTOR_DISPATCH(DataflowVarNode);
RELAX_EXPR_FUNCTOR_DISPATCH(ShapeExprNode);
RELAX_EXPR_FUNCTOR_DISPATCH(ExternFuncNode);
RELAX_EXPR_FUNCTOR_DISPATCH(GlobalVarNode);
RELAX_EXPR_FUNCTOR_DISPATCH(FunctionNode);
RELAX_EXPR_FUNCTOR_DISPATCH(CallNode);
RELAX_EXPR_FUNCTOR_DISPATCH(SeqExprNode);
RELAX_EXPR_FUNCTOR_DISPATCH(IfNode);
RELAX_EXPR_FUNCTOR_DISPATCH(OpNode);
RELAX_EXPR_FUNCTOR_DISPATCH(TupleGetItemNode);
RELAX_EXPR_FUNCTOR_DISPATCH(StringImmNode);
RELAX_EXPR_FUNCTOR_DISPATCH(DataTypeImmNode);
vtable.Finalize();
return vtable;
}
};
/*!
* \brief A simple visitor wrapper around ExprFunctor.
* Recursively visit the content.
*/
class ExprVisitor : public ExprFunctor<void(const Expr&)> {
public:
/*!
* \brief Generic dispatcher for Expr.
* \param expr The expr to be visited.
*/
void VisitExpr(const Expr& expr) override;
// specific leaf level visitor functions
void VisitExpr_(const ConstantNode* op) override;
void VisitExpr_(const TupleNode* op) override;
void VisitExpr_(const VarNode* op) override;
void VisitExpr_(const DataflowVarNode* op) override;
void VisitExpr_(const ShapeExprNode* op) override;
void VisitExpr_(const ExternFuncNode* op) override;
void VisitExpr_(const GlobalVarNode* op) override;
void VisitExpr_(const FunctionNode* op) override;
void VisitExpr_(const CallNode* op) override;
void VisitExpr_(const SeqExprNode* op) override;
void VisitExpr_(const IfNode* op) override;
void VisitExpr_(const OpNode* op) override;
void VisitExpr_(const TupleGetItemNode* op) override;
void VisitExprFallback_(const ExprNode* op) override;
void VisitExpr_(const StringImmNode* op) override;
void VisitExpr_(const DataTypeImmNode* op) override;
/*!
* \brief Generic dispatcher for bindings.
* \param binding The binding to be visited.
*/
virtual void VisitBinding(const Binding& binding);
// specific leaf level visitor functions
virtual void VisitBinding_(const VarBindingNode* binding);
virtual void VisitBinding_(const MatchCastNode* binding);
// second level dispatching based on binding value type.
// these dispatching functions get called from first-level dispatch on VarBinding
virtual void VisitBinding_(const VarBindingNode* binding, const ConstantNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const TupleNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const VarNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const DataflowVarNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const ShapeExprNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const ExternFuncNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const GlobalVarNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const FunctionNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const CallNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const SeqExprNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const IfNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const OpNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const TupleGetItemNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const ExprNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const StringImmNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const DataTypeImmNode* val);
/*!
* \brief Generic dispatcher for binding blocks.
* \param block The binding block to be visited.
*/
virtual void VisitBindingBlock(const BindingBlock& block);
// specific leaf level visitor functions
virtual void VisitBindingBlock_(const BindingBlockNode* block);
virtual void VisitBindingBlock_(const DataflowBlockNode* block);
/*!
* \brief Generic dispatcher for visiting the var definition site.
* \param var The var to be visited.
* \note VisitExpr_(const VarNode*) will only visit the usage site of an Var
*/
virtual void VisitVarDef(const Var& var);
/*!
* \brief Visit ty may recursively contain Expr/PrimExpr.
*
* By default, this function recurse into type such as
* TensorType and ShapeType and call VisitExpr/VisitTypePrimExprField
* accordingly. It does not recurse into FunctionType as it does
* not contain Expr defined in the current scope.
*
* Pass writers can overload this function to change to other behaviors.
* For example, if we are not interested in Expr in Type, we can
* override this function by a no-op.
*
* \param ty Input type field.
*/
virtual void VisitExprDepTypeField(const Type& ty);
// specific leaf level visitor functions
virtual void VisitVarDef_(const VarNode* var);
virtual void VisitVarDef_(const DataflowVarNode* var);
virtual void VisitSpan(const Span& span);
virtual void VisitTypePrimExprField(const PrimExpr& expr);
private:
using TSelf = ExprVisitor;
using VisitBindingVTable = tvm::NodeFunctor<void(const ffi::ObjectRef& n, ExprVisitor* self,
const VarBindingNode* binding)>;
// initialize the vtable.
static VisitBindingVTable InitVisitBindingVTable();
/*!
* \brief Private internal type field visitor.
*
* Support default visiting of type field and recursive into
* their Expr fields.
*
* We use component instead of sub-classing so there can be other
* joint inheritance between ExprVisitor and TypeVisitor.
*/
class DefaultTypeFieldVisitor : public TypeVisitor {
public:
explicit DefaultTypeFieldVisitor(ExprVisitor* parent);
// Override defaults in type visitor.
void VisitTypeExprField(const Expr& expr) final;
void VisitTypeExprField(const PrimExpr& expr) final;
void VisitType_(const FuncTypeNode* op) final;
private:
ExprVisitor* parent_;
};
// This visitor is not visible to child classes and only
// used to supported default visiting behavior.
DefaultTypeFieldVisitor default_tyfield_visitor_{this};
};
void PostOrderVisit(const Expr& node, std::function<void(const Expr&)> fvisit);
/*!
* \brief A mutator works in unnormalized form.
*
* ExprMutatorBase expects input AST to be in the unnormalized form, i.e., ty
* of expressions can be nullptr, and the expressions may nest(and as a result the AST is not in
* ANF).
*/
class ExprMutatorBase : public ExprFunctor<Expr(const Expr&)> {
public:
Expr VisitExpr(const Expr& expr) override;
Expr VisitExpr_(const ConstantNode* op) override;
Expr VisitExpr_(const TupleNode* op) override;
Expr VisitExpr_(const VarNode* op) override;
Expr VisitExpr_(const DataflowVarNode* op) override;
Expr VisitExpr_(const ShapeExprNode* op) override;
Expr VisitExpr_(const ExternFuncNode* op) override;
Expr VisitExpr_(const GlobalVarNode* op) override;
Expr VisitExpr_(const FunctionNode* op) override;
Expr VisitExpr_(const CallNode* op) override;
Expr VisitExpr_(const SeqExprNode* op) override;
Expr VisitExpr_(const IfNode* op) override;
Expr VisitExpr_(const OpNode* op) override;
Expr VisitExpr_(const TupleGetItemNode* op) override;
Expr VisitExprFallback_(const ExprNode* op) override;
Expr VisitExpr_(const StringImmNode* op) override;
Expr VisitExpr_(const DataTypeImmNode* op) override;
/*!
* \brief Mutate BindingBlock.
* \param block The binding block to be visited.
* \return The binding block after transformation.
*/
virtual BindingBlock VisitBindingBlock(const BindingBlock& block);
/*!
* \brief Used to visit the PrimExpr inside of expressions.
*
* Can be overloaded to transform the shape expressions.
*/
virtual PrimExpr VisitTypePrimExprField(const PrimExpr& expr);
/*!
* \brief Visit ty that may recursively contain Expr/PrimExpr.
*
* By default, this function recurse into type such as
* TensorType and ShapeType and call VisitExpr/VisitTypePrimExprField
* accordingly. It does not recurse into FunctionType as it does
* not contain Expr defined in the current scope.
*
* Pass writers can overload this function to change to other behaviors.
* For example, if in Expr in Type won't change, we can
* override this function by an identity function.
*
* \param ty Input type field.
* \return The updated type.
*/
virtual Type VisitExprDepTypeField(const Type& ty);
protected:
/*!
* \brief Check whether VisitExprDepTypeField change ty.
* \return Whether type changed.
* \note This function is used by mutator implementations to check if
* previous Expr update will trigger a change in ty.
* If change is detected, the implementation can generate a fresh
* node without ty, and trigger normalizer to re-derive.
*/
bool VisitAndCheckTypeFieldUnchanged(const ffi::ObjectRef& ty) {
if (const TypeNode* ty_node = ty.as<TypeNode>()) {
Type type = ffi::GetRef<Type>(ty_node);
return type.IsMissing() || this->VisitExprDepTypeField(type).same_as(ty);
} else {
return true;
}
}
private:
/*!
* \brief Private internal type field visitor to support
* Default visiting of type field and recursive into their Expr fields.
*
* We use component instead of sub-classing so there can be other
* joint inheritance between ExprMutator and TypeMutator.
*/
class DefaultTypeFieldMutator : public TypeMutator {
public:
explicit DefaultTypeFieldMutator(ExprMutatorBase* parent);
// Override defaults in type visitor.
Expr VisitTypeExprField(const Expr& expr) final;
PrimExpr VisitTypeExprField(const PrimExpr& expr) final;
Type VisitType_(const FuncTypeNode* op) final;
private:
ExprMutatorBase* parent_;
};
// This visitor is not visible to child classes and only
// used to supported default visiting behavior.
DefaultTypeFieldMutator default_tyfield_mutator_{this};
};
/*!
* \brief A mutator works in normal form.
*
* ExprMutator expects input AST to be in the normal form, i.e., the expressions are normalized(no
* nesting and hence the AST is in ANF), and all ty of expressions are
* available.
*/
class ExprMutator : public ExprMutatorBase {
public:
using ExprMutatorBase::VisitExpr_;
ExprMutator(ffi::Optional<IRModule> mod = std::nullopt) { builder_ = BlockBuilder::Create(mod); }
Expr VisitExpr(const Expr& expr) override;
Expr VisitExpr_(const VarNode* op) override;
Expr VisitExpr_(const DataflowVarNode* op) override;
Expr VisitExpr_(const FunctionNode* op) override;
Expr VisitExpr_(const SeqExprNode* op) override;
Expr VisitExpr_(const IfNode* op) override;
/*!
* \brief Generic dispatcher for bindings.
* \param binding The binding to be visited.
*/
virtual void VisitBinding(const Binding& binding);
// specific leaf level visitor functions
virtual void VisitBinding_(const VarBindingNode* binding);
virtual void VisitBinding_(const MatchCastNode* binding);
// second level dispatching based on binding value type.
// these dispatching functions get called from first-level dispatch on VarBinding
virtual void VisitBinding_(const VarBindingNode* binding, const ConstantNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const TupleNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const VarNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const DataflowVarNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const ShapeExprNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const ExternFuncNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const GlobalVarNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const FunctionNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const CallNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const SeqExprNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const IfNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const OpNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const TupleGetItemNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const ExprNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const StringImmNode* val);
virtual void VisitBinding_(const VarBindingNode* binding, const DataTypeImmNode* val);
/*!
* \brief Generic dispatcher for binding blocks.
* \param block The binding block to be visited.
* \return The binding block after transformation.
*/
virtual BindingBlock VisitBindingBlock(const BindingBlock& block) override; // NOLINT(*)
// specific leaf level visitor functions
virtual BindingBlock VisitBindingBlock_(const BindingBlockNode* block);
virtual BindingBlock VisitBindingBlock_(const DataflowBlockNode* block);
/*!
* \brief Generic dispatcher for rewriting the var definition site.
* \param var The var to be visited.
* \return The var after post-order rewritten.
* \note VisitExpr_(const VarNode*) will only visit the usage site of an Var
*/
virtual Var VisitVarDef(const Var& var);
// specific leaf level visitor functions
virtual Var VisitVarDef_(const VarNode* var);
virtual Var VisitVarDef_(const DataflowVarNode* var);
protected:
/*!
* \brief Try to remit binding and bind it to a new_value
*
* This function is called after VisitExpr(binding->value) in
* VisitBinding_(const VarBinding*).
* It will try to reuse the current binding when the new value's shape/type
* matches the original binding and no changes in var is needed.
*
* Otherwise, a new binding will be emitted to replace the var specified in
* the current binding.
*/
void ReEmitBinding(const VarBindingNode* binding, Expr new_value);
/*!
* \brief Rewrite the expr with a new scope, used in a Function's body.
*
* Visit an expression that may neither access variables from the
* current scope, nor may export definitions into the current scope.
*
* \param body_expr The body to be visited.
* \param params Optional parameters that are visible within the scope.
* \return The expr after visiting.
*
* \note The body_expr must be an SeqExpr in the normal form.
*/
Expr VisitWithNewScope(const Expr& body_expr,
ffi::Optional<ffi::Array<Var>> params = std::nullopt);
/*!
* \brief Rewrite the expr with a new scope, used in the branches of If.
*
* Visit an expression that may access variables from the current
* scope, but may not export definitions into the current scope.
*
* \param body_expr The body to be visited.
*
* \return The expr after visiting.
*
* \sa VisitWithNewScope
*
* \note The body_expr must be an SeqExpr in the normal form.
*/
Expr VisitWithInnerScope(const Expr& body_expr);
/*!
* \brief Look up the value bound to a variable.
* \param var The var to be looked up.
* \return The value bound to the input \p var.
* \note For function parameters, this function returns std::nullopt.
*/
ffi::Optional<Expr> LookupBinding(const Var& var);
/*!
* \brief Post-order rewrite a node and normalize.
* \tparam T The node type to be rewritten.
* \param op The node to be rewritten.
* \return The node after post rewritten.
*/
template <typename T>
Expr VisitExprPostOrder_(const T* op) {
return builder_->Normalize(ExprMutator::VisitExpr_(op));
}
/*!
* \brief Create a new var with specified type if the original var's shape or type does not
* match with the specified ones.
* \param var The var to be updated.
* \param ty The type to be updated.
* \return The var filled with type information.
*/
Var WithType(Var var, Type ty);
/*! \brief Internal block builder to emit bindings during rewriting. */
BlockBuilder builder_;
/*! \brief Remap a var to a new var in use-site. */
std::unordered_map<Var, Var, ffi::ObjectPtrHash, ffi::ObjectPtrEqual> var_remap_;
private:
using TSelf = ExprMutator;
using VisitBindingVTable = tvm::NodeFunctor<void(const ffi::ObjectRef& n, ExprMutator* self,
const VarBindingNode* binding)>;
// initialize the vtable.
static VisitBindingVTable InitVisitBindingVTable();
};
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_EXPR_FUNCTOR_H_
+687
View File
@@ -0,0 +1,687 @@
/*
* 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.
*/
/*!
* \file tvm/relax/nested_msg.h
* \brief Helper container to store nested message for robust tuple-aware analysis.
*
* Please see NestedMsg for description of usage.
*
* \sa NestedMsg
*/
#ifndef TVM_RELAX_NESTED_MSG_H_
#define TVM_RELAX_NESTED_MSG_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/optional.h>
#include <tvm/relax/expr.h>
#include <tvm/relax/type.h>
#include <string>
#include <utility>
#include <vector>
namespace tvm {
namespace relax {
/*!
* \brief Container that stores possibly nested message with leaf message type T.
*
* NestedMsg is a helper structure to store intermediate
* message state in pass analysis so we can robustly handle message
* passing with the presence of nested tuple types.
*
* Under the hood, NestedMsg[T] = Union[T, std::nullopt, Array[NestedMsg[T]]].
* Each nested message corresponds to the same nesting structure as
* the nested tuple types when we encounter them in analysis.
*
* Relax support nested tuple structures in the IR. Nested tuple structure
* is important to support advanced groupings in cases such as gradient calculation
* and other scenarios.
*
* The possible presence of nested tuple does mean that we need to
* to robustly handle analysis that contains nested tuple structures
* in a dataflow graph.
*
* \code
*
* v1 = relu(v0)
* v2 = exp(v0)
* t = ((v0, v1), (v2,), v0)
* t1 = t[0]
* v3 = concat(t1)
* v4 = t[2]
* v5 = add(v4, v3)
*
* \endcode
*
* Consider the above code sequence that contains a mixture of tuple
* nesting and normal operations. A common message-passing-based analysis
* will track messages attached to each intermediate variable.
*
* Because the intermediate value can contain nested-tuples, we need to have
* abilities to nest messages according to tuple structure and propagate them
* along the way. In python, this simply corresponds to using a tuple to hold
* nested messages. This class provides a helper wrapper in C++ to present such
* possibly nested message for a given leaf message.
*
* This design pattern is necessary to handle tuple values regardless of
* the normal form design of the IR to enable different messages for each
* tuple component without enforcing all tuple elements to have the same message.
*
* Please consider the following patterns in our pass:
*
* On a forward propagation message passing analysis:
* - Create map [leafnode=>NestedMsg<T>], scan forward
* - input_msg = [MapToNestedMsg<T>(x, lookup_map) for x in call->args]
* - output_msg = ForwardProp[call->op](input_msg, call)
* - map[binding->var] = output_msg
* - Use MapToNestedMsg to remap the remaining body.
*
* On a backward propagation message passing analysis:
* - Create map [leafnode=>NestedMsg<T>], scan backward
* - output_msg = lookup map(binding->var)
* - handle case when output_msg is null
* - input_msg = BackProp[call->op](out_msg, call)
* - for arg, msg in zip(call->args, input_msg),
* DecomposeNestedMessage(arg, msg, lambda node, m: update_map(node, m))
* - update_map(node, m) => CombineNestedMessage(map[node], m)
*
* Here leafnode is a node that you would like to propagate messages to
* such as constant, var and should not include tuple.
*
* We also recommend writing unit-test cases that involve nested tuple composition
* and decomposition.
*
* \sa MapToNestedMsg, DecomposeNestedMsg, CombineNestedMsg, ForEachLeaf, Equal
*
* \note If you want to write robust message passing-based analysis for
* programs that can contain nested tuples, you likely need to
* use this class or logic of a similar kind.
*/
template <typename T>
class NestedMsg {
public:
// default constructors.
NestedMsg() = default;
NestedMsg(const NestedMsg<T>&) = default;
NestedMsg(NestedMsg<T>&&) = default;
NestedMsg<T>& operator=(const NestedMsg<T>&) = default;
NestedMsg<T>& operator=(NestedMsg<T>&&) = default;
/*! \brief Nullopt handling */
NestedMsg(std::nullopt_t) {} // NOLINT(*)
// nullptr handling.
// disallow implicit conversion as 0 can be implicitly converted to nullptr_t
explicit NestedMsg(std::nullptr_t) {}
NestedMsg<T>& operator=(std::nullptr_t) {
data_ = nullptr;
return *this;
}
// normal value handling.
NestedMsg(T other) // NOLINT(*)
: data_(std::move(other)) {}
NestedMsg<T>& operator=(T other) {
data_ = std::move(other);
return *this;
}
// ffi::Array<NestedMsg<T>> handling
NestedMsg(ffi::Array<NestedMsg<T>, void> other) // NOLINT(*)
: data_(other) {}
NestedMsg<T>& operator=(ffi::Array<NestedMsg<T>, void> other) {
data_ = std::move(other);
return *this;
}
// initializer list handling
NestedMsg(std::initializer_list<NestedMsg<T>> other) // NOLINT(*)
: NestedMsg(ffi::Array<NestedMsg<T>, void>(other)) {}
NestedMsg<T>& operator=(std::initializer_list<NestedMsg<T>> other) {
return operator=(ffi::Array<NestedMsg<T>, void>(other));
}
// delete the int constructor
// since NestedMsg<IntImm>(0) is ambiguous
// 0 can be implicitly casted to nullptr_t
explicit NestedMsg(int val) = delete;
NestedMsg<T>& operator=(int val) = delete;
// operator overloadings
bool operator==(std::nullptr_t) const { return data_ == nullptr; }
bool operator!=(std::nullptr_t) const { return data_ != nullptr; }
/*! \return Whether the nested message is not-null leaf value */
bool IsLeaf() const {
return data_.type_index() != ffi::TypeIndex::kTVMFFINone &&
data_.type_index() != ffi::TypeIndex::kTVMFFIArray;
}
/*! \return Whether the nested message is null */
bool IsNull() const { return data_.type_index() == ffi::TypeIndex::kTVMFFINone; }
/*! \return Whether the nested message is nested */
bool IsNested() const { return data_.type_index() == ffi::TypeIndex::kTVMFFIArray; }
/*!
* \return The underlying leaf value.
* \note This function checks if the msg is leaf.
*/
T LeafValue() const {
TVM_FFI_ICHECK(IsLeaf());
return ffi::details::AnyUnsafe::CopyFromAnyViewAfterCheck<T>(data_);
}
/*!
* \return a corresponding nested array.
* \note This checks if the underlying data type is array.
*/
ffi::Array<NestedMsg<T>, void> NestedArray() const {
return ffi::details::AnyUnsafe::CopyFromAnyViewAfterCheck<ffi::Array<NestedMsg<T>, void>>(
data_);
}
private:
ffi::Any data_;
// private constructor
explicit NestedMsg(ffi::Any data) : data_(data) {}
template <typename, typename>
friend struct ffi::TypeTraits;
};
/*!
* \brief Apply fvisit for each leaf elements in the nested message.
* \param fvisit The visit callback.
* \param msg The input nested message.
* \tparam T the content type of nested msg
* \tparam FType the visitor type with signature void fvisit(T)
*/
template <typename T, typename FType>
void ForEachLeaf(const NestedMsg<T>& msg, FType fvisit) {
if (msg == nullptr) return;
if (msg.IsLeaf()) {
fvisit(msg.LeafValue());
} else {
for (NestedMsg<T> x : msg.NestedArray()) {
ForEachLeaf(x, fvisit);
}
}
}
/*!
* \brief Recursively compare two nested messages.
*
* \param lhs The left operand.
* \param rhs The right operand.
* \param fequal The equal functor with signature bool fequal(T, T)
* \tparam T the content type of nested msg
* \tparam FType the equal comparator type
*/
template <typename T, typename FType>
bool Equal(const NestedMsg<T>& lhs, const NestedMsg<T>& rhs, FType fequal) {
if (lhs.IsNull()) return rhs.IsNull();
if (rhs.IsNull()) return lhs.IsNull();
if (lhs.IsLeaf()) {
return rhs.IsLeaf() && fequal(lhs.LeafValue(), rhs.LeafValue());
} else {
if (!rhs.IsNested()) return false;
ffi::Array<NestedMsg<T>> arr_lhs = lhs.NestedArray();
ffi::Array<NestedMsg<T>> arr_rhs = rhs.NestedArray();
if (arr_lhs.size() != arr_rhs.size()) return false;
for (size_t i = 0; i < arr_lhs.size(); ++i) {
if (!Equal(arr_lhs[i], arr_rhs[i], fequal)) return false;
}
return true;
}
}
/*!
* \brief Map expr with possible nested-tuple to nested message.
*
* This function will unpack recursive tuples and run fmapleaf for each leaf,
* then recursively combines the results together into a NestedMsg.
*
* The nesting structure will corresponds to the tuple structure.
*
* \param expr The input expression.
* \param fmapleaf The mapping function for each leaf with signature `NestedMsg<T> fmap(Expr)`
* \tparam T the content type of nested msg
* \tparam FType The mapping function type
*/
template <typename T, typename FType>
NestedMsg<T> MapToNestedMsg(Expr expr, FType fmapleaf) {
if (auto* tuple = expr.as<TupleNode>()) {
ffi::Array<NestedMsg<T>> res;
res.reserve(tuple->fields.size());
for (Expr x : tuple->fields) {
res.push_back(MapToNestedMsg<T, FType>(x, fmapleaf));
}
return res;
} else {
return fmapleaf(expr);
}
}
/*!
* \brief Map structinfo with possible nested-ty to nested message.
*
* This function will unpack recursive ty and run fmapleaf for each leaf,
* then recursively combines the results together into a NestedMsg.
*
* The nesting structure will corresponds to the tuple structure.
*
* \param ty The input type.
* \param fmapleaf The mapping function for each leaf with signature `NestedMsg<T> fmap(Type)`
* \tparam T the content type of nested msg
* \tparam FType The mapping function type
*/
template <typename T, typename FType>
NestedMsg<T> MapToNestedMsg(Type ty, FType fmapleaf) {
if (auto* tuple = ty.as<TupleTypeNode>()) {
ffi::Array<NestedMsg<T>> res;
res.reserve(tuple->fields.size());
for (Type x : tuple->fields) {
res.push_back(MapToNestedMsg<T, FType>(x, fmapleaf));
}
return res;
} else {
return fmapleaf(ty);
}
}
/*!
* \brief Map expr with possible nested-tuple to nested message.
*
* This function will unpack recursive expr by its type and
* run fmapleaf for each leaf, then recursively combines the results
* together into a NestedMsg.
*
* The nesting structure will corresponds to the type of expr.
*
* \param expr The input expression which should have type.
* \param fmapleaf The mapping function for each leaf with signature `NestedMsg<T> fmapleaf(Expr)`
* \tparam T the content type of nested msg
* \tparam FType The mapping function type
*/
template <typename T, typename FType>
NestedMsg<T> MapToNestedMsgByType(Expr expr, FType fmapleaf) {
auto ty = GetType(expr);
if (auto* tuple = ty.as<TupleTypeNode>()) {
ffi::Array<NestedMsg<T>> res;
res.reserve(tuple->fields.size());
for (size_t i = 0; i < tuple->fields.size(); ++i) {
Expr field;
if (const auto* expr_tuple = expr.as<TupleNode>()) {
field = expr_tuple->fields[i];
} else {
field = TupleGetItem(expr, i);
}
res.push_back(MapToNestedMsgByType<T, FType>(field, fmapleaf));
}
return res;
} else {
return fmapleaf(expr);
}
}
/*!
* \brief Map nested message back to TargetType.
*
* This function will decompose the nested message and
* run fmapleaf for each leaf message and get the leaf value,
* then recursively combines the results by fcombine.
*
* \param msg The input nested message.
* \param fmapleaf The mapping function for each leaf with signature
* `TargetType fmapleaf(ffi::Optional<T>)`.
* \param fcombine The function for combining all childs of a node into TargetType with signature
* `TargetType fmapleaf(ffi::Array<TargetType>)`.
* \tparam TargetType the target type to map nested msg to.
* \tparam T the content type of nested msg.
* \tparam FMapLeaf The leaf mapping function type.
* \tparam FCombine The combining function type.
*/
template <typename TargetType, typename T, typename FMapLeaf, typename FCombine>
TargetType NestedMsgTo(NestedMsg<T> msg, FMapLeaf fmapleaf, FCombine fcombine) {
if (msg.IsNull()) {
return fmapleaf(std::nullopt);
} else if (msg.IsLeaf()) {
return fmapleaf(msg.LeafValue());
} else {
TVM_FFI_ICHECK(msg.IsNested());
ffi::Array<NestedMsg<T>> arr = msg.NestedArray();
ffi::Array<TargetType> subexpr;
subexpr.reserve(arr.size());
for (size_t i = 0; i < arr.size(); ++i) {
subexpr.push_back(NestedMsgTo<TargetType>(arr[i], fmapleaf, fcombine));
}
return fcombine(subexpr);
}
}
/*!
* \brief Map nested message back to the expr.
*
* This function will decompose the nested message and
* run fmapleaf for each leaf message and get the leaf expr,
* then recursively combines the results as tuple expr.
*
* \param msg The input nested message.
* \param fmapleaf The mapping function for each leaf with signature `Expr
* fmapleaf(ffi::Optional<T>)`. \tparam T the content type of nested msg. \tparam FType The mapping
* function type.
*/
template <typename T, typename FType>
Expr NestedMsgToExpr(NestedMsg<T> msg, FType fmapleaf) {
return NestedMsgTo<Expr>(msg, fmapleaf, [](ffi::Array<Expr> arr) {
ffi::Optional<Expr> simplified_tuple;
bool simplified_flag = false;
if (arr.size() >= 1) {
simplified_flag = true;
for (size_t i = 0; i < arr.size() && simplified_flag; ++i) {
auto* node = arr[i].as<TupleGetItemNode>();
if (node == nullptr || node->index != static_cast<int>(i)) {
simplified_flag = false;
} else {
if (simplified_tuple.has_value()) {
simplified_flag &= simplified_tuple.value().same_as(node->tuple);
} else {
simplified_tuple = node->tuple;
TVM_FFI_ICHECK(simplified_tuple.has_value());
}
}
}
}
return simplified_flag ? simplified_tuple.value() : Tuple(arr);
});
}
/*!
* \brief Recursively combine two nested message into one.
*
* This function requires the two messages to be compatible with each other.
* The combination rule is as follows:
* - combine(null, msg) => msg
* - combine(leaf1, leaf2) => fcombine(leaf1, leaf2)
* - combine(array1, array2) => [combine(x, y) for x, y in zip(array1, array2)]
* - This function will throw an error if array have different size
*
* \param lhs The left operand.
* \param rhs The right operand.
* \param fcombine with signature T fcombine(T lhs, T rhs)
* \tparam T the content type of nested msg
* \tparam FType combine function type.
*/
template <typename T, typename FType>
NestedMsg<T> CombineNestedMsg(NestedMsg<T> lhs, NestedMsg<T> rhs, FType fcombine) {
if (lhs.IsNull()) return rhs;
if (rhs.IsNull()) return lhs;
if (lhs.IsLeaf()) {
TVM_FFI_ICHECK(rhs.IsLeaf()) << "Cannot combine leaf with nested";
return NestedMsg<T>(fcombine(lhs.LeafValue(), rhs.LeafValue()));
} else {
TVM_FFI_ICHECK(lhs.IsNested());
TVM_FFI_ICHECK(rhs.IsNested()) << "Cannot combine leaf with nested";
ffi::Array<NestedMsg<T>> arr_lhs = lhs.NestedArray();
ffi::Array<NestedMsg<T>> arr_rhs = rhs.NestedArray();
TVM_FFI_ICHECK_EQ(arr_lhs.size(), arr_rhs.size())
<< "Cannot combine two nested array with different sizes";
ffi::Array<NestedMsg<T>> res;
res.reserve(arr_lhs.size());
for (size_t i = 0; i < arr_lhs.size(); ++i) {
res.push_back(CombineNestedMsg<T, FType>(arr_lhs[i], arr_rhs[i], fcombine));
}
return NestedMsg<T>(res);
}
}
/*!
* \brief Recursively map a nested message to another one, with leaf mapped by the input fmapleaf.
* \param msg The nested message to be mapped.
* \param fmapleaf The leaf map function, with signature NestedMsg<T> fmapleaf(T msg)
* \tparam T The content type of nested message.
* \tparam FType The leaf map function type.
* \return The new nested message.
*/
template <typename T, typename FType>
NestedMsg<T> MapNestedMsg(NestedMsg<T> msg, FType fmapleaf) {
if (msg.IsNull()) {
return msg;
} else if (msg.IsLeaf()) {
return fmapleaf(msg.LeafValue());
} else {
TVM_FFI_ICHECK(msg.IsNested());
ffi::Array<NestedMsg<T>> arr = msg.NestedArray();
ffi::Array<NestedMsg<T>> res;
res.reserve(arr.size());
for (int i = 0; i < static_cast<int>(arr.size()); ++i) {
res.push_back(MapNestedMsg(arr[i], fmapleaf));
}
return NestedMsg<T>(res);
}
}
/*!
* \brief Recursively decompose the tuple structure in expr and msg along with it.
*
* This function will call fvisitleaf for each leaf expression in expr.
* This function will throw an error if the nesting structure in msg does not
* match the tuple nesting structure in expr.
*
* \param expr The input expression to be decomposed.
* \param msg The input nested message.
* \param fvisitleaf with signature fvisitleaf(Expr expr, NestedMsg<T> msg)
* \tparam T the content type of nested msg
* \tparam FType The visit function type.
*/
template <typename T, typename FType>
void DecomposeNestedMsg(Expr expr, NestedMsg<T> msg, FType fvisitleaf) {
if (auto* tuple = expr.as<TupleNode>()) {
TVM_FFI_ICHECK(msg.IsNested()) << "Expected nested to match tuple";
ffi::Array<NestedMsg<T>> arr = msg.NestedArray();
TVM_FFI_ICHECK_EQ(arr.size(), tuple->fields.size())
<< "Expected nested array size to match tuple size";
for (size_t i = 0; i < arr.size(); ++i) {
DecomposeNestedMsg(tuple->fields[i], arr[i], fvisitleaf);
}
} else {
fvisitleaf(expr, msg);
}
}
/*!
* \brief Recursively transform the tuple structure in expr and msgs along with it.
*
* This function will call ftransleaf for each leaf expression in expr.
* This function will throw an error if the nesting structure in msg does not
* match the tuple nesting structure in expr.
*
* \param expr The input expression to be transform. 
* \param msgs The input messages to guide the transformation.
* \param ftransleaf with signature ftransleaf(Expr, ffi::Array<NestedMsg<T>>)->Expr
* \tparam T the content type of nested msg
* \tparam N the number of messages
* \tparam FType The visit function type.
*/
template <typename T, std::size_t N, typename FType>
Expr TransformTupleLeaf(Expr expr, std::array<NestedMsg<T>, N> msgs, FType ftransleaf) {
Type ty = GetType(expr);
if (const auto* tuple = ty.as<TupleTypeNode>()) {
std::array<ffi::Array<NestedMsg<T>>, N> msg_arrays;
for (size_t i = 0; i < N; ++i) {
TVM_FFI_ICHECK(msgs[i].IsNested()) << "Expected nested to match tuple";
msg_arrays[i] = msgs[i].NestedArray();
}
bool same = true;
ffi::Array<Expr> fields;
fields.reserve(tuple->fields.size());
for (size_t i = 0; i < tuple->fields.size(); ++i) {
Expr field;
if (const auto* expr_tuple = expr.as<TupleNode>()) {
field = expr_tuple->fields[i];
} else {
field = TupleGetItem(expr, i);
}
std::array<NestedMsg<T>, N> sub_msgs;
for (size_t j = 0; j < N; ++j) {
sub_msgs[j] = msg_arrays[j][i];
}
fields.push_back(TransformTupleLeaf(field, std::move(sub_msgs), ftransleaf));
same &= (fields.back().same_as(field));
}
return same ? expr : Tuple(fields);
} else {
for (const auto& msg : msgs) {
TVM_FFI_ICHECK(msg.IsLeaf()) << "Expected leaf to match non-tuple";
}
return ftransleaf(expr, msgs);
}
}
/*!
* \brief Recursively transform the tuple structure in ty and msgs along with it.
*
* This function will call ftransleaf for each leaf ty in ty.
* This function will throw an error if the nesting structure in msg does not
* match the tuple nesting structure in ty.
*
* \param ty The input ty to be transform. 
* \param msgs The input messages to guide the transformation.
* \param ftransleaf with signature ftransleaf(Type, ffi::Array<NestedMsg<T>>)->Type
* \tparam T the content type of nested msg
* \tparam N the number of messages
* \tparam FType The visit function type.
*/
template <typename T, std::size_t N, typename FType>
Type TransformTupleLeaf(Type ty, std::array<NestedMsg<T>, N> msgs, FType ftransleaf) {
if (const auto* tuple = ty.as<TupleTypeNode>()) {
std::array<ffi::Array<NestedMsg<T>>, N> msg_arrays;
for (size_t i = 0; i < N; ++i) {
TVM_FFI_ICHECK(msgs[i].IsNested()) << "Expected nested to match tuple";
msg_arrays[i] = msgs[i].NestedArray();
}
bool same = true;
ffi::Array<Type> fields;
fields.reserve(tuple->fields.size());
for (size_t i = 0; i < tuple->fields.size(); ++i) {
Type field = tuple->fields[i];
std::array<NestedMsg<T>, N> sub_msgs;
for (size_t j = 0; j < N; ++j) {
sub_msgs[j] = msg_arrays[j][i];
}
fields.push_back(TransformTupleLeaf(field, std::move(sub_msgs), ftransleaf));
same &= (fields.back().same_as(field));
}
return same ? ty : TupleType(fields);
} else {
for (const auto& msg : msgs) {
TVM_FFI_ICHECK(msg.IsLeaf()) << "Expected leaf to match non-tuple";
}
return ftransleaf(ty, msgs);
}
}
} // namespace relax
namespace ffi {
template <typename T>
inline constexpr bool use_default_type_traits_v<relax::NestedMsg<T>> = false;
template <typename T>
struct TypeTraits<relax::NestedMsg<T>> : public TypeTraitsBase {
TVM_FFI_INLINE static void CopyToAnyView(const relax::NestedMsg<T>& src, TVMFFIAny* result) {
*result = ffi::AnyView(src.data_).CopyToTVMFFIAny();
}
TVM_FFI_INLINE static void MoveToAny(relax::NestedMsg<T> src, TVMFFIAny* result) {
*result = details::AnyUnsafe::MoveAnyToTVMFFIAny(std::move(src.data_));
}
TVM_FFI_INLINE static std::string GetMismatchTypeInfo(const TVMFFIAny* src) {
return TypeTraitsBase::GetMismatchTypeInfo(src);
}
static bool CheckAnyStrict(const TVMFFIAny* src) {
if (src->type_index == TypeIndex::kTVMFFINone) {
return true;
}
if (TypeTraits<T>::CheckAnyStrict(src)) {
return true;
}
if (src->type_index == TypeIndex::kTVMFFIArray) {
const ffi::ArrayObj* array = reinterpret_cast<const ffi::ArrayObj*>(src->v_obj);
for (size_t i = 0; i < array->size(); ++i) {
const Any& any_v = (*array)[i];
if (!details::AnyUnsafe::CheckAnyStrict<relax::NestedMsg<T>>(any_v)) return false;
}
}
return true;
}
TVM_FFI_INLINE static relax::NestedMsg<T> CopyFromAnyViewAfterCheck(const TVMFFIAny* src) {
return relax::NestedMsg<T>(Any(AnyView::CopyFromTVMFFIAny(*src)));
}
TVM_FFI_INLINE static relax::NestedMsg<T> MoveFromAnyAfterCheck(TVMFFIAny* src) {
return relax::NestedMsg<T>(details::AnyUnsafe::MoveTVMFFIAnyToAny(src));
}
static std::optional<relax::NestedMsg<T>> TryCastFromAnyView(const TVMFFIAny* src) {
if (CheckAnyStrict(src)) {
return CopyFromAnyViewAfterCheck(src);
}
// slow path run conversion
if (src->type_index == TypeIndex::kTVMFFINone) {
return relax::NestedMsg<T>(std::nullopt);
}
if (auto opt_value = TypeTraits<T>::TryCastFromAnyView(src)) {
return relax::NestedMsg<T>(*std::move(opt_value));
}
if (src->type_index == TypeIndex::kTVMFFIArray) {
const ArrayObj* n = reinterpret_cast<const ArrayObj*>(src->v_obj);
ffi::Array<relax::NestedMsg<T>> result;
result.reserve(n->size());
for (size_t i = 0; i < n->size(); i++) {
const Any& any_v = (*n)[i];
if (auto opt_v = any_v.try_cast<relax::NestedMsg<T>>()) {
result.push_back(*std::move(opt_v));
} else {
return std::nullopt;
}
}
return relax::NestedMsg<T>(result);
}
return std::nullopt;
}
TVM_FFI_INLINE static std::string TypeStr() {
return "NestedMsg<" + details::Type2Str<T>::v() + ">";
}
TVM_FFI_INLINE static std::string TypeSchema() {
std::ostringstream oss;
oss << R"({"type":"NestedMsg","args":[)";
oss << details::TypeSchema<T>::v();
oss << "]}";
return oss.str();
}
};
} // namespace ffi
} // namespace tvm
#endif // TVM_RELAX_NESTED_MSG_H_
+151
View File
@@ -0,0 +1,151 @@
/*
* 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.
*/
/*!
* \file tvm/relax/op_attr_types.h
* \brief Data structures that can appear in operator attributes.
*/
#ifndef TVM_RELAX_OP_ATTR_TYPES_H_
#define TVM_RELAX_OP_ATTR_TYPES_H_
#include <tvm/relax/block_builder.h>
#include <tvm/relax/expr.h>
#include <tvm/relax/type.h>
#include <tvm/te/tensor.h>
namespace tvm {
namespace relax {
enum OpPatternKind {
// Elementwise operation
kElemWise = 0,
// Broadcasting operator, can always map output axis to the input in order.
// for example :code:`out[i, ax1, j, ax2] = input[i, j]`.
// Note that the axis need to be in order so transpose is not a bcast operator.
kBroadcast = 1,
// Injective operator, can always injectively map output axis to a single input axis.
// All injective operator can still be safely fused to injective and reduction.
kInjective = 2,
// Communicative reduction operator.
kCommReduce = 3,
// Complex operation, can still fuse elemwise operations into its output.
// but cannot chain another complex op
kOutEWiseFusable = 4,
// The pattern for tuple nodes. Can fuse into subsequent injective ops,
// but treated specially
kTuple = 7,
// Opaque operation, cannot fuse anything.
kOpaque = 8
};
/*!
* \brief Packed function implementation for operators. The relax operator will be lowered to
* this packed function call during codegen.
*/
using FCallPacked = ffi::String;
/*!
* \brief Infer output type given the call
*
* \param call The call expression to be derived.
* \param ctx The builder context.
*/
using FInferType = ffi::TypedFunction<Type(const Call& call, const BlockBuilder& ctx)>;
/*!
* \brief The function type of a normalization function.
*
* A normalization function is used when a `relax::Call` may be
* expressed in multiple syntactically valid and semantically
* equivalent forms, to normalize to a single representation.
*
* Note: `FNormalize` is applied for each expression as part of the
* `relax::BlockBuilder`. While operator-specific validation may
* be performed within the `FNormalize` implementation, ensuring
* that errors are caught as early as possible, this should only be
* used when validation is fast to apply. If the validation logic
* may be slow, it should instead be implemented in `FValidate`,
* which is only run as part of the well-formed checker.
*
* \param bb The BlockBuilder context.
*
* \param call The call to be normalized. It is provided by-value, to
* avoid copies for the common case where the call is already normalized.
*/
using FNormalize = ffi::TypedFunction<Expr(const BlockBuilder& bb, Call call)>;
/*!
* \brief The function type of a validation function.
*
* A validation function is used to define constraints that should be
* verified for an operator as part of the well-formed checker.
*
* Note: `FValidate` is only applied as part of the well-formed
* checker. While this minimizes overhead while compiling Relax,
* this delay between generating an ill-formed `relax::Call` and
* identifying the ill-formed call may complicate debugging. If
* the validation logic is very fast to check, and doing so would
* not introduce a significant overhead, consider validating as part
* of `FNormalize`, which is applied by the block builder for each
* `relax::Call`.
*
* \param call The call to be validated.
*/
using FValidate = ffi::TypedFunction<void(const Call& call)>;
/*! \brief The function type of a legalization function.
*
* A legalization function is used to replace a `relax::Call` with
* more concrete implementations. For example, the operation
* `relax.op.add` may be replaced with a call to a TIR function
* implementing addition of two tensors.
*
* The purpose of `FLegalize` is to remove calls to the operator while
* lowering. Therefore, unlike `FNormalize`, the resulting expression
* may *not* contain the original operator.
*
* \param bb The BlockBuilder context.
* \param call The call to be legalized.
*/
using FLegalize = ffi::TypedFunction<Expr(const BlockBuilder& bb, const Call& call)>;
/*! \brief The function type of a function to lower the runtime builtin.
*
* A builtin function may be lowered to a lowered form in `LowerRuntimeBuiltin`.
*
* \param bb The BlockBuilder context.
* \param call The call to be lowered.
*/
using FLowerBuiltin = ffi::TypedFunction<Expr(const BlockBuilder& bb, const Call& call)>;
/*!
* \brief Gradient for a specific op.
*
* \param orig_var the original var corresponding to orig_call.
* \param orig_call the original Call(op) expr.
* \param output_grad the gradient of the Expr.
* \param ctx the current block builder context.
* \return the gradient for each parameter.
*/
using FPrimalGradient = ffi::TypedFunction<tvm::ffi::Array<Expr>(
const Var& orig_var, const Call& orig_call, const Var& output_grad, const BlockBuilder& ctx)>;
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_OP_ATTR_TYPES_H_
+332
View File
@@ -0,0 +1,332 @@
/*
* 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_SCRIPT_BUILDER_FRAME_H_
#define TVM_RELAX_SCRIPT_BUILDER_FRAME_H_
#include <tvm/ffi/reflection/registry.h>
#include <tvm/relax/block_builder.h>
#include <tvm/relax/expr.h>
#include <tvm/script/ir_builder/base.h>
#include <tvm/script/ir_builder/ir/frame.h>
#include <tvm/script/ir_builder/ir/ir.h>
#include <utility>
namespace tvm {
namespace script {
namespace ir_builder {
namespace relax {
/*! \brief The base ir_builder frame for the relax dialect. */
class RelaxFrameNode : public IRBuilderFrameNode {
public:
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<RelaxFrameNode>();
}
TVM_FFI_DECLARE_OBJECT_INFO("script.ir_builder.relax.RelaxFrame", RelaxFrameNode,
IRBuilderFrameNode);
};
class RelaxFrame : public IRBuilderFrame {
public:
explicit RelaxFrame(ffi::ObjectPtr<RelaxFrameNode> data) : IRBuilderFrame(ffi::UnsafeInit{}) {
TVM_FFI_ICHECK(data != nullptr);
data_ = std::move(data);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(RelaxFrame, IRBuilderFrame, RelaxFrameNode);
protected:
RelaxFrame() = default;
};
/*! \brief The base ir_builder frame for frames with SeqExpr
i.e. Functions, If branches
*/
class SeqExprFrameNode : public RelaxFrameNode {
public:
/*! \brief The binding blocks inside the frame. */
ffi::Array<tvm::relax::BindingBlock> binding_blocks;
/*! \brief The frame output expr. `std::nullopt` when undefined. */
ffi::Optional<tvm::relax::Expr> output;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SeqExprFrameNode>()
.def_ro("binding_blocks", &SeqExprFrameNode::binding_blocks)
.def_ro("output", &SeqExprFrameNode::output);
}
TVM_FFI_DECLARE_OBJECT_INFO("script.ir_builder.relax.SeqExprFrame", SeqExprFrameNode,
RelaxFrameNode);
public:
void EnterWithScope() override;
void ExitWithScope() override;
};
class SeqExprFrame : public RelaxFrame {
public:
explicit SeqExprFrame(ffi::ObjectPtr<SeqExprFrameNode> data) : RelaxFrame(data) {
TVM_FFI_ICHECK(data != nullptr);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(SeqExprFrame, RelaxFrame, SeqExprFrameNode);
};
/*! \brief The ir_builder frame for the relax function. */
class FunctionFrameNode : public SeqExprFrameNode {
public:
/*!
* \brief The function name.
* \note The name will not be specified in constructor, so it is "Optional",
* However, we must specify the name by `R.func_name` before exit this frame.
*/
ffi::Optional<ffi::String> name;
/*! \brief The function params. */
ffi::Array<tvm::relax::Var> params;
/*!
* \brief The function return type.
* \note Usually the function return type can be deduced by the function body.
* But we can use this field to specify a more "accurate" return type.
* i.e. If the `ret_ty` is None, try to use the deduced type from body
* If the `ret_ty` is not None, we can still take body.ty
* if we ret_ty is base of body.ty. If not, we will
* take the specified `ret_ty`.
*/
ffi::Optional<tvm::Type> ret_ty;
/*! \brief Whether the function is annotated as pure */
ffi::Optional<bool> is_pure;
/*! \brief Whether the function is annotated as private */
ffi::Optional<bool> is_private;
/*! \brief The function attributes. */
ffi::Map<ffi::String, Any> attrs;
/*! \brief The block builder to create Relax function. */
tvm::relax::BlockBuilder block_builder;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<FunctionFrameNode>()
.def_ro("name", &FunctionFrameNode::name)
.def_ro("params", &FunctionFrameNode::params)
.def_ro("ret_ty", &FunctionFrameNode::ret_ty)
.def_ro("is_pure", &FunctionFrameNode::is_pure)
.def_ro("attrs", &FunctionFrameNode::attrs);
// `binding_blocks` and `output` are inherited from SeqExprFrameNode.
// `block_builder` is not registered as it's not visited.
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("script.ir_builder.relax.FunctionFrame", FunctionFrameNode,
SeqExprFrameNode);
public:
void EnterWithScope() final;
void ExitWithScope() final;
};
class FunctionFrame : public SeqExprFrame {
public:
explicit FunctionFrame(ffi::ObjectPtr<FunctionFrameNode> data) : SeqExprFrame(data) {
TVM_FFI_ICHECK(data != nullptr);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(FunctionFrame, SeqExprFrame, FunctionFrameNode);
};
/*! \brief The ir_builder frame for relax binding blocks. */
class BindingBlockFrameNode : public RelaxFrameNode {
public:
/*! \brief The flag that indicates whether the block is a dataflow block. */
bool is_dataflow;
/*! \brief The variables emitted in this block. */
ffi::Array<tvm::relax::Var> emitted_vars;
/*!
* \brief A boolean indicating if the dataflow block is ended of construction.
* If it is true, any new binding trying to be emitted into this block will cause an error.
* \note Only used for a dataflow block.
*/
bool block_ended;
/*!
* \brief The output vars of the dataflow block.
* \note Only used for a dataflow block.
*/
ffi::Array<tvm::relax::Var> output_vars;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<BindingBlockFrameNode>()
.def_ro("is_dataflow", &BindingBlockFrameNode::is_dataflow)
.def_ro("emitted_vars", &BindingBlockFrameNode::emitted_vars)
.def_ro("output_vars", &BindingBlockFrameNode::output_vars);
// `block_ended` is not registered as it's not visited.
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("script.ir_builder.relax.BindingBlockFrame",
BindingBlockFrameNode, RelaxFrameNode);
public:
void EnterWithScope() final;
void ExitWithScope() final;
};
class BindingBlockFrame : public RelaxFrame {
public:
explicit BindingBlockFrame(ffi::ObjectPtr<BindingBlockFrameNode> data) : RelaxFrame(data) {
TVM_FFI_ICHECK(data != nullptr);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(BindingBlockFrame, RelaxFrame,
BindingBlockFrameNode);
};
/*!
* \brief A frame that represents if statement.
*
* \sa IfFrame
*/
class IfFrameNode : public RelaxFrameNode {
public:
/*! \brief The condition of the if statement. */
tvm::relax::Expr condition;
/*! \brief The Bindings in the true branch. */
ffi::Optional<tvm::relax::Expr> then_expr;
/*! \brief The Bindings in the false branch. */
ffi::Optional<tvm::relax::Expr> else_expr;
/*! \brief The Binding var. */
tvm::relax::Var var;
/*! \brief The binding var name. */
ffi::String var_name;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<IfFrameNode>()
.def_ro("condition", &IfFrameNode::condition)
.def_ro("then_expr", &IfFrameNode::then_expr)
.def_ro("else_expr", &IfFrameNode::else_expr)
.def_ro("var", &IfFrameNode::var)
.def_ro("var_name", &IfFrameNode::var_name);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("script.ir_builder.relax.IfFrame", IfFrameNode, RelaxFrameNode);
public:
/*!
* \brief The method called when entering RAII scope.
* \sa tvm::support::With
*/
void EnterWithScope() final;
/*!
* \brief The method called when exiting RAII scope.
* \sa tvm::support::With
*/
void ExitWithScope() final;
};
/*!
* \brief Managed reference to IfFrameNode.
*
* \sa IfFrameNode
*/
class IfFrame : public RelaxFrame {
public:
explicit IfFrame(ffi::ObjectPtr<IfFrameNode> data) : RelaxFrame(data) {
TVM_FFI_ICHECK(data != nullptr);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(IfFrame, RelaxFrame, IfFrameNode);
};
/*!
* \brief A frame that represents then.
*
* \sa ThenFrame
*/
class ThenFrameNode : public SeqExprFrameNode {
public:
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ThenFrameNode>();
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("script.ir_builder.relax.ThenFrame", ThenFrameNode,
SeqExprFrameNode);
public:
/*!
* \brief The method called when entering RAII scope.
* \sa tvm::support::With
*/
void EnterWithScope() final;
/*!
* \brief The method called when exiting RAII scope.
* \sa tvm::support::With
*/
void ExitWithScope() final;
};
/*!
* \brief Managed reference to ThenFrameNode.
*
* \sa ThenFrameNode
*/
class ThenFrame : public SeqExprFrame {
public:
explicit ThenFrame(ffi::ObjectPtr<ThenFrameNode> data) : SeqExprFrame(data) {
TVM_FFI_ICHECK(data != nullptr);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(ThenFrame, SeqExprFrame, ThenFrameNode);
};
/*!
* \brief A frame that represents else.
*
* \sa ElseFrame
*/
class ElseFrameNode : public SeqExprFrameNode {
public:
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ElseFrameNode>();
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("script.ir_builder.relax.ElseFrame", ElseFrameNode,
SeqExprFrameNode);
public:
/*!
* \brief The method called when entering RAII scope.
* \sa tvm::support::With
*/
void EnterWithScope() final;
/*!
* \brief The method called when exiting RAII scope.
* \sa tvm::support::With
*/
void ExitWithScope() final;
};
/*!
* \brief Managed reference to ElseFrameNode.
*
* \sa ElseFrameNode
*/
class ElseFrame : public SeqExprFrame {
public:
explicit ElseFrame(ffi::ObjectPtr<ElseFrameNode> data) : SeqExprFrame(data) {
TVM_FFI_ICHECK(data != nullptr);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(ElseFrame, SeqExprFrame, ElseFrameNode);
};
} // namespace relax
} // namespace ir_builder
} // namespace script
} // namespace tvm
#endif // TVM_RELAX_SCRIPT_BUILDER_FRAME_H_
+144
View File
@@ -0,0 +1,144 @@
/*
* 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_SCRIPT_BUILDER_IR_H_
#define TVM_RELAX_SCRIPT_BUILDER_IR_H_
#include <tvm/relax/expr.h>
#include <tvm/relax/script/builder/frame.h>
#include <tvm/relax/type.h>
#include <tvm/script/ir_builder/base.h>
namespace tvm {
namespace script {
namespace ir_builder {
namespace relax {
/////////////////////////////// Function ////////////////////////////////
/*!
* \brief Start a function frame.
* \param is_pure Whether the function is annotated as pure.
* \param is_private Whether the function is annotated as private.
* \return The created ir_builder Function frame.
*/
TVM_DLL FunctionFrame Function(bool is_pure, bool is_private);
/*!
* \brief Add a parameter to the last function frame.
* \param name The name of the parameter.
* \param ty The ty of the parameter.
* \return The created function parameter var.
*/
TVM_DLL tvm::relax::Var Arg(const ffi::String& name, const tvm::Type& ty);
/*!
* \brief Specify the name of the last function frame.
* \param name The function name.
*/
TVM_DLL void FuncName(const ffi::String& name);
/*!
* \brief Specify the attrs of the last function frame.
* \param attrs The function attrs.
*/
TVM_DLL void FuncAttrs(ffi::Map<ffi::String, Any> attrs);
/*!
* \brief Specify the return type of the last function frame.
* \param ret_ty The return type.
*/
TVM_DLL void FuncRetType(const tvm::Type& ret_ty);
/*!
* \brief Specify the return value of the last function frame.
* \param value The return value.
*/
TVM_DLL void FuncRetValue(const tvm::relax::Expr& value);
///////////////////////////// BindingBlock //////////////////////////////
/*!
* \brief Start a binding block frame.
* \return The created ir_builder Block frame.
*/
TVM_DLL BindingBlockFrame BindingBlock();
/*!
* \brief Start a dataflow binding block frame.
* \return The created ir_builder Block frame.
*/
TVM_DLL BindingBlockFrame Dataflow();
/*!
* \brief Expose the dataflow block output variables as global ones
* \param vars The output variables of a dataflow block
*/
TVM_DLL void DataflowBlockOutput(const ffi::Array<tvm::relax::Var>& vars);
////////////////////////////// Bindings ////////////////////////////////
/*!
* \brief Emit a binding to the last binding block frame.
* \param value The right side value of the bindings to be emitted.
* \param annotate_ty The optional type annotation for the emitted value.
* \return The left side var of the emitted binding.
*/
TVM_DLL tvm::relax::Var Emit(const tvm::relax::Expr& value,
const ffi::Optional<tvm::Type>& annotate_ty = std::nullopt);
/*!
* \brief Emit a match_cast binding to the last binding block frame.
* \param value The value of the MatchCast to be emitted.
* \param ty The type of the MatchCast to be emitted.
* \return The left side var of the emitted binding.
*/
TVM_DLL tvm::relax::Var EmitMatchCast(const tvm::relax::Expr& value, const tvm::Type& ty);
/*!
* \brief Emit a binding to the last binding block frame.
* \param binding The binding to be emitted.
* \return The left side var of the emitted binding.
*/
TVM_DLL tvm::relax::Var EmitVarBinding(const tvm::relax::VarBinding& binding);
///////////////////////////// If Then Else /////////////////////////////
/*!
* \brief Create an if statement.
* \param condition The condition of if statement.
* \return The result IfFrame.
*/
IfFrame If(tvm::relax::Expr condition);
/*!
* \brief Create a then.
* \return The result ThenFrame.
*/
ThenFrame Then();
/*!
* \brief Create an else.
* \return The result ElseFrame.
*/
ElseFrame Else();
} // namespace relax
} // namespace ir_builder
} // namespace script
} // namespace tvm
#endif // TVM_RELAX_SCRIPT_BUILDER_IR_H_
+78
View File
@@ -0,0 +1,78 @@
/*
* 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.
*/
/*!
* \file tir_pattern.h
* \brief Data Structure of TIR Pattern used for matching.
*/
#ifndef TVM_RELAX_TIR_PATTERN_H_
#define TVM_RELAX_TIR_PATTERN_H_
#include <tvm/ffi/reflection/registry.h>
#include <tvm/tirx/function.h>
namespace tvm {
namespace relax {
using TIRPattern = tirx::PrimFunc;
/*
* \brief The match result of a TIR pattern.
*/
class MatchResultNode : public ffi::Object {
public:
/*! The matched tirx pattern*/
TIRPattern pattern;
/*! \brief The evaluated values of symbolic vars. */
ffi::Array<PrimExpr> symbol_values;
/*! \brief The matched buffers of input and output. */
ffi::Array<tirx::Buffer> matched_buffers;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<MatchResultNode>()
.def_ro("pattern", &MatchResultNode::pattern)
.def_ro("symbol_values", &MatchResultNode::symbol_values)
.def_ro("matched_buffers", &MatchResultNode::matched_buffers);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.MatchResult", MatchResultNode, ffi::Object);
};
/*!
* \brief Managed reference to MatchResultNode.
*/
class MatchResult : public ffi::ObjectRef {
public:
/*!
* \brief Constructor
* \param pattern The matched tirx pattern.
* \param symbol_values The evaluated values of symbolic vars.
* \param matched_buffers The matched buffers of input and output.
*/
TVM_DLL explicit MatchResult(TIRPattern pattern, ffi::Array<PrimExpr> symbol_values,
ffi::Array<tirx::Buffer> matched_buffers);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(MatchResult, ffi::ObjectRef, MatchResultNode);
};
using FCodegen = ffi::TypedFunction<ffi::Array<ffi::Any>(ffi::Array<MatchResult> match_results)>;
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_TIR_PATTERN_H_
+687
View File
@@ -0,0 +1,687 @@
/*
* 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.
*/
/*!
* \file tvm/relax/transform.h
* \brief Relax specific transformation passes.
*/
#ifndef TVM_RELAX_TRANSFORM_H_
#define TVM_RELAX_TRANSFORM_H_
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/transform.h>
#include <tvm/relax/dataflow_pattern.h>
#include <tvm/relax/expr.h>
#include <tvm/tirx/function.h>
#include <tvm/tirx/index_map.h>
namespace tvm {
namespace relax {
namespace transform {
using Pass = tvm::transform::Pass;
using PassInfo = tvm::transform::PassInfo;
using PassContext = tvm::transform::PassContext;
using Function = tvm::relax::Function;
using DataflowBlock = tvm::relax::DataflowBlock;
using tvm::transform::CreateModulePass;
using LayoutCb = ffi::TypedFunction<ffi::Map<ffi::String, ffi::Array<ffi::String>>(Call)>;
/*!
* \brief Create a function pass.
*
* \param pass_func The packed function that contains the optimization.
* \param opt_level The optimization level of the function pass.
* \param name The name of the function pass.
* \param required The list of the passes that the function pass is dependent on.
* \param traceable Boolean variable whether the dataflowblock pass is traceable.
*
* \return The created function pass.
*/
TVM_DLL Pass CreateFunctionPass(std::function<Function(Function, IRModule, PassContext)> pass_func,
int opt_level, ffi::String name,
tvm::ffi::Array<ffi::String> required, bool traceable = false);
/*!
* \brief Create a dataflowblock pass.
*
* \param pass_func The packed function that contains the optimization.
* \param opt_level The optimization level of the dataflowblock pass.
* \param name The name of the dataflowblock pass.
* \param required The list of the passes that the dataflowblock pass is dependent on.
* \param traceable Boolean variable whether the dataflowblock pass is traceable.
*
* \return The created dataflowblock pass.
*/
TVM_DLL Pass CreateDataflowBlockPass(
std::function<DataflowBlock(DataflowBlock, IRModule, PassContext)> pass_func, int opt_level,
ffi::String name, tvm::ffi::Array<ffi::String> required, bool traceable = false);
/*!
* \brief Perform lambda lifting to lift functions from nested into global.
*
* \return The Pass.
*/
TVM_DLL Pass LambdaLift();
/*!
* \brief Transform all dataflow structure to non-dataflow version.
*
* \return The Pass.
*/
TVM_DLL Pass ToNonDataflow();
/*!
* \brief Activate force_pure on all pure functions in the module
* and unwrap all pure override ops into the normal versions.
*
* This effectively means that there will be no more purity tracking,
* useful for low-level code generation.
*
* \return The Pass.
*
* \note Should be used after ToNonDataflow()
*/
TVM_DLL Pass RemovePurityChecking();
/*!
* \brief Perform explicit tensor allocation for call_tir and call_dps_packed.
*
* \return The Pass.
*/
TVM_DLL Pass CallTIRRewrite();
/*!
* \brief Convert all reshape-like call_tir whose corresponding binding
* vars are DataflowVars to relax.reshape operator calls. The relax.reshape
* calls will be lowered an external builtin function call in a subsequent
* pass, where the external builtin function does a CreateView operation
* at runtime, instead of doing real data copy.
* Here "reshape-like" includes reshape, expand_dims, flatten, etc.
*
* \return The Pass.
* \note The pass is applied at the first stage of Relax VM build, before
* rewriting call_tir, as this pass requires dataflow information.
*/
TVM_DLL Pass RewriteDataflowReshape();
/*!
* \brief The static memory planning pass on BindingBlock level.
* The pass will reuse allocated memory to its best effort, in order to
* reduce the total amount of allocated memory size.
*
* The pass "supports" dynamic shape in the way of TIR variable bound
* annotations. We can optionally annotate the attributes "tir_var_upper_bound"
* and "tir_var_lower_bound" to Relax functions. The attribute values are dicts
* from strings to integers, denoting the name of TIR variables to the bound
* values of the TIR vars.
* Note: The annotated bound attributes only apply to TIR vars in the
* function signature for clarity.
*
* For example, we can annotate a Relax function with
* `R.func_attr({"tir_var_lower_bound": {"n": 1}, "tir_var_upper_bound": {"n": 1024}})`.
* It means the variable that names "n" in the function signature will have
* range [1, 1024]. And we will use these bounds during memory planning.
* If lower bound is not specified, it defaults to 0.
*
* \return The pass.
*/
TVM_DLL Pass StaticPlanBlockMemory();
/*!
* \brief Attach global_symbol to Relax functions and TIR Primfuncs for codegen.
*
* \return The Pass.
*/
TVM_DLL Pass AttachGlobalSymbol();
/*!
* \brief Transform Relax IR to normal form: transform AST to A-normal form, and fill the
* ty of expressions.
*
* \return The Pass.
*/
TVM_DLL Pass Normalize();
/*!
* \brief Possibly rename the GlobalVar in an IRModule to ensure these properties:
* 1. (Invariant) First ensure every public function has the same name as its "global_symbol"
* attribute;
* 2. To ensure 1., we may need to rename private functions with conflicting names;
* 3. Finally, the name of every GlobalVar is unique in the IRModule.
*/
TVM_DLL Pass NormalizeGlobalVar();
/*!
* \brief Simplify a Relax module by folding var bindings and match shape nodes,
* as well as tuple indices.
* Best used alongside constant folding and eliminating unused bindings.
*
* \note If a dataflow var is used only in a binding to the dataflow block
* output var (i.e., a non-dataflow var), this pass will also remove the dataflow var
* and replaces the output var's binding with the dataflow var's direct definition.
*
* \return The Pass.
*/
TVM_DLL Pass CanonicalizeBindings();
/*!
* Eliminate common subexpressions within functions.
* \return The pass that eliminates common subexpressions.
*
* \note For nested functions, this pass performs CSE *within* those functions.
* \param call_only If true, enable eliminating only call nodes.
*/
TVM_DLL Pass EliminateCommonSubexpr(bool call_only = false);
/*!
* \brief Bind params of function of the module to constant tensors.
*
* \param func_name The name of the function to bind parameters.
* \param params The parameters to bind.
*
* \return The Pass.
*/
TVM_DLL Pass BindParams(ffi::String func_name, ffi::Map<Any, ffi::ObjectRef> params);
/*!
* \brief Bind symbolic vars to constant shape values.
*
* \param binding_map The dictionary of symbolic variables and their
* constant shape values. Dictionary 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 each function where it is used.
*
* \param func_name The name of the function in which to bind shape
* values. If std::nullopt, all functions in the module will be
* updated.
*
* \return The Pass.
*/
TVM_DLL Pass BindSymbolicVars(ffi::Map<ffi::Variant<tirx::Var, ffi::String>, PrimExpr> binding_map,
ffi::Optional<ffi::String> func_name = std::nullopt);
/*!
* \brief Fold constant expressions within dataflow blocks.
*
* \note ConvertToDataflow may need to be called first to provide dataflow blocks.
*
* \return The Pass.
*/
TVM_DLL Pass FoldConstant();
/*!
* \brief Legalize high-level operator calls in Relax functions to call_tir
* with corresponding low-level TIR PrimFuncs.
*
* For each high-level operator, we register the way of legalizing it as a
* function, which takes a context BlockBuilder and the Call being legalized
* as input, and returns the legalized call. Here the input BlockBuilder is
* mainly used for adding the PrimFunc created by call_te into the context
* IRModule.
*
* The legalization function for each operator is registered as an attribute (with
* attribute key `FLegalize`) of the operator.
*
* For customizability, the user can pass their own legalization by an optional customized map,
* with the key to be the operator name and value to be the legalization function.
* The default legalization function will be overridden by the customized one.
*
* \param cmap The customized operator legalization function map. The customized function
* will override the default one.
* \param skip_ops The list operator names which need to be skipped from legalization
* \param enable_warning A boolean value indicating if to print warnings for TIR functions not
* showing up in the database.
* \return The Pass.
*/
TVM_DLL Pass LegalizeOps(ffi::Optional<ffi::Map<ffi::String, ffi::Function>> cmap,
ffi::Optional<ffi::Array<ffi::String>> skip_ops,
bool enable_warning = false);
/*!
* \brief Propagate virtual device information.
* \return The Pass.
*/
TVM_DLL Pass RealizeVDevice();
/*!
* \brief Attach layout free buffers to the tirx::PrimFunc.
*
* This pass is used to attach layout free buffers to the tirx::PrimFunc according to
* the function usage in the relax function. Currently, the layout free buffers are the model
* weights and relax constants.
*
* \note We recommend applying CanonicalizeBindings before this pass.
* \return The Pass.
*/
TVM_DLL Pass AttachAttrLayoutFreeBuffers();
/*!
* \brief Split the layout rewrite preproc block to a separate tirx::PrimFunc.
*
* This pass is used in the prepack weight after meta_schedule tuning.
*
* \return The Pass.
*/
TVM_DLL Pass SplitLayoutRewritePreproc();
/*!
* \brief Lift transformation of the parameters of a function.
*
* When some inputs of the function is marked as 'parameters' (the model weights), this pass
* identifies the transformation of the parameters and lifts them to a separate function called
* `transform_params`. `transform_params` takes a tuple of the original parameters as input and
* returns a tuple of the transformed parameters. The original function will be rewritten to accept
* a tuple of transformed parameters as input.
*
* Users are expected to invoke the `transform_params` function in runtime and pass the transformed
* parameters to the original function as input.
*
* \param shared_transform Indicates how the parameter transformation function will be produced.
* - `False` (default): A separate parameter transformation function will be produced for each
* function with the `"num_input"` attribute.
*
* - `True`: A single parameter transformation function will be produced, containing the
* preprocessing steps common across all functions with the `"num_input"` attribute.
*
* - List[str]: A single parameter transformation function will be produced, containing the
* preprocessing steps common across each function whose name is in the list. Passing a list of
* all functions with the `"num_input"` attribute or an empty list is equivalent to passing
* `True`.
*
* \return The Pass.
*/
TVM_DLL Pass
LiftTransformParams(ffi::Variant<bool, ffi::Array<ffi::String>> shared_transform = false);
/*!
* \brief Update virtual device.
* \param new_vdevice The new virtual device.
* \param index The device index indicates the device on which the update will be performed.
* \return The Pass.
*/
TVM_DLL Pass UpdateVDevice(VDevice new_vdevice, int64_t index);
/*! \brief Expand tuple arguments to internal functions
*
* \return The Pass
*/
TVM_DLL Pass ExpandTupleArguments();
/*! \brief Remove unused parameters to internal functions
*
* \return The Pass
*/
TVM_DLL Pass RemoveUnusedParameters();
/*! \brief Remove unused outputs from internal functions
*
* \return The Pass
*/
TVM_DLL Pass RemoveUnusedOutputs();
/*!
* \brief Annotate Op Pattern Kind for TIR functions, which is used in FuseOps.
* \note It is an auto-detect pass for "unscheduled prim_funcs", the op_pattern will be
* "opaque" of we can't detect it. Users can manually annotate the attr `op_pattern`
* to prim_func.
* \return The Pass.
*/
TVM_DLL Pass AnnotateTIROpPattern();
/*!
* \brief This pass groups bindings in a dataflow block of Relax functions and generates a new
* grouped Relax function for each group, according to the fusion algorithm described in the pass
* implementation. By grouping bindings into new Relax functions, we substitute the bindings in the
* function being manipulated into function calls to the new grouped function.
*
* A follow-up pass named "FuseTIR" will generate a TIR PrimFunc for each grouped function.
* \param fuse_opt_level The level of fuse optimization.
* -1 indicates that the level will be inferred from pass context.
* \return The Pass.
*/
TVM_DLL Pass FuseOps(int fuse_opt_level = -1);
/*!
* \brief The pattern object used as the input of FuseOpsByPattern. For bindings to be
* fused, it needs to be matched with `pattern` and the `check` function needs to return
* true.
*/
class FusionPatternNode : public ffi::Object {
public:
/*!
* \brief The name of pattern. It becomes the value of the kComposite attribute
* of a fused function after successful matching
*/
ffi::String name;
/*!
* \brief The dataflow pattern that will be used to match expression in the DataflowBlock.
* All the call nodes covered by the pattern will be extracted into the fused function.
*/
DFPattern pattern;
/*!
* \brief The map which is used to extract important expressions from the pattern match
* result. All DFPattern in this map should be part of the `pattern`.
*/
ffi::Map<ffi::String, DFPattern> annotation_patterns;
/*!
* \brief The function to determine whether the match result is accepted. This can be
* std::nullopt if check function is not necessary for this pattern.
*
* It should have signature
* bool(const PatternCheckContext& context)
*/
ffi::Optional<ffi::Function> check;
/*!
* \brief The function to get attributes for fused function
*
* It should have signature
* ffi::Map<ffi::String, Any>(const ffi::Map<ffi::String, Expr>& context)
*/
ffi::Optional<ffi::Function> attrs_getter;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<FusionPatternNode>()
.def_ro("name", &FusionPatternNode::name)
.def_ro("pattern", &FusionPatternNode::pattern)
.def_ro("annotation_patterns", &FusionPatternNode::annotation_patterns)
.def_ro("check", &FusionPatternNode::check)
.def_ro("attrs_getter", &FusionPatternNode::attrs_getter);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.transform.FusionPattern", FusionPatternNode,
ffi::Object);
};
class FusionPattern : public ffi::ObjectRef {
public:
FusionPattern(ffi::String name, DFPattern pattern,
ffi::Map<ffi::String, DFPattern> annotation_patterns,
ffi::Optional<ffi::Function> check, ffi::Optional<ffi::Function> attrs_getter);
FusionPattern(ffi::String name, DFPattern pattern)
: FusionPattern(name, pattern, {}, std::nullopt, std::nullopt) {}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(FusionPattern, ffi::ObjectRef, FusionPatternNode);
};
/*!
* \brief The input of FusionPattern::check.
*/
class PatternCheckContextNode : public ffi::Object {
public:
/*!
* \brief The expression that's matched with the FusionPattern::pattern.
*/
Expr matched_expr;
/*!
* \brief A map which contains all expressions matched by the sub patterns in
* FusionPattern::annotation_patterns.
*/
ffi::Map<ffi::String, Expr> annotated_expr;
/*!
* \brief Map from variable to its value. It contains variables from bindings that
* is being fused by FuseOpsByPattern.
*/
ffi::Map<Var, Expr> matched_bindings;
/*!
* \brief A map mapping variable definitions to a set of uses. It has all variables
* used in the function.
*/
ffi::Map<Var, ffi::Array<Var>> var_usages;
/*!
* \brief Map from value to its bound variable. It doesn't have variables after the
* matched expression.
*/
ffi::Map<Expr, Var> value_to_bound_var;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PatternCheckContextNode>()
.def_ro("matched_expr", &PatternCheckContextNode::matched_expr)
.def_ro("annotated_expr", &PatternCheckContextNode::annotated_expr)
.def_ro("matched_bindings", &PatternCheckContextNode::matched_bindings)
.def_ro("var_usages", &PatternCheckContextNode::var_usages)
.def_ro("value_to_bound_var", &PatternCheckContextNode::value_to_bound_var);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.transform.PatternCheckContext", PatternCheckContextNode,
ffi::Object);
};
class PatternCheckContext : public ffi::ObjectRef {
public:
PatternCheckContext(Expr matched_expr, ffi::Map<ffi::String, Expr> annotated_expr,
ffi::Map<Var, Expr> matched_bindings,
ffi::Map<Var, ffi::Array<Var>> var_usages,
ffi::Map<Expr, Var> value_to_bound_var);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(PatternCheckContext, ffi::ObjectRef,
PatternCheckContextNode);
};
/*!
* \brief Reverse-mode automatic differentiation.
*
* This pass will differentiate one function in the IRModule. Now the input function must have only
* one dataflow block.
*
* For a given function specified by `func_name`, it generates a new function with the name
* `func_name + "_adjoint"`. The new function computes the gradient of the **differentiation
* target** with respect to the arguments specified by `require_grads` of the original function.
*
* If the function has only one return value, the return value will be specified as target. If the
* function has more than one return values, the target will be specified as the target_index-th
* return value. The target must be a scalar (0-dim tensor).
*
* \param func_name The name of the specified function.
* \param require_grads The relax variables whose adjoints is needed. Must be parameters of the
* given function and should not be duplicate. If it is not specified, adjoints of all parameters
* would be computed.
* \param target_index If the specified function has more than one return values, specify the index
* of the return value as the target. If it is not specified, the first return value will be the
* target.
* \return The Pass.
*
* \note ConvertToDataflow may need to be called first to provide dataflow blocks.
*/
TVM_DLL Pass Gradient(ffi::String func_name,
ffi::Optional<ffi::Array<Var>> require_grads = std::nullopt,
int target_index = 0);
/*!
* \brief Apply pattern matching to each function in the given module, and group matched
* expressions into a new function. The end result is similar to FuseOps, but fusion is driven
* completely by the provided patterns.
*
* \param patterns The patterns to detect. The order of the patterns determines the order
* of priority in which they are matched. Higher-priority patterns should come earlier in the list.
* \param bind_constants Whether or not to keep bound constants of the grouped function.
* \param annotate_codegen If true, wrap each created composite function with another function,
* whose body consists only of a call to the composite function, and annotate the outer function
* with kCodegen and kGlobalSymbol attributes. The kCodegen attribute is set as the prefix of the
* corresponding pattern name. For example, "dnnl" if the pattern name is "dnnl.conv2d_relu".
* This must be True if the created composite functions are intended to be offloaded to
* an external backend without using the MergeCompositeFunctions pass.
* \param entry_function_names The names of functions that should be considered as entry points. If
* not specified, all externally exposed functions will be considered as entry points.
* \return The Pass.
*
* \note Only operates within dataflow blocks. ConvertToDataflow may need to be called first.
*/
TVM_DLL Pass FuseOpsByPattern(const tvm::ffi::Array<FusionPattern>& patterns,
bool bind_constants = true, bool annotate_codegen = false,
const tvm::ffi::Array<ffi::String>& entry_function_names = {});
/*!
* \brief Group one or multiple composite functions created by FuseOpsByPattern into a new
* function. The new function will be annotated with kCodegen and GlobalSymbol attributes,
* and it is intented to be offloaded to an external backend.
*
* \return The Pass.
*/
TVM_DLL Pass MergeCompositeFunctions();
/*!
* \brief Fuse relax sub-function into a larger TIR function if possible.
this pass works together with FuseOps to perform operator fusion.
* \return The Pass.
*/
TVM_DLL Pass FuseTIR();
/*!
* \brief Run codegen.
* \param target_options pairs of target name and compilation options
* \param entry_functions list of entry functions
* \return The Pass.
*/
TVM_DLL Pass
RunCodegen(ffi::Optional<ffi::Map<ffi::String, ffi::Map<ffi::String, ffi::Any>>> target_options,
ffi::Array<ffi::String> entry_functions);
/*!
* \brief Decompose composite operators during inference. For example, The result of batch norm (a
* triple) will be simplified. Operators like Attention, Erf, etc. can be also simplified into
* several operators as well.
*
* \param func_name The name of the specified function. If not specified, the pass will run in
* all functions.
*/
TVM_DLL Pass DecomposeOpsForInference(ffi::Optional<ffi::String> func_name);
/*!
* \brief Decompose composite operators during training. For example, The result of batch norm (a
* triple) will be simplified. Operators like Attention, Erf, etc. can be also simplified into
* several operators as well.
*
* \param func_name The name of the specified function. If not specified, the pass will run in
* all functions.
*/
TVM_DLL Pass DecomposeOpsForTraining(ffi::Optional<ffi::String> func_name);
/*!
* \brief Returns a pass which replaces PrimFuncs which have matching kOperatorName attribute in \p
* op_impl_map, with replacement PrimFunc that could possibly have different layouts on i/o
* buffers. The layout transformations on i/o buffers is present in the \p op_buffer_transforms. The
* pass inserts the layout transformations in the call sites of PrimFuncs being replaced to
* transform i/o buffers into expected layout.
*
* \param op_impl_map Map from kOperatorName attr (e.g., relax.conv2d) to replacement PrimFunc
* \param op_buffer_transforms Map from kOperatorName attr to layout transformations on each of the
* PrimFunc i/o buffers.
* \param axis_separators Map from kOperatorName attr to axis_separators of each buffer_transforms
* \param input_axis_separators Map from kOperatorName attr to axis_separator for input buffer
* \return The Pass.
*/
TVM_DLL Pass AlterOpImpl(
const ffi::Map<ffi::String, tirx::PrimFunc>& op_impl_map,
const ffi::Map<ffi::String, ffi::Array<tirx::IndexMap>>& op_buffer_transforms,
const ffi::Map<ffi::String, ffi::Optional<ffi::Array<ffi::Array<IntImm>>>>& axis_separators,
const ffi::Map<ffi::String, ffi::Optional<ffi::Array<ffi::Array<IntImm>>>>&
input_axis_separators);
/*!
* \brief Layout conversion pass.
* \param desired_layouts The desired layouts for some operators.
* \param layout_cb custom call back to define layouts dynamically.
* \return The Pass.
* \note Operates only on dataflow blocks. ConvertToDataflow may need to be called first.
*/
TVM_DLL Pass ConvertLayout(ffi::Map<ffi::String, ffi::Array<ffi::String>> desired_layouts,
LayoutCb layout_cb);
/*!
* \brief A pass that converts consecutive dataflow operations
* inside binding blocks into dataflow blocks.
* \param min_size The minimum number of consecutive dataflow bindings
* required for the pass to create a new dataflow block
* \return The Pass.
*/
TVM_DLL Pass ConvertToDataflow(int min_size = 2);
/*!
* \brief Dead code elimination.
* \sa RemoveAllUnused
* Currently it removes:
* 1. Unused local VarBindings
* (those where the bound var is unused and no impure operation is used).
* 2. Unused Relax functions in the module.
* We detect the call chain from the entry function, and remove all unused functions.
*
* Any binding blocks that are left empty will be removed by the normalizer.
*
* \param entry_functions Names of functions that should be considered
* as entry points, in addition to any externally exposed functions.
*
* \return The Pass.
*/
TVM_DLL Pass DeadCodeElimination(ffi::Array<ffi::String> entry_functions = {});
/*!
* \brief Pass that changes calls to operators that can be done in-place
* (generally, these are elementwise operations) in dataflow blocks into in-place implementations.
* Supported operators will be replaced by calls to `call_tir_inplace` that invoke in-place
* PrimFunc implementations of those operators (which are based on the legalizations of those
* operators).
* \note ConvertToDataflow may need to be called first to provide dataflow blocks.
* \return The pass.
*/
TVM_DLL Pass DataflowUseInplaceCalls();
/*!
* \brief Automatic mixed precision pass. Currently the pass assumes the input module to be fp32
* only, and will automatically cast fp32 to fp16 for certain ops.
* \param out_dtype The output data type of gemm/conv, which is the data type of the accumulator.
* \param fp16_input_names The names of function parameters whose dtype should become fp16. The
* function signature would change accordingly.
* \return The Pass.
*
* \note Mainly operates within dataflow blocks. ConvertToDataflow may need to be called first.
*/
TVM_DLL Pass ToMixedPrecision(
DLDataType out_dtype, ffi::Optional<ffi::Array<ffi::String>> fp16_input_names = std::nullopt);
/*!
* \brief Rewrite a Relax module for executing with CUDA graph. This pass identifies
* the regions that can be executed with CUDA graph and lifts them into new functions for runtime
* graph capturing.
*/
TVM_DLL Pass RewriteCUDAGraph();
/*!
* \brief This pass updates the var_buffer mapping of PrimFunctions from the call_tir info.
* Primarily used to update the VDevice information if any changes occurred from the caller.
* This pass recreates the buffers and updates the map.
*/
TVM_DLL Pass SpecializePrimFuncBasedOnCallSite();
} // namespace transform
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_TRANSFORM_H_
+429
View File
@@ -0,0 +1,429 @@
/*
* 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.
*/
/*!
* \file tvm/relax/type.h
* \brief Relax types, including the richer dependent Relax type nodes.
*/
#ifndef TVM_RELAX_TYPE_H_
#define TVM_RELAX_TYPE_H_
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/attrs.h>
#include <tvm/ir/env_func.h>
#include <tvm/ir/global_info.h>
#include <tvm/ir/type.h>
#include <tvm/tirx/expr.h>
#include <string>
#include <utility>
namespace tvm {
namespace relax {
using Expr = tvm::Expr;
using ExprNode = tvm::ExprNode;
class BlockBuilder;
/*! \brief Indicates the number of dimensions of a tensor is unknown at compile time. */
static constexpr int kUnknownNDim = -1;
using tvm::TupleType;
using tvm::TupleTypeNode;
class PackedFuncTypeNode : public TypeNode {
public:
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PackedFuncTypeNode>();
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.PackedFuncType", PackedFuncTypeNode, TypeNode);
};
class PackedFuncType : public Type {
public:
TVM_DLL PackedFuncType(Span span = Span());
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(PackedFuncType, Type, PackedFuncTypeNode);
};
/*!
* \brief Base type of all Relax type information.
*
* Type stores possible type information deduced during compile-time.
* It encapsulates both static type and runtime information such as shape.
*
* Type of each non-primitive Expr can be deduced during compilation in a
* "best-effort" manner.
*
* When ty appears in function parameter and return signatures, it
* implies a runtime check that matches the type information with the value.
*
* When it appears in Expr, it follows "assume-semantics", which means the
* compiler will take the deduced information as it is and only do best effort
* proofs and checks.
*
* Each type can be uniquely erased to a static-type. The compiler will
* still compile the code, with less information, when we erase to the static
* type.
*
* If a Type contains an Expr field, then that field must already be
* normalized through NormalizeArg. This invariant is checked in constructors
* and simplifies assumptions during type deduction.
*/
/*!
* \brief Any Relax value.
*/
class AnyTypeNode : public TypeNode {
public:
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<AnyTypeNode>();
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.AnyType", AnyTypeNode, TypeNode);
};
/*!
* \brief Managed reference to AnyTypeNode.
* \sa AnyTypeNode
*/
class AnyType : public Type {
public:
TVM_DLL AnyType(Span span = Span());
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(AnyType, Type, AnyTypeNode);
};
// Compatibility aliases for existing C++ callers. New code should use AnyType.
using ObjectTypeNode = AnyTypeNode;
using ObjectType = AnyType;
/*!
* \brief Type of shape value.
*/
class ShapeTypeNode : public TypeNode {
public:
/*! \brief optionally stores the symbolic value patterns of the shape */
ffi::Optional<ffi::Array<PrimExpr>> values;
/*!
* \brief The number of dimension of the shape, can be unknown.
* \sa kUnknownNDim
*/
int ndim{kUnknownNDim};
/*! \return Whether the type contains unknown ndim. */
bool IsUnknownNdim() const { return ndim == kUnknownNDim; }
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ShapeTypeNode>()
.def_ro("values", &ShapeTypeNode::values)
.def_ro("ndim", &ShapeTypeNode::ndim);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.ShapeType", ShapeTypeNode, TypeNode);
};
/*!
* \brief Managed reference to ShapeTypeNode.
* \sa ShapeTypeNode
*/
class ShapeType : public Type {
public:
/*!
* \brief Construction with known symbolic shape patterns
* \param values The symbolic shape values
* \param span The span of the AST.
*/
TVM_DLL ShapeType(ffi::Array<PrimExpr> values, Span span = Span());
/*!
* \brief Construction with known unknown symbolic shape patterns.
* \param ndim Number of dimensions -- can be kUnknownNDim
* \param span The span of the AST.
*/
TVM_DLL ShapeType(int ndim, Span span = Span());
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(ShapeType, Type, ShapeTypeNode);
};
/*!
* \brief Type of Tensor.
*/
class TensorTypeNode : public TypeNode {
public:
/*!
* \brief optionally store the shape expression of the tensor.
* \note shape must be normalized: it can only be std::nullopt or ShapeExpr or Var.
*/
ffi::Optional<Expr> shape;
/*! \brief The virtual device, indicates where the tensor
* is expected to be executed.
*/
ffi::Optional<VDevice> vdevice;
/*! \brief The content dtype, or nullopt if the dtype is unknown. */
ffi::Optional<tvm::PrimType> dtype{std::nullopt};
/*!
* \brief The number of dimension of the tensor, can be unknown.
* \sa kUnknownNDim
*/
int ndim{kUnknownNDim};
/*! \return Whether the type contains unknown ndim. */
bool IsUnknownNdim() const { return ndim == kUnknownNDim; }
/*! \return Whether the type contains unknown dtype. */
bool IsUnknownDtype() const { return !dtype.has_value(); }
/*! \return Shape if it is known. */
ffi::Optional<ffi::Array<PrimExpr>> GetShape() const {
if (!shape.has_value()) return {};
const Expr& shape_expr = this->shape.value();
if (shape_expr->ty.IsMissing()) return {};
if (const auto* shape_ty = shape_expr->ty.as<ShapeTypeNode>()) {
return shape_ty->values;
}
return {};
}
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TensorTypeNode>()
.def_ro("shape", &TensorTypeNode::shape)
.def_ro("dtype", &TensorTypeNode::dtype)
.def_ro("vdevice", &TensorTypeNode::vdevice)
.def_ro("ndim", &TensorTypeNode::ndim);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.TensorType", TensorTypeNode, TypeNode);
};
/*!
* \brief Managed reference to TensorTypeNode.
* \sa TensorTypeNode
*/
class TensorType : public Type {
public:
explicit TensorType(ffi::ObjectPtr<TensorTypeNode> data) : Type(ffi::UnsafeInit{}) {
TVM_FFI_ICHECK(data != nullptr);
data_ = std::move(data);
}
/*!
* \brief Construction with a known shape expression.
* \param shape The shape of the tensor.
* \param dtype The data type of tensor's elements.
* \param vdevice The virtual device.
* \param span The span of the AST.
*
* \note shape must already be normalized.
*/
TVM_DLL TensorType(Expr shape, ffi::Optional<tvm::PrimType> dtype = std::nullopt,
ffi::Optional<VDevice> vdevice = std::nullopt, Span span = Span());
/*!
* \brief Construction with an unknown shape expression.
* \param dtype The data type of tensor's elements.
* \param ndim The number of dimensions
* \param vdevice The virtual device.
* \param span The span of the AST.
*/
TVM_DLL TensorType(ffi::Optional<tvm::PrimType> dtype, int ndim,
ffi::Optional<VDevice> vdevice = std::nullopt, Span span = Span());
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(TensorType, Type, TensorTypeNode);
};
/*!
* \brief custom-defined Type derivation function.
* \param call The call expression to be derived.
* \param ctx The builder context.
* \return The derived type of the call.
*/
using TypeDeriveFunc = TypedEnvFunc<Type(const Call& call, const BlockBuilder& ctx)>;
/*!
* \brief Function type information.
*
* This data structure contains enough information for us to do best-effort
* type deduction.
*/
class FuncTypeNode : public TypeNode {
public:
/*!
* \brief The parameter type of the function.
* \note When params is std::nullopt means the function can take arbitrary number of arguments.
* We define such functions as Opaque function.
*/
ffi::Optional<ffi::Array<Type>> params;
/*!
* \brief The type of the function's return value.
*/
Type ret = Type::Missing();
/*!
* \brief Derivation function of opaque functions that may take any number of parameters.
* \note When derive_func is not empty, then params should be std::nullopt,
* ret should be AnyType()
*/
ffi::Optional<TypeDeriveFunc> derive_func;
/*!
* \brief Whether the function is pure.
* \note This parameter should be set to true only if the function is pure on all inputs.
* If the function _may_ have visible side effects, set it to false.
*/
bool purity;
/*!
* \return Whether the func type is opaque.
* \note We define a function as opaque we have no constraints on params.
*/
bool IsOpaque() const { return !params.has_value(); }
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<FuncTypeNode>()
.def_ro("params", &FuncTypeNode::params, refl::AttachFieldFlag::SEqHashDefRecursive())
.def_ro("ret", &FuncTypeNode::ret)
.def_ro("derive_func", &FuncTypeNode::derive_func)
.def_ro("purity", &FuncTypeNode::purity);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.FuncType", FuncTypeNode, TypeNode);
};
/*!
* \brief Managed reference to FuncTypeNode.
* \sa FuncTypeNode
*/
class FuncType : public Type {
public:
explicit FuncType(ffi::ObjectPtr<FuncTypeNode> data) : Type(ffi::UnsafeInit{}) {
TVM_FFI_ICHECK(data != nullptr);
data_ = std::move(data);
}
/*!
* \brief Constructor from parameter type and return value type.
* \param params The type of function parameters.
* \param ret The return value type.
* \param purity The purity of the function (true by default).
* \param span The span of the AST.
*
* \note If the ret contains variables(tirx::Var and relax::Var), they must be deducible from
* params. If you are unsure, you can always erase ret to static.
*/
TVM_DLL FuncType(ffi::Array<Type> params, Type ret, bool purity = true, Span span = Span());
/*!
* \brief Constructing an opaque function type using derive_func.
*
* \param derive_func Derivation function.
* \param purity The purity of the function
* (false by default: most external functions are not pure).
* \param span The span of the AST.
*
* \return The FuncType for opaque packedfunc.
* \note Defaults to an derive func that always return AnyType if not specified.
*/
TVM_DLL static FuncType OpaqueFunc(TypeDeriveFunc derive_func, bool purity = false,
Span span = Span());
/*!
* \brief Construct an opaque function using from return type.
*
* \param ret The type of the return value.
* \param purity The purity of the function
* (false by default: most external functions are not pure).
* \param span The span of the AST.
*
* \return The FuncType for opaque packedfunc.
* \note Defaults to an derive func that always return AnyType if not specified.
*/
TVM_DLL static FuncType OpaqueFunc(Type ret = AnyType(), bool purity = false, Span span = Span());
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(FuncType, Type, FuncTypeNode);
};
/*!
* \brief Match and check if expr has Relax type T and return it.
*
* \param expr The input expression.
* \return The result of match.
* \tparam T the underlying Relax type
*/
template <typename T>
inline ffi::Optional<T> MatchType(const Expr& expr) {
if (!expr.defined()) {
return std::nullopt;
}
using TNode = typename T::ContainerType;
if (const TNode* ptr = expr->ty.as<TNode>()) {
return ffi::GetRef<T>(ptr);
} else {
return std::nullopt;
}
}
/*!
* \brief Get the type of a given expr and try to cast it as const T*.
*
* \param expr The input expression.
* \return The pointer. Returns nullptr if the type does not match.
* \tparam T the underlying Relax type node
*/
template <typename T>
inline const T* GetTypeAs(const Expr& expr) {
TVM_FFI_ICHECK(!expr->ty.IsMissing())
<< "The type is not populated, check if you have normalized the expr";
return expr->ty.as<T>();
}
/*!
* \brief Get the underlying Relax type of expr.
*
* \param expr The input expression.
* \return underlying Relax type.
*/
inline Type GetType(const Expr& expr) {
TVM_FFI_ICHECK(!expr->ty.IsMissing())
<< "The type is not populated, check if you have normalized the expr";
return expr->ty;
}
/*!
* \brief Whether the expr has void type.
*
* \param expr The input expression.
* \return Whether the expr has void type.
*/
inline bool HasVoidType(const Expr& expr) {
auto* ptr = expr->ty.as<TupleTypeNode>();
return ptr != nullptr && ptr->fields.size() == 0;
}
/*!
* \brief Update the type of an Expr.
* \param expr The Expr whose type to be updated.
* \param ty The type assigned.
* \note We ensure idempotence, that is we can only update the type of an Expr only
* if the original one is nullptr.
*/
TVM_DLL void UpdateType(Expr expr, Type ty);
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_TYPE_H_
+154
View File
@@ -0,0 +1,154 @@
/*
* 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.
*/
/*!
* \file tvm/relax/type_functor.h
* \brief Functors and visitors for Relax type nodes.
*/
#ifndef TVM_RELAX_TYPE_FUNCTOR_H_
#define TVM_RELAX_TYPE_FUNCTOR_H_
#include <tvm/ir/node_functor.h>
#include <tvm/relax/distributed/type.h>
#include <tvm/relax/type.h>
#include <utility>
namespace tvm {
namespace relax {
template <typename FType>
class TypeFunctor;
// functions to be overriden.
#define RELAX_TYPE_FUNCTOR_DEFAULT \
{ \
return VisitTypeDefault_(op, std::forward<Args>(args)...); \
}
#define TVM_RELAX_TYPE_FUNCTOR_DISPATCH(OP) \
vtable.template set_dispatch<OP>([](const ffi::ObjectRef& n, TSelf* self, Args... args) { \
return self->VisitType_(static_cast<const OP*>(n.get()), std::forward<Args>(args)...); \
});
template <typename R, typename... Args>
class TypeFunctor<R(const Type& n, Args...)> {
private:
using TSelf = TypeFunctor<R(const Type& n, Args...)>;
using FType = tvm::NodeFunctor<R(const ffi::ObjectRef& n, TSelf* self, Args...)>;
public:
/*! \brief the result type of this functor */
using result_type = R;
/*! \brief virtual destructor */
virtual ~TypeFunctor() {}
/*!
* \brief Same as call.
* \param n The type node.
* \param args Additional arguments.
* \return The result of the call
*/
R operator()(const Type& n, Args... args) { return VisitType(n, std::forward<Args>(args)...); }
/*!
* \brief The functor call.
* \param n The type node.
* \param args Additional arguments.
* \return The result of the call
*/
virtual R VisitType(const Type& n, Args... args) {
TVM_FFI_ICHECK(n.defined());
TVM_FFI_ICHECK_NE(n->type_index(), TypeNode::RuntimeTypeIndex())
<< "TypeFunctor cannot visit Type::Missing()";
static FType vtable = InitVTable();
return vtable(n, this, std::forward<Args>(args)...);
}
// Functions that can be overriden by subclass
virtual R VisitType_(const AnyTypeNode* op, Args... args) RELAX_TYPE_FUNCTOR_DEFAULT;
virtual R VisitType_(const PrimTypeNode* op, Args... args) RELAX_TYPE_FUNCTOR_DEFAULT;
virtual R VisitType_(const ShapeTypeNode* op, Args... args) RELAX_TYPE_FUNCTOR_DEFAULT;
virtual R VisitType_(const TensorTypeNode* op, Args... args) RELAX_TYPE_FUNCTOR_DEFAULT;
virtual R VisitType_(const distributed::DTensorTypeNode* op,
Args... args) RELAX_TYPE_FUNCTOR_DEFAULT;
virtual R VisitType_(const TupleTypeNode* op, Args... args) RELAX_TYPE_FUNCTOR_DEFAULT;
virtual R VisitType_(const FuncTypeNode* op, Args... args) RELAX_TYPE_FUNCTOR_DEFAULT;
virtual R VisitTypeDefault_(const ffi::Object* op, Args...) {
TVM_FFI_THROW(InternalError) << "Do not have a default for " << op->GetTypeKey();
throw; // unreachable, written to stop compiler warning
}
private:
// initialize the vtable.
static FType InitVTable() {
FType vtable;
// Set dispatch
TVM_RELAX_TYPE_FUNCTOR_DISPATCH(AnyTypeNode);
TVM_RELAX_TYPE_FUNCTOR_DISPATCH(PrimTypeNode);
TVM_RELAX_TYPE_FUNCTOR_DISPATCH(ShapeTypeNode);
TVM_RELAX_TYPE_FUNCTOR_DISPATCH(TensorTypeNode);
TVM_RELAX_TYPE_FUNCTOR_DISPATCH(distributed::DTensorTypeNode);
TVM_RELAX_TYPE_FUNCTOR_DISPATCH(TupleTypeNode);
TVM_RELAX_TYPE_FUNCTOR_DISPATCH(FuncTypeNode);
vtable.Finalize();
return vtable;
}
};
#undef TVM_RELAX_TYPE_FUNCTOR_DISPATCH
/*!
* \brief A type visitor.
*/
class TVM_DLL TypeVisitor : public TypeFunctor<void(const Type& n)> {
public:
void VisitType_(const AnyTypeNode* op) override;
void VisitType_(const PrimTypeNode* op) override;
void VisitType_(const ShapeTypeNode* op) override;
void VisitType_(const TensorTypeNode* op) override;
void VisitType_(const distributed::DTensorTypeNode* op) override;
void VisitType_(const TupleTypeNode* op) override;
void VisitType_(const FuncTypeNode* op) override;
protected:
// two functions to override when visit expr fields in type nodes.
virtual void VisitTypeExprField(const Expr& expr) {}
virtual void VisitTypeExprField(const PrimExpr& expr) {}
};
/*!
* \brief TypeMutator that mutates Relax type nodes.
*/
class TVM_DLL TypeMutator : public TypeFunctor<Type(const Type& n)> {
public:
Type VisitType_(const AnyTypeNode* op) override;
Type VisitType_(const PrimTypeNode* op) override;
Type VisitType_(const ShapeTypeNode* op) override;
Type VisitType_(const TensorTypeNode* op) override;
Type VisitType_(const distributed::DTensorTypeNode* op) override;
Type VisitType_(const TupleTypeNode* op) override;
Type VisitType_(const FuncTypeNode* op) override;
protected:
// two functions to override when visit expr fields in type nodes.
virtual Expr VisitTypeExprField(const Expr& expr) { return expr; }
virtual PrimExpr VisitTypeExprField(const PrimExpr& expr) { return expr; }
};
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_TYPE_FUNCTOR_H_
+149
View File
@@ -0,0 +1,149 @@
/*
* 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.
*/
/*!
* \file tvm/relax/utils.h
* \brief Utility classes and functions for working with the Relax IR.
*/
#ifndef TVM_RELAX_UTILS_H_
#define TVM_RELAX_UTILS_H_
#include <tvm/arith/analyzer.h>
#include <tvm/ffi/error.h>
#include <tvm/ir/module.h>
#include <tvm/relax/expr.h>
namespace tvm {
namespace relax {
/*!
* \brief Bind the variables to a Relax expression. This is a helper
* function usually called by other pass functions to help optimizations.
* If any free variables are introduced into a function, those are added
* to the function parameters.
* Additionally this may change the order of parameters if you map a variable
* to a variable.
*
* \param expr The input expression.
* \param binds The variable to expression map that will be used to help the
* binding.
* \param symbolic_var_map The map from symbolic var to the expr it binds to.
*
* \return The updated expression.
*/
TVM_DLL Expr Bind(const Expr& expr, const tvm::ffi::Map<Var, Expr>& binds,
const tvm::ffi::Map<tirx::Var, PrimExpr>& symbolic_var_map = {});
/*!
* \brief Bind the symbolic variables to a Type. This is a helper function usually called by
* other pass functions to help optimizations.
*/
TVM_DLL Type Bind(const Type& ty, const tvm::ffi::Map<tirx::Var, PrimExpr>& symbolic_var_map);
/*!
* \brief Infer a binding map for symbolic variables
*
* If a set of relax variables are replaced within an expression, this
* may result in removal of the definition site of a symbolic
* variable. This utility function determines the symbolic variable
* replacements that can be inferred based on the replaced relax
* variables, and can be used alongside the `Bind` utility function to
* replace both the relax variables and the implied symbolic
* variables.
*
* \param binds A map of relax variables to relax expressions
*
* \param analyzer The analyzer to use for simplifications
*
* \return A map of TIR variables to TIR expressions
*/
TVM_DLL tvm::ffi::Map<tirx::Var, PrimExpr> InferSymbolicVarMap(
const tvm::ffi::Map<relax::Var, relax::Expr>& binds, const arith::Analyzer& analyzer);
/*!
* \brief Check if the given Type is for a boolean scalar (tensor of rank 0 with a boolean
* dtype).
*
* \param ty The input Type.
* \param permit_unknown_rank If true, it will permit the input type to have unknown rank
* (ndim of -1), which will require a dynamic check.
* \param permit_unknown_dtype If true, it will permit the input type to have an unknown dtype
* (namely, void), which will require a dynamic check.
*
* \return True iff the input type is a boolean scalar type (or, depending on options, has unknown
* rank or dtype)
*/
TVM_DLL bool IsBoolType(const Type& ty, bool permit_unknown_rank = true,
bool permit_unknown_dtype = true);
/*!
* \brief Check if the given expression is a "leaf" node or tuple node for normalization purposes.
*
* The following expressions are defined as leaf nodes: Var, Constant, ShapeExpr,
* GlobalVar, Op, ExternFunc.
*
* Tuples are included in this list mainly for convenience in grouping operator arguments.
* *Note*: Since tuples can contain nested expressions, it is necessary to ensure that
* values nested inside them are also leaves.
*
* \param expr The input expression
*
* \return True iff the input expression is a "leaf" node (a value allowed to appear
* inline without being bound to a var during normalization).
*/
TVM_DLL bool IsLeafOrTuple(const Expr& expr);
/*!
* \brief Check if the given Call node is an impure operation. If the callee is a general
* expression, this simply requires checking the purity field of the FuncType. If it is an Op,
* then this checks the `fPurity` field.
*
* \param call The input call
*
* \return True iff the call is impure (definitely or possibly results in a visible side effect).
* That is, a call is considered pure only if definitely does not result in a visible side effect.
*/
TVM_DLL bool IsImpureCall(const Call& call);
/*!
* \brief Copy the given function. All variables that are bound inside the original function
* would be copied to satisfy the restriction in the well-formed check: Variables in
* Relax must be bound exactly once. This also ensures that both the function and its copy
* can be inserted into the same IRModule, and be asserted on the structural equality
* agaisnt IRModule created by TVMScript.
*
* \param func The relax function to copy.
* \return The copied function.
*/
TVM_DLL Function CopyWithNewVars(Function func);
/*!
* \brief Transform all dataflow structure to non-dataflow version.
*/
Expr ToNonDataflow(const Expr& e);
/*!
* \brief Get the value bound in the binding.
*/
Expr GetBoundValue(const Binding& b);
} // namespace relax
} // namespace tvm
#endif // TVM_RELAX_UTILS_H_
+96
View File
@@ -0,0 +1,96 @@
/*
* 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.
*/
/*
* \file tvm/runtime/base.h
* \brief base macros
*/
#ifndef TVM_RUNTIME_BASE_H_
#define TVM_RUNTIME_BASE_H_
// TVM runtime fully relies on TVM FFI C API
// we will avoid defining extra C APIs here
#include <tvm/ffi/c_api.h>
// TVM version. Overridable at build time via -DTVM_VERSION="..." (scikit-build-core
// passes the setuptools_scm-resolved version through CMake). The literal below is the
// fallback for a bare build with no override.
#ifndef TVM_VERSION
#define TVM_VERSION "0.26.dev0"
#endif
// TVM ships two shared libraries: libtvm_compiler and libtvm_runtime.
// Each exposes its own DLL macro pair. The two families are defined
// independently so that each can be overridden separately by downstream
// embedders who need custom visibility on only one of the two libraries.
//
// TVM_DLL / TVM_DLL_EXPORT: symbols in libtvm_compiler.
// - TVM_DLL is dllexport when TVM_EXPORTS is defined (compiler build),
// dllimport otherwise (downstream consumers, runtime TUs).
// - TVM_DLL_EXPORT is always dllexport.
//
// TVM_RUNTIME_DLL / TVM_RUNTIME_DLL_EXPORT: symbols in libtvm_runtime.
// - TVM_RUNTIME_DLL is dllexport when TVM_RUNTIME_EXPORTS is defined
// (runtime build), dllimport otherwise.
// - TVM_RUNTIME_DLL_EXPORT is always dllexport.
//
// On non-MSVC platforms the import/export decision is made by the dynamic
// loader, so all four macros expand to visibility("default"). Under
// Emscripten they expand to EMSCRIPTEN_KEEPALIVE.
#ifdef __EMSCRIPTEN__
#include <emscripten/emscripten.h>
#endif
// --- TVM_DLL family (libtvm_compiler) ---
#if !defined(TVM_DLL) && defined(__EMSCRIPTEN__)
#define TVM_DLL EMSCRIPTEN_KEEPALIVE
#define TVM_DLL_EXPORT EMSCRIPTEN_KEEPALIVE
#endif
#if !defined(TVM_DLL) && defined(_MSC_VER)
#ifdef TVM_EXPORTS
#define TVM_DLL __declspec(dllexport)
#else
#define TVM_DLL __declspec(dllimport)
#endif
#define TVM_DLL_EXPORT __declspec(dllexport)
#endif
#ifndef TVM_DLL
#define TVM_DLL __attribute__((visibility("default")))
#define TVM_DLL_EXPORT __attribute__((visibility("default")))
#endif
// --- TVM_RUNTIME_DLL family (libtvm_runtime) ---
#if !defined(TVM_RUNTIME_DLL) && defined(__EMSCRIPTEN__)
#define TVM_RUNTIME_DLL EMSCRIPTEN_KEEPALIVE
#define TVM_RUNTIME_DLL_EXPORT EMSCRIPTEN_KEEPALIVE
#endif
#if !defined(TVM_RUNTIME_DLL) && defined(_MSC_VER)
#ifdef TVM_RUNTIME_EXPORTS
#define TVM_RUNTIME_DLL __declspec(dllexport)
#else
#define TVM_RUNTIME_DLL __declspec(dllimport)
#endif
#define TVM_RUNTIME_DLL_EXPORT __declspec(dllexport)
#endif
#ifndef TVM_RUNTIME_DLL
#define TVM_RUNTIME_DLL __attribute__((visibility("default")))
#define TVM_RUNTIME_DLL_EXPORT __attribute__((visibility("default")))
#endif
#endif // TVM_RUNTIME_BASE_H_
+109
View File
@@ -0,0 +1,109 @@
/*
* 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.
*/
/*!
* \file tvm/runtime/c_backend_api.h
* \brief TVM runtime backend API.
*
* The functions defined in this header are intended to be
* used by compiled tvm operators, usually user do not need to use these
* function directly.
*/
#ifndef TVM_RUNTIME_C_BACKEND_API_H_
#define TVM_RUNTIME_C_BACKEND_API_H_
#include <tvm/runtime/base.h>
#ifdef __cplusplus
extern "C" {
#endif
/*!
* \brief Backend function to allocate temporal workspace.
*
* \note The result allocated space is ensured to be aligned to kTempAllocaAlignment.
*
* \param nbytes The size of the space requested.
* \param device_type The device type which the space will be allocated.
* \param device_id The device id which the space will be allocated.
* \param dtype_code_hint The type code of the array elements. Only used in
* certain backends such as OpenGL.
* \param dtype_bits_hint The type bits of the array elements. Only used in
* certain backends such as OpenGL.
* \return nullptr when error is thrown, a valid ptr if success
*/
TVM_RUNTIME_DLL void* TVMBackendAllocWorkspace(int device_type, int device_id, uint64_t nbytes,
int dtype_code_hint, int dtype_bits_hint);
/*!
* \brief Backend function to free temporal workspace.
*
* \param ptr The result allocated space pointer.
* \param device_type The device type which the space will be allocated.
* \param device_id The device id which the space will be allocated.
* \return 0 when no error is thrown, -1 when failure happens
*
* \sa TVMBackendAllocWorkspace
*/
TVM_RUNTIME_DLL int TVMBackendFreeWorkspace(int device_type, int device_id, void* ptr);
/*!
* \brief Environment for TVM parallel task.
*/
typedef struct {
/*!
* \brief Auxiliary used for synchronization
*/
void* sync_handle;
/*! \brief total amount of task */
int32_t num_task;
} TVMParallelGroupEnv;
/*!
* \brief The callback function to execute a parallel lambda
* \param task_id the task id of the function.
* \param penv The parallel environment backs the execution.
* \param cdata The supporting closure data.
*/
typedef int (*FTVMParallelLambda)(int task_id, TVMParallelGroupEnv* penv, void* cdata);
/*!
* \brief Backend function for running parallel jobs.
*
* \param flambda The parallel function to be launched.
* \param cdata The closure data.
* \param num_task Number of tasks to launch, can be 0, means launch
* with all available threads.
*
* \return 0 when no error is thrown, -1 when failure happens
*/
TVM_RUNTIME_DLL int TVMBackendParallelLaunch(FTVMParallelLambda flambda, void* cdata, int num_task);
/*!
* \brief BSP barrrier between parallel threads
* \param task_id the task id of the function.
* \param penv The parallel environment backs the execution.
* \return 0 when no error is thrown, -1 when failure happens
*/
TVM_RUNTIME_DLL int TVMBackendParallelBarrier(int task_id, TVMParallelGroupEnv* penv);
#ifdef __cplusplus
} // TVM_EXTERN_C
#endif
#endif // TVM_RUNTIME_C_BACKEND_API_H_
+427
View File
@@ -0,0 +1,427 @@
/*
* 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.
*/
/*!
* \file tvm/runtime/device_api.h
* \brief Abstract device memory management API
*/
#ifndef TVM_RUNTIME_DEVICE_API_H_
#define TVM_RUNTIME_DEVICE_API_H_
#include <tvm/ffi/any.h>
#include <tvm/ffi/error.h>
#include <tvm/ffi/optional.h>
#include <tvm/ffi/string.h>
#include <tvm/runtime/base.h>
#include <string>
/*!
* \brief The stream that is specific to device
* can be NULL, which indicates the default one.
*/
typedef void* TVMStreamHandle;
namespace tvm {
// alias DLDevice
using Device = DLDevice;
namespace runtime {
/*! \brief Extension device types in TVM
*
* Additional enumerators to supplement those provided by
* DLPack's `DLDeviceType` enumeration.
*
* MAINTAINERS NOTE #1: We need to ensure that the two devices
* are identified by the same integer.
* Currently this requires manual verification.
* Discussed here: https://github.com/dmlc/dlpack/issues/111
* As of DLPack v0.7, the highest-valued enumerator in
* `DLDeviceType` is kDLHexagon = 16.
*
* MAINTAINERS NOTE #2: As of DLPack v0.7, the definition for
* `DLDeviceType` specifies an underlying storage type of
* `int32_t`. That guarantees a variable of type
* `DLDeviceType` is capable of holding any integers provided
* by *either* of these enumerations.
*
* However, the `int32_t` specification only applies when the
* header file is compiled as C++, and this header file is also
* meant to work as C code. So the unspecified storage type
* could be a latent bug when compiled as C.
*/
#ifdef __cplusplus
typedef enum : int32_t {
#else
typedef enum {
#endif
// To help avoid accidental conflicts between `DLDeviceType`
// and this enumeration, start numbering the new enumerators
// a little higher than (currently) seems necessary.
TVMDeviceExtType_End = 36, // sentinel value
} TVMDeviceExtType;
/*!
* \brief the query type into GetAttr
*/
enum DeviceAttrKind : int {
kExist = 0,
kMaxThreadsPerBlock = 1,
kWarpSize = 2,
kMaxSharedMemoryPerBlock = 3,
kComputeVersion = 4,
kDeviceName = 5,
kMaxClockRate = 6,
kMultiProcessorCount = 7,
kMaxThreadDimensions = 8,
kMaxRegistersPerBlock = 9,
kGcnArch = 10,
kApiVersion = 11,
kDriverVersion = 12,
kL2CacheSizeBytes = 13,
kTotalGlobalMemory = 14,
kAvailableGlobalMemory = 15,
kImagePitchAlignment = 16,
};
#ifdef TVM_KALLOC_ALIGNMENT
/*! \brief Number of bytes each allocation must align to */
constexpr int kAllocAlignment = TVM_KALLOC_ALIGNMENT;
/*! \brief Number of bytes each allocation must align to in temporary allocation */
constexpr int kTempAllocaAlignment = TVM_KALLOC_ALIGNMENT;
#else
/*! \brief Number of bytes each allocation must align to */
constexpr int kAllocAlignment = 64;
/*! \brief Number of bytes each allocation must align to in temporary allocation */
constexpr int kTempAllocaAlignment = 64;
#endif // TVM_KALLOC_ALIGNMENT
/*! \brief Maximum size that can be allocated on stack */
constexpr int kMaxStackAlloca = 1024;
/*! \brief Number of bytes each allocation must align to by default in the workspace buffer to
* service intermediate tensors */
constexpr int kDefaultWorkspaceAlignment = 1;
/*!
* \brief TVM Runtime Device API, abstracts the device
* specific interface for memory management.
*/
class TVM_RUNTIME_DLL DeviceAPI {
public:
/*! \brief virtual destructor */
virtual ~DeviceAPI() {}
/*!
* \brief Set the environment device id to device
* \param dev The device to be set.
*/
virtual void SetDevice(Device dev) = 0;
/*!
* \brief Get attribute of specified device.
* \param dev The device device
* \param kind The result kind
* \param rv The return value.
* \sa DeviceAttrKind
*/
virtual void GetAttr(Device dev, DeviceAttrKind kind, ffi::Any* rv) = 0;
/*!
* \brief Get the physical memory size required.
* \param arr the tensor object.
* \param mem_scope the memory scope if any
* \return the memory size.
*/
virtual size_t GetDataSize(const DLTensor& arr,
ffi::Optional<ffi::String> mem_scope = std::nullopt);
/*!
* \brief Query the device for specified properties.
*
* This is used to expand "-from_device=N" in the target string to
* all properties that can be determined from that device.
*/
virtual void GetTargetProperty(Device dev, const std::string& property, ffi::Any* rv) {}
/*!
* \brief Allocate a data space on device.
* \param dev The device device to perform operation.
* \param nbytes The number of bytes in memory.
* \param alignment The alignment of the memory.
* \param type_hint The type of elements. Only needed by certain backends such
* as OpenGL, as nbytes & alignment are sufficient for most backends.
* \return The allocated device pointer.
*/
virtual void* AllocDataSpace(Device dev, size_t nbytes, size_t alignment,
DLDataType type_hint) = 0;
/*!
* \brief Allocate a data space on device with memory scope support.
* \param dev The device device to perform operation.
* \param ndim The number of dimension of allocated tensor.
* \param shape The shape of allocated tensor.
* \param dtype The type of elements.
* \param mem_scope The memory scope of allocated tensor.
* \return The allocated device pointer.
*/
virtual void* AllocDataSpace(Device dev, int ndim, const int64_t* shape, DLDataType dtype,
ffi::Optional<ffi::String> mem_scope = std::nullopt);
/*!
* \brief Free a data space on device.
* \param dev The device device to perform operation.
* \param ptr The data space.
*/
virtual void FreeDataSpace(Device dev, void* ptr) = 0;
/*!
* \brief copy data from one place to another
* \note This API is designed to support special memory with shape dependent layout.
* We pass in DLTensor* with shape information to support these cases.
* \param from The source array.
* \param to The target array.
* \param stream Optional stream object.
* \note The copy may happen asynchronously if it involves a GPU context.
* Call StreamSync to ensure the copy completes from host's pov.
*/
virtual void CopyDataFromTo(DLTensor* from, DLTensor* to, TVMStreamHandle stream);
/*!
* \brief Create a new stream of execution.
*
* \param dev The device of allocation.
*/
virtual TVMStreamHandle CreateStream(Device dev);
/*!
* \brief Free a stream of execution
*
* \param dev The device of the stream
* \param stream The pointer to be freed.
*/
virtual void FreeStream(Device dev, TVMStreamHandle stream);
/*!
* \brief Synchronize the stream
* \param dev The device to perform operation.
* \param stream The stream to be sync.
*/
virtual void StreamSync(Device dev, TVMStreamHandle stream) = 0;
/*!
* \brief Set the stream
* \param dev The device to set stream.
* \param stream The stream to be set.
*/
virtual void SetStream(Device dev, TVMStreamHandle stream);
/*!
* \brief Get the current stream
* \param dev The device to get stream.
* \return The current stream of the device.
*/
virtual TVMStreamHandle GetCurrentStream(Device dev);
/*!
* \brief Synchronize 2 streams of execution.
*
* An event is created in event_src stream that the second then
* stream waits on. Neither event_src or event_dst need to be of
* the same device ID as the device, but they must be of the same
* device type.
*
* \param dev The device of the streams.
* \param event_src The source stream to synchronize.
* \param event_dst The destination stream to synchronize.
*/
virtual void SyncStreamFromTo(Device dev, TVMStreamHandle event_src, TVMStreamHandle event_dst);
/*!
* \brief Allocate temporal workspace for backend execution.
*
* \note We have the following assumption about backend temporal
* workspace allocation, and backend will optimize for such assumption:
*
* - Only a few allocation will happen, and space will be released after use.
* - The release order is usually in reverse order of allocate (stack style).
* - Repeative pattern of same allocations over different runs.
* - Workspace should not overlap between different threads(i.e. be threadlocal)
*
* \param dev The device of allocation.
* \param nbytes The size to be allocated.
* \param type_hint The type of elements. Only needed by certain backends such
* as OpenGL, as nbytes is sufficient for most backends.
*/
virtual void* AllocWorkspace(Device dev, size_t nbytes, DLDataType type_hint = {});
/*!
* \brief Free temporal workspace in backend execution.
*
* \param dev The device of allocation.
* \param ptr The pointer to be freed.
*/
virtual void FreeWorkspace(Device dev, void* ptr);
/*!
* \brief Get device API based on device.
* \param dev The device
* \param allow_missing Whether allow missing
* \return The corresponding device API.
*/
static DeviceAPI* Get(Device dev, bool allow_missing = false);
/*!
* \brief Whether a certian device type requires set device device
* before launching the kernel function.
* \param device_type The device type.
*/
static bool NeedSetDevice(int device_type) { return device_type != kDLCPU; }
/*!
* \brief Whether pointer arithmetics on a device owned pointer may be performed on the host.
*/
virtual bool SupportsDevicePointerArithmeticsOnHost() { return false; }
protected:
/*!
* \brief copy data from one place to another
* \param from The source array.
* \param from_offset The byte offeset in the from.
* \param to The target array.
* \param to_offset The byte offset in the to.
* \param num_bytes The size of the memory in bytes
* \param dev_from The source device
* \param dev_to The target device
* \param type_hint The type of elements, only neded by certain backends.
* can be useful for cross device endian converison.
* \param stream Optional stream object.
*/
virtual void CopyDataFromTo(const void* from, size_t from_offset, void* to, size_t to_offset,
size_t num_bytes, Device dev_from, Device dev_to,
DLDataType type_hint, TVMStreamHandle stream);
};
/*!
* \brief The name of DLDeviceType.
* \param type The device type.
* \return the device name.
*/
inline const char* DLDeviceType2Str(int type) {
switch (type) {
case kDLCPU:
return "cpu";
case kDLCUDA:
return "cuda";
case kDLCUDAHost:
return "cuda_host";
case kDLCUDAManaged:
return "cuda_managed";
case kDLOpenCL:
return "opencl";
case kDLVulkan:
return "vulkan";
case kDLMetal:
return "metal";
case kDLVPI:
return "vpi";
case kDLROCM:
return "rocm";
case kDLROCMHost:
return "rocm_host";
case kDLExtDev:
return "ext_dev";
case kDLOneAPI:
return "oneapi";
case kDLWebGPU:
return "webgpu";
case kDLHexagon:
return "hexagon";
case kDLTrn:
return "trn";
default:
TVM_FFI_THROW(InternalError) << "unknown type = " << type;
}
throw;
}
/*! \brief The device type bigger than this is RPC device */
constexpr int kRPCSessMask = 128;
static_assert(kRPCSessMask >= TVMDeviceExtType_End);
/*!
* \brief Return true if a Device is owned by an RPC session.
*/
inline bool IsRPCSessionDevice(Device dev) { return (dev.device_type / kRPCSessMask) > 0; }
/*!
* \brief Return the RPCSessTable index of the RPC Session that owns this device.
* \return the table index.
*/
inline int GetRPCSessionIndex(Device dev) {
TVM_FFI_ICHECK(IsRPCSessionDevice(dev)) << "GetRPCSessionIndex: dev has no RPC session";
return dev.device_type / kRPCSessMask - 1;
}
/*!
* \brief Remove the RPC session mask from a Device.
* RPC clients typically do this when encoding a Device for transmission to an RPC remote.
* On the wire, RPCdevice are expected to be valid on the server without interpretation.
* \param dev A Device with non-zero RPC Session mask, valid on the RPC client.
* \return A Device without any RPC Session mask, valid on the RPC server.
*/
inline Device RemoveRPCSessionMask(Device dev) {
dev.device_type = static_cast<DLDeviceType>(dev.device_type % kRPCSessMask);
return dev;
}
inline std::ostream& operator<<(std::ostream& os, DLDevice dev) { // NOLINT(*)
if (tvm::runtime::IsRPCSessionDevice(dev)) {
os << "remote[" << tvm::runtime::GetRPCSessionIndex(dev) << "]-";
dev = tvm::runtime::RemoveRPCSessionMask(dev);
}
os << tvm::runtime::DLDeviceType2Str(static_cast<int>(dev.device_type)) << ":" << dev.device_id;
return os;
}
/*!
* \brief Add a RPC session mask to a Device.
* RPC clients typically do this when decoding a Device received from a RPC remote.
* \param dev A Device without any RPC Session mask, valid on the RPC server.
* \param session_table_index Numeric index of the RPC session in the session table.
* \return A Device with RPC session mask added, valid on the RPC client.
*/
inline Device AddRPCSessionMask(Device dev, int session_table_index) {
TVM_FFI_ICHECK(!IsRPCSessionDevice(dev))
<< "AddRPCSessionMask: dev already non-zero RPCSessionIndex: " << dev;
dev.device_type =
static_cast<DLDeviceType>(dev.device_type | (kRPCSessMask * (session_table_index + 1)));
return dev;
}
/*!
* \brief Check if runtime module is enabled for target.
* \param target The target module name.
* \return Whether runtime is enabled.
*/
TVM_RUNTIME_DLL bool RuntimeEnabled(const ffi::String& target);
/*! \brief namespace for constant symbols */
namespace symbol {
constexpr const char* tvm_global_barrier_state = "__tvm_global_barrier_state";
/*! \brief global function to set device */
constexpr const char* tvm_set_device = "__tvm_set_device";
} // namespace symbol
} // namespace runtime
} // namespace tvm
#endif // TVM_RUNTIME_DEVICE_API_H_
+154
View File
@@ -0,0 +1,154 @@
/*
* 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_RUNTIME_DISCO_BUILTIN_H_
#define TVM_RUNTIME_DISCO_BUILTIN_H_
#include <tvm/ffi/dtype.h>
#include <tvm/ffi/extra/module.h>
#include <tvm/runtime/tensor.h>
#include <string>
namespace tvm {
namespace runtime {
/*!
* \brief Possible kinds of reduction operations.
*/
enum class ReduceKind : int32_t {
kSum = 0,
kProd = 1,
kMin = 2,
kMax = 3,
kAvg = 4,
};
/*! \brief Converts `ReduceKind` to string */
inline std::string ReduceKind2String(ReduceKind kind) {
switch (kind) {
case ReduceKind::kSum:
return "kSum";
case ReduceKind::kProd:
return "kProd";
case ReduceKind::kMin:
return "kMin";
case ReduceKind::kMax:
return "kMax";
case ReduceKind::kAvg:
return "kAvg";
}
TVM_FFI_THROW(ValueError) << "Unknown ReduceKind: " << static_cast<int>(kind);
}
/*!
* \brief Load a runtime Module, then create and initialize a RelaxVM
* \param path The path to the runtime Module (a DSO file) to be loaded
* \param device The default device used to initialize the RelaxVM
* \return The RelaxVM as a runtime Module
*/
TVM_RUNTIME_DLL ffi::Module LoadVMModule(std::string path, ffi::Optional<Device> device);
/*!
* \brief Create an uninitialized empty Tensor
* \param shape The shape of the Tensor
* \param dtype The dtype of the Tensor
* \param device The device the Tensor is created on. If None, use the thread local default device
* \return The Tensor created
*/
TVM_RUNTIME_DLL Tensor DiscoEmptyTensor(ffi::Shape shape, DLDataType dtype,
ffi::Optional<Device> device);
/*!
* \brief Perform an allreduce operation using the underlying communication library
* \param send The array send to perform allreduce on
* \param reduce_kind The kind of reduction operation (e.g. sum, avg, min, max)
* \param in_group Whether the allreduce operation performs globally or in group as default.
* \param recv The array receives the outcome of allreduce
*/
TVM_RUNTIME_DLL void AllReduce(Tensor send, ReduceKind reduce_kind, bool in_group, Tensor recv);
/*!
* \brief Perform an allgather operation using the underlying communication library
* \param send The array send to perform allgather on
* \param in_group Whether the allgather operation performs globally or in group as default.
* \param recv The array receives the outcome of allgather
*/
TVM_RUNTIME_DLL void AllGather(Tensor send, bool in_group, Tensor recv);
/*!
* \brief Perform a broadcast operation from worker-0
* \param send The buffer to be broadcasted
* \param in_group Whether the broadcast operation performs globally or in group as default.
* \param recv The buffer receives the broadcasted array
*/
TVM_RUNTIME_DLL void BroadcastFromWorker0(Tensor send, bool in_group, Tensor recv);
/*!
* \brief Perform a scatter operation from worker-0, chunking the given buffer into equal parts.
* \param send For worker-0, it must be provided, and otherwise, the buffer must be None.
* The buffer will be divided into equal parts and sent to each worker accordingly.
* \param in_group Whether the scatter operation performs globally or in group as default.
* \param recv The receiving buffer, which must not be None.
*/
TVM_RUNTIME_DLL void ScatterFromWorker0(ffi::Optional<Tensor> send, bool in_group, Tensor recv);
/*!
* \brief Perform a gather operation to worker-0.
* \param send The sending buffer, which must not be None.
* \param in_group Whether the gather operation performs globally or in group as default.
* \param recv For worker-0, it must be provided, and otherwise, the buffer must be None. The
* receiving buffer will be divided into equal parts and receive from each worker accordingly.
*/
TVM_RUNTIME_DLL void GatherToWorker0(Tensor send, bool in_group, ffi::Optional<Tensor> recv);
/*!
* \brief Receive a buffer from worker-0. No-op if the current worker is worker-0.
* \param buffer The buffer to be received
*/
TVM_RUNTIME_DLL void RecvFromWorker0(Tensor buffer);
/*!
* \brief Send a buffer to the corresponding worker in the next group.
* An error is thrown if the worker is already in the last group.
* \param buffer The sending buffer.
*/
TVM_RUNTIME_DLL void SendToNextGroup(Tensor buffer);
/*!
* \brief Receive a buffer from the corresponding worker in the previous group.
* An error is thrown if the worker is already in the first group.
* \param buffer The receiving buffer.
*/
TVM_RUNTIME_DLL void RecvFromPrevGroup(Tensor buffer);
/*!
* \brief Send a buffer to the target receiver worker (globally across all groups).
* \param buffer The sending buffer.
* \param receiver_id The global receiver worker id.
*/
TVM_RUNTIME_DLL void SendToWorker(Tensor buffer, int receiver_id);
/*!
* \brief Receive a buffer from the target sender worker (globally across all groups).
* \param buffer The receiving buffer.
* \param sender_id The global sender worker id.
*/
TVM_RUNTIME_DLL void RecvFromWorker(Tensor buffer, int sender_id);
/*! \brief Get the local worker id */
TVM_RUNTIME_DLL int WorkerId();
/*!
* \brief Called by the worker thread. Waiting until the worker completes all its tasks.
* As a specific example, on a CUDA worker, it blocks until all kernels are launched and
* cudaStreamSynchronize is complete.
*/
TVM_RUNTIME_DLL void SyncWorker();
} // namespace runtime
} // namespace tvm
#endif // TVM_RUNTIME_DISCO_BUILTIN_H_
@@ -0,0 +1,99 @@
/*
* 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_RUNTIME_DISCO_CUDA_IPC_MEMORY_H_
#define TVM_RUNTIME_DISCO_CUDA_IPC_MEMORY_H_
#include <tvm/runtime/base.h>
#include <tvm/runtime/memory/memory_manager.h>
#include <vector>
namespace tvm {
namespace runtime {
namespace cuda_ipc {
/*!
* \brief The CUDA IPC (interprocess communication) memory object,
* which internally contains data pointers to CUDA IPC memory.
* It is be useful for efficient all-reduce implementation.
* \note Right now the class members are closely tied with customized
* all-reduce kernel. They may also be extended for other uses in
* the future.
*/
class CUDAIPCMemoryObj : public ffi::Object {
public:
/*! \brief The number of GPU workers. */
int num_workers;
/*! \brief The worker id corresponding to this IPC memory object. */
int worker_id;
/*!
* \brief The data pointers of all all-reduce inputs.
* It has "num_workers" pointers. The i-th pointer is the data pointer on worker i.
* If "i != worker_id", the pointer is an IPC data pointer.
* Otherwise, the pointer is a local CUDA data pointer.
*/
std::vector<void*> remote_data;
// We introduce the barrier helper data below per CUDAIPCMemory object
// so that they can be used by custom collective operations and allow
// fine-grained synchronization on each buffer. These barriers have
// low overhead, and can potentially enable concurrent execution of
// kernels in future.
/*!
* \brief The pointers to input barrier signals of all workers for all-reduce.
* It has "num_workers" pointers, and the pointer arrangement is the same as "remote_data".
*/
std::vector<void*> barrier_in;
/*!
* \brief The pointers to output barrier signals of all workers for all-reduce.
* It has "num_workers" pointers, and the pointer arrangement is the same as "remote_data".
*/
std::vector<void*> barrier_out;
/*! \brief The integer buffer flag for all-reduce. */
int barrier_flag;
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO("tvm.runtime.disco.cuda_ipc_memory", CUDAIPCMemoryObj, ffi::Object);
};
/*!
* \brief Managed reference to CUDAIPCMemoryObj.
* \sa CUDAIPCMemory
*/
class CUDAIPCMemory : public ffi::ObjectRef {
public:
/*! \brief Get the global singleton CUDAIPCMemory allocator. */
TVM_RUNTIME_DLL static memory::Allocator* GlobalAllocator();
/*!
* \brief Given a local CUDA data pointer, return the CUDAIPCMemory object of the pointer.
* \note The pointer's CUDAIPCMemory is expected to have been allocated
* through global function "cuda_ipc.alloc_storage". Or otherwise this
* function will raise exception.
*/
TVM_RUNTIME_DLL static CUDAIPCMemory GetIPCMemoryFromDevicePtr(void* ptr);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(CUDAIPCMemory, ffi::ObjectRef, CUDAIPCMemoryObj);
};
} // namespace cuda_ipc
} // namespace runtime
} // namespace tvm
#endif // TVM_RUNTIME_DISCO_CUDA_IPC_MEMORY_H_
+118
View File
@@ -0,0 +1,118 @@
/*
* 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.
*/
/*!
* \file disco_worker.h
* \brief This file defines a worker in Disco. A worker can be launched in a separate thread or
* process as long as the channel supports bi-directional communication in-between the worker and
* the controler.
*/
#ifndef TVM_RUNTIME_DISCO_DISCO_WORKER_H_
#define TVM_RUNTIME_DISCO_DISCO_WORKER_H_
#include <tvm/ffi/function.h>
#include <tvm/runtime/disco/session.h>
#include <vector>
namespace tvm {
namespace runtime {
/*!
* \brief A worker in Disco. It takes a channel to communication with the controler.
* The worker can be run in a separate thread or process as long as the channel supports
* bi-directional communication in-between.
*/
class DiscoWorker {
public:
/*!
* \brief Construct a worker.
* \param worker_id The id of the worker.
* \param num_workers The number of the workers.
* \param num_groups The number of the worker groups.
* \param worker_zero_data The data shared between worker-0 and the controler. It's a nullptr if
* the worker is not worker-0.
* \param channel The communication channel between the worker and the controler.
*/
explicit DiscoWorker(int worker_id, int num_workers, int num_groups,
WorkerZeroData* worker_zero_data, DiscoChannel* channel)
: worker_id(worker_id),
local_worker_id(worker_id),
num_workers(num_workers),
num_groups(num_groups),
default_device(Device{DLDeviceType::kDLCPU, 0}),
worker_zero_data(worker_zero_data),
channel(channel),
register_file{} {}
/*! \brief Main loop of the worker */
void MainLoop();
/*! \brief Get the worker instance on the current thread */
TVM_RUNTIME_DLL static DiscoWorker* ThreadLocal();
/*! \brief Set the specific register to a specific value */
void SetRegister(int reg_id, ffi::AnyView value);
/*! \brief The id of the worker.*/
int worker_id;
/*! \brief The local id of the worker. This can be different from worker_id if the session is
* consisted with multiple sub-sessions. */
int local_worker_id;
/*! \brief Total number of workers */
int num_workers;
/*! \brief Total number of workers */
int num_groups;
/*! \brief The default device to allocate data if not specified */
Device default_device;
/*! \brief The name of the underlying collective communication library. */
ffi::String ccl;
/*!
* \brief The data shared between worker-0 and the controler. It's a nullptr if
* the worker is not worker-0.
* \note This data structure is owned by the controler.
*/
WorkerZeroData* worker_zero_data;
/*!
* \brief The communication channel between the worker and the controler.
* \note This data structure is owned by the controler.
*/
DiscoChannel* channel;
/*! \brief The registers in the worker */
std::vector<ffi::Any> register_file;
struct Impl;
friend struct DiscoWorker::Impl;
};
/*!
* \brief A threadlocal wrapper of DiscoWorker.
*/
struct ThreadLocalDiscoWorker {
/*! \brief The Disco worker */
DiscoWorker* worker;
/*!
* \brief Get the threadlocal Disco worker.
*/
static ThreadLocalDiscoWorker* Get() {
thread_local static ThreadLocalDiscoWorker worker;
return &worker;
}
};
} // namespace runtime
} // namespace tvm
#endif // TVM_RUNTIME_DISCO_DISCO_WORKER_H_
+385
View File
@@ -0,0 +1,385 @@
/*
* 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.
*/
/*!
* \file session.h
* \brief This file serves as the entry point of Disco and defines key data structures and
* interfaces.
*
* Disco is a distributed runtime that consists of a controler and a cluster of workers. The
* controler is responsible for managing the workers by broadcasting commands to all the workers
* together, and the workers are responsible for executing the commands and. The controler and
* workers communicate with each other through a bi-directional channel.
*
* Different from a generic system, Disco is designed to as "single-program-multiple-data" (SPMD)
* runtime, which means that all the workers execute the same instruction at the same time, but the
* data they are working on may be different. For example, in data parallelism, each worker may
* work on a different batches of the data, but they all execute the same set of instructions.
* Therefore, imagine there is a virtual machine that executes the program, the structures of
* workers' register files could be considered as "identical" (single program) although the values
* may differ (multiple data).
*
*
* **DRef.** Following the design above, consider the program in SPMD in a virtual ISA, then each
* worker is a virtual machine instance to execute the ISA maintaining its own register file.
* The controler denotes each of their register files with a unique integer "register id",
* and the workers use this id to refer to the register file that resides on itself.
* DRef is a control-side object backed by such a register id. The data it contains is not assumed
* to be directly accessible by the controler, with an exception for worker-0, which is a special
* worker that is always co-located with the controler.
*
* **Worker-0.** Worker-0 is a special worker that is always co-located with the controler.
* It is assumed that the controler can synchronize with and access the registers of worker-0.
* The Disco session provides multiple APIs to interact specifically with the worker-0.
* To shared data with other workers, a common paradigm in Disco is to copy data from the
* controler-side Tensor to the worker-0, and then copy it to other workers using primitives on
* the data plane, for example, `broadcast` and `send`.
*
* **Control plane.** The controler broadcasts commands to all the workers as control signals.
* For example, the control may ask all workers to load a library or call a function respectively.
* Common control signals include: shutdown, retrievel a global ffi::Function, call packed function,
* etc. The controler is assumed to keep a message channel to each worker to implement the broadcast
* behavior, and the message channel may vary depends on usecases.
*
* **Data plane.** The data channel is usually used to exchange data between workers, especially for
* tensor data which is usually large. For example, performing an allreduce operator for sharded
* matrix multiplication, or broadcasting for an input tensor. For efficiency, the data channel is
* usually backed by NCCL on NVIDIA GPUs, RCCL on AMD GPUs, or MPI on CPUs.
*
* **Session.** A Disco session is a primary interface to interact with the Disco runtime, serving
* as a global context that manages the control and workers. It could be implemented as a
* multi-threaded with a pool of workers for single-node multi-gpu scenarios, or TCP sockets for
* workloads that span over a cluster of nodes.
*
* **Channel.** Disco channel is a bi-directional communication channel between the controler and
* workers for exchanging control signals. It is no different from a generic RPC channel, but
* adopts TVM's ffi::Function calling convention to support polymorphic and variadic arguments.
*/
#ifndef TVM_RUNTIME_DISCO_SESSION_H_
#define TVM_RUNTIME_DISCO_SESSION_H_
#include <tvm/ffi/container/shape.h>
#include <tvm/ffi/function.h>
#include <tvm/runtime/tensor.h>
#include <mutex>
#include <queue>
#include <string>
#include <utility>
namespace tvm {
namespace runtime {
/*!
* \brief Static FFI type index for `runtime::disco::DRef`.
*
* Allocated within the [kTVMFFIDynObjectBegin - 16, kTVMFFIDynObjectBegin)
* custom-static slot range. The sibling constant `kRuntimeRPCObjectRef`
* lives in `src/runtime/rpc/rpc_session.h` and uses `... - 13`; values must
* remain disjoint across this small reserved block.
*/
constexpr int32_t kRuntimeDiscoDRef = TVMFFITypeIndex::kTVMFFIDynObjectBegin - 14;
static_assert(kRuntimeDiscoDRef >= TVMFFITypeIndex::kTVMFFIStaticObjectEnd &&
kRuntimeDiscoDRef < TVMFFITypeIndex::kTVMFFIDynObjectBegin,
"kRuntimeDiscoDRef must live in the static custom-index slot range");
/*!
* \brief All possible kinds of Disco commands.
*/
enum class DiscoAction : int32_t {
kShutDown = 0,
kKillReg = 1,
kGetGlobalFunc = 2,
kCallPacked = 3,
kSyncWorker = 4,
kCopyFromWorker0 = 5,
kCopyToWorker0 = 6,
kDebugGetFromRemote = 7,
kDebugSetRegister = 8,
};
/*! \brief Converts the enum class `DiscoAction` to string */
inline std::string DiscoAction2String(DiscoAction action) {
switch (action) {
case DiscoAction::kShutDown:
return "kShutDown";
case DiscoAction::kKillReg:
return "kKillReg";
case DiscoAction::kGetGlobalFunc:
return "kGetGlobalFunc";
case DiscoAction::kCallPacked:
return "kCallPacked";
case DiscoAction::kSyncWorker:
return "kSyncWorker";
case DiscoAction::kCopyFromWorker0:
return "kCopyFromWorker0";
case DiscoAction::kCopyToWorker0:
return "kCopyToWorker0";
case DiscoAction::kDebugGetFromRemote:
return "kDebugGetFromRemote";
case DiscoAction::kDebugSetRegister:
return "kDebugSetRegister";
}
TVM_FFI_THROW(ValueError) << "Unknown DiscoAction: " << static_cast<int>(action);
}
class SessionObj;
/*!
* \brief An object that exists on all workers.
*
* The controler assigns a unique "register id" to each object, and the worker uses this id to
* refer to the object residing on itself.
*/
class DRefObj : public ffi::Object {
public:
/*!\ brief Send dellocation command for `reg_id` */
inline ~DRefObj();
/*!
* \brief Get the value of a DRef from a remote worker.
* \param worker_id The id of the worker to be fetched from.
* \return The value of the register.
*/
inline ffi::Any DebugGetFromRemote(int worker_id);
/*!
* \brief Copy from the Tensor provided to a remote worker.
* \param worker_id The id of the worker to be copied to.
* \param source The Tensor to be copied.
*/
inline void DebugCopyFrom(int worker_id, ffi::AnyView source);
static constexpr const uint32_t _type_index = kRuntimeDiscoDRef;
static const constexpr bool _type_final = true;
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO_STATIC("runtime.disco.DRef", DRefObj, ffi::Object);
/*! \brief The id of the register */
int64_t reg_id;
/*! \brief Back-pointer to the host controler session */
ffi::ObjectRef session{nullptr};
private:
inline SessionObj* GetSession();
};
/*!
* \brief Managed reference to DRefObj.
* \sa DRefObj
* \note No public constructor is provided as it is not supposed to be directly created by users.
*/
class DRef : public ffi::ObjectRef {
public:
explicit DRef(ffi::ObjectPtr<DRefObj> data) : ffi::ObjectRef(data) {
TVM_FFI_ICHECK(data != nullptr);
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(DRef, ffi::ObjectRef, DRefObj);
};
/*!
* \brief A Disco interactive session. It allows users to interact with the Disco command queue with
* various ffi::Function calling convention.
*/
class SessionObj : public ffi::Object {
public:
virtual ~SessionObj() = default;
/*!
* \brief Call a ffi::Function on workers providing variadic arguments.
* \tparam Args In the variadic arguments, the supported types include:
* - integers and floating point numbers;
* - DataType;
* - Device;
* - std::string;
* - DRef.
* Examples of unsupported types:
* - Tensor, DLTensor;
* - TVM Objects, including ffi::Function, Module and String;
* \param func The function to be called.
* \param args The variadic arguments.
* \return The return value of function call
*/
template <typename... Args>
TVM_FFI_INLINE DRef CallPacked(const DRef& func, Args&&... args);
/*!
* \brief Call packed function on each worker using a packed sequence. The calling convention:
* The first element must be DiscoAction::kCallPacked,
* The second element must be 0, which will later be updated by the session to return reg_id
* The thirtd element is the function to be called.
*/
TVM_RUNTIME_DLL virtual DRef CallWithPacked(const ffi::PackedArgs& args) = 0;
/*! \brief Get the number of workers in the session. */
TVM_RUNTIME_DLL virtual int64_t GetNumWorkers() = 0;
/*! \brief Get a global functions on workers. */
TVM_RUNTIME_DLL virtual DRef GetGlobalFunc(const std::string& name) = 0;
/*!
* \brief Copy an Tensor from worker-0 to the controler-side Tensor
* \param host_array The array to be copied to worker-0
* \param remote_array The Tensor on worker-0
*/
TVM_RUNTIME_DLL virtual void CopyFromWorker0(const Tensor& host_array,
const DRef& remote_array) = 0;
/*!
* \brief Copy the controler-side Tensor to worker-0
* \param host_array The array to be copied to worker-0
* \param remote_array The Tensor on worker-0
*/
TVM_RUNTIME_DLL virtual void CopyToWorker0(const Tensor& host_array,
const DRef& remote_array) = 0;
/*!
* \brief Synchrnoize the controler with a worker, and it will wait until worker finishes
* executing this instruction.
* \param worker_id The id of the worker to be synced with.
* \note This function is usually used for worker-0, because it is the only worker that is
* assumed to collocate with the controler. Syncing with other workers may not be supported.
*/
TVM_RUNTIME_DLL virtual void SyncWorker(int worker_id) = 0;
/*! \brief Signal all the workers to shutdown */
TVM_RUNTIME_DLL virtual void Shutdown() = 0;
/*!
* \brief Initialize the data plane between workers.
* \param ccl The name of the communication backend, e.g., nccl, rccl, mpi.
* \param device_ids The device ids of the workers.
*/
TVM_RUNTIME_DLL virtual void InitCCL(ffi::String ccl, ffi::Shape device_ids) = 0;
/*!
* \brief Get the value of a register from a remote worker.
* \param reg_id The id of the register to be fetched.
* \param worker_id The id of the worker to be fetched from.
* \return The value of the register.
*/
TVM_RUNTIME_DLL virtual ffi::Any DebugGetFromRemote(int64_t reg_id, int worker_id) = 0;
/*!
* \brief Set the value of a register on a remote worker.
* \param reg_id The id of the register to be set.
* \param value The value to be set.
* \param worker_id The id of the worker to be set.
*/
TVM_RUNTIME_DLL virtual void DebugSetRegister(int64_t reg_id, ffi::AnyView value,
int worker_id) = 0;
struct FFI;
friend struct SessionObj::FFI;
friend class DRefObj;
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO("runtime.disco.Session", SessionObj, ffi::Object);
protected:
/*! \brief Deallocate a register id, kill it on all workers, and append it to `free_regs_`. */
virtual void DeallocReg(int reg_id) = 0;
};
/*!
* \brief Managed reference to SessionObj
* \sa SessionObj
*/
class Session : public ffi::ObjectRef {
public:
/*!
* \brief Create a session backed by a thread pool of workers
* \param num_workers The number of workers.
* \param num_groups The number of worker groups.
*/
TVM_RUNTIME_DLL static Session ThreadedSession(int num_workers, int num_groups);
/*!
* \brief Create a session backed by pipe-based multiprocessing
* \param num_workers The number of workers.
* \param num_groups The number of worker groups.
* \param process_pool_creator The name of a global function that takes `num_workers` as an input,
* and returns a ffi::Function, which takes an integer `worker_id` as the input and returns None.
* When `worker-id` is 0, it shuts down the process pool; Otherwise, it retursn a tuple
* (read_fd, writefd) used to communicate with the corresponding worker.
* \param entrypoint The entrypoint of DiscoWorker main worker function.
* \note Worker-0 is always co-located with the controler as a separate thread, and therefore
* worker-0 does not exist in the process pool.
*/
TVM_RUNTIME_DLL static Session ProcessSession(int num_workers, int num_groups,
ffi::String process_pool_creator,
ffi::String entrypoint);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Session, ffi::ObjectRef, SessionObj);
};
/*!
* \brief A bi-directional channel for controler-worker communication.
* This channel is primarily used to transfer control messages but not data.
*/
class DiscoChannel {
public:
virtual ~DiscoChannel() = default;
/*! \brief Send a packed sequence to the receiver */
virtual void Send(const ffi::PackedArgs& args) = 0;
/*! \brief Receive a packed sequence from worker */
virtual ffi::PackedArgs Recv() = 0;
/*! \brief Reply a packed sequence to the sender */
virtual void Reply(const ffi::PackedArgs& args) = 0;
/*! \brief Receive a reply from the worker */
virtual ffi::PackedArgs RecvReply() = 0;
};
/*!
* \brief A special communication channel between controler and worker-0,
* assuming they are always collocated in the same process.
*/
class WorkerZeroData {
public:
/*!
* \brief The host-side arrays to passed to worker-0 for special uses, for example,
* copy-to-worker0 and copy-from-worker0
*/
std::queue<Tensor> host_arrays;
/*! \brief The mutex that guards `host_arrays` */
std::mutex queue_mutex_;
};
// Implementation details
inline SessionObj* DRefObj::GetSession() {
return const_cast<SessionObj*>(static_cast<const SessionObj*>(session.get()));
}
DRefObj::~DRefObj() {
if (this->session.defined()) {
GetSession()->DeallocReg(reg_id);
}
}
ffi::Any DRefObj::DebugGetFromRemote(int worker_id) {
return GetSession()->DebugGetFromRemote(this->reg_id, worker_id);
}
void DRefObj::DebugCopyFrom(int worker_id, ffi::AnyView value) {
return GetSession()->DebugSetRegister(this->reg_id, value, worker_id);
}
template <typename... Args>
DRef SessionObj::CallPacked(const DRef& func, Args&&... args) {
constexpr int offset = 3;
constexpr int kNumArgs = offset + sizeof...(Args);
ffi::AnyView packed_args[kNumArgs];
ffi::PackedArgs::Fill(packed_args,
/*.0=*/static_cast<int>(DiscoAction::kCallPacked), // action
/*.1=*/0, // reg_id, which will be updated by this->CallWithPacked
/*.2=*/func, // the function to be called
std::forward<Args>(args)...);
return this->CallWithPacked(ffi::PackedArgs(packed_args, kNumArgs));
}
} // namespace runtime
} // namespace tvm
#endif // TVM_RUNTIME_DISCO_SESSION_H_
+402
View File
@@ -0,0 +1,402 @@
/*
* 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.
*/
/*!
* \file tvm/runtime/logging.h
* \brief logging utilities
*
* We use the following facilities from tvm/ffi/error.h for
* error handling and checking:
*
* - TVM_FFI_THROW(ErrorKind) << "msg";
* - TVM_FFI_CHECK(cond, ErrorKind) << "msg";
* - TVM_FFI_CHECK_EQ(x, y, ErrorKind) << "msg";
* - TVM_FFI_ICHECK(x) << "msg"; // InternalError
* - TVM_FFI_ICHECK_EQ(x, y) << "msg";
* - TVM_FFI_DCHECK(x) << "msg"; // Debug-only InternalError
*
* LOG(INFO), LOG(WARNING), LOG(ERROR) are kept for logging.
* LOG(FATAL) is kept for completeness, it throws InternalError.
*/
#ifndef TVM_RUNTIME_LOGGING_H_
#define TVM_RUNTIME_LOGGING_H_
#include <tvm/ffi/error.h>
#include <tvm/runtime/base.h>
#include <ctime>
#include <iomanip>
#include <iostream>
#include <sstream>
#include <string>
#include <unordered_map>
#include <vector>
/*!
* \brief Whether or not customize the logging output.
* If log customize is enabled, the user must implement
* tvm::runtime::detail::LogFatalImpl and tvm::runtime::detail::LogMessageImpl.
*/
#ifndef TVM_LOG_CUSTOMIZE
#define TVM_LOG_CUSTOMIZE 0
#endif
namespace tvm {
namespace runtime {
/*! \brief Internal implementation */
namespace detail {
// Provide support for customized logging.
#if TVM_LOG_CUSTOMIZE
/*!
* \brief Custom implementations of LogFatal.
*
* \sa TVM_LOG_CUSTOMIZE
*/
[[noreturn]] TVM_RUNTIME_DLL void LogFatalImpl(const std::string& file, int lineno,
const std::string& message);
/*!
* \brief Custom implementations of LogMessage.
*
* \sa TVM_LOG_CUSTOMIZE
*/
TVM_RUNTIME_DLL void LogMessageImpl(const std::string& file, int lineno, int level,
const std::string& message);
/*!
* \brief Class to accumulate an error message and throw it. Do not use
* directly, instead use LOG(FATAL).
*/
class LogFatal {
public:
LogFatal(const std::string& file, int lineno) : file_(file), lineno_(lineno) {}
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4722)
#endif
[[noreturn]] ~LogFatal() noexcept(false) { LogFatalImpl(file_, lineno_, stream_.str()); }
#ifdef _MSC_VER
#pragma warning(pop)
#endif
std::ostringstream& stream() { return stream_; }
private:
std::ostringstream stream_;
std::string file_;
int lineno_;
};
/*!
* \brief Class to accumulate an log message. Do not use directly, instead use
* LOG(INFO), LOG(WARNING), LOG(ERROR).
*/
class LogMessage {
public:
LogMessage(const std::string& file, int lineno, int level)
: file_(file), lineno_(lineno), level_(level) {}
~LogMessage() { LogMessageImpl(file_, lineno_, level_, stream_.str()); }
std::ostringstream& stream() { return stream_; }
private:
std::string file_;
int lineno_;
int level_;
std::ostringstream stream_;
};
#else
/*!
* \brief Class to accumulate an error message and throw it. Do not use
* directly, instead use LOG(FATAL).
* \note The `LogFatal` class is designed to be an empty class to reduce stack size usage.
* To play this trick, we use the thread-local storage to store its internal data.
*/
class LogFatal {
public:
TVM_FFI_NO_INLINE LogFatal(const char* file, int lineno) { GetEntry().Init(file, lineno); }
#ifdef _MSC_VER
#pragma warning(push)
#pragma warning(disable : 4722)
#endif
[[noreturn]] ~LogFatal() noexcept(false) {
GetEntry().Finalize();
throw;
}
#ifdef _MSC_VER
#pragma warning(pop)
#endif
std::ostringstream& stream() { return GetEntry().stream_; }
private:
struct Entry {
void Init(const char* file, int lineno) {
this->stream_.str("");
this->file_ = file;
this->lineno_ = lineno;
}
[[noreturn]] TVM_FFI_NO_INLINE ffi::Error Finalize() noexcept(false) {
ffi::Error error("InternalError", stream_.str(),
TVMFFIBacktrace(file_.c_str(), lineno_, "", 0));
throw error;
}
std::ostringstream stream_;
std::string file_;
int lineno_;
};
TVM_FFI_NO_INLINE TVM_RUNTIME_DLL static Entry& GetEntry();
};
/*!
* \brief Class to accumulate an log message. Do not use directly, instead use
* LOG(INFO), LOG(WARNING), LOG(ERROR).
*/
class LogMessage {
public:
LogMessage(const std::string& file, int lineno, int level) {
// Use inline constexpr to avoid ODR-issues with hidden static members
// when libtvm_runtime.so is built with -fvisibility=hidden.
static constexpr const char* kLevelStrings[] = {
": Debug: ", // TVM_LOG_LEVEL_DEBUG
": ", // TVM_LOG_LEVEL_INFO
": Warning: ", // TVM_LOG_LEVEL_WARNING
": Error: ", // TVM_LOG_LEVEL_ERROR
};
std::time_t t = std::time(nullptr);
stream_ << "[" << std::put_time(std::localtime(&t), "%H:%M:%S") << "] " << file << ":" << lineno
<< kLevelStrings[level];
}
TVM_FFI_NO_INLINE ~LogMessage() { std::cerr << stream_.str() << std::endl; }
std::ostringstream& stream() { return stream_; }
private:
std::ostringstream stream_;
};
#endif
// This class is used to explicitly ignore values in the conditional
// logging macros. This avoids compiler warnings like "value computed
// is not used" and "statement has no effect".
class LogMessageVoidify {
public:
LogMessageVoidify() {}
// This has to be an operator with a precedence lower than << but
// higher than "?:". See its usage.
void operator&(std::ostream&) {}
};
/*! \brief Captures the state of the \p TVM_LOG_DEBUG environment flag. */
class TVM_RUNTIME_DLL TvmLogDebugSettings {
public:
/*!
* \brief Parses the \p TVM_LOG_DEBUG environment flag as per the specification given by
* \p DebugLoggingEnabled and \p VerboseLoggingEnabled, and caches the result.
*/
inline static const TvmLogDebugSettings& FromFlag() {
// Parse and cache the verbosity level map.
static const auto* settings =
new TvmLogDebugSettings(TvmLogDebugSettings::ParseSpec(std::getenv("TVM_LOG_DEBUG")));
return *settings;
}
/*!
* \brief Parses \p opt_spec as per specification for \p TVM_LOG_DEBUG given by
* \p DebugLoggingEnabled and \p VerboseLoggingEnabled. Throws if specification is ill-formed.
*/
static TvmLogDebugSettings ParseSpec(const char* opt_spec);
/*!
* \brief Implements \p VerboseLoggingEnabled below w.r.t. the already parsed \p TVM_LOG_DEBUG
* environment variable.
*/
inline bool VerboseEnabled(const char* opt_filename, int level) const {
if (opt_filename == nullptr || level < 0 || vlog_level_map_.empty()) {
return false;
}
return VerboseEnabledImpl(opt_filename, level);
}
/*! \brief Returns true if \p DLOG statements should be executed. */
bool dlog_enabled() const { return dlog_enabled_; }
private:
// Slow path for VerboseEnabled.
bool VerboseEnabledImpl(const std::string& filename, int level) const;
/*! \brief If true, DLOG statements are enabled. */
bool dlog_enabled_ = false;
/*!
* \brief A map from canonicalized filenames to the maximum VLOG verbosity level for that file.
* May also contain the 'wildcard' entry \p "DEFAULT" representing the level for all other files.
*/
std::unordered_map<std::string, int> vlog_level_map_;
};
/*!
* \brief Returns true if a DLOG statement is enabled by the \p TVM_LOG_DEBUG environment
* variable. Requires:
* \code
* TVM_LOG_DEBUG=1
* \endcode
* or a valid setting as described by \p VerboseLoggingEnabled below.
*/
inline bool DebugLoggingEnabled() {
static int state = 0;
if (state == 0) {
state = TvmLogDebugSettings::FromFlag().dlog_enabled() ? 1 : -1;
}
return state == 1;
}
/*!
* \brief Returns true if a VLOG statement in \p filename is enabled by the \p TVM_LOG_DEBUG
* environment variable for logging at verbosity \p level. Levels should be non-negative.
*
* Filenames are canonicalized to be w.r.t. the src/ dir of the TVM tree. (VLOG's should not
* appear under include/).
*
* To enable file \p ir/bar.cc for level 0 only set:
* \code
* TVM_LOG_DEBUG="ir/bar.cc=0"
* \endcode
*
* To enable all files up to level 3 but disable \p ir/bar.cc set:
* \code
* TVM_LOG_DEBUG="DEFAULT=2,ir/bar.cc=-1"
* \endcode
*
* Any of these settings will also enable DLOG statements.
*/
inline bool VerboseLoggingEnabled(const char* opt_filename, int level) {
return TvmLogDebugSettings::FromFlag().VerboseEnabled(opt_filename, level);
}
/*!
* \brief A stack of VLOG context messages.
*
* For use by \p VLOG_CONTEXT macro only.
*/
class VLogContext {
public:
void Push(std::stringstream* stream) { context_stack_.push_back(stream); }
void Pop() {
if (!context_stack_.empty()) {
context_stack_.pop_back();
}
}
std::string str() const;
private:
std::vector<std::stringstream*> context_stack_;
};
/*! \brief Get thread local \p VLogContext for tracking a stack of VLOG context messages. */
inline VLogContext* ThreadLocalVLogContext() {
static thread_local VLogContext inst;
return &inst;
}
/*!
* \brief A RAII class to push/pos a VLOG context message onto the thread-local stack.
*
* For use by \p VLOG_CONTEXT macro only.
*/
class VLogContextEntry {
public:
VLogContextEntry() { ThreadLocalVLogContext()->Push(&sstream_); }
~VLogContextEntry() { ThreadLocalVLogContext()->Pop(); }
std::ostream& stream() { return sstream_; }
private:
std::stringstream sstream_;
};
} // namespace detail
#define TVM_LOG_LEVEL_DEBUG 0
#define TVM_LOG_LEVEL_INFO 1
#define TVM_LOG_LEVEL_WARNING 2
#define TVM_LOG_LEVEL_ERROR 3
#define TVM_LOG_LEVEL_FATAL 4
#define LOG(level) LOG_##level
#define LOG_DEBUG \
::tvm::runtime::detail::LogMessage(__FILE__, __LINE__, TVM_LOG_LEVEL_DEBUG).stream()
#define LOG_FATAL ::tvm::runtime::detail::LogFatal(__FILE__, __LINE__).stream()
#define LOG_INFO ::tvm::runtime::detail::LogMessage(__FILE__, __LINE__, TVM_LOG_LEVEL_INFO).stream()
#define LOG_ERROR \
::tvm::runtime::detail::LogMessage(__FILE__, __LINE__, TVM_LOG_LEVEL_ERROR).stream()
#define LOG_WARNING \
::tvm::runtime::detail::LogMessage(__FILE__, __LINE__, TVM_LOG_LEVEL_WARNING).stream()
#define LOG_IF(severity, condition) \
!(condition) ? (void)0 : ::tvm::runtime::detail::LogMessageVoidify() & LOG(severity)
#if TVM_LOG_DEBUG
#define LOG_DFATAL LOG_FATAL
#define DFATAL FATAL
#define DLOG(severity) LOG_IF(severity, ::tvm::runtime::detail::DebugLoggingEnabled())
#define DLOG_IF(severity, condition) \
LOG_IF(severity, ::tvm::runtime::detail::DebugLoggingEnabled() && (condition))
/*!
* \brief If the \p TVM_LOG_DEBUG build flag is enabled, push a context message onto an internal
* stack. All VLOG messages will include this stack in their prefix to help with debugging. E.g.:
* \code
* VLOG_CONTEXT << "my context";
* VLOG(1) << "my log message";
* \endcode
* Thread safe. No-op with no execution overhead if the \p TVM_LOG_DEBUG build flag is not enabled.
*/
#define VLOG_CONTEXT \
::tvm::runtime::detail::VLogContextEntry vlog_entry_; \
vlog_entry_.stream()
#else
#define LOG_DFATAL LOG_ERROR
#define DFATAL ERROR
#define DLOG(severity) true ? (void)0 : ::tvm::runtime::detail::LogMessageVoidify() & LOG(severity)
#define DLOG_IF(severity, condition) \
(true || !(condition)) ? (void)0 : ::tvm::runtime::detail::LogMessageVoidify() & LOG(severity)
#define VLOG_CONTEXT true ? (void)0 : ::tvm::runtime::detail::LogMessageVoidify() & LOG(INFO)
#endif
/*!
* \brief If the \p TVM_LOG_DEBUG build flag is enabled, and the containing file has been enabled
* at \p level or greater in the \p TVM_LOG_DEBUG environment variable, then log a message at
* \p INFO severity.
*
* See \p VerboseLoggingEnabled for the format of the \p TVM_LOG_DEBUG environment variable.
* Thread safe. No-op with no execution overhead if the \p TVM_LOG_DEBUG build flag is not enabled.
* No-op with some execution overhead if the \p TVM_LOG_DEBUG build flag is enabled but the
* containing file is not enabled.
*/
#define VLOG(level) \
DLOG_IF(INFO, ::tvm::runtime::detail::VerboseLoggingEnabled(__FILE__, (level))) \
<< ::tvm::runtime::detail::ThreadLocalVLogContext()->str()
} // namespace runtime
} // namespace tvm
#endif // TVM_RUNTIME_LOGGING_H_
+199
View File
@@ -0,0 +1,199 @@
/*
* 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.
*/
/*!
* \file tvm/runtime/memory/memory_manager.h
* \brief Abstract device memory management API
*/
#ifndef TVM_RUNTIME_MEMORY_MEMORY_MANAGER_H_
#define TVM_RUNTIME_MEMORY_MEMORY_MANAGER_H_
#include <tvm/runtime/base.h>
#include <tvm/runtime/tensor.h>
#include <functional>
#include <memory>
#include <mutex>
#include <string>
#include <unordered_map>
#include <vector>
namespace tvm {
namespace runtime {
namespace memory {
enum AllocatorType {
kNaive = 1,
kPooled,
};
struct Buffer {
/*! \brief The pointer to the allocated block of memory. */
void* data{nullptr};
/*! \brief The size of the block. */
size_t size{0};
/*! \brief The context of the allocated buffers. */
Device device;
/*! \brief The allocator that created this buffer. */
AllocatorType alloc_type;
};
class TVM_RUNTIME_DLL Allocator {
public:
explicit Allocator(AllocatorType type) : type_(type) {}
virtual ~Allocator() = default;
/*! \brief Allocate an empty Tensor using from the allocator.
* \param shape The shape of the Tensor.
* \param dtype The datatype of the Tensor.
* \param dev The device where the array is allocated.
* \param mem_scope The device memory scope hint.
* \return The empty Tensor.
*/
Tensor Empty(ffi::Shape shape, DLDataType dtype, Device dev,
ffi::Optional<ffi::String> mem_scope = std::nullopt);
/*! \brief Return the allocator type. */
inline AllocatorType type() const { return type_; }
/*! \brief Allocate a buffer given a size, alignment and type.
* \param dev The device where the array is allocated.
* \param nbytes The size of the buffer.
* \param alignment The alignment of the buffer.
* \param type_hint A type hint to the allocator.
* \return A sized allocation in the form of a buffer.
*/
virtual Buffer Alloc(Device dev, size_t nbytes, size_t alignment, DLDataType type_hint) = 0;
/*! \brief Allocate a buffer given a shape and type.
* \param dev The device where the array is allocated.
* \param shape The shape of the tensor.
* \param type_hint A type hint to the allocator.
* \param mem_scope A memory scope of the buffer.
* \return A sized allocation in the form of a buffer.
*/
virtual Buffer Alloc(Device dev, ffi::Shape shape, DLDataType type_hint,
const std::string& mem_scope = "");
/*! \brief Create a view for the buffer given a shape, type and scope.
* \param buffer The existing buffer upon which we need to create a view.
* \param shape The shape of the view.
* \param type_hint A type hint to the view.
* \param mem_scope A memory scope of the view.
* \return A device pointer to the created view.
*/
virtual void* CreateView(const Buffer& buffer, ffi::Shape shape, DLDataType type_hint,
const std::string& mem_scope = "global") {
return buffer.data;
}
/*! \brief Release the view .
* \param dev is the device where this view is created
* \param data The view pointer to be freed.
*/
virtual void FreeView(Device dev, void* data) {}
/*! \brief Free a buffer allocated by the allocator.
* \param buffer The buffer to free.
*/
virtual void Free(const Buffer& buffer) = 0;
/*! \brief Clear the allocated memory. */
virtual void Clear();
/*! \brief The amount of memory currently allocated.
* \return The amount of memory currently allocated.
*/
virtual size_t UsedMemory() const = 0;
protected:
/*! \brief Check if the given memory scope is allowed to allocate by the allocator. */
virtual bool AllowMemoryScope(const std::string& mem_scope) const;
private:
AllocatorType type_;
};
class MemoryManager {
public:
TVM_RUNTIME_DLL static MemoryManager* Global();
/*!
* \brief Get or create an allocator given the context and allocator type.
* \param dev The TVM device
* \param type The allocator type
* \return The memory allocator.
*/
TVM_RUNTIME_DLL static Allocator* GetOrCreateAllocator(Device dev, AllocatorType type);
/*!
* \brief Get an allocator given the context.
* \param dev The TVM device
* \param type The allocator type
* \return The memory allocator.
*/
TVM_RUNTIME_DLL static Allocator* GetAllocator(Device dev, AllocatorType type);
/*! \brief Clear the allocators. */
static void Clear();
private:
MemoryManager() {}
protected:
std::mutex mu_;
std::unordered_map<Device, std::unordered_map<AllocatorType, std::unique_ptr<Allocator>>>
allocators_;
};
/*! \brief An object representing a storage allocation. */
class StorageObj : public ffi::Object {
public:
/*! \brief The index into the VM function table. */
Buffer buffer;
/*! \brief The allocator where the storage buffer is allocated from. */
Allocator* allocator = nullptr;
/*! \brief Allocate an Tensor from a given piece of storage. */
TVM_RUNTIME_DLL Tensor AllocTensor(int64_t offset, ffi::Shape shape, DLDataType dtype);
/*! \brief Allocate an Tensor with memory scope from a given piece of storage. */
TVM_RUNTIME_DLL Tensor AllocTensorScoped(int64_t offset, ffi::Shape shape, DLDataType dtype,
ffi::String scope = "global");
~StorageObj() {
if (allocator) {
allocator->Free(buffer);
}
}
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("vm.Storage", StorageObj, ffi::Object);
};
/*! \brief reference to storage. */
class Storage : public ffi::ObjectRef {
public:
TVM_RUNTIME_DLL explicit Storage(Buffer buffer, Allocator* allocator);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Storage, ffi::ObjectRef, StorageObj);
};
} // namespace memory
using memory::Allocator;
using memory::AllocatorType;
using memory::MemoryManager;
using memory::StorageObj;
} // namespace runtime
} // namespace tvm
#endif // TVM_RUNTIME_MEMORY_MEMORY_MANAGER_H_
+360
View File
@@ -0,0 +1,360 @@
/*
* 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.
*/
/*!
* \file tvm/runtime/tensor.h
* \brief A device-independent managed Tensor abstraction.
*/
#ifndef TVM_RUNTIME_TENSOR_H_
#define TVM_RUNTIME_TENSOR_H_
#include <tvm/ffi/container/shape.h>
#include <tvm/ffi/container/tensor.h>
#include <tvm/ffi/dtype.h>
#include <tvm/ffi/optional.h>
#include <tvm/ffi/string.h>
#include <tvm/runtime/base.h>
#include <tvm/runtime/device_api.h>
#include <tvm/support/io.h>
#include <tvm/support/serializer.h>
#include <atomic>
#include <functional>
#include <utility>
#include <vector>
namespace tvm {
namespace runtime {
/*!
* \brief Managed Tensor.
* The array is backed by reference counted blocks.
*/
class Tensor : public tvm::ffi::Tensor {
public:
Tensor() = default;
/*!
* \brief constructor.
* \param data ffi::ObjectPtr to the data container.
*/
explicit Tensor(ffi::ObjectPtr<ffi::TensorObj> data) : tvm::ffi::Tensor(data) {}
explicit Tensor(ffi::UnsafeInit tag) : tvm::ffi::Tensor(tag) {}
Tensor(ffi::Tensor&& other) : tvm::ffi::Tensor(std::move(other)) {} // NOLINT(*)
Tensor(const ffi::Tensor& other) : tvm::ffi::Tensor(other) {} // NOLINT(*)
ffi::ShapeView Shape() const { return this->shape(); }
DLDataType DataType() const { return this->dtype(); }
// DLPack handling
static Tensor FromDLPack(DLManagedTensor* tensor) {
return tvm::ffi::Tensor::FromDLPack(tensor, kAllocAlignment, true);
}
static Tensor FromDLPackVersioned(DLManagedTensorVersioned* tensor) {
return tvm::ffi::Tensor::FromDLPackVersioned(tensor, kAllocAlignment, true);
}
inline const DLTensor* operator->() const { return this->get(); }
/*!
* \brief Copy data content from another array.
* \param other The source array to be copied from.
* \note The copy may happen asynchronously if it involves a GPU context.
* TVMSynchronize is necessary.
*/
inline void CopyFrom(const DLTensor* other);
inline void CopyFrom(const Tensor& other);
/*!
* \brief Copy data content from a byte buffer.
* \param data The source bytes to be copied from.
* \param nbytes The size of the buffer in bytes
* Must be equal to the size of the Tensor.
* \note The copy always triggers a TVMSynchronize.
*/
TVM_RUNTIME_DLL void CopyFromBytes(const void* data, size_t nbytes);
/*!
* \brief Copy data content into another array.
* \param other The source array to be copied from.
* \note The copy may happen asynchronously if it involves a GPU context.
* TVMSynchronize is necessary.
*/
inline void CopyTo(DLTensor* other) const;
inline void CopyTo(const Tensor& other) const;
/*!
* \brief Copy data content into another array.
* \param data The source bytes to be copied from.
* \param nbytes The size of the data buffer.
* Must be equal to the size of the Tensor.
* \note The copy always triggers a TVMSynchronize.
*/
TVM_RUNTIME_DLL void CopyToBytes(void* data, size_t nbytes) const;
/*!
* \brief Copy the data to another device.
* \param dev The target device.
* \param mem_scope The memory scope of the target array.
* \return The array under another device.
* \note The copy always triggers a TVMSynchronize.
*/
TVM_RUNTIME_DLL Tensor CopyTo(const Device& dev,
ffi::Optional<ffi::String> mem_scope = std::nullopt) const;
/*!
* \brief Load Tensor from stream
* \param stream The input data stream
* \return Whether load is successful
*/
inline bool Load(support::Stream* stream);
/*!
* \brief Save Tensor to stream
* \param stream The output data stream
*/
inline void Save(support::Stream* stream) const;
/*!
* \brief Create a Tensor that shares the data memory with the current one.
*
* \param shape The shape of the new array.
*
* \param dtype The data type of the new array.
*
* \param relative_byte_offset The offset of the output Tensor,
* relative to the current byte offset.
*
* By default, the offset of the view is the same as the offset
* of the current array.
*
* \note The new array must not allow access of addresses which
* would be out of bounds in the current array. If the new
* array is larger than the current array, or if the
* `relative_byte_offset` would place the end of the new array
* outside the bounds of the current array, this function will
* raise an exception.
*/
TVM_RUNTIME_DLL Tensor CreateView(ffi::Shape shape, DLDataType dtype,
uint64_t relative_byte_offset = 0) const;
/*!
* \brief Create an empty Tensor.
* \param shape The shape of the new array.
* \param dtype The data type of the new array.
* \param dev The device of the array.
* \param mem_scope The memory scope of the array.
* \return The created Array
*/
TVM_RUNTIME_DLL static Tensor Empty(ffi::Shape shape, DLDataType dtype, Device dev,
ffi::Optional<ffi::String> mem_scope = std::nullopt);
/*!
* \brief Function to copy data from one array to another.
* \param from The source array.
* \param to The target array.
* \param stream The stream used in copy.
*/
TVM_RUNTIME_DLL static void CopyFromTo(const DLTensor* from, DLTensor* to,
TVMStreamHandle stream = nullptr);
/*!
* \brief Function to copy data from one array to a byte buffer.
* \param from The source array.
* \param to The target byte buffer.
* \param nbytes The size of the data buffer.
* \param stream The stream used in copy.
*/
TVM_RUNTIME_DLL static void CopyToBytes(const DLTensor* from, void* to, size_t nbytes,
TVMStreamHandle stream = nullptr);
/*!
* \brief Function to copy data from one array to a byte buffer.
* \param from The source array.
* \param to The target byte buffer.
* \param nbytes The size of the data buffer.
* \param stream The stream used in copy.
*/
TVM_RUNTIME_DLL static void CopyFromBytes(const DLTensor* to, void* from, size_t nbytes,
TVMStreamHandle stream = nullptr);
/*!
* \brief Check if two tensors share the same underlying storage.
*
* This detects runtime storage aliasing (e.g. views from CreateView, etc.) but does
* not imply either tensor was created by CreateView.
*
* \param a The first tensor.
* \param b The second tensor.
* \return True if the tensors share the same storage.
*/
TVM_RUNTIME_DLL static bool IsStorageShared(const DLTensor* a, const DLTensor* b);
/*!
* \brief Tensor overload of IsStorageShared.
* \param a The first tensor.
* \param b The second tensor.
* \return True if the tensors share the same storage.
*/
static bool IsStorageShared(const Tensor& a, const Tensor& b);
};
/*!
* \brief Save a DLTensor to stream
* \param strm The output stream
* \param tensor The tensor to be saved.
*/
inline bool SaveDLTensor(support::Stream* strm, const DLTensor* tensor);
inline void Tensor::CopyFrom(const DLTensor* other) {
TVM_FFI_ICHECK(data_ != nullptr);
CopyFromTo(other, get_mutable());
}
inline void Tensor::CopyFrom(const Tensor& other) {
TVM_FFI_ICHECK(data_ != nullptr);
TVM_FFI_ICHECK(other.data_ != nullptr);
CopyFromTo(other.get_mutable(), get_mutable());
}
inline void Tensor::CopyTo(DLTensor* other) const {
TVM_FFI_ICHECK(data_ != nullptr);
CopyFromTo(get_mutable(), other);
}
inline void Tensor::CopyTo(const Tensor& other) const {
TVM_FFI_ICHECK(data_ != nullptr);
TVM_FFI_ICHECK(other.data_ != nullptr);
CopyFromTo(get_mutable(), other.get_mutable());
}
/*! \brief Magic number for Tensor file */
constexpr uint64_t kTVMTensorMagic = 0xDD5E40F096B4A13F;
inline bool SaveDLTensor(support::Stream* strm, const DLTensor* tensor) {
uint64_t header = kTVMTensorMagic, reserved = 0;
strm->Write(header);
strm->Write(reserved);
// Always save data as CPU context
//
// Parameters that get serialized should be in CPU by default.
// So even the array's context is GPU, it will be stored as CPU array.
// This is used to prevent case when another user loads the parameters
// back on machine that do not have GPU or related context.
//
// We can always do array.CopyTo(target_dev) to get a corresponding
// array in the target context.
Device cpu_dev;
cpu_dev.device_type = kDLCPU;
cpu_dev.device_id = 0;
strm->Write(cpu_dev);
strm->Write(tensor->ndim);
strm->Write(tensor->dtype);
int ndim = tensor->ndim;
strm->WriteArray(tensor->shape, ndim);
int type_bytes = (tensor->dtype.bits + 7) / 8;
int64_t num_elems = 1;
for (int i = 0; i < ndim; ++i) {
num_elems *= tensor->shape[i];
}
int64_t data_byte_size = type_bytes * num_elems;
strm->Write(data_byte_size);
if (TVM_FFI_IO_NO_ENDIAN_SWAP && tensor->device.device_type == kDLCPU &&
ffi::IsContiguous(*tensor) && tensor->byte_offset == 0) {
// quick path
strm->Write(tensor->data, data_byte_size);
} else {
std::vector<uint8_t> bytes(data_byte_size);
Tensor::CopyToBytes(const_cast<DLTensor*>(tensor), bytes.data(), data_byte_size);
if (!TVM_FFI_IO_NO_ENDIAN_SWAP) {
ffi::ByteSwap(bytes.data(), type_bytes, num_elems);
}
strm->Write(bytes.data(), data_byte_size);
}
return true;
}
inline void Tensor::Save(support::Stream* strm) const { SaveDLTensor(strm, operator->()); }
inline bool Tensor::Load(support::Stream* strm) {
uint64_t header, reserved;
TVM_FFI_ICHECK(strm->Read(&header)) << "Invalid DLTensor file format";
TVM_FFI_ICHECK(strm->Read(&reserved)) << "Invalid DLTensor file format";
TVM_FFI_ICHECK(header == kTVMTensorMagic) << "Invalid DLTensor file format";
Device dev;
int ndim;
DLDataType dtype;
TVM_FFI_ICHECK(strm->Read(&dev)) << "Invalid DLTensor file format";
TVM_FFI_ICHECK(strm->Read(&ndim)) << "Invalid DLTensor file format";
TVM_FFI_ICHECK(strm->Read(&dtype)) << "Invalid DLTensor file format";
TVM_FFI_ICHECK_EQ(dev.device_type, kDLCPU)
<< "Invalid DLTensor device: can only save as CPU tensor";
std::vector<int64_t> shape(ndim);
if (ndim != 0) {
TVM_FFI_ICHECK(strm->ReadArray(&shape[0], ndim)) << "Invalid DLTensor file format";
}
Tensor ret = Tensor::Empty(ffi::Shape(shape), dtype, dev);
int64_t num_elems = 1;
int elem_bytes = (ret->dtype.bits + 7) / 8;
for (int i = 0; i < ret->ndim; ++i) {
num_elems *= ret->shape[i];
}
int64_t data_byte_size;
TVM_FFI_ICHECK(strm->Read(&data_byte_size)) << "Invalid DLTensor file format";
TVM_FFI_ICHECK(data_byte_size == num_elems * elem_bytes) << "Invalid DLTensor file format";
auto read_ret = strm->Read(ret->data, data_byte_size);
// Only check non-empty data
if (ndim > 0 && shape[0] != 0) {
TVM_FFI_ICHECK(read_ret) << "Invalid DLTensor file format";
}
if (!TVM_FFI_IO_NO_ENDIAN_SWAP) {
ffi::ByteSwap(ret->data, elem_bytes, num_elems);
}
*this = ret;
return true;
}
/*!
* \brief Get the preferred host device from the input device.
* - For CUDA and ROCm, CUDAHost and ROCMHost will be returned for pinned memory,
* since pinned memory reduces copy overhead.
* - For other devices, CPU is returned as a fallback.
*/
inline Device GetPreferredHostDevice(Device device) {
if (device.device_type == DLDeviceType::kDLCUDA) {
return Device{DLDeviceType::kDLCUDAHost, 0};
} else if (device.device_type == DLDeviceType::kDLROCM) {
return Device{DLDeviceType::kDLROCMHost, 0};
} else {
// Fallback to CPU.
return Device{DLDeviceType::kDLCPU, 0};
}
}
} // namespace runtime
} // namespace tvm
namespace std {
template <>
struct hash<tvm::Device> {
std::size_t operator()(const tvm::Device& dev) const {
return ((dev.device_id << 8) | dev.device_type);
}
};
template <>
struct equal_to<tvm::Device> {
bool operator()(const tvm::Device& lhs, const tvm::Device& rhs) const {
return (lhs.device_type == rhs.device_type && lhs.device_id == rhs.device_id);
}
};
} // namespace std
#endif // TVM_RUNTIME_TENSOR_H_
+194
View File
@@ -0,0 +1,194 @@
/*
* 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.
*/
/*!
* \file include/tvm/runtime/timer.h
* \brief Runtime timer primitives: Timer, TimerNode, WrapTimeEvaluator.
*/
#ifndef TVM_RUNTIME_TIMER_H_
#define TVM_RUNTIME_TIMER_H_
#include <tvm/ffi/function.h>
#include <tvm/runtime/base.h>
#include <tvm/runtime/device_api.h>
#include <tvm/runtime/tensor.h>
namespace tvm {
namespace runtime {
/*! \brief Base class for all timer implementations.
*
* New implementations of this interface should make sure that `Start` and `Stop`
* are as lightweight as possible. Expensive state synchronization should be
* done in `SyncAndGetElapsedNanos`.
*/
class TimerNode : public ffi::Object {
public:
/*! \brief Start the timer.
*
* Note: this function should only be called once per object.
*/
virtual void Start() = 0;
/*! \brief Stop the timer.
*
* Note: this function should only be called once per object.
*/
virtual void Stop() = 0;
/*! \brief Synchronize timer state and return elapsed time between `Start` and `Stop`.
* \return The time in nanoseconds between `Start` and `Stop`.
*
* This function is necessary because we want to avoid timing the overhead of
* doing timing. When using multiple timers, it is recommended to stop all of
* them before calling `SyncAndGetElapsedNanos` on any of them.
*
* Note: this function should be only called once per object. It may incur
* a large synchronization overhead (for example, with GPUs).
*/
virtual int64_t SyncAndGetElapsedNanos() = 0;
virtual ~TimerNode() {}
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO("runtime.TimerNode", TimerNode, ffi::Object);
};
/*! \brief Timer for a specific device.
*
* This is a managed reference to a TimerNode.
*
* \sa TimerNode
*/
class Timer : public ffi::ObjectRef {
public:
/*!
* \brief Get a device specific timer.
* \param dev The device to time.
* \return A `Timer` that has already been started.
*
* Use this function to time runtime of arbitrary regions of code on a specific
* device. The code that you want to time should be running on the device
* otherwise the timer will not return correct results. This is a lower level
* interface than TimeEvaluator and only runs the timed code once
* (TimeEvaluator runs the code multiple times).
*
* A default timer is used if a device specific one does not exist. This
* timer performs synchronization between the device and CPU, which can lead
* to overhead in the reported results.
*
* Example usage:
* \code{.cpp}
* Timer t = Timer::Start(Device::cpu());
* my_long_running_function();
* t->Stop();
* ... // some more computation
* int64_t nanosecs = t->SyncAndGetElapsedNanos() // elapsed time in nanoseconds
* \endcode
*
* To add a new device-specific timer, register a new function
* "runtime.timer.my_device" (where `my_device` is the `DeviceName` of your
* device). This function should accept a `Device` and return a new `Timer`
* that has already been started.
*
* For example, this is how the CPU timer is implemented:
* \code{.cpp}
* class CPUTimerNode : public TimerNode {
* public:
* virtual void Start() { start_ = std::chrono::high_resolution_clock::now(); }
* virtual void Stop() { duration_ = std::chrono::high_resolution_clock::now() - start_; }
* virtual int64_t SyncAndGetElapsedNanos() { return duration_.count(); }
* virtual ~CPUTimerNode() {}
*
* static constexpr const char* _type_key = "runtime.CPUTimerNode";
* TVM_FFI_DECLARE_OBJECT_INFO_FINAL(CPUTimerNode, TimerNode);
*
* private:
* std::chrono::high_resolution_clock::time_point start_;
* std::chrono::duration<int64_t, std::nano> duration_;
* };
*
*
* TVM_FFI_STATIC_INIT_BLOCK() {
* namespace refl = tvm::ffi::reflection;
* refl::GlobalDef().def("runtime.timer.cpu", [](Device dev) {
* return Timer(ffi::make_object<CPUTimerNode>());
* });
* }
* \endcode
*/
static TVM_RUNTIME_DLL Timer Start(Device dev);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Timer, ffi::ObjectRef, TimerNode);
};
/*!
* \brief Wrap a timer function to measure the time cost of a given packed function.
*
* Approximate implementation:
* \code{.py}
* f() // warmup
* for i in range(repeat)
* f_preproc()
* while True:
* start = time()
* for j in range(number):
* f()
* duration_ms = time() - start
* if duration_ms >= min_repeat_ms:
* break
* else:
* number = (min_repeat_ms / (duration_ms / number) + 1
* if cooldown_interval_ms and i % repeats_to_cooldown == 0:
* sleep(cooldown_interval_ms)
* \endcode
*
* \param f The function argument.
* \param dev The device.
* \param number The number of times to run this function for taking average.
* We call these runs as one `repeat` of measurement.
* \param repeat The number of times to repeat the measurement.
* In total, the function will be invoked (1 + number x repeat) times,
* where the first one is warm up and will be discarded.
* The returned result contains `repeat` costs,
* each of which is an average of `number` costs.
* \param min_repeat_ms The minimum duration of one `repeat` in milliseconds.
* By default, one `repeat` contains `number` runs. If this parameter is set,
* the parameters `number` will be dynamically adjusted to meet the
* minimum duration requirement of one `repeat`.
* i.e., When the run time of one `repeat` falls below this time,
* the `number` parameter will be automatically increased.
* \param limit_zero_time_iterations The maximum number of repeats when
* measured time is equal to 0. It helps to avoid hanging during measurements.
* \param cooldown_interval_ms The cooldown interval in milliseconds between the number of repeats
* defined by `repeats_to_cooldown`.
* \param repeats_to_cooldown The number of repeats before the
* cooldown is activated.
* \param cache_flush_bytes The number of bytes to flush from cache before
* \param f_preproc The function to be executed before we execute time
* evaluator.
* \return f_timer A timer function.
*/
ffi::Function WrapTimeEvaluator(ffi::Function f, Device dev, int number, int repeat,
int min_repeat_ms, int limit_zero_time_iterations,
int cooldown_interval_ms, int repeats_to_cooldown,
int cache_flush_bytes = 0, ffi::Function f_preproc = nullptr);
} // namespace runtime
} // namespace tvm
#endif // TVM_RUNTIME_TIMER_H_
+89
View File
@@ -0,0 +1,89 @@
/*
* 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.
*/
/*!
* \file tvm/runtime/vm/builtin.h
* \brief Builtin runtime APIs.
*/
#ifndef TVM_RUNTIME_VM_BUILTIN_H_
#define TVM_RUNTIME_VM_BUILTIN_H_
namespace tvm {
namespace runtime {
namespace vm {
/*!
* \brief Op code used in built-in match-shape function.
*
* The function takes the following signature:
* MatchShape(input_shape, shape_heap, n, c[0], r[0], c[1], r[1], ... c[n], r[n], err_ctx)
*
* This function provides runtime shape population and checking support for match-cast.
* When a shape variable appears in the first time, we should load the shape and
* populate the variable. When a shape variable already appears, we should
* assert that it already equals an existing shape value.
*
* NOTE: It is OK to pass nullptr shape_heap if all code are AssertEqualToImm.
*/
enum class MatchShapeCode : int {
/*!
* \brief Perform an assertion that shape equals immediate.
*
* assert input_shape[i] == r[i]
*/
kAssertEqualToImm = 0,
/*!
* \brief This is the first time we see a symbolic shape variable, store to heap.
*
* shape_heap[r[i]] = input_shape[i]
*/
kStoreToHeap = 1,
/*!
* \brief skip and do not do anything.
*/
kNoOp = 2,
/*!
* \brief Peform an assertion that the shape equals a loaded value.
*
* assert input_shape[i] == shape_heap[r[i]]
*/
kAssertEqualToLoad = 3,
};
/*!
* \brief Op code used in builtin function MakeShape.
*
* MakeShape(shape_heap, n, c[0], r[0], c[1], r[1], ... c[n], r[n]).
*
* \note It is OK to pass nullptr to shape_heap if all code are UseImm.
*/
enum class MakeShapeCode : int {
/*! \brief Use the following r[i] as immediate shape value. */
kUseImm = 0,
/*!
* \brief Load shape value from the shape_heap[[r[i]].
*/
kLoadShape = 1,
};
} // namespace vm
} // namespace runtime
} // namespace tvm
#endif // TVM_RUNTIME_VM_BUILTIN_H_
+246
View File
@@ -0,0 +1,246 @@
/*
* 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.
*/
/*!
* \file tvm/runtime/vm/bytecode.h
* \brief The bytecode for the virtual machine.
*/
#ifndef TVM_RUNTIME_VM_BYTECODE_H_
#define TVM_RUNTIME_VM_BYTECODE_H_
#include <tvm/ffi/dtype.h>
#include <tvm/ffi/error.h>
#include <iostream>
#include <vector>
namespace tvm {
namespace runtime {
namespace vm {
/*!
* \brief The storage type for the bytecode in the VM.
*/
using ExecWord = int64_t;
/*! \brief A register name. */
using RegName = ExecWord;
/*!
* \brief An alias for the integer type used ubiquitously in the VM.
*/
using Index = ExecWord;
/*!
* \brief An enumeration of Relax's opcodes.
*
* The opcode is used to implement instruction
* as a tagged union.
*/
enum class Opcode {
Call = 1U,
Ret = 2U,
Goto = 3U,
If = 4U,
};
/*! \brief A single virtual machine instruction.
*
* The representation of the instruction is as
* a tagged union.
*
* The first field represents which instruction,
* and by extension which field of the union
* is active.
*/
struct Instruction {
/*! \brief The number of bit for storing value. */
static constexpr ExecWord kKindBit = 8;
/*! \brief The number of bit for storing value. */
static constexpr ExecWord kValueBit = sizeof(ExecWord) * 8 - kKindBit;
/*! \brief The bit mask of the value part. */
static constexpr ExecWord kValueMask = (static_cast<ExecWord>(1) << kValueBit) - 1;
/*! \brief Maximum possible value, use 1 bit for sign. */
static constexpr ExecWord kValueMaxLimit = (static_cast<ExecWord>(1) << (kValueBit - 1)) - 1;
/*! \brief Minimum possible value, remove 1 slot to keep things symmetric. */
static constexpr ExecWord kValueMinLimit = -kValueMaxLimit;
/*! \brief Beginning of special register section. */
static constexpr RegName kBeginSpecialReg = static_cast<ExecWord>(1) << 54;
/*! \brief Random magic number that represents void argument, indicate null value */
static constexpr RegName kVoidRegister = kBeginSpecialReg + 0;
/*! \brief Random magic number that represents the VM context */
static constexpr RegName kVMRegister = kBeginSpecialReg + 1;
/*!
* \brief The kind of instruction's argument.
*/
enum class ArgKind : int { kRegister = 0, kImmediate = 1, kConstIdx = 2, kFuncIdx = 3 };
friend std::ostream& operator<<(std::ostream& os, const ArgKind& kind) {
switch (kind) {
case ArgKind::kRegister:
os << "kRegister";
break;
case ArgKind::kImmediate:
os << "kImmediate";
break;
case ArgKind::kConstIdx:
os << "kConstIdx";
break;
case ArgKind::kFuncIdx:
os << "kFuncIdx";
break;
default:
TVM_FFI_THROW(InternalError)
<< "Internal error: "
<< "Invalid ArgKind with integer value " << static_cast<int>(kind);
}
return os;
}
/*!
* \brief The auxiliary data structure for instruction argument.
*/
class Arg {
public:
/*! \brief Construct a void argument. */
Arg() : data_(Instruction::kVoidRegister) {}
/*!
* \brief construct Arg from data.
* \param data The data repr.
*/
static Arg FromData(ExecWord data) { return Arg(data); }
/*!
* \brief construct a register Arg.
* \param reg The register number.
* \return The constructed arg.
*/
static Arg Register(RegName reg) { return Arg(ArgKind::kRegister, reg); }
/*!
* \brief construct a ConstIdx arg.
* \param index The constant index.
* \return The constructed arg.
*/
static Arg ConstIdx(Index index) { return Arg(ArgKind::kConstIdx, index); }
/*!
* \brief construct a immediate arg.
* \param imm_value The immediate value.
* \return The constructed arg.
*/
static Arg Immediate(int64_t imm_value) { return Arg(ArgKind::kImmediate, imm_value); }
/*!
* \brief construct a FuncIdx arg.
* \param index The func index in the function table.
* \return The constructed arg.
*/
static Arg FuncIdx(Index index) { return Arg(ArgKind::kFuncIdx, index); }
/*!
* \brief Get the kind of argument.
* \return The kind of argument.
*/
ArgKind kind() const {
uint8_t kind = (data_ >> kValueBit) & 0xFF;
return Instruction::ArgKind(kind);
}
/*!
* \brief Get the value of argument.
* \return The value of argument.
* \note We store both positive and negative values by sign extension.
*/
ExecWord value() const { return ((data_ & kValueMask) << kKindBit) >> kKindBit; }
/*!
* \brief Get the raw data repr of the arg.
* \return The raw data.
*/
ExecWord data() const { return data_; }
private:
/*! \brief Construct from the data. */
explicit Arg(ExecWord data) : data_(data) {}
/*! \brief Construct from the kind and value. */
Arg(ArgKind kind, Index value) {
TVM_FFI_ICHECK_LE(value, kValueMaxLimit);
TVM_FFI_ICHECK_GE(value, kValueMinLimit);
data_ = (static_cast<ExecWord>(kind) << kValueBit) | (value & kValueMask);
}
/*! \brief The underlying stored data. */
ExecWord data_;
};
/*! \brief The instruction opcode. */
Opcode op;
union {
struct /* Call */ {
/*! \brief The destination register. */
RegName dst;
/*! \brief The index into the packed function table. */
Index func_idx;
/*! \brief The number of arguments to the packed function. */
Index num_args;
/*! \brief The arguments of the packed function. */
Arg* args;
};
struct /* Ret */ {
/*! \brief The return result. */
RegName result;
};
struct /* Goto */ {
/*! \brief The jump offset. */
Index pc_offset;
};
struct /* If */ {
/*! \brief The register containing the cond value. */
RegName cond;
/*! \brief The program counter offset for the false branch. */
Index false_offset;
};
};
/*!
* \brief Construct a Call instruction.
* \param func_idx The index of the function to call.
* \param num_args The number of arguments.
* \param args The input arguments.
* \param dst The destination register.
* \return The call instruction.
*/
static Instruction Call(Index func_idx, Index num_args, Arg* args, RegName dst);
/*!
* \brief Construct a return instruction.
* \param result The register containing the return value.
* \return The return instruction.
*/
static Instruction Ret(RegName result);
/*!
* \brief Construct a goto instruction.
* \param pc_offset The register containing the jump offset.
* \return The goto instruction.
*/
static Instruction Goto(RegName pc_offset);
/*!
* \brief Construct an If instruction.
* \param cond The register containing the cond value.
* \param false_offset The program counter offset for the false branch.
* \return The If instruction.
*/
static Instruction If(RegName cond, Index false_offset);
};
} // namespace vm
} // namespace runtime
} // namespace tvm
#endif // TVM_RUNTIME_VM_BYTECODE_H_
+242
View File
@@ -0,0 +1,242 @@
/*
* 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.
*/
/*!
* \file tvm/runtime/vm/executable.h
*/
#ifndef TVM_RUNTIME_VM_EXECUTABLE_H_
#define TVM_RUNTIME_VM_EXECUTABLE_H_
#include <tvm/ffi/extra/module.h>
#include <tvm/ffi/function.h>
#include <tvm/support/io.h>
#include <tvm/support/serializer.h>
#include <string>
#include <unordered_map>
#include <vector>
#include "./bytecode.h"
// Convention: this version should set to minimum TVM version it support
// NOTE: this file only changes if we change relax vm format
// for example if relax vm format do not change in 0.15, this should remain as 0.14
// if it changes in 0.16, we will change it to 0.16
#define VM_VERSION "0.14"
namespace tvm {
namespace runtime {
namespace vm {
/*!
* \brief Information entry in executable function table.
*
* Contains metadata about the compiled function, as
* well as the compiled VM instructions.
*/
struct VMFuncInfo {
/*! \brief kind of the function. */
enum class FuncKind : int {
/*! \brief system level packed function */
kPackedFunc = 0,
/*! \brief VM function. */
kVMFunc = 1,
/*! \brief VMTIR function. */
kVMTIRFunc = 2,
};
/*! \brief The kind of function. */
FuncKind kind;
/*! \brief The function's name, global symbol */
std::string name;
/*! \brief The start instruction index of the function. */
Index start_instr = 0;
/*! \brief The end instruction index of the function. */
Index end_instr = 0;
/*! \brief The number of arguments of the function. */
Index num_args = 0;
/*! \brief The register file size of the function. */
Index register_file_size = 0;
/*! \brief The function parameter names.*/
std::vector<std::string> param_names;
// defined customized loader save
void Save(support::Stream* writer) const;
bool Load(support::Stream* reader);
};
/*!
* \brief The virtual machine executable emitted by the VM compiler.
*
* The executable contains information (e.g. data in different memory regions)
* to run in a virtual machine.
*/
class TVM_RUNTIME_DLL VMExecutable : public ffi::ModuleObj {
public:
/*! \brief Get the property of the runtime module .*/
int GetPropertyMask() const final { return ffi::Module::kBinarySerializable; };
/*!
* \brief Print the detailed statistics of the given code, i.e. number of
* globals and constants, etc.
* \return The statistics represented by a string.
*/
std::string Stats() const;
/*!
* \brief Get the i-th instruction from the executable.
* \param i The index of the instruction to be fetched.
* \return The instruction.
*/
Instruction GetInstruction(Index i) const;
/*!
* \brief Set j-th byte data of i-th instruction to val.
* \param i The index of the instruction to be updated.
* \param j The index of the byte data of the instruction to be updated.
* \param val The value to be set
*/
void SetInstructionData(Index i, Index j, ExecWord val);
/*!
* \brief Print the instructions as text format.
* \return The text format of the instructions.
*/
ffi::String AsText() const;
/*!
* \brief Print the instructions as python program.
* \return The python program of the instructions, represented by a string.
*/
ffi::String AsPython() const;
/*!
* \brief Write the VMExecutable to the binary stream in serialized form.
* \return The binary bytes that save the executable to.
*/
ffi::Bytes SaveToBytes() const final;
/*!
* \brief Load VMExecutable from the binary stream in serialized form.
* \param bytes The binary bytes that load the executable from.
* \return The loaded executable, in the form of a `runtime::Module`.
*/
static ffi::Module LoadFromBytes(const ffi::Bytes& bytes);
/*!
* \brief Write the VMExecutable to the provided path as a file containing its serialized content.
* \param file_name The name of the file to write the serialized data to.
* \param format The target format of the saved file.
*/
void WriteToFile(const ffi::String& file_name, const ffi::String& format) const final;
/*! \brief Create a Relax virtual machine and load `this` as the executable. */
ffi::Module VMLoadExecutable() const;
/*! \brief Check if the VMExecutable contains a specific function. */
bool HasFunction(const ffi::String& name) const;
/*!
* \brief Load VMExecutable from the file.
* \param file_name The path of the file that load the executable from.
* \return The loaded executable, in the form of a `runtime::Module`.
*/
static ffi::Module LoadFromFile(const ffi::String& file_name);
/*! \brief The virtual machine's function table. */
std::vector<VMFuncInfo> func_table;
/*! \brief A map from globals (as strings) to their index in the function map. */
std::unordered_map<std::string, Index> func_map;
/*! \brief The global constant pool. */
std::vector<ffi::Any> constants;
/*! \brief The VDevice memory scopes */
std::unordered_map<Index, std::string> memory_scopes;
/*! \brief The offset of instruction. */
std::vector<Index> instr_offset;
/*! \brief The byte data of instruction. */
std::vector<ExecWord> instr_data;
virtual ~VMExecutable() {}
/*! \brief Module type key. */
const char* kind() const final;
/*!
* \brief Look up an exported function by name.
* \param name The function name.
* \return The function if found, otherwise std::nullopt.
*/
ffi::Optional<ffi::Function> GetFunction(const ffi::String& name) override;
private:
/*!
* \brief Save the globals.
* \param strm The input stream.
*/
void SaveGlobalSection(support::Stream* strm) const;
/*!
* \brief Save the memory scopes.
* \param strm The output stream.
*/
void SaveMemoryScopeSection(support::Stream* strm) const;
/*!
* \brief Save the constant pool.
* \param strm The input stream.
*/
void SaveConstantSection(support::Stream* strm) const;
/*!
* \brief Save the instructions.
* \param strm The input stream.
*/
void SaveCodeSection(support::Stream* strm) const;
/*!
* \brief Save the packed functions.
* \param strm The input stream.
*/
void SavePackedFuncNames(support::Stream* strm) const;
/*!
* \brief Load the globals.
* \param strm The input stream.
*/
void LoadGlobalSection(support::Stream* strm);
/*!
* \brief Load the memory scopes.
* \param strm The input stream.
*/
void LoadMemoryScopeSection(support::Stream* strm);
/*!
* \brief Load the constant pool.
* \param strm The input stream.
*/
void LoadConstantSection(support::Stream* strm);
/*!
* \brief Load the instructions.
* \param strm The input stream.
*/
void LoadCodeSection(support::Stream* strm);
/*!
* \brief Save the packed functions.
* \param strm The input stream.
*/
void LoadPackedFuncNames(support::Stream* strm);
};
} // namespace vm
} // namespace runtime
} // namespace tvm
namespace tvm {
namespace support {
template <>
struct Serializer<::tvm::runtime::vm::VMFuncInfo> {
static constexpr bool enabled = true;
static void Write(Stream* strm, const ::tvm::runtime::vm::VMFuncInfo& data) { data.Save(strm); }
static bool Read(Stream* strm, ::tvm::runtime::vm::VMFuncInfo* data) { return data->Load(strm); }
};
} // namespace support
} // namespace tvm
#endif // TVM_RUNTIME_VM_EXECUTABLE_H_
@@ -0,0 +1,97 @@
/*
* 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_RUNTIME_VM_TENSOR_CACHE_SUPPORT_H_
#define TVM_RUNTIME_VM_TENSOR_CACHE_SUPPORT_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/function.h>
#include <tvm/runtime/tensor.h>
#include <string>
#include <unordered_map>
#include <vector>
namespace tvm {
namespace runtime {
namespace vm {
/*!
* \brief Metadata for Tensor cache, which by default, is named as "tensor-cache.json".
*/
struct TensorCacheMetadata {
/*! \brief Each shard of Tensor cache, which by default, is named as "params_shard_x.bin". */
struct FileRecord {
/*! \brief Metadata of each parameter */
struct ParamRecord {
/*!
* \brief Load the parameter from raw data.
* \param device The device to load the parameter onto.
* \param raw_data The raw data stream
* \param staging_buffer The buffer to be used to avoid extra OpenCL copies. Pass in a nullptr
* in other cases
*/
TVM_RUNTIME_DLL Tensor Load(Device device, const std::string* raw_data,
ffi::Optional<Tensor>* staging_buffer = nullptr) const;
/*! \brief Name of the parameter */
std::string name;
/*! \brief Shape of the parameter */
ffi::Shape shape;
/*! \brief Data type of the parameter */
DLDataType dtype;
/*! \brief Format of the parameter */
std::string format;
/*! \brief Number of bytes */
int64_t nbytes;
/*! \brief Offset from the raw stream */
int64_t byte_offset;
};
/*! \brief Load a FileRecord into memory */
TVM_RUNTIME_DLL ffi::Array<Tensor> Load(Device device, //
const std::string& path_prefix, //
std::string* raw_data_buffer, //
ffi::Optional<Tensor>* staging_buffer = nullptr) const;
/*! \brief Relative path to the bin file */
std::string data_path;
/*! \brief Format of the file */
std::string format;
/*! \brief Size of the file */
int64_t nbytes;
/*! \brief The parameters in the file */
std::vector<ParamRecord> records;
};
/*! \brief The files in the Tensor cache */
std::vector<FileRecord> records;
/*! \brief The path to the `tensor-cache.json` file */
std::string path;
/*! \brief Load the metadata from a specific directory */
TVM_RUNTIME_DLL static TensorCacheMetadata Load(const std::string& path);
/*! \brief Load the metadata from a given JSON string */
TVM_RUNTIME_DLL static TensorCacheMetadata LoadFromStr(const std::string& json_str,
const std::string& path);
};
} // namespace vm
} // namespace runtime
} // namespace tvm
#endif // TVM_RUNTIME_VM_TENSOR_CACHE_SUPPORT_H_
+226
View File
@@ -0,0 +1,226 @@
/*
* 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.
*/
/*!
* \file tvm/runtime/vm/vm.h
*/
#ifndef TVM_RUNTIME_VM_VM_H_
#define TVM_RUNTIME_VM_VM_H_
#include <tvm/ffi/extra/module.h>
#include <memory>
#include <string>
#include <unordered_map>
#include <vector>
#include "../memory/memory_manager.h"
#include "./bytecode.h"
#include "./executable.h"
namespace tvm {
namespace runtime {
using memory::Allocator;
using memory::AllocatorType;
using memory::MemoryManager;
using memory::Storage;
using memory::StorageObj;
namespace vm {
/*!
* \brief Possible instrument actions.
*/
enum class VMInstrumentReturnKind : int {
/*! \brief Running as normal. */
kNoOp = 0,
/*! \brief Skip the following run, only valid in before. */
kSkipRun = 1,
};
/*!
* \brief An object representing a vm closure.
*/
class VMClosureObj : public ffi::Object {
public:
/*!
* \brief The function name. The function could be any
* function object that is compatible to the VM runtime.
*/
ffi::String func_name;
/*!
* \brief The implementation of the Closure.
* \note This function takes context pointer(VirtualMachine*)
* as the first argument. The rest of arguments follows
* the same arguments as the normal function call.
*/
ffi::Function impl;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("relax.vm.Closure", VMClosureObj, ffi::Object);
};
/*! \brief reference to closure. */
class VMClosure : public ffi::ObjectRef {
public:
VMClosure(ffi::String func_name, ffi::Function impl);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(VMClosure, ffi::ObjectRef, VMClosureObj);
/*!
* \brief Create another ffi::Function with last arguments already bound to last_args.
*
* This is a helper function to create captured closures.
* \param func The input func, can be a VMClosure or ffi::Function.
* \param last_args The arguments to bound to in the end of the function.
* \note The new function takes in arguments and append the last_args in the end.
*/
static ffi::Function BindLastArgs(ffi::Function func, std::vector<ffi::Any> last_args);
};
/*!
* \brief Represent a VM extension.
* A VM extension allows the user to extend the VM with target specific functionalities.
* The VM holds the reference of the extensions to ensure the extensions have the same lifetime
* as the VM.
*
* This is the base class for all VM extensions and should not be used directly.
*/
class VMExtensionNode : public ffi::Object {
protected:
TVM_FFI_DECLARE_OBJECT_INFO("runtime.VMExtension", VMExtensionNode, ffi::Object);
};
/*! \brief Managed reference to VM extension. */
class VMExtension : public ffi::ObjectRef {
public:
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(VMExtension, ffi::ObjectRef, VMExtensionNode);
};
/*!
* \brief The virtual machine.
*
* The virtual machine contains all the current execution state,
* as well as the executable.
*
* The goal is to have a single self-contained object,
* enabling one to easily pass around VMs, execute them on
* multiple threads, or serialize them to disk or over the
* wire.
*/
class VirtualMachine : public ffi::ModuleObj {
public:
/*!
* \brief Initialize the virtual machine for a set of devices.
* \param devices The set of TVM devices.
* \param alloc_types The allocator types for each device.
*/
virtual void Init(const std::vector<Device>& devices,
const std::vector<AllocatorType>& alloc_types) = 0;
/*!
* \brief Load the executable for the virtual machine.
* \param exec The executable.
*/
virtual void LoadExecutable(ffi::ObjectPtr<VMExecutable> exec) = 0;
/*!
* \brief Get global function in the VM.
* \param func_name The name of the function.
* \return The closure
*/
virtual VMClosure GetClosure(const ffi::String& func_name) = 0;
/*!
* \brief Invoke closure or packed function using ffi::Function convention.
* \param closure_or_packedfunc A VM closure or a packed_func.
* \param args The input arguments.
* \param rv The return value.
*/
virtual void InvokeClosurePacked(const ffi::ObjectRef& closure_or_packedfunc,
ffi::PackedArgs args, ffi::Any* rv) = 0;
/*!
* \brief Set an instrumentation function.
*
* If instrument is present, the function will be called
* before/after each Call instruction.
*
* bool instrument(func, func_symbol, before_run, args...)
*
* - func: Union[VMClosure, ffi::Function], the function object.
* - func_symbol: string, the symbol of the function.
* - before_run: bool, whether it is before or after call.
* - ret_value: Only valid in after run, otherwise it is null.
* - args: the arguments being passed to call.
*
* instrument can return an int which corresponds to the action value.
* \sa VMInstrumentAction
*
* \param instrument The instrument function.
*/
virtual void SetInstrument(ffi::Function instrument) = 0;
/*!
* \brief Get or create a VM extension. Once created, the extension will be stored in the VM
* and held until the VM is destructed.
*
* \tparam T The type of the extension
* \return The extension instance
*/
template <typename T, typename = std::enable_if_t<std::is_base_of<VMExtension, T>::value>>
T GetOrCreateExtension() {
using ContainerType = typename T::ContainerType;
uint32_t key = ContainerType::RuntimeTypeIndex();
if (auto it = extensions.find(key); it != extensions.end()) {
ffi::Any value = (*it).second;
return value.cast<T>();
}
auto [it, _] = extensions.emplace(key, T::Create());
ffi::Any value = (*it).second;
return value.cast<T>();
}
/*!
* \brief Create a specific instance of VM.
* \return Created VM
*/
static ffi::ObjectPtr<VirtualMachine> Create();
/*!
* \brief Helper function for vm closure functions to get the context ptr
* \param arg The argument value.
*/
static VirtualMachine* GetContextPtr(ffi::AnyView arg) {
return static_cast<VirtualMachine*>(arg.cast<void*>());
}
~VirtualMachine() {}
//--------------------------------------------------------------------------
// The following section contains states that other builtin can depend on
//--------------------------------------------------------------------------
/*! \brief The memory allocators. */
std::vector<Allocator*> allocators;
/*! \brief Runtime physical device list. */
std::vector<Device> devices;
/*! \brief The VM extensions. Mapping from the type index of the extension to the extension
* instance. */
std::unordered_map<uint32_t, Any> extensions;
};
} // namespace vm
} // namespace runtime
} // namespace tvm
#endif // TVM_RUNTIME_VM_VM_H_
+211
View File
@@ -0,0 +1,211 @@
/*
* 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.
*/
/*!
* \file tvm/s_tir/analysis.h
* \brief Analysis utilities for Schedulable TensorIR (S-TIR).
*/
#ifndef TVM_S_TIR_ANALYSIS_H_
#define TVM_S_TIR_ANALYSIS_H_
#include <tvm/ir/module.h>
#include <tvm/ir/transform.h>
#include <tvm/target/target.h>
#include <tvm/tirx/function.h>
#include <tvm/tirx/stmt.h>
#include <optional>
namespace tvm {
namespace tirx {
/*!
* \brief Auto detect the block access region according to its body stmt
* It will detect the access region as an array in order of appearance in AST
* \param block The block to be detected
* \param buffer_var_map The outside buffers which may be accessed the block.
* It is a map from buffer var to the buffer.
* \return Array of access regions.
* There are three arrays of BufferRegion:
* - first: read regions
* - second: write regions
* - third: opaque regions
*/
TVM_DLL ffi::Array<ffi::Array<BufferRegion>> GetSBlockAccessRegion(
const SBlock& block, const ffi::Map<Var, Buffer>& buffer_var_map);
/*!
* \brief Auto detect the block read/write region according to its body stmt. An opaque access will
* be counted as both a read and a write access
* \param block The block to be detected
* \param buffer_var_map The outside buffers which may be accessed the block.
* It is a map from buffer var to the buffer
* \return An array only consisting of the read regions and write regions of the input block
*/
TVM_DLL ffi::Array<ffi::Array<BufferRegion>> GetSBlockReadWriteRegion(
const SBlock& block, const ffi::Map<Var, Buffer>& buffer_var_map);
/*!
* \brief Detect the lowest common ancestor(LCA) of buffer access, including both high-level
* access(BufferLoad, BufferStore) and low-level access(Load, Store and opaque access).
* The LCA may be a For loop or a Block.
* \param func The PrimFunc to be detected.
* \return The Map from buffer to the LCA of all access to it. The lca is function root if the
* return stmt is std::nullopt.
*/
TVM_DLL ffi::Map<Buffer, ffi::Optional<Stmt>> DetectBufferAccessLCA(const PrimFunc& func);
/*!
* \brief Find the "anchor block" of the given module.
* We define the anchor block to be the block with (1) an init statement and (2) having
* the biggest flops count. The latter condition is only used when there are multiple blocks
* with an init statement.
* For example, if the input module is conv2d + fused spatial blocks, conv2d is the anchor block.
* The input module may not contain more than one such block. For example, a module having
* two conv2d is not allowed as an input.
* However, a module created from winograd convolution has multiple blocks with an init statement
* (input transform, batched GEMM, and output transform). We use the second condition, the flops
* count, to determine that the batched GEMM block is the anchor block.
* \param mod The input TIR module.
* \return The anchor block if found, nullptr otherwise.
*/
const tirx::SBlockNode* FindAnchorBlock(const IRModule& mod);
} // namespace tirx
namespace arith {
class AnalyzerObj;
class Analyzer;
} // namespace arith
namespace s_tir {
using namespace tvm::tirx;
/*!
* \brief Estimate the FLOPs of a TIR fragment.
* \param stmt The TIR fragment to be estimated.
* \return The estimated FLOPs.
*/
TVM_DLL double EstimateTIRFlops(const Stmt& stmt);
/*!
* \brief Estimate the FLOPs of TIRs in an IRModule.
* \param mod The IRModule to be estimated.
* \return The estimated FLOPs.
*/
TVM_DLL double EstimateTIRFlops(const IRModule& mod);
/*!
* \brief Analyze the side effect of a function
* \param func The function to be checked.
* \param assert_on_error If true, an error will be thrown for an impure function.
* \return The purity of the function.
*/
TVM_DLL bool IsPureFunction(const PrimFunc& func, bool assert_on_error = false);
/*!
* \brief Verify the correctness of a GPU code
* \param func The function to be checked.
* \param constraints The dict to specify constraints to check.
* \return valid Whether it is a valid GPU code.
*/
TVM_DLL bool VerifyGPUCode(const PrimFunc& func, ffi::Map<ffi::String, PrimExpr> constraints);
/*! \brief Helper struct for return value of IdentifyMemCpy */
struct MemCpyDetails {
BufferRegion source;
BufferRegion dest;
};
/*! \brief Identify whether a For loop is semantically equivalent to MemCpy
* \param loop The loop to be checked
* \param analyzer The analyzer with which to check any algebraic expressions
* \returns The source and destination regions being copied, if the loop is equivalent to memcpy.
*/
TVM_DLL std::optional<MemCpyDetails> IdentifyMemCpy(const For& loop,
const arith::Analyzer& analyzer);
/*!
* \brief Calculate the allocated memory per scope in bytes needed inside the TIR PrimFunc
* \param func The TIR PrimFunc for which the allocated memory size to be calculated
* \return Allocated memory size per scope in bytes.
*/
TVM_DLL ffi::Map<ffi::String, ffi::Map<ffi::String, int64_t>> CalculateAllocatedBytes(
const PrimFunc& func);
/*!
* \brief Calculate the allocated memory per scope in bytes for each function inside the module
* \param mod The IRModule for which the allocated memory size has to be calculated
* \return Allocated memory size per scope in bytes for each function.
*/
TVM_DLL ffi::Map<ffi::String, ffi::Map<ffi::String, int64_t>> CalculateAllocatedBytes(
const IRModule& mod);
/**
* \brief Get the list of lowering passes to calculate the compacted VTCM allocation size.
* \return The list of passes.
*/
TVM_DLL ffi::Array<tvm::transform::Pass> GetVTCMCompactionPasses();
/*!
* \brief Verifies that the VTCM usage for all prim_funcs in the given IRModule.
* \param mod The module to be checked.
* \param limit The limit to check.
* \return true if the VTCM usage is within the provided limit.
*/
TVM_DLL bool VerifyVTCMLimit(const IRModule& mod, int64_t limit);
/*!
* \brief Verifies that the VTCM usage of the given prim_func is within the provided limit.
* \param func The function to be checked.
* \param limit The limit to check.
* \return true if the VTCM usage is within the provided limit.
*/
TVM_DLL bool VerifyVTCMLimit(const PrimFunc& func, int64_t limit);
namespace transform {
using tvm::transform::Pass;
using tvm::transform::PassContext;
/*!
* \brief Pass to verify GPU code constraints.
* \param constraints The dict to specify constraints.
* \return The pass.
*/
TVM_DLL Pass VerifyGPUCode(ffi::Map<ffi::String, PrimExpr> constraints);
/*!
* \brief Pass to check if VTCM usage is within limit.
* \param default_target The default target for functions without target attribute.
* \return The pass.
*/
TVM_DLL Pass VerifyVTCMLimit(ffi::Optional<Target> default_target = std::nullopt);
/*!
* \brief Statically check TIR code for out of bounds array access.
* \return The pass.
*/
TVM_DLL Pass OOBChecker();
} // namespace transform
} // namespace s_tir
} // namespace tvm
#endif // TVM_S_TIR_ANALYSIS_H_
@@ -0,0 +1,64 @@
/*
* 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.
*/
/*!
* \file tvm/s_tir/backend/adreno/transform.h
* \brief S-TIR specific Adreno GPU transformation passes.
*/
#ifndef TVM_S_TIR_BACKEND_ADRENO_TRANSFORM_H_
#define TVM_S_TIR_BACKEND_ADRENO_TRANSFORM_H_
#include <tvm/ir/transform.h>
#include <tvm/s_tir/transform.h>
#include <tvm/target/target.h>
#include <tvm/tirx/expr.h>
#include <tvm/tirx/function.h>
#include <string>
#include <vector>
namespace tvm {
namespace s_tir {
namespace backend {
namespace adreno {
namespace transform {
using tirx::transform::CreatePrimFuncPass;
using tvm::transform::Pass;
using tvm::transform::PassContext;
/*!
* \brief Texture flattening pass.
* \return The pass.
*/
TVM_DLL Pass TextureFlatten();
/*!
* \brief Inject Texture Allocation intrinsic.
* \return The pass.
*/
TVM_DLL Pass InjectTextureAlloc();
} // namespace transform
} // namespace adreno
} // namespace backend
} // namespace s_tir
} // namespace tvm
#endif // TVM_S_TIR_BACKEND_ADRENO_TRANSFORM_H_
+411
View File
@@ -0,0 +1,411 @@
/*
* 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.
*/
/*!
* \file tvm/s_tir/data_layout.h
* \brief SLayout expression to describe the data organization of a tensor.
* And SBijectiveLayout to mapping two data layouts between each other.
*/
#ifndef TVM_S_TIR_DATA_LAYOUT_H_
#define TVM_S_TIR_DATA_LAYOUT_H_
#include <tvm/ffi/reflection/registry.h>
#include <tvm/tirx/expr.h>
#include <tvm/tirx/op.h>
#include <algorithm>
#include <sstream>
#include <string>
#include <utility>
#include <vector>
#include "tvm/tirx/var.h"
namespace tvm {
namespace tirx {
class SLayout;
class SLayoutAxis {
public:
static const SLayoutAxis& Get(const char name);
// Get the singleton SLayoutAxis using itvar->var->name_hint
static const SLayoutAxis& Get(const tirx::IterVar& itvar);
// Get the singleton SLayoutAxis using name[0] (size of name must be 1).
static const SLayoutAxis& Get(const std::string& name);
inline bool IsPrimal() const { return name_ >= 'A' && name_ <= 'Z'; }
inline std::string name() const { return std::string(1, name_); }
// if current axis is primal, switch the axis to its subordinate one,
// else switch to the primal.
inline const SLayoutAxis& ToDual() const {
if (name_ >= 'A' && name_ <= 'Z') {
return SLayoutAxis::Get(name_ - 'A' + 'a');
} else {
return SLayoutAxis::Get(name_ - 'a' + 'A');
}
}
// return the primal axis. If it is already primal, return itself.
const SLayoutAxis& ToPrimal() const { return IsPrimal() ? *this : ToDual(); }
// return the subordinate axis. If it is already subordinate, return itself.
const SLayoutAxis& ToSubordinate() const { return IsPrimal() ? ToDual() : *this; }
inline bool operator==(const SLayoutAxis& rhs) const { return name_ == rhs.name_; }
friend std::ostream& operator<<(std::ostream& os, const SLayoutAxis& l) {
os << l.name();
return os;
}
private:
static const SLayoutAxis UPPER_CASE[];
static const SLayoutAxis LOWER_CASE[];
SLayoutAxis(const SLayoutAxis&);
SLayoutAxis& operator=(const SLayoutAxis&);
explicit SLayoutAxis(const char name) : name_(name) {}
const char name_;
};
/*!
* \brief SLayout is to describe how data is organized within an N-dimention tensor.
* It is composed of upper cases, lower cases and numbers,
* where upper case indicates a primal axis and
* the corresponding lower case with factor size indicates the subordinate axis.
* For example, NCHW16c can describe a 5-D tensor of
* [batch_size, channel, height, width, channel_block].
* Here subordinate axis channel_block=16 is the factor size of the primal axis C (channel).
* SLayout for scalar is defined, while both its name and axes have size 0.
*/
class SLayoutNode : public ffi::Object {
public:
/*! \brief string representation of layout, "" for scalar. */
ffi::String name;
/*! \brief specify each axis of the layout,
* in which the variable name is the name of the axis.
* The IterVar's extent indicates the size of the axis,
* it is a variable for a primal axis, but a constant for a subordinate axis.
* Empty for scalar's layout.
*/
ffi::Array<tirx::IterVar> axes;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SLayoutNode>()
.def_ro("name", &SLayoutNode::name)
.def_ro("axes", &SLayoutNode::axes);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.SLayout", SLayoutNode, ffi::Object);
};
/*!
* \brief Managed reference to SLayoutNode
* \sa SLayoutNode
*/
class SLayout : public ffi::ObjectRef {
public:
explicit SLayout(const ffi::Array<tirx::IterVar>& axes);
/*! \brief construct from a string */
SLayout(const tvm::ffi::String& name) : SLayout(name.operator std::string()) {} // NOLINT(*)
/*! \brief construct from a string */
SLayout(const char* name) : SLayout(std::string(name)) {} // NOLINT(*)
/*!
* \brief construct from a string.
* \param name input in layout convention:
* upper case indicates a dimension and
* the corresponding lower case with factor size
* indicates the split dimension.
* return undefined layout if "__undef__" is passed.
* \param index_ty The type of generated axes vars in the returned layout.
* It is required to be integer type.
*/
TVM_DLL SLayout(const std::string& name, PrimType index_ty = PrimType::Int(32)); // NOLINT(*)
/*!
* \brief access the internal node container
* \return the pointer to the internal node container
*/
SLayoutNode* operator->() { return static_cast<SLayoutNode*>(get_mutable()); }
/*!
* \brief Return an undefined layout.
* \return a (global) undefined layout.
*/
static const SLayout& Undef() {
static SLayout undef;
return undef;
}
/*!
* \brief Packs the Given Array of IterVars into a Single IterVar. Each IterVar in the Array
* should represent either a single primal axis or one or more subordinate axis
* \param iters Array of iter vars to be packed
* \return A packed iter var
*/
static IterVar PackIterVar(ffi::Array<IterVar> iters);
/*!
* \brief Unpacks a Packed IterVar into its constituents
* \param packed_iter A Packed IterVar containing a single primal axis or one or more subordinate
* axis
* \return Constituent IterVars
*/
static ffi::Array<IterVar> UnpackIterVar(IterVar packed_iter);
/*!
* \brief Returns a sub-layout which is the portion of the object
* that starts at dimension \p pos and spans \p len dimensions
* (or until the end of the layout, whichever comes first).
* \param pos The start position.
* \param len The length of the sub-layout. if 0, return layout of scalar
* \return A newly constructed SLayout object.
*/
SLayout SubLayout(size_t pos, size_t len) const;
/*!
* \brief Split \p axis by \p size and put the sub-axis to position \p target_pos.
* \param axis The source axis to be split. It must be a primal-axis;
* \param target_pos The target position of the newly split subordinate-axis.
* \param factor size of the sub-dimension.
* \return A newly constructed SLayout object.
*/
SLayout Split(const SLayoutAxis& axis, size_t target_pos, int32_t factor) const;
/*! \return number of dimensions */
inline size_t ndim() const {
if (!defined()) return 0;
return operator->()->axes.size();
}
/*! \return number of super dimensions */
inline size_t ndim_primal() const {
if (!defined()) return 0;
size_t ct = 0;
for (auto px : operator->()->axes) {
auto iter_vars = UnpackIterVar(px);
for (auto x : iter_vars) {
if (SLayoutAxis::Get(x).IsPrimal()) {
ct++;
}
}
}
return ct;
}
/*!
* \brief Returns a new layout where the dims have been expanded to match the primal dimensions.
* \param dst_layout The dst layout to which current layout has to be expanded.
* \return The expanded SLayout.
*/
inline SLayout ExpandPrimal(const SLayout& dst_layout) {
SLayout new_src_layout;
// 1) Find the axis which are missing in the current layout. Make them the prefix.
std::string new_src_layout_str = "";
for (auto packed_axis : dst_layout->axes) {
auto iter_vars = UnpackIterVar(packed_axis);
for (auto dst_axis : iter_vars) {
if (SLayoutAxis::Get(dst_axis).IsPrimal()) {
if (!this->Contains(SLayoutAxis::Get(dst_axis))) {
new_src_layout_str += dst_axis->var->name_hint;
}
}
}
}
// 2) Now, add the primal axis of the current layout.
new_src_layout_str += this->name();
new_src_layout = SLayout(new_src_layout_str);
return new_src_layout;
}
/*!
* \brief return the index of the input axis.
* If it is not found in the layout or the layout is undefined,
* return -1.
* \param axis The input axis either a layout axis, or a packed axis
* \return the index or -1 if not found.
*/
inline int32_t IndexOf(const std::string& axis) const {
if (!this->defined()) return -1;
const auto axes = operator->()->axes;
for (size_t i = 0; i < axes.size(); ++i) {
if (axes[i]->var->name_hint == axis) return static_cast<int32_t>(i);
}
return -1;
}
/*!
* \brief return the index of the input axis.
* If it is not found in the layout or the layout is undefined,
* return -1.
* \param axis the input layout axis.
* \return the index or -1 if not found.
*/
inline int32_t IndexOf(const SLayoutAxis& axis) const { return IndexOf(axis.name()); }
/*!
* \brief return the index of the input axis.
* If it is not found in the layout or the layout is undefined,
* return -1.
* \param iter the input iter var.
* \return the index or -1 if not found.
*/
inline int32_t IndexOf(const tirx::IterVar& iter) const { return IndexOf(iter->var->name_hint); }
/*!
* \brief Get the factor size of the subordinate axis.
* \param axis the input primal-axis or subordinate-axis.
* \return the size of the subordinate-axis of \p axis (if \p axis is a primal-axis),
* or the size of \p axis itself (if \p axis is a subordinate-axis).
* Return -1 if \p axis is not in the layout the layout is undefined.
*/
int32_t FactorOf(const SLayoutAxis& axis) const;
/*!
* \brief Whether the layout contains an axis.
* \param axis axis to be checked.
* \return Whether the layout contains the axis.
*/
bool Contains(const SLayoutAxis& axis) const {
if (!defined()) return false;
for (const tirx::IterVar packed_var : operator->()->axes) {
auto iter_vars = UnpackIterVar(packed_var);
for (auto var : iter_vars) {
if (var->var->name_hint == axis.name()) {
return true;
}
}
}
return false;
}
const SLayoutAxis& operator[](int32_t i) const {
TVM_FFI_ICHECK(defined()) << "Try to access axis from an undefined layout.";
int32_t index = i < 0 ? static_cast<int32_t>(ndim() + i) : i;
TVM_FFI_ICHECK(index >= 0 && static_cast<size_t>(index) < ndim()) << "Invalid index " << i;
const tirx::IterVar axis = operator->()->axes[index];
return SLayoutAxis::Get(axis);
}
IterVar PackedAxisAt(int32_t i) const {
TVM_FFI_ICHECK(defined()) << "Try to access axis from an undefined layout.";
int32_t index = i < 0 ? static_cast<int32_t>(ndim() + i) : i;
TVM_FFI_ICHECK(index >= 0 && static_cast<size_t>(index) < ndim()) << "Invalid index " << i;
const tirx::IterVar axis = operator->()->axes[index];
return axis;
}
/*! \return the string description of the layout */
inline std::string name() const {
if (!defined()) return "__undef__";
return operator->()->name;
}
/*!
* \brief Whether the two layouts are equal.
* \param rhs Another layout.
* \return whether the two layouts are equal.
*/
inline bool Equals(const SLayout& rhs) const { return name() == rhs.name(); }
/*!
* \brief allow output string of layout to ostream
* \param os the output stream
* \param l the layout
* \return the ostream
*/
friend std::ostream& operator<<(std::ostream& os, const SLayout& l) {
os << l.name();
return os;
}
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(SLayout, ffi::ObjectRef, SLayoutNode);
};
// Internal node container SBijectiveLayout
class SBijectiveLayoutNode : public ffi::Object {
public:
/*! \brief Describes how source axes can be mapped to the destination axes,
* e.g., [i0 / 16, i1, i0 % 16] can describe NC -> NC16n
*/
ffi::Array<PrimExpr> index_forward_rule;
/*! \brief Describes how destination axes can be mapped to the source axes */
ffi::Array<PrimExpr> index_backward_rule;
/*! \brief Describes how source shapes can be mapped to the destination shapes */
ffi::Array<PrimExpr> shape_forward_rule;
/*! \brief Describes how destination shapes can be mapped to the source shapes */
ffi::Array<PrimExpr> shape_backward_rule;
/*! \brief The source layout */
SLayout src_layout;
/*! \brief The destination layout */
SLayout dst_layout;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<SBijectiveLayoutNode>()
.def_ro("src_layout", &SBijectiveLayoutNode::src_layout)
.def_ro("dst_layout", &SBijectiveLayoutNode::dst_layout)
.def_ro("index_forward_rule", &SBijectiveLayoutNode::index_forward_rule)
.def_ro("index_backward_rule", &SBijectiveLayoutNode::index_backward_rule)
.def_ro("shape_forward_rule", &SBijectiveLayoutNode::shape_forward_rule)
.def_ro("shape_backward_rule", &SBijectiveLayoutNode::shape_backward_rule);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.SBijectiveLayout", SBijectiveLayoutNode, ffi::Object);
};
/*!
* \brief Bijective function mapping for data layout transformation.
* Given two SLayout, SBijectiveLayout build and store the mapping rules,
* provides API to transform N-dimention tensor from the source indices (i0, i1, .., im)
* to the destination indices (j0, j1, .., jm).
*/
class SBijectiveLayout : public ffi::ObjectRef {
public:
/*!
* \brief The constructor
* \param src_layout The source layout
* \param dst_layout The destination layout
*/
TVM_DLL SBijectiveLayout(SLayout src_layout, SLayout dst_layout);
// Given the source shape, infer the destination shape.
TVM_DLL ffi::Array<PrimExpr> ForwardShape(const ffi::Array<PrimExpr>& shape) const;
// Given the destination shape, recover the source shape.
TVM_DLL ffi::Array<PrimExpr> BackwardShape(const ffi::Array<PrimExpr>& dst_shape) const;
// Given the destination indices, infer the destination indices.
TVM_DLL ffi::Array<PrimExpr> ForwardIndex(const ffi::Array<PrimExpr>& index) const;
// Given the destination indices, recover the source indices.
TVM_DLL ffi::Array<PrimExpr> BackwardIndex(const ffi::Array<PrimExpr>& dst_index) const;
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(SBijectiveLayout, ffi::ObjectRef,
SBijectiveLayoutNode);
};
} // namespace tirx
} // namespace tvm
#endif // TVM_S_TIR_DATA_LAYOUT_H_
+121
View File
@@ -0,0 +1,121 @@
/*
* 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_S_TIR_META_SCHEDULE_ARG_INFO_H_
#define TVM_S_TIR_META_SCHEDULE_ARG_INFO_H_
#include <tvm/ffi/container/shape.h>
#include <tvm/ffi/dtype.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ir/module.h>
#include <tvm/tirx/function.h>
namespace tvm {
namespace s_tir {
namespace meta_schedule {
/*! \brief The argument information. */
class ArgInfoNode : public ffi::Object {
public:
TVM_FFI_DECLARE_OBJECT_INFO("s_tir.meta_schedule.ArgInfo", ArgInfoNode, ffi::Object);
public:
/*! \brief Default destructor. */
virtual ~ArgInfoNode() = default;
/*! \brief Converts the ArgInfo to its corresponding JSON representation. */
virtual ffi::ObjectRef AsJSON() const = 0;
};
/*!
* \brief Managed reference to ArgInfoNode
* \sa ArgInfoNode
*/
class ArgInfo : public ffi::ObjectRef {
public:
/*!
* \brief Parse the argument information from a JSON object.
* \param json_obj The json object to parse.
* \return The argument information parsed.
*/
TVM_DLL static ArgInfo FromJSON(const ffi::ObjectRef& json_obj);
/*!
* \brief Extract a list of the argument information from PrimFunc.
* \param func The PrimFunc to get argument information from.
* \return An array of the argument information derived.
*/
TVM_DLL static ffi::Array<ArgInfo, void> FromPrimFunc(const tirx::PrimFunc& func);
/*!
* \brief Extract a list of the argument information from the entry func of an IRModule
* \param mod The IRModule to extract argument information from.
* \param remove_preproc Whether to remove the preprocessing blocks.
* \return An array of the argument information derived.
*/
TVM_DLL static ffi::Array<ArgInfo, void> FromEntryFunc(const IRModule& mod, bool remove_preproc);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(ArgInfo, ffi::ObjectRef, ArgInfoNode);
protected:
ArgInfo() = default;
};
/*! \brief The tensor argument information. */
class TensorInfoNode : public ArgInfoNode {
public:
/*! \brief The data type of the tensor. */
DLDataType dtype;
/*! \brief The shape of the tensor. */
ffi::Shape shape;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TensorInfoNode>()
.def_ro("dtype", &TensorInfoNode::dtype)
.def_ro("shape", &TensorInfoNode::shape);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.TensorInfo", TensorInfoNode, ArgInfoNode);
public:
ffi::ObjectRef AsJSON() const;
};
/*!
* \brief Managed reference to TensorInfoNode
* \sa TensorInfoNode
*/
class TensorInfo : public ArgInfo {
public:
/*!
* \brief Constructor of TensorInfo.
* \param dtype The data type of the tensor argument.
* \param shape The shape tuple of the tensor argument.
*/
TVM_DLL explicit TensorInfo(DLDataType dtype, ffi::Shape shape);
/*!
* \brief Parse the argument information from a JSON object.
* \param json_obj The json object to parse.
* \return The argument information parsed.
*/
TVM_DLL static TensorInfo FromJSON(const ffi::ObjectRef& json_obj);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(TensorInfo, ArgInfo, TensorInfoNode);
};
} // namespace meta_schedule
} // namespace s_tir
} // namespace tvm
#endif // TVM_S_TIR_META_SCHEDULE_ARG_INFO_H_
+175
View File
@@ -0,0 +1,175 @@
/*
* 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_S_TIR_META_SCHEDULE_BUILDER_H_
#define TVM_S_TIR_META_SCHEDULE_BUILDER_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/container/map.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/optional.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include <tvm/ir/module.h>
#include <tvm/runtime/tensor.h>
#include <tvm/target/target.h>
namespace tvm {
namespace s_tir {
namespace meta_schedule {
/*! \brief The builder's input, containing an IRModule and the target. */
class BuilderInputNode : public ffi::Object {
public:
/*! \brief The IRModule to be built. */
IRModule mod;
/*! \brief The target to be built for. */
Target target;
/*! \brief Parameters for Relax build module. */
ffi::Optional<ffi::Map<ffi::String, runtime::Tensor>> params;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<BuilderInputNode>()
.def_ro("mod", &BuilderInputNode::mod)
.def_ro("target", &BuilderInputNode::target)
.def_ro("params", &BuilderInputNode::params);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.BuilderInput", BuilderInputNode,
ffi::Object);
};
/*!
* \brief Managed reference to BuilderInputNode
* \sa BuilderInputNode
*/
class BuilderInput : public ffi::ObjectRef {
public:
/*!
* \brief Constructor of BuilderInput.
* \param mod The IRModule to be built.
* \param target The target to be built for.
* \param params Parameters for Relax build module.
*/
TVM_DLL explicit BuilderInput(
IRModule mod, Target target,
ffi::Optional<ffi::Map<ffi::String, runtime::Tensor>> params = std::nullopt);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(BuilderInput, ffi::ObjectRef, BuilderInputNode);
};
/*! \brief The builder's output, containing the artifact path or error message if any. */
class BuilderResultNode : public ffi::Object {
public:
/*! \brief The path to the built artifact. */
ffi::Optional<ffi::String> artifact_path;
/*! \brief The error message if any. */
ffi::Optional<ffi::String> error_msg;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<BuilderResultNode>()
.def_ro("artifact_path", &BuilderResultNode::artifact_path)
.def_ro("error_msg", &BuilderResultNode::error_msg);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.BuilderResult", BuilderResultNode,
ffi::Object);
};
/*!
* \brief Managed reference to BuilderResultNode
* \sa BuilderResultNode
*/
class BuilderResult : public ffi::ObjectRef {
public:
/*!
* \brief Constructor of BuilderResult.
* \param artifact_path The path to the built artifact.
* \param error_msg The error message if any.
*/
TVM_DLL explicit BuilderResult(ffi::Optional<ffi::String> artifact_path,
ffi::Optional<ffi::String> error_msg);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(BuilderResult, ffi::ObjectRef, BuilderResultNode);
};
/*! \brief The abstract builder interface. */
class BuilderNode : public ffi::Object {
public:
/*! \brief Default destructor */
virtual ~BuilderNode() = default;
/*!
* \brief Generate the build results from build inputs.
* \param build_inputs The inputs to be built.
* \return The build results.
*/
virtual ffi::Array<BuilderResult> Build(const ffi::Array<BuilderInput>& build_inputs) = 0;
/*!
* \brief The function type of `Build` method.
* \param build_inputs The inputs to be built.
* \return The build results.
*/
using FBuild = ffi::TypedFunction<ffi::Array<BuilderResult>(const ffi::Array<BuilderInput>&)>;
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO("s_tir.meta_schedule.Builder", BuilderNode, ffi::Object);
};
/*!
* \brief Managed reference to BuilderNode
* \sa BuilderNode
*/
class Builder : public ffi::ObjectRef {
public:
/*!
* \brief Constructor from ffi::ObjectPtr<BuilderNode>.
* \param data The object pointer.
*/
explicit Builder(ffi::ObjectPtr<BuilderNode> data) : ffi::ObjectRef(data) {
TVM_FFI_ICHECK(data != nullptr);
}
/*!
* \brief Create a builder with customized build method on the python-side.
* \param f_build The packed function to the `Build` function..
* \return The Builder created.
*/
static Builder PyBuilder(BuilderNode::FBuild f_build);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Builder, ffi::ObjectRef, BuilderNode);
};
/*! \brief An abstract builder with customized build method on the python-side. */
class PyBuilderNode : public BuilderNode {
public:
/*! \brief The packed function to the `Build` function. */
FBuild f_build;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PyBuilderNode>().def_ro("f_build", &PyBuilderNode::f_build);
}
ffi::Array<BuilderResult> Build(const ffi::Array<BuilderInput>& build_inputs) final {
TVM_FFI_ICHECK(f_build != nullptr) << "PyBuilder's Build method not implemented!";
return f_build(build_inputs);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.PyBuilder", PyBuilderNode, BuilderNode);
};
} // namespace meta_schedule
} // namespace s_tir
} // namespace tvm
#endif // TVM_S_TIR_META_SCHEDULE_BUILDER_H_
@@ -0,0 +1,155 @@
/*
* 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_S_TIR_META_SCHEDULE_COST_MODEL_H_
#define TVM_S_TIR_META_SCHEDULE_COST_MODEL_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include <tvm/runtime/base.h>
#include <tvm/s_tir/meta_schedule/arg_info.h>
#include <tvm/s_tir/meta_schedule/measure_candidate.h>
#include <tvm/s_tir/meta_schedule/runner.h>
#include <tvm/s_tir/schedule/schedule.h>
#include <vector>
namespace tvm {
namespace s_tir {
namespace meta_schedule {
class TuneContext;
/*! \brief Cost model. */
class CostModelNode : public ffi::Object {
public:
/*! \brief Virtual destructor. */
virtual ~CostModelNode() = default;
/*!
* \brief Load the cost model from given file location.
* \param path The file path.
*/
virtual void Load(const ffi::String& path) = 0;
/*!
* \brief Save the cost model to given file location.
* \param path The file path.
*/
virtual void Save(const ffi::String& path) = 0;
/*!
* \brief Update the cost model given running results.
* \param context The tuning context.
* \param candidates The measure candidates.
* \param results The running results of the measure candidates.
*/
virtual void Update(const TuneContext& context, const ffi::Array<MeasureCandidate>& candidates,
const ffi::Array<RunnerResult>& results) = 0;
/*!
* \brief Predict the normalized score (the larger the better) of given measure candidates.
* \param context The tuning context.
* \param candidates The measure candidates.
* \return The predicted normalized score.
*/
virtual std::vector<double> Predict(const TuneContext& context,
const ffi::Array<MeasureCandidate>& candidates) = 0;
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO("s_tir.meta_schedule.CostModel", CostModelNode, ffi::Object);
};
/*! \brief The cost model with customized methods on the python-side. */
class PyCostModelNode : public CostModelNode {
public:
/*!
* \brief Load the cost model from given file location.
* \param path The file path.
*/
using FLoad = ffi::TypedFunction<void(ffi::String)>;
/*!
* \brief Save the cost model to given file location.
* \param path The file path.
*/
using FSave = ffi::TypedFunction<void(ffi::String)>;
/*!
* \brief Update the cost model given running results.
* \param context The tuning context.
* \param candidates The measure candidates.
* \param results The running results of the measure candidates.
* \return Whether cost model was updated successfully.
*/
using FUpdate = ffi::TypedFunction<void(const TuneContext&, const ffi::Array<MeasureCandidate>&,
const ffi::Array<RunnerResult>&)>;
/*!
* \brief Predict the running results of given measure candidates.
* \param context The tuning context.
* \param candidates The measure candidates.
* \param p_addr The address to save the estimated running results.
*/
using FPredict = ffi::TypedFunction<void(const TuneContext&, const ffi::Array<MeasureCandidate>&,
void* p_addr)>;
/*! \brief The packed function to the `Load` function. */
FLoad f_load;
/*! \brief The packed function to the `Save` function. */
FSave f_save;
/*! \brief The packed function to the `Update` function. */
FUpdate f_update;
/*! \brief The packed function to the `Predict` function. */
FPredict f_predict;
void Load(const ffi::String& path);
void Save(const ffi::String& path);
void Update(const TuneContext& context, const ffi::Array<MeasureCandidate>& candidates,
const ffi::Array<RunnerResult>& results);
std::vector<double> Predict(const TuneContext& context,
const ffi::Array<MeasureCandidate>& candidates);
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.PyCostModel", PyCostModelNode,
CostModelNode);
};
/*!
* \brief Managed reference to CostModelNode
* \sa CostModelNode
*/
class CostModel : public ffi::ObjectRef {
public:
/*!
* \brief Create a cost model with customized methods on the python-side.
* \param f_load The packed function of `Load`.
* \param f_save The packed function of `Save`.
* \param f_update The packed function of `Update`.
* \param f_predict The packed function of `Predict`.
* \return The cost model created.
*/
TVM_DLL static CostModel PyCostModel(PyCostModelNode::FLoad f_load, //
PyCostModelNode::FSave f_save, //
PyCostModelNode::FUpdate f_update, //
PyCostModelNode::FPredict f_predict);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(CostModel, ffi::ObjectRef, CostModelNode);
};
} // namespace meta_schedule
} // namespace s_tir
} // namespace tvm
#endif // TVM_S_TIR_META_SCHEDULE_COST_MODEL_H_
+552
View File
@@ -0,0 +1,552 @@
/*
* 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_S_TIR_META_SCHEDULE_DATABASE_H_
#define TVM_S_TIR_META_SCHEDULE_DATABASE_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include <tvm/ir/expr.h>
#include <tvm/ir/module.h>
#include <tvm/s_tir/meta_schedule/arg_info.h>
#include <tvm/s_tir/schedule/schedule.h>
#include <tvm/s_tir/schedule/trace.h>
#include <tvm/target/target.h>
#include <filesystem>
#include <memory>
namespace tvm {
namespace s_tir {
namespace meta_schedule {
class ModuleEquality;
/*! \brief A workload, i.e. an IRModule and its structural hash. */
class WorkloadNode : public ffi::Object {
public:
/*! \brief The type of structural hash */
using THashCode = size_t;
/*! \brief The workload's IRModule. */
IRModule mod;
/*! \brief The workload's structural hash. */
THashCode shash;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<WorkloadNode>().def_ro("mod", &WorkloadNode::mod);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.Workload", WorkloadNode, ffi::Object);
/*!
* \brief Export the workload to a JSON string.
* \return An array containing the structural hash and the base64 json string.
*/
ffi::ObjectRef AsJSON() const;
};
/*!
* \brief Managed reference to WorkloadNode.
* \sa WorkloadNode
*/
class Workload : public ffi::ObjectRef {
public:
using THashCode = WorkloadNode::THashCode;
explicit Workload(ffi::ObjectPtr<WorkloadNode> data) : ffi::ObjectRef(data) {}
/*!
* \brief Constructor of Workload.
* \param mod The workload's IRModule.
*/
TVM_DLL explicit Workload(IRModule mod);
/*!
* \brief Constructor of Workload.
* \param mod The workload's IRModule.
* \param shash The workload's structural hash.
*/
TVM_DLL explicit Workload(IRModule mod, THashCode shash);
/*!
* \brief Create a workload from a json object.
* \param json_obj The json object.
* \return The created workload.
*/
TVM_DLL static Workload FromJSON(const ffi::ObjectRef& json_obj);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Workload, ffi::ObjectRef, WorkloadNode);
};
/*! \brief The hash method for Workload */
struct WorkloadHash {
size_t operator()(const Workload& a) const { return a->shash; }
};
/*! \brief The equality check for Workload */
struct WorkloadEqual {
explicit WorkloadEqual(const ModuleEquality& mod_eq) : mod_eq_(mod_eq) {}
bool operator()(const Workload& a, const Workload& b) const;
private:
/*! \brief The module equality testing and hashing method */
const ModuleEquality& mod_eq_;
};
/*! \brief The class of measure candidates. */
class MeasureCandidate;
/*! \brief The class of tuning records. */
class TuningRecordNode : public ffi::Object {
public:
/*! \brief The trace tuned. */
s_tir::Trace trace;
/*! \brief The workload. */
Workload workload{ffi::UnsafeInit()};
/*! \brief The profiling result in seconds. */
ffi::Optional<ffi::Array<FloatImm>> run_secs;
/*! \brief The target for tuning. */
ffi::Optional<Target> target;
/*! \brief The argument information. */
ffi::Optional<ffi::Array<ArgInfo>> args_info;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<TuningRecordNode>()
.def_ro("trace", &TuningRecordNode::trace)
.def_ro("workload", &TuningRecordNode::workload)
.def_ro("run_secs", &TuningRecordNode::run_secs)
.def_ro("target", &TuningRecordNode::target)
.def_ro("args_info", &TuningRecordNode::args_info);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.TuningRecord", TuningRecordNode,
ffi::Object);
/*! \brief Construct the measure candidate given the initial IR module and trace
* stored in the tuning record. */
MeasureCandidate AsMeasureCandidate() const;
/*!
* \brief Export the tuning record to a JSON string.
* \return An array containing the trace, running secs, serialized target, and
* argument information.
*/
ffi::ObjectRef AsJSON() const;
/*!
* \brief Check if this tuning record has valid trace instructions and successful run results.
* \return The check result.
*/
bool IsValid() const;
};
/*!
* \brief The managed reference of TuningRecordNode.
* \sa TuningRecordNode
*/
class TuningRecord : public ffi::ObjectRef {
public:
/*!
\brief Constructor of a tuning record.
\param trace The trace of the tuning record.
\param workload The workload of the tuning record.
\param run_secs The running time of the tuning record.
\param target The target of the tuning record.
\param args_info The argument information of the tuning record.
*/
TVM_DLL explicit TuningRecord(s_tir::Trace trace, Workload workload,
ffi::Optional<ffi::Array<FloatImm>> run_secs,
ffi::Optional<Target> target,
ffi::Optional<ffi::Array<ArgInfo>> args_info);
/*!
* \brief Create a tuning record from a json object.
* \param json_obj The json object.
* \param workload The workload.
* \return The tuning record created.
*/
TVM_DLL static TuningRecord FromJSON(const ffi::ObjectRef& json_obj, const Workload& workload);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(TuningRecord, ffi::ObjectRef, TuningRecordNode);
};
class Database;
/* \brief The abstract interface of database. */
class DatabaseNode : public ffi::Object {
public:
/*!
* \brief Constructor
* \param mod_eq_name A string to specify the module equality testing and hashing method.
* It must be one of the followings:
* - "structural": Use StructuralEqual/Hash
* - "ignore-tensor": Same as "structural", but ignore tensor raw data during
* equality testing and hashing.
* - "anchor-block": Apply equality testing and hashing on the anchor block extracted from a
* given module. The "ignore-tensor" varint is used for the extracted blocks
* or in case no anchor block is found.
* For the definition of the anchor block, see tvm/tirx/analysis.h.
*/
explicit DatabaseNode(ffi::String mod_eq_name = "structural");
/*! \brief Default destructor */
virtual ~DatabaseNode();
/*!
* \brief Check if the database has the given workload.
* \param mod The IRModule to be searched for.
* \return Whether the database has the given workload.
*/
virtual bool HasWorkload(const IRModule& mod) = 0;
/*!
* \brief Look up or add workload to the database if missing.
* \param mod The IRModule to be searched for or added.
* \return The workload corresponding to the given IRModule.
*/
virtual Workload CommitWorkload(const IRModule& mod) = 0;
/*!
* \brief Add a tuning record to the database.
* \param record The tuning record to be added.
*/
virtual void CommitTuningRecord(const TuningRecord& record) = 0;
/*!
* \brief Get the top K valid tuning records of given workload from the database.
* \param workload The workload to be searched for.
* \param top_k The number of top records to be returned.
* \return An array of top K tuning records for the given workload.
*/
virtual ffi::Array<TuningRecord> GetTopK(const Workload& workload, int top_k) = 0;
/*!
* \brief Get all tuning records from the database.
* \return An Array of all the tuning records in the database.
*/
virtual ffi::Array<TuningRecord> GetAllTuningRecords() = 0;
/*!
* \brief Get the size of the database.
* \return The size of the database.
*/
virtual int64_t Size() = 0;
/*!
* \brief Query the best record of the given workload from the database.
* \param mod The IRModule to be searched for.
* \param target The target to be searched for.
* \param workload_name The name of the workload to be searched for.
* \return The best record of the given workload; std::nullopt if not found.
*/
virtual ffi::Optional<TuningRecord> QueryTuningRecord(const IRModule& mod, const Target& target,
const ffi::String& workload_name);
/*!
* \brief Query the best schedule of the given workload from the database.
* \param mod The IRModule to be searched for.
* \param target The target to be searched for.
* \param workload_name The name of the workload to be searched for.
* \return The schedule in the best schedule of the given workload; std::nullopt if not found.
*/
virtual ffi::Optional<s_tir::Schedule> QuerySchedule(const IRModule& mod, const Target& target,
const ffi::String& workload_name);
/*!
* \brief Query the best IRModule of the given workload from the database.
* \param mod The IRModule to be searched for.
* \param target The target to be searched for.
* \param workload_name The name of the workload to be searched for.
* \return The IRModule in the best IRModule of the given workload; std::nullopt if not found.
*/
virtual ffi::Optional<IRModule> QueryIRModule(const IRModule& mod, const Target& target,
const ffi::String& workload_name);
/*!
* \brief Prune the database and dump it a given database.
* \param destination The destination database to be dumped to.
*/
void DumpPruned(Database destination);
/*! \brief Return a reference to the owned module equality method instance. */
const ModuleEquality& GetModuleEquality() const {
TVM_FFI_ICHECK(mod_eq_);
return *mod_eq_;
}
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO("s_tir.meta_schedule.Database", DatabaseNode, ffi::Object);
private:
/*! \brief The module equality testing and hashing method */
std::unique_ptr<ModuleEquality> mod_eq_;
};
/*! \brief The database with customized methods on the python-side. */
class PyDatabaseNode : public DatabaseNode {
public:
/*!
* \brief Constructor
* \param mod_eq_name A string to specify the module equality testing and hashing method.
* It must be one of the followings:
* - "structural": Use StructuralEqual/Hash
* - "ignore-tensor": Same as "structural", but ignore tensor raw data during
* equality testing and hashing.
* - "anchor-block": Apply equality testing and hashing on the anchor block extracted from a
* given module. The "ignore-tensor" varint is used for the extracted blocks
* or in case no anchor block is found.
* For the definition of the anchor block, see tvm/tirx/analysis.h.
*/
explicit PyDatabaseNode(ffi::String mod_eq_name = "structural");
/*!
* \brief The function type of `HasWorkload` method.
* \param mod The IRModule to be searched for.
* \return Whether the database has the given workload.
*/
using FHasWorkload = ffi::TypedFunction<bool(const IRModule&)>;
/*!
* \brief The function type of `CommitWorkload` method.
* \param mod The IRModule to be searched for or added.
* \return The workload corresponding to the given IRModule.
*/
using FCommitWorkload = ffi::TypedFunction<Workload(const IRModule&)>;
/*!
* \brief The function type of `CommitTuningRecord` method.
* \param record The tuning record to be added.
*/
using FCommitTuningRecord = ffi::TypedFunction<void(const TuningRecord&)>;
/*!
* \brief The function type of `GetTopK` method.
* \param workload The workload to be searched for.
* \param top_k The number of top records to be returned.
* \return An array of top K tuning records for the given workload.
*/
using FGetTopK = ffi::TypedFunction<ffi::Array<TuningRecord>(const Workload&, int)>;
/*!
* \brief The function type of `GetAllTuningRecords` method.
* \return An Array of all the tuning records in the database.
*/
using FGetAllTuningRecords = ffi::TypedFunction<ffi::Array<TuningRecord>()>;
/*!
* \brief The function type of `QueryTuningRecord` method.
* \param mod The IRModule to be searched for.
* \param target The target to be searched for.
* \param workload_name The name of the workload to be searched for.
* \return The best record of the given workload; std::nullopt if not found.
*/
using FQueryTuningRecord = ffi::TypedFunction<ffi::Optional<TuningRecord>(
const IRModule&, const Target&, const ffi::String&)>;
/*!
* \brief The function type of `QuerySchedule` method.
* \param mod The IRModule to be searched for.
* \param target The target to be searched for.
* \param workload_name The name of the workload to be searched for.
* \return The schedule in the best schedule of the given workload; std::nullopt if not found.
*/
using FQuerySchedule = ffi::TypedFunction<ffi::Optional<s_tir::Schedule>(
const IRModule&, const Target&, const ffi::String&)>;
/*!
* \brief The function type of `QueryIRModule` method.
* \param mod The IRModule to be searched for.
* \param target The target to be searched for.
* \param workload_name The name of the workload to be searched for.
* \return The IRModule in the best IRModule of the given workload; std::nullopt if not found.
*/
using FQueryIRModule = ffi::TypedFunction<ffi::Optional<IRModule>(const IRModule&, const Target&,
const ffi::String&)>;
/*!
* \brief The function type of `Size` method.
* \return The size of the database.
*/
using FSize = ffi::TypedFunction<int64_t()>;
/*! \brief The packed function to the `HasWorkload` function. */
FHasWorkload f_has_workload;
/*! \brief The packed function to the `CommitWorkload` function. */
FCommitWorkload f_commit_workload;
/*! \brief The packed function to the `CommitTuningRecord` function. */
FCommitTuningRecord f_commit_tuning_record;
/*! \brief The packed function to the `GetTopK` function. */
FGetTopK f_get_top_k;
/*! \brief The packed function to the `GetAllTuningRecords` function. */
FGetAllTuningRecords f_get_all_tuning_records;
/*! \brief The packed function to the `QueryTuningRecord` function. */
FQueryTuningRecord f_query_tuning_record;
/*! \brief The packed function to the `QuerySchedule` function. */
FQuerySchedule f_query_schedule;
/*! \brief The packed function to the `QueryIRModule` function. */
FQueryIRModule f_query_ir_module;
/*! \brief The packed function to the `Size` function. */
FSize f_size;
static void RegisterReflection() {
// ffi::Functions are all not registered, because the reflection system doesn't take care of
// them, so it cannot be accessible on the python side. If there is such need from the future,
// we can then add corresponding accessor methods to help access on python.
// `f_has_workload` is not registered
// `f_commit_workload` is not registered
// `f_commit_tuning_record` is not registered
// `f_get_top_k` is not registered
// `f_get_all_tuning_records` is not registered
// `f_query_tuning_record` is not registered
// `f_query_schedule` is not registered
// `f_query_ir_module` is not registered
// `f_size` is not registered
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PyDatabaseNode>();
}
bool HasWorkload(const IRModule& mod) final {
TVM_FFI_ICHECK(f_has_workload != nullptr) << "PyDatabase's HasWorkload method not implemented!";
return f_has_workload(mod);
}
Workload CommitWorkload(const IRModule& mod) final {
TVM_FFI_ICHECK(f_commit_workload != nullptr)
<< "PyDatabase's CommitWorkload method not implemented!";
return f_commit_workload(mod);
}
void CommitTuningRecord(const TuningRecord& record) final {
TVM_FFI_ICHECK(f_commit_tuning_record != nullptr)
<< "PyDatabase's CommitTuningRecord method not implemented!";
f_commit_tuning_record(record);
}
ffi::Array<TuningRecord> GetTopK(const Workload& workload, int top_k) final {
TVM_FFI_ICHECK(f_get_top_k != nullptr) << "PyDatabase's GetTopK method not implemented!";
return f_get_top_k(workload, top_k);
}
ffi::Array<TuningRecord> GetAllTuningRecords() final {
TVM_FFI_ICHECK(f_get_all_tuning_records != nullptr)
<< "PyDatabase's GetAllTuningRecords method not implemented!";
return f_get_all_tuning_records();
}
ffi::Optional<TuningRecord> QueryTuningRecord(const IRModule& mod, const Target& target,
const ffi::String& workload_name) final {
if (f_query_tuning_record == nullptr) {
return DatabaseNode::QueryTuningRecord(mod, target, workload_name);
} else {
return f_query_tuning_record(mod, target, workload_name);
}
}
ffi::Optional<s_tir::Schedule> QuerySchedule(const IRModule& mod, const Target& target,
const ffi::String& workload_name) final {
if (f_query_schedule == nullptr) {
return DatabaseNode::QuerySchedule(mod, target, workload_name);
} else {
return f_query_schedule(mod, target, workload_name);
}
}
ffi::Optional<IRModule> QueryIRModule(const IRModule& mod, const Target& target,
const ffi::String& workload_name) final {
if (f_query_ir_module == nullptr) {
return DatabaseNode::QueryIRModule(mod, target, workload_name);
} else {
return f_query_ir_module(mod, target, workload_name);
}
}
int64_t Size() final {
TVM_FFI_ICHECK(f_size != nullptr) << "PyDatabase's Size method not implemented!";
return f_size();
}
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.PyDatabase", PyDatabaseNode, DatabaseNode);
};
/*!
* \brief Managed reference to DatabaseNode.
* \sa DatabaseNode
*/
class Database : public ffi::ObjectRef {
public:
/*!
* \brief Constructor from ffi::ObjectPtr<DatabaseNode>.
* \param data The object pointer.
*/
explicit Database(ffi::ObjectPtr<DatabaseNode> data) : ffi::ObjectRef(data) {
TVM_FFI_ICHECK(data != nullptr);
}
/*!
* \brief An in-memory database.
* \param mod_eq_name A string to specify the module equality testing and hashing method.
*/
TVM_DLL static Database MemoryDatabase(ffi::String mod_eq_name = "structural");
/*!
* \brief A database for injecting handcrafted schedule functions.
* \param schedule_fn The function to do scheduling, which takes a TIR schedule,
* and returns a boolean indicating if the schedule is successful.
* \param mod_eq_name A string to specify the module equality testing and hashing method.
*/
TVM_DLL static Database ScheduleFnDatabase(ffi::TypedFunction<bool(s_tir::Schedule)> schedule_fn,
ffi::String mod_eq_name = "structural");
/*!
* \brief Create a default database that uses JSON file for tuning records.
* \param path_workload The path to the workload table.
* \param path_tuning_record The path to the database table.
* \param allow_missing Whether to create new file when the given path is not found.
* \param mod_eq_name A string to specify the module equality testing and hashing method.
*/
TVM_DLL static Database JSONDatabase(ffi::String path_workload, ffi::String path_tuning_record,
bool allow_missing, ffi::String mod_eq_name = "structural");
/*!
* \brief A database composed of multiple databases, allowing users to guide IR rewriting using
* combined knowledge of those databases. To each query, it returns the best record among all the
* databases given.
* \param databases The list of databases to be combined.
* \return The combined database.
*/
TVM_DLL static Database UnionDatabase(ffi::Array<Database, void> databases);
/*!
* \brief A database composed of multiple databases, allowing users to guide IR rewriting using
* combined knowledge of those databases. To each query, it returns the record from the first
* database that responds to the query.
* \param databases The database to be subsetted.
* \return The subsetted database.
*/
TVM_DLL static Database OrderedUnionDatabase(ffi::Array<Database, void> databases);
/*!
* \brief Create a database with customized methods on the python-side.
* \param f_has_workload The packed function of `HasWorkload`.
* \param f_commit_workload The packed function of `CommitWorkload`.
* \param f_commit_tuning_record The packed function of `CommitTuningRecord`.
* \param f_get_top_k The packed function of `GetTopK`.
* \param f_get_all_tuning_records The packed function of `GetAllTuningRecords`.
* \param f_query_tuning_record The packed function of `QueryTuningRecord`.
* \param f_query_schedule The packed function of `QuerySchedule`.
* \param f_query_ir_module The packed function of `QueryIRModule`.
* \param f_size The packed function of `Size`.
* \param mod_eq_name A string to specify the module equality testing and hashing method.
* \return The created database.
*/
TVM_DLL static Database PyDatabase(PyDatabaseNode::FHasWorkload f_has_workload,
PyDatabaseNode::FCommitWorkload f_commit_workload,
PyDatabaseNode::FCommitTuningRecord f_commit_tuning_record,
PyDatabaseNode::FGetTopK f_get_top_k,
PyDatabaseNode::FGetAllTuningRecords f_get_all_tuning_records,
PyDatabaseNode::FQueryTuningRecord f_query_tuning_record,
PyDatabaseNode::FQuerySchedule f_query_schedule,
PyDatabaseNode::FQueryIRModule f_query_ir_module,
PyDatabaseNode::FSize f_size,
ffi::String mod_eq_name = "structural");
/*! \return The current Database in the scope. */
static ffi::Optional<Database> Current();
/*! \brief Entering the scope of the context manager */
void EnterWithScope();
/*! \brief Exiting the scope of the context manager */
void ExitWithScope();
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Database, ffi::ObjectRef, DatabaseNode);
};
} // namespace meta_schedule
} // namespace s_tir
} // namespace tvm
#endif // TVM_S_TIR_META_SCHEDULE_DATABASE_H_
@@ -0,0 +1,85 @@
/*
* 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_S_TIR_META_SCHEDULE_EXTRACTED_TASK_H_
#define TVM_S_TIR_META_SCHEDULE_EXTRACTED_TASK_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include <tvm/ir/module.h>
#include <tvm/target/target.h>
namespace tvm {
namespace tirx {
class PrimFunc;
} // namespace tirx
namespace te {
class Tensor;
} // namespace te
} // namespace tvm
namespace tvm {
namespace s_tir {
namespace meta_schedule {
/*! \brief A tuning task extracted from the high-level IR */
class ExtractedTaskNode : public ffi::Object {
public:
/*! \brief The name of the task extracted */
ffi::String task_name;
/*! \brief The high-level IR */
IRModule mod;
/*! \brief Target */
Target target;
/*! \brief A list of low-level IRs that the high-level IR could potentially dispatch to */
ffi::Array<IRModule> dispatched;
/*! \brief Weight of the task */
int weight;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ExtractedTaskNode>()
.def_ro("task_name", &ExtractedTaskNode::task_name)
.def_ro("mod", &ExtractedTaskNode::mod)
.def_ro("target", &ExtractedTaskNode::target)
.def_ro("dispatched", &ExtractedTaskNode::dispatched)
.def_ro("weight", &ExtractedTaskNode::weight);
}
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.ExtractedTask", ExtractedTaskNode,
ffi::Object);
};
/*!
* \brief Managed reference to ExtractedTaskNode
* \sa ExtractedTaskNode
*/
class ExtractedTask : public ffi::ObjectRef {
public:
explicit ExtractedTask(ffi::String task_name, IRModule mod, Target target,
ffi::Array<IRModule> dispatched, int weight);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(ExtractedTask, ffi::ObjectRef, ExtractedTaskNode);
};
} // namespace meta_schedule
} // namespace s_tir
} // namespace tvm
#endif // TVM_S_TIR_META_SCHEDULE_EXTRACTED_TASK_H_
@@ -0,0 +1,120 @@
/*
* 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_S_TIR_META_SCHEDULE_FEATURE_EXTRACTOR_H_
#define TVM_S_TIR_META_SCHEDULE_FEATURE_EXTRACTOR_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include <tvm/runtime/tensor.h>
#include <tvm/s_tir/meta_schedule/measure_candidate.h>
namespace tvm {
namespace s_tir {
namespace meta_schedule {
class TuneContext;
/*! \brief Extractor for features from measure candidates for use in cost model. */
class FeatureExtractorNode : public ffi::Object {
public:
/*! \brief Virtual destructor. */
virtual ~FeatureExtractorNode() = default;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<FeatureExtractorNode>();
}
/*!
* \brief Extract features from the given measure candidate.
* \param context The tuning context for feature extraction.
* \param candidates The measure candidates to extract features from.
* \return The feature tensor extracted.
*/
virtual ffi::Array<tvm::runtime::Tensor> ExtractFrom(
const TuneContext& context, const ffi::Array<MeasureCandidate>& candidates) = 0;
TVM_FFI_DECLARE_OBJECT_INFO("s_tir.meta_schedule.FeatureExtractor", FeatureExtractorNode,
ffi::Object);
};
/*! \brief The feature extractor with customized methods on the python-side. */
class PyFeatureExtractorNode : public FeatureExtractorNode {
public:
/*!
* \brief Extract features from the given measure candidate.
* \param context The tuning context for feature extraction.
* \param candidates The measure candidates to extract features from.
* \return The feature tensor extracted.
*/
using FExtractFrom = ffi::TypedFunction<ffi::Array<tvm::runtime::Tensor>(
const TuneContext& context, const ffi::Array<MeasureCandidate>& candidates)>;
/*! \brief The packed function to the `ExtractFrom` function. */
FExtractFrom f_extract_from;
static void RegisterReflection() {
// `f_extract_from` is not registered
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PyFeatureExtractorNode>();
}
ffi::Array<tvm::runtime::Tensor> ExtractFrom(
const TuneContext& context, const ffi::Array<MeasureCandidate>& candidates) final;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.PyFeatureExtractor",
PyFeatureExtractorNode, FeatureExtractorNode);
};
/*!
* \brief Managed reference to FeatureExtractorNode
* \sa FeatureExtractorNode
*/
class FeatureExtractor : public ffi::ObjectRef {
public:
/*!
* \brief Create a feature extractor that extracts features from each BufferStore
* \param buffers_per_store The number of buffers in each BufferStore; Pad or truncate if
* necessary.
* \param arith_intensity_curve_num_samples The number of samples used in the arithmetic intensity
* curve.
* \param cache_line_bytes The number of bytes in a cache line.
* \param extract_workload Whether to extract features in the workload in tuning context or not.
* \return The feature extractor created.
*/
TVM_DLL static FeatureExtractor PerStoreFeature(int buffers_per_store = 5,
int arith_intensity_curve_num_samples = 10,
int cache_line_bytes = 64,
bool extract_workload = false);
/*!
* \brief Create a feature extractor with customized methods on the python-side.
* \param f_extract_from The packed function of `ExtractFrom`.
* \return The feature extractor created.
*/
TVM_DLL static FeatureExtractor PyFeatureExtractor(
PyFeatureExtractorNode::FExtractFrom f_extract_from);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(FeatureExtractor, ffi::ObjectRef,
FeatureExtractorNode);
};
} // namespace meta_schedule
} // namespace s_tir
} // namespace tvm
#endif // TVM_S_TIR_META_SCHEDULE_FEATURE_EXTRACTOR_H_
@@ -0,0 +1,141 @@
/*
* 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_S_TIR_META_SCHEDULE_MEASURE_CALLBACK_H_
#define TVM_S_TIR_META_SCHEDULE_MEASURE_CALLBACK_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include <tvm/runtime/base.h>
#include <tvm/s_tir/meta_schedule/builder.h>
#include <tvm/s_tir/meta_schedule/measure_candidate.h>
#include <tvm/s_tir/meta_schedule/runner.h>
#include <tvm/s_tir/meta_schedule/search_strategy.h>
#include <tvm/s_tir/meta_schedule/tune_context.h>
namespace tvm {
namespace s_tir {
namespace meta_schedule {
class TaskScheduler;
/*! \brief Rules to apply after measure results is available. */
class MeasureCallbackNode : public ffi::Object {
public:
/*! \brief Virtual destructor. */
virtual ~MeasureCallbackNode() = default;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<MeasureCallbackNode>();
}
/*!
* \brief Apply a measure callback rule with given arguments.
* \param task_scheduler The task scheduler.
* \param task_id The id of the task (tune context) to apply measure callbacks.
* \param measure_candidates The measure candidates.
* \param builder_results The builder results by building the measure candidates.
* \param runner_results The runner results by running the built measure candidates.
*/
virtual void Apply(const TaskScheduler& task_scheduler, //
int task_id, //
const ffi::Array<MeasureCandidate>& measure_candidates, //
const ffi::Array<BuilderResult>& builder_results, //
const ffi::Array<RunnerResult>& runner_results) = 0;
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO("s_tir.meta_schedule.MeasureCallback", MeasureCallbackNode,
ffi::Object);
};
/*! \brief The measure callback with customized methods on the python-side. */
class PyMeasureCallbackNode : public MeasureCallbackNode {
public:
/*!
* \brief Apply a measure callback to the given schedule.
* \param task_scheduler The task scheduler.
* \param tasks The list of tune context to process.
* \param measure_candidates The measure candidates.
* \param builds The builder results by building the measure candidates.
* \param results The runner results by running the built measure candidates.
* \return Whether the measure callback was successfully applied.
*/
using FApply = ffi::TypedFunction<void(const TaskScheduler& task_scheduler, //
int task_id, //
const ffi::Array<MeasureCandidate>& measure_candidates, //
const ffi::Array<BuilderResult>& builds, //
const ffi::Array<RunnerResult>& results)>;
/*! \brief The packed function to the `Apply` function. */
FApply f_apply;
static void RegisterReflection() {
// `f_apply` is not registered
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PyMeasureCallbackNode>();
}
void Apply(const TaskScheduler& task_scheduler, //
int task_id, //
const ffi::Array<MeasureCandidate>& measure_candidates, //
const ffi::Array<BuilderResult>& builds, //
const ffi::Array<RunnerResult>& results);
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.PyMeasureCallback", PyMeasureCallbackNode,
MeasureCallbackNode);
};
/*!
* \brief Managed reference to MeasureCallbackNode
* \sa MeasureCallbackNode
*/
class MeasureCallback : public ffi::ObjectRef {
public:
/*!
* \brief Create a measure callback that adds the measurement results into the database
* \return The measure callback created.
*/
TVM_DLL static MeasureCallback AddToDatabase();
/*!
* \brief Create a measure callback that removes the build artifacts from the disk
* \return The measure callback created.
*/
TVM_DLL static MeasureCallback RemoveBuildArtifact();
/*!
* \brief Create a measure callback that updates the cost model with measurement result.
* \return The measure callback created.
*/
TVM_DLL static MeasureCallback UpdateCostModel();
/*!
* \brief Create a measure callback with customized methods on the python-side.
* \param f_apply The packed function of `Apply`.
* \return The measure callback created.
*/
TVM_DLL static MeasureCallback PyMeasureCallback(PyMeasureCallbackNode::FApply f_apply);
/*! \brief The default list of measure callbacks. */
TVM_DLL static ffi::Array<MeasureCallback, void> Default();
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(MeasureCallback, ffi::ObjectRef, MeasureCallbackNode);
};
} // namespace meta_schedule
} // namespace s_tir
} // namespace tvm
#endif // TVM_S_TIR_META_SCHEDULE_MEASURE_CALLBACK_H_
@@ -0,0 +1,71 @@
/*
* 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_S_TIR_META_SCHEDULE_MEASURE_CANDIDATE_H_
#define TVM_S_TIR_META_SCHEDULE_MEASURE_CANDIDATE_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/runtime/base.h>
#include <tvm/s_tir/meta_schedule/arg_info.h>
#include <tvm/s_tir/schedule/schedule.h>
namespace tvm {
namespace s_tir {
namespace meta_schedule {
/*! \brief The schedule (with input shapes) to be measured. */
class MeasureCandidateNode : public ffi::Object {
public:
/*! \brief The schedule for measurement. */
s_tir::Schedule sch;
/*! \brief The argument information, e.g., (shape, dtype) for tensors. */
ffi::Array<ArgInfo> args_info;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<MeasureCandidateNode>()
.def_ro("sch", &MeasureCandidateNode::sch)
.def_ro("args_info", &MeasureCandidateNode::args_info);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.MeasureCandidate", MeasureCandidateNode,
ffi::Object);
};
/*!
* \brief Managed reference to MeasureCandidateNode.
* \sa MeasureCandidateNode
*/
class MeasureCandidate : public ffi::ObjectRef {
public:
/*!
* \brief Constructor of MeasureCandidate.
* \param sch The schedule for measurement.
* \param args_info The argument information, e.g., (shape, dtype) for tensors.
*/
TVM_DLL MeasureCandidate(s_tir::Schedule sch, ffi::Array<ArgInfo> args_info);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(MeasureCandidate, ffi::ObjectRef,
MeasureCandidateNode);
};
} // namespace meta_schedule
} // namespace s_tir
} // namespace tvm
#endif // TVM_S_TIR_META_SCHEDULE_MEASURE_CANDIDATE_H_
+174
View File
@@ -0,0 +1,174 @@
/*
* 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_S_TIR_META_SCHEDULE_MUTATOR_H_
#define TVM_S_TIR_META_SCHEDULE_MUTATOR_H_
#include <tvm/ffi/function.h>
#include <tvm/ffi/optional.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/runtime/base.h>
#include <tvm/s_tir/random_engine.h>
#include <tvm/s_tir/schedule/schedule.h>
#include <tvm/s_tir/schedule/trace.h>
namespace tvm {
namespace s_tir {
namespace meta_schedule {
class TuneContext;
class Mutator;
/*! \brief Mutator is designed to mutate the trace to explore the design space. */
class MutatorNode : public ffi::Object {
public:
/*! \brief Virtual destructor. */
virtual ~MutatorNode() = default;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<MutatorNode>();
}
/*!
* \brief Initialize the design space generator with tuning context.
* \param context The tuning context for initialization.
* \note This method is supposed to be called only once before every other method.
*/
virtual void InitializeWithTuneContext(const TuneContext& context) = 0;
/*!
* \brief Apply the mutator function to the given trace.
* \param trace The given trace for mutation.
* \param rand_state The random state for mutation.
* \return None if mutator failed, otherwise return the mutated trace.
*/
virtual ffi::Optional<s_tir::Trace> Apply(const s_tir::Trace& trace,
LinearCongruentialEngine::TRandState* rand_state) = 0;
/*!
* \brief Clone the mutator.
* \return The cloned mutator.
*/
virtual Mutator Clone() const = 0;
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO("s_tir.meta_schedule.Mutator", MutatorNode, ffi::Object);
};
/*!
* \brief Managed reference to MutatorNode
* \sa MutatorNode
*/
class Mutator : public ffi::ObjectRef {
public:
/*!
* \brief The function type of `InitializeWithTuneContext` method.
* \param context The tuning context for initialization.
*/
using FInitializeWithTuneContext = ffi::TypedFunction<void(const TuneContext&)>;
/*!
* \brief Apply the mutator function to the given trace.
* \param trace The given trace for mutation.
* \return None if mutator failed, otherwise return the mutated trace.
*/
using FApply = ffi::TypedFunction<ffi::Optional<s_tir::Trace>(
const s_tir::Trace&, LinearCongruentialEngine::TRandState rand_state)>;
/*!
* \brief Clone the mutator.
* \return The cloned mutator.
*/
using FClone = ffi::TypedFunction<Mutator()>;
/*! \brief Create a Mutator that mutates the decision of instruction Sample-Perfect-Tile */
TVM_DLL static Mutator MutateTileSize();
/*!
* \brief Create a Mutator that mutates the parallel extent
* \param max_jobs_per_core The maximum number of parallel jobs per core.
* \return The created mutator.
*/
TVM_DLL static Mutator MutateParallel(int64_t max_jobs_per_core);
/*!
* \brief Create a Mutator that mutates auto unroll step
* \return The mutator created
*/
TVM_DLL static Mutator MutateUnroll();
/*!
* \brief Create a Mutator that mutates the outcome of SampleComputeLocation
* \return The mutator created
*/
TVM_DLL static Mutator MutateComputeLocation();
/*!
* \brief Create a Mutator that mutates auto thread binding.
* \return The mutator created
*/
TVM_DLL static Mutator MutateThreadBinding();
/*!
* \brief Create a mutator with customized methods on the python-side.
* \param f_initialize_with_tune_context The packed function of `InitializeWithTuneContext`.
* \param f_apply The packed function of `Apply`.
* \param f_clone The packed function of `Clone`.
* \return The mutator created.
*/
TVM_DLL static Mutator PyMutator(FInitializeWithTuneContext f_initialize_with_tune_context,
FApply f_apply, FClone f_clone);
/*! \brief Create default mutators for LLVM */
TVM_DLL static ffi::Map<Mutator, FloatImm, void> DefaultLLVM();
/*! \brief Create default mutators for CUDA */
TVM_DLL static ffi::Map<Mutator, FloatImm, void> DefaultCUDA();
/*! \brief Create default mutators for CUDA with TensorCore */
TVM_DLL static ffi::Map<Mutator, FloatImm, void> DefaultCUDATensorCore();
/*! \brief Create default mutators for Hexagon */
TVM_DLL static ffi::Map<Mutator, FloatImm, void> DefaultHexagon();
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Mutator, ffi::ObjectRef, MutatorNode);
};
/*! \brief The mutator with customized methods on the python-side. */
class PyMutatorNode : public MutatorNode {
public:
using FInitializeWithTuneContext = Mutator::FInitializeWithTuneContext;
using FApply = Mutator::FApply;
using FClone = Mutator::FClone;
/*! \brief The packed function to the `InitializeWithTuneContext` function. */
FInitializeWithTuneContext f_initialize_with_tune_context;
/*! \brief The packed function to the `Apply` function. */
FApply f_apply;
/*! \brief The packed function to the `Clone` function. */
FClone f_clone;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PyMutatorNode>();
// `f_initialize_with_tune_context` is not registered
// `f_apply` is not registered
// `f_clone` is not registered
}
void InitializeWithTuneContext(const TuneContext& context) final;
ffi::Optional<s_tir::Trace> Apply(const s_tir::Trace& trace,
LinearCongruentialEngine::TRandState* rand_state) final;
Mutator Clone() const final;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.PyMutator", PyMutatorNode, MutatorNode);
};
} // namespace meta_schedule
} // namespace s_tir
} // namespace tvm
#endif // TVM_S_TIR_META_SCHEDULE_MUTATOR_H_
+207
View File
@@ -0,0 +1,207 @@
/*
* 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_S_TIR_META_SCHEDULE_POSTPROC_H_
#define TVM_S_TIR_META_SCHEDULE_POSTPROC_H_
#include <tvm/ffi/function.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/runtime/base.h>
#include <tvm/s_tir/schedule/schedule.h>
namespace tvm {
namespace s_tir {
namespace meta_schedule {
class TuneContext;
class Postproc;
/*!
* \brief Rules to apply a postprocessor to a schedule.
*/
class PostprocNode : public ffi::Object {
public:
/*! \brief Virtual destructor. */
virtual ~PostprocNode() = default;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PostprocNode>();
}
/*!
* \brief Initialize the design space generator with tuning context.
* \param context The tuning context for initialization.
* \note This method is supposed to be called only once before every other method.
*/
virtual void InitializeWithTuneContext(const TuneContext& context) = 0;
/*!
* \brief Apply a postprocessor to the given schedule.
* \param sch The schedule to be post processed.
* \return Whether the postprocessor was successfully applied.
*/
virtual bool Apply(const s_tir::Schedule& sch) = 0;
/*!
* \brief Clone the postprocessor.
* \return The cloned postprocessor.
*/
virtual Postproc Clone() const = 0;
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO("s_tir.meta_schedule.Postproc", PostprocNode, ffi::Object);
};
/*!
* \brief Managed reference to PostprocNode
* \sa PostprocNode
*/
class Postproc : public ffi::ObjectRef {
public:
/*!
* \brief The function type of `InitializeWithTuneContext` method.
* \param context The tuning context for initialization.
*/
using FInitializeWithTuneContext = ffi::TypedFunction<void(const TuneContext&)>;
/*!
* \brief Apply a postprocessor to the given schedule.
* \param sch The schedule to be post processed.
* \return Whether the postprocessor was successfully applied.
*/
using FApply = ffi::TypedFunction<bool(const s_tir::Schedule&)>;
/*!
* \brief Clone the postprocessor.
* \return The cloned postprocessor.
*/
using FClone = ffi::TypedFunction<Postproc()>;
/*!
* \brief Create a postprocessor with customized methods on the python-side.
* \param f_initialize_with_tune_context The packed function of `InitializeWithTuneContext`.
* \param f_apply The packed function of `Apply`.
* \param f_clone The packed function of `Clone`.
* \return The postprocessor created.
*/
TVM_DLL static Postproc PyPostproc(FInitializeWithTuneContext f_initialize_with_tune_context, //
FApply f_apply, //
FClone f_clone);
/*!
* \brief Create a postprocessor that checks if all loops are static
* \return The postprocessor created
*/
TVM_DLL static Postproc DisallowDynamicLoop();
/*!
* \brief Create a postprocessor that checks if all async mem copies are not strided.
* \return The postprocessor created
*/
TVM_DLL static Postproc DisallowAsyncStridedMemCopy();
/*!
* \brief Create a postprocessor that rewrites the cooperative fetch annotation to
* actual vectorized cooperative fetching in loop bindings.
* \return The postprocessor created.
*/
TVM_DLL static Postproc RewriteCooperativeFetch();
/*!
* \brief Creates a postprocessor that applies parallelization, vectorization and auto unrolling
* according to the annotation of each block
* \return The postprocessor created
*/
TVM_DLL static Postproc RewriteParallelVectorizeUnroll();
/*!
* \brief Create a postprocessor that rewrites reduction block by moving the init block out.
* \return The postprocessor created.
*/
TVM_DLL static Postproc RewriteReductionBlock();
/*!
* \brief Create a postprocessor that adds thread binding to unbound blocks
* \param max_threadblocks The max number of threadblocks in the CUDA device.
* \return The postprocessor created.
*/
TVM_DLL static Postproc RewriteUnboundBlock(int max_threadblocks);
/*!
* \brief Create a postprocessor that applies tensorization to annotated blocks
* \param vectorize_init_loop Whether or not vectorize the initialization loop produced by
* DecomposeReduction
* \return The postprocessor created.
*/
TVM_DLL static Postproc RewriteTensorize(bool vectorize_init_loop = false);
/*!
* \brief Creates a postprocessor that verifies if the GPU code is correct
* \return The postprocessor created
*/
TVM_DLL static Postproc VerifyGPUCode();
/*!
* \brief Verifies that the VTCM usage of a given schedule is within the provided limit.
* \return The postprocessor created
*/
TVM_DLL static Postproc VerifyVTCMLimit();
/*!
* \brief Creates a postprocessor that rewrites the layout of input tensor
* \note Weight layout rewrite is supported so far, activation layout rewrite will be added.
* \return The postprocessor created
*/
TVM_DLL static Postproc RewriteLayout();
/*! \brief Create default postprocessors for LLVM */
TVM_DLL static ffi::Array<Postproc, void> DefaultLLVM();
/*! \brief Create default postprocessors for x86 (AVX512 and VNNI) */
TVM_DLL static ffi::Array<Postproc, void> DefaultCPUTensorization();
/*! \brief Create default postprocessors for RISCV */
TVM_DLL static ffi::Array<Postproc, void> DefaultRISCV();
/*! \brief Create default postprocessors for CUDA */
TVM_DLL static ffi::Array<Postproc, void> DefaultCUDA();
/*! \brief Create default postprocessors for CUDA with TensorCore */
TVM_DLL static ffi::Array<Postproc, void> DefaultCUDATensorCore();
/*! \brief Create default postprocessors for Hexagon */
TVM_DLL static ffi::Array<Postproc, void> DefaultHexagon();
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NULLABLE(Postproc, ffi::ObjectRef, PostprocNode);
};
/*! \brief The postprocessor with customized methods on the python-side. */
class PyPostprocNode : public PostprocNode {
public:
using FInitializeWithTuneContext = Postproc::FInitializeWithTuneContext;
using FApply = Postproc::FApply;
using FClone = Postproc::FClone;
/*! \brief The packed function to the `InitializeWithTuneContext` function. */
FInitializeWithTuneContext f_initialize_with_tune_context;
/*! \brief The packed function to the `Apply` function. */
FApply f_apply;
/*! \brief The packed function to the `Clone` function. */
FClone f_clone;
static void RegisterReflection() {
// `f_initialize_with_tune_context` is not registered
// `f_apply` is not registered
// `f_clone` is not registered
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PyPostprocNode>();
}
void InitializeWithTuneContext(const TuneContext& context) final;
bool Apply(const s_tir::Schedule& sch) final;
Postproc Clone() const final;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.PyPostproc", PyPostprocNode, PostprocNode);
};
} // namespace meta_schedule
} // namespace s_tir
} // namespace tvm
#endif // TVM_S_TIR_META_SCHEDULE_POSTPROC_H_
+104
View File
@@ -0,0 +1,104 @@
/*
* 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_S_TIR_META_SCHEDULE_PROFILER_H_
#define TVM_S_TIR_META_SCHEDULE_PROFILER_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/optional.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include <tvm/ir/module.h>
#include <tvm/target/target.h>
#include <string>
#include <unordered_map>
#include <utility>
#include <vector>
namespace tvm {
namespace s_tir {
namespace meta_schedule {
class ScopedTimer {
public:
~ScopedTimer() {
if (deferred_ != nullptr) {
deferred_();
}
}
private:
friend class Profiler;
explicit ScopedTimer(ffi::TypedFunction<void()> deferred) : deferred_(deferred) {}
ffi::TypedFunction<void()> deferred_;
};
/*! \brief A generic profiler */
class ProfilerNode : public ffi::Object {
public:
/*! \brief The segments that are already profiled */
std::unordered_map<std::string, double> stats_sec;
/*! \brief Counter for the total time used */
ffi::Function total_timer;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<ProfilerNode>();
}
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.Profiler", ProfilerNode, ffi::Object);
public:
/*! \brief Get the internal stats of the running time */
ffi::Map<ffi::String, FloatImm> Get() const;
/*! \brief Return a summary of profiling results as table format */
ffi::String Table() const;
};
/*!
* \brief Managed reference to ProfilerNode
* \sa ProfilerNode
*/
class Profiler : public ffi::ObjectRef {
public:
Profiler();
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Profiler, ffi::ObjectRef, ProfilerNode);
/*! \brief Entering the scope of the context manager */
void EnterWithScope();
/*! \brief Exiting the scope of the context manager */
void ExitWithScope();
/*! \brief Returns the current profiler */
static ffi::Optional<Profiler> Current();
/*!
* \brief Profile the time usage in the given scope in the given name.
* \param name Name for the scope.
* \return A scope timer for time profiling.
*/
static ScopedTimer TimedScope(ffi::String name);
};
} // namespace meta_schedule
} // namespace s_tir
} // namespace tvm
#endif // TVM_S_TIR_META_SCHEDULE_PROFILER_H_
+250
View File
@@ -0,0 +1,250 @@
/*
* 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_S_TIR_META_SCHEDULE_RUNNER_H_
#define TVM_S_TIR_META_SCHEDULE_RUNNER_H_
#include <tvm/ffi/container/array.h>
#include <tvm/ffi/function.h>
#include <tvm/ffi/optional.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/ffi/string.h>
#include <tvm/ir/expr.h>
#include <tvm/s_tir/meta_schedule/arg_info.h>
namespace tvm {
namespace s_tir {
namespace meta_schedule {
/*! \brief Runner's input containing path of artifact, type of device and argument info. */
class RunnerInputNode : public ffi::Object {
public:
/*! \brief The path to the built artifact. */
ffi::String artifact_path;
/*! \brief The type of device. */
ffi::String device_type;
/*! \brief The argument information. */
ffi::Array<ArgInfo> args_info;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<RunnerInputNode>()
.def_ro("artifact_path", &RunnerInputNode::artifact_path)
.def_ro("device_type", &RunnerInputNode::device_type)
.def_ro("args_info", &RunnerInputNode::args_info);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.RunnerInput", RunnerInputNode,
ffi::Object);
};
/*!
* \brief Managed reference to RunnerInputNode
* \sa RunnerInputNode
*/
class RunnerInput : public ffi::ObjectRef {
public:
/*!
* \brief Constructor of RunnerInput
* \param artifact_path The path to the built artifact.
* \param device_type The type of device.
* \param args_info The argument information.
*/
TVM_DLL explicit RunnerInput(ffi::String artifact_path, ffi::String device_type,
ffi::Array<ArgInfo> args_info);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(RunnerInput, ffi::ObjectRef, RunnerInputNode);
};
/*! \brief Runner's output containing measurement result of MeasureCandidate or error msg if any. */
class RunnerResultNode : public ffi::Object {
public:
/*! \brief The run time in seconds.*/
ffi::Optional<ffi::Array<FloatImm>> run_secs;
/*! \brief The error message, if any. */
ffi::Optional<ffi::String> error_msg;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<RunnerResultNode>()
.def_ro("run_secs", &RunnerResultNode::run_secs)
.def_ro("error_msg", &RunnerResultNode::error_msg);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.RunnerResult", RunnerResultNode,
ffi::Object);
};
/*!
* \brief Managed reference to RunnerResultNode
* \sa RunnerResultNode
*/
class RunnerResult : public ffi::ObjectRef {
public:
/*!
* \brief Constructor
* \brief The run time in seconds.
* \brief The error message, if any.
*/
TVM_DLL explicit RunnerResult(ffi::Optional<ffi::Array<FloatImm>> run_secs,
ffi::Optional<ffi::String> error_msg);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(RunnerResult, ffi::ObjectRef, RunnerResultNode);
};
/*!
* \brief A class to asynchronously fetch runner's output.
* \note The API design is consistent with python's concurrent.futures.Future:
* https://docs.python.org/3/library/concurrent.futures.html#concurrent.futures.Future
*/
class RunnerFutureNode : public ffi::Object {
public:
/*!
* \brief The function type to check whether the runner has finished.
* \return Whether the runner's output is ready.
*/
using FDone = ffi::TypedFunction<bool()>;
/*!
* \brief The function type to fetch runner output if it is ready.
* \return The runner's output.
*/
using FResult = ffi::TypedFunction<RunnerResult()>;
/*! \brief The packed function to check whether the runner has finished. */
FDone f_done;
/*! \brief The packed function to fetch runner output if it is ready. */
FResult f_result;
static void RegisterReflection() {
// `f_done` is not registered
// `f_result` is not registered
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<RunnerFutureNode>();
}
/*!
* \brief Check whether the runner has finished.
* \return A boolean indicating whether the runner has finished.
*/
bool Done() const {
TVM_FFI_ICHECK(f_done != nullptr) << "PyRunnerFuture's Done method not implemented!";
return f_done();
}
/*!
* \brief Fetch the runner's output if it is ready.
* \return The runner's output.
*/
RunnerResult Result() const {
TVM_FFI_ICHECK(f_result != nullptr) << "PyRunnerFuture's Result method not implemented!";
return f_result();
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.RunnerFuture", RunnerFutureNode,
ffi::Object);
};
/*!
* \brief Managed reference to RunnerFutureNode
* \sa RunnerFutureNode
*/
class RunnerFuture : public ffi::ObjectRef {
public:
using FDone = RunnerFutureNode::FDone;
using FResult = RunnerFutureNode::FResult;
/*!
* \brief Constructor of RunnerFuture
* \param f_done The packed function to check whether the runner has finished.
* \param f_result The packed function to fetch runner output if it is ready.
*/
TVM_DLL explicit RunnerFuture(FDone f_done, FResult f_result);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(RunnerFuture, ffi::ObjectRef, RunnerFutureNode);
};
/*! \brief The abstract runner interface. */
class RunnerNode : public ffi::Object {
public:
/*!
* \brief The function type to run the built artifacts and get runner futures.
* \param input The runner's inputs.
* \return The runner futures.
* \sa RunnerFuture
*/
using FRun = ffi::TypedFunction<ffi::Array<RunnerFuture>(ffi::Array<RunnerInput>)>;
/*! \brief Default destructor */
virtual ~RunnerNode() = default;
/*!
* \brief Run the built artifact and get runner futures.
* \param runner_inputs The runner's inputs.
* \return The runner futures.
*/
virtual ffi::Array<RunnerFuture> Run(ffi::Array<RunnerInput> runner_inputs) = 0;
static void RegisterReflection() {
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<RunnerNode>();
}
static constexpr const bool _type_mutable = true;
TVM_FFI_DECLARE_OBJECT_INFO("s_tir.meta_schedule.Runner", RunnerNode, ffi::Object);
};
/*!
* \brief Managed reference to RunnerNode
* \sa RunnerNode
*/
class Runner : public ffi::ObjectRef {
public:
using FRun = RunnerNode::FRun;
/*!
* \brief Constructor from ffi::ObjectPtr<RunnerNode>.
* \param data The object pointer.
*/
explicit Runner(ffi::ObjectPtr<RunnerNode> data) : ffi::ObjectRef(data) {
TVM_FFI_ICHECK(data != nullptr);
}
/*!
* \brief Create a runner with customized build method on the python-side.
* \param f_run The packed function to run the built artifacts and get runner futures.
* \return The runner created.
*/
TVM_DLL static Runner PyRunner(FRun f_run);
TVM_FFI_DEFINE_OBJECT_REF_METHODS_NOTNULLABLE(Runner, ffi::ObjectRef, RunnerNode);
};
/*! \brief An abstract runner with customized build method on the python-side. */
class PyRunnerNode : public RunnerNode {
public:
/*! \brief The packed function to run the built artifacts and get runner futures. */
FRun f_run;
static void RegisterReflection() {
// `f_run` is not registered
namespace refl = tvm::ffi::reflection;
refl::ObjectDef<PyRunnerNode>();
}
ffi::Array<RunnerFuture> Run(ffi::Array<RunnerInput> runner_inputs) final {
TVM_FFI_ICHECK(f_run != nullptr) << "PyRunner's Run method not implemented!";
return f_run(runner_inputs);
}
TVM_FFI_DECLARE_OBJECT_INFO_FINAL("s_tir.meta_schedule.PyRunner", PyRunnerNode, RunnerNode);
};
} // namespace meta_schedule
} // namespace s_tir
} // namespace tvm
#endif // TVM_S_TIR_META_SCHEDULE_RUNNER_H_
@@ -0,0 +1,72 @@
/*
* 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_S_TIR_META_SCHEDULE_SCHEDULE_CUDA_THREAD_BIND_H_
#define TVM_S_TIR_META_SCHEDULE_SCHEDULE_CUDA_THREAD_BIND_H_
#include <tvm/s_tir/schedule/schedule.h>
#include <algorithm>
#include <limits>
#include <utility>
namespace tvm {
namespace s_tir {
namespace meta_schedule {
/*!
* \brief Given candidates of thread_extents, make a sampler that use `sch->SampleCategorical`
* to return a random thread extent.
* \param sch The schedule
* \param thread_extents The candidate thread extents.
* \return A sampler that returns a random thread extent.
*/
std::function<s_tir::ExprRV(int64_t)> MakeFactorSampler(s_tir::Schedule sch,
ffi::Array<int64_t> thread_extents);
/*!
* \brief Bind blockIdx.x and threadIdx.x to the given loop
* \param sch The schedule.
* \param loop The loop to be bound.
* \param max_threadblocks The maximum number of threadblocks allowed.
* \param max_threads_per_block The maximum number of threads allowed.
* \param get_factor A function that returns the tiling factor.
* \return The binded loops in the order of blockIdx.x, threadIdx.x, and the rest.
*/
ffi::Array<s_tir::LoopRV> BindSpatialLoop(
s_tir::Schedule sch, s_tir::LoopRV loop, //
int64_t max_threadblocks, int64_t max_threads_per_block,
std::function<s_tir::ExprRV(int64_t)> get_factor = nullptr);
/*!
* \brief Bind the given block if it is not bound to blockIdx or threadIdx.
* \param sch The schedule.
* \param block The block to be bound.
* \param max_threadblocks The maximum number of threadblocks allowed.
* \param max_threads_per_block The maximum number of threads allowed.
* \param get_factor A function that returns the tiling factor.
*/
void BindBlockThreadIdx(s_tir::Schedule sch, s_tir::SBlockRV block, //
int64_t max_threadblocks, int64_t max_threads_per_block,
std::function<s_tir::ExprRV(int64_t max_extent)> get_factor = nullptr);
} // namespace meta_schedule
} // namespace s_tir
} // namespace tvm
#endif // TVM_S_TIR_META_SCHEDULE_SCHEDULE_CUDA_THREAD_BIND_H_
@@ -0,0 +1,39 @@
/*
* 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_S_TIR_META_SCHEDULE_SCHEDULE_GENERIC_WINOGRAD_H_
#define TVM_S_TIR_META_SCHEDULE_SCHEDULE_GENERIC_WINOGRAD_H_
#include <tvm/s_tir/schedule/schedule.h>
namespace tvm {
namespace s_tir {
namespace meta_schedule {
/*!
* \brief Get the producer block of a given block.
* If there is a constant winograd transform matrix, inline it.
* \return The only producer block.
*/
s_tir::SBlockRV GetWinogradProducerAndInlineConst(s_tir::Schedule sch, s_tir::SBlockRV block);
} // namespace meta_schedule
} // namespace s_tir
} // namespace tvm
#endif // TVM_S_TIR_META_SCHEDULE_SCHEDULE_GENERIC_WINOGRAD_H_

Some files were not shown because too many files have changed in this diff Show More