Files
wehub-resource-sync 26446540fa
Lint / lint (push) Waiting to run
CI / MacOS (push) Waiting to run
CI / Windows (push) Waiting to run
chore: import upstream snapshot with attribution
2026-07-13 13:36:25 +08:00

339 lines
14 KiB
C++

/*
* 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_