blob: 2e004a59e5550849216fa24577947c457c6b37ad [file]
/*
* 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.
*/
#include "transform/utils.h"
#include <tvm/ffi/cast.h>
#include <tvm/ffi/reflection/registry.h>
#include <tvm/relax/analysis.h>
#include <tvm/relax/attrs/index.h>
#include <tvm/relax/expr_functor.h>
#include <tvm/relax/utils.h>
#include <tvm/tirx/op_attr_types.h>
#include <tvm/tirx/stmt_functor.h>
namespace tvm {
namespace relax {
/*! \brief Helper to implement bind params.*/
class ExprBinder : public ExprMutator {
public:
explicit ExprBinder(const tvm::ffi::Map<Var, Expr>& bindings) : bindings_(bindings) {}
private:
using ExprMutator::VisitExpr_;
Expr VisitExpr_(const ShapeExprNode* op) final {
ffi::Array<PrimExpr> values =
op->values.Map([this](const PrimExpr& value) { return BindShapeValue(value); });
return values.same_as(op->values) ? ffi::GetRef<Expr>(op) : ShapeExpr(values, op->span);
}
Expr VisitExpr_(const FunctionNode* op) final {
tvm::ffi::Array<Var> params;
bool all_params_unchanged = true;
for (const Var& param : op->params) {
if (bindings_.count(param)) {
all_params_unchanged = false;
} else {
Var new_param = this->VisitVarDef(param);
params.push_back(new_param);
if (!param.same_as(new_param)) {
this->var_remap_[param] = new_param;
all_params_unchanged = false;
}
}
}
Expr body = this->VisitWithNewScope(op->body, params);
// FuncType does not depend on Expr
if (all_params_unchanged && body.same_as(op->body)) {
return ffi::GetRef<Expr>(op);
} else {
// purity won't be affected, no need to update annotation
return Function(params, body, VisitExprDepTypeField(op->ret_ty), op->is_pure, op->attrs);
}
}
Expr VisitExpr_(const VarNode* op) final {
auto id = ffi::GetRef<Var>(op);
auto it = bindings_.find(id);
if (it != bindings_.end()) {
return (*it).second;
} else {
return ExprMutator::VisitExpr_(op);
}
}
PrimExpr VisitTypePrimExprField(const PrimExpr& expr) final { return BindShapeValue(expr); }
PrimExpr BindShapeValue(const PrimExpr& expr) {
PrimExpr output = tirx::Substitute(expr, [this](const Var& var) -> ffi::Optional<Expr> {
auto it = bindings_.find(var);
if (it == bindings_.end()) return std::nullopt;
if (auto value = (*it).second.as<PrimExpr>()) {
return ffi::Optional<Expr>(*value);
}
return std::nullopt;
});
return output.same_as(expr) ? expr : analyzer_->Simplify(output);
}
const tvm::ffi::Map<Var, Expr>& bindings_;
arith::Analyzer analyzer_;
};
/*!
* \brief Bind params on expr
* \param expr The expr where to bind params
* \param binds The map from param var to the expr it binds to
* \return The result expr after bind params
*/
Expr Bind(const Expr& expr, const tvm::ffi::Map<Var, Expr>& binds) {
return ExprBinder(binds).VisitExpr(expr);
}
Type Bind(const Type& ty, const tvm::ffi::Map<Var, Expr>& binds) {
return ExprBinder(binds).VisitExprDepTypeField(ty);
}
tvm::ffi::Map<Var, Expr> InferSymbolicVarMap(
const tvm::ffi::Map<tvm::Var, relax::Expr>& relax_var_remap, const arith::Analyzer& analyzer) {
tvm::ffi::Map<Var, Expr> var_remap = relax_var_remap;
for (const auto& [var, value] : relax_var_remap) {
if (!var.as<tirx::PrimVar>()) continue;
TVM_FFI_CHECK(value.as<PrimExpr>().has_value(), ValueError)
<< "Explicit binding for symbolic variable " << var
<< " must be a primitive expression, but received " << value;
}
auto bind_from_prim_expr = [&relax_var_remap, &var_remap, &analyzer](const PrimExpr& var_shape,
const PrimExpr& expr_shape) {
if (auto var = var_shape.as<tirx::PrimVar>()) {
if (auto it = relax_var_remap.find(var.value()); it != relax_var_remap.end()) {
auto explicit_value = (*it).second.as<PrimExpr>();
TVM_FFI_CHECK(explicit_value.has_value(), ValueError)
<< "Explicit binding for symbolic variable " << var.value()
<< " must be a primitive expression, but received " << (*it).second;
TVM_FFI_CHECK(analyzer->CanProveEqual(explicit_value.value(), expr_shape), ValueError)
<< "Explicit binding for symbolic variable " << var.value()
<< " conflicts with the value inferred from a parameter shape: explicit value "
<< explicit_value.value() << ", inferred value " << expr_shape;
return;
}
var_remap.Set(var.value(), expr_shape);
}
};
auto bind_from_shape = [&bind_from_prim_expr](const Type& var, const Type& expr) {
auto var_shape = var.as<ShapeTypeNode>();
if (!var_shape) return;
if (!var_shape->values.has_value()) return;
auto expr_shape = expr.as<ShapeTypeNode>();
if (!expr_shape) return;
if (!expr_shape->values.has_value()) return;
auto var_shape_arr = var_shape->values.value();
auto expr_shape_arr = expr_shape->values.value();
if (var_shape_arr.size() != expr_shape_arr.size()) return;
for (size_t i = 0; i < var_shape_arr.size(); i++) {
bind_from_prim_expr(var_shape_arr[i], expr_shape_arr[i]);
}
};
auto bind_from_tensor = [&bind_from_shape](const Type& var, const Type& expr) {
auto var_tensor = var.as<TensorTypeNode>();
if (!var_tensor) return;
if (!var_tensor->shape.has_value()) return;
auto expr_tensor = expr.as<TensorTypeNode>();
if (!expr_tensor) return;
if (!expr_tensor->shape.has_value()) return;
bind_from_shape(GetType(var_tensor->shape.value()), GetType(expr_tensor->shape.value()));
};
std::function<void(const Type&, const Type&)> bind_from_ty = nullptr;
auto bind_from_tuple = [&bind_from_ty](const Type& var, const Type& expr) {
auto var_tuple = var.as<TupleTypeNode>();
if (!var_tuple) return;
auto expr_tuple = expr.as<TupleTypeNode>();
if (!expr_tuple) return;
if (var_tuple->fields.size() != expr_tuple->fields.size()) return;
for (size_t i = 0; i < var_tuple->fields.size(); i++) {
bind_from_ty(var_tuple->fields[i], expr_tuple->fields[i]);
}
};
bind_from_ty = [&](const Type& var, const Type& expr) {
bind_from_tensor(var, expr);
bind_from_shape(var, expr);
bind_from_tuple(var, expr);
};
for (const auto& [relax_var, relax_expr] : relax_var_remap) {
auto var_ty = GetType(relax_var);
auto expr_ty = GetType(relax_expr);
bind_from_ty(var_ty, expr_ty);
}
return var_remap;
}
bool IsBoolType(const Type& ty, bool permit_unknown_rank, bool permit_unknown_dtype) {
DLDataType dtype;
int ndim;
if (const auto* tensor = ty.as<TensorTypeNode>()) {
ndim = tensor->ndim;
if (tensor->IsUnknownDtype()) {
bool correct_rank = ndim == 0 || (permit_unknown_rank && ndim == -1);
return permit_unknown_dtype && correct_rank;
}
dtype = tensor->dtype.value()->dtype;
} else if (const auto* prim = ty.as<PrimTypeNode>()) {
dtype = prim->dtype;
ndim = 0;
} else {
return false;
}
// Bool-type matching uses element-code-only behavior; rank is checked separately.
// Unknown dtype is already handled above via IsUnknownDtype().
bool correct_dtype = dtype.code == DLDataTypeCode::kDLBool;
bool correct_rank = ndim == 0 || (permit_unknown_rank && ndim == -1);
return correct_dtype && correct_rank;
}
bool IsLeafOrTuple(const Expr& expr) {
return !expr.as<CallNode>() && !expr.as<TupleGetItemNode>() && !expr.as<SeqExprNode>() &&
!expr.as<IfNode>() && !expr.as<FunctionNode>();
}
bool IsImpureCall(const Call& call) {
if (auto op_ptr = call->op.as<OpNode>()) {
auto op = ffi::GetRef<Op>(op_ptr);
static auto purity_map = Op::GetAttrMap<bool>("FPurity");
if (purity_map.count(op)) {
return !(purity_map[op]);
}
static auto effect_map = Op::GetAttrMap<tirx::TCallEffectKind>("TCallEffectKind");
TVM_FFI_ICHECK(effect_map.count(op))
<< "Cannot find the registered purity or call effect of this op: " << op->name;
auto effect = static_cast<tirx::CallEffectKind>(effect_map[op]);
return effect > tirx::CallEffectKind::kPure;
}
// the Type must be FuncType
auto func_ty = GetTypeAs<FuncTypeNode>(call->op);
return !func_ty->purity;
}
Expr GetBoundValue(const Binding& b) {
if (auto* var_binding = b.as<VarBindingNode>()) {
return var_binding->value;
} else if (auto* match_binding = b.as<MatchCastNode>()) {
return match_binding->value;
} else {
TVM_FFI_ICHECK(false) << "Invalid binding (should never happen)";
}
}
/*!
* \brief Copy a new Relax function with new remapped vars and symbolic vars.
* To get the var mapping from old vars to new vars, see FuncCopier in src/relax/transform/utils.h.
* \param func The Relax function we want to copy.
* \return The copied function.
*/
Function CopyWithNewVars(Function func) { return FunctionCopier().Copy(func); }
TVM_FFI_STATIC_INIT_BLOCK() {
namespace refl = tvm::ffi::reflection;
refl::GlobalDef().def("relax.CopyWithNewVars", CopyWithNewVars);
}
} // namespace relax
} // namespace tvm