blob: a6751933a88c7a39e28c502fcd144c5b8d310d44 [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.
*/
/*!
* \file src/relay/transforms/simplify_expr.cc
* \brief A pass for simplifying the Relay expression.
*/
#include "simplify_expr.h"
#include <tvm/relay/dataflow_matcher.h>
#include <tvm/relay/expr.h>
#include <tvm/relay/expr_functor.h>
#include <tvm/relay/transform.h>
#include <tvm/runtime/logging.h>
#include <algorithm>
#include <limits>
#include <memory>
#include <string>
#include <utility>
#include "../op/tensor/transform.h"
#include "pattern_utils.h"
namespace tvm {
namespace relay {
/*!
* \brief SimplifyReshape matches the pattern of consecutive reshape or reverse_reshape ops,
* and merges into one reshape op.
*/
class SimplifyReshape : public DFPatternRewrite {
public:
SimplifyReshape() {
x_ = IsWildcard();
auto reshape1 = IsOp("reshape") || IsOp("contrib_reverse_reshape");
auto reshape2 = IsOp("reshape") || IsOp("contrib_reverse_reshape");
pattern_ = reshape1({reshape2({x_})});
}
Expr Callback(const Expr& pre, const Expr& post,
const Map<DFPattern, Array<Expr>>& node_map) const override {
auto x = node_map[x_][0];
bool const_shape = true;
Array<Integer> newshape;
for (auto dim : Downcast<TensorType>(pre->checked_type())->shape) {
if (dim.as<IntImmNode>() == nullptr) {
const_shape = false;
break;
}
newshape.push_back(Downcast<Integer>(dim));
}
if (const_shape) {
return MakeReshape(x, newshape);
}
return post;
}
private:
/*! \brief Pattern input */
DFPattern x_;
};
/*!
* \brief SimplifySameCast matches the pattern of cast data to the same dtype.
*/
class SimplifySameCast : public DFPatternRewrite {
public:
SimplifySameCast() {
data_pat_ = IsWildcard();
like_pat_ = IsWildcard();
pattern_ = IsOp("cast_like")({data_pat_, like_pat_}) || IsOp("cast")({data_pat_});
}
Expr Callback(const Expr& pre, const Expr& post,
const Map<DFPattern, Array<Expr>>& node_map) const override {
const CallNode* call = pre.as<CallNode>();
const TensorTypeNode* data_ty = call->args[0]->checked_type().as<TensorTypeNode>();
const TensorTypeNode* like_ty = pre->checked_type().as<TensorTypeNode>();
if (like_ty->dtype == data_ty->dtype) {
return node_map[data_pat_][0];
}
return post;
}
protected:
DFPattern data_pat_;
DFPattern like_pat_;
};
/*!
* \brief SimplifyConsecutiveCast matches the pattern of consecutive cast/cast_like ops
*/
class SimplifyConsecutiveCast : public DFPatternRewrite {
public:
SimplifyConsecutiveCast() {
data_ = IsWildcard();
cast1_ = IsOp("cast_like")({data_, IsWildcard()}) || IsOp("cast")({data_});
pattern_ = IsOp("cast_like")({cast1_, IsWildcard()}) || IsOp("cast")({cast1_});
}
Expr Callback(const Expr& pre, const Expr& post,
const Map<DFPattern, Array<Expr>>& node_map) const override {
auto data = node_map[data_][0];
auto cast1 = Downcast<Call>(node_map[cast1_][0]);
auto data_type = Downcast<TensorType>(data->checked_type());
DataType cast1_dtype = Downcast<TensorType>(cast1->checked_type())->dtype;
if (!IsWidenCast(data_type->dtype, cast1_dtype)) {
// Cannot remove the narrow cast
return post;
}
const CallNode* cast2 = post.as<CallNode>();
DataType cast2_dtype = Downcast<TensorType>(cast2->checked_type())->dtype;
auto expr = MakeCast(data, cast2_dtype);
// We need to set the checked type as it may be needed in the next callback
expr->checked_type_ = TensorType(data_type->shape, cast2_dtype);
return expr;
}
bool IsWidenCast(DataType origin, DataType cast) const {
/* Return whether casting from origin to cast results in more or the same precision.*/
if (origin.code() == cast.code() && origin.bits() <= cast.bits()) {
return true;
}
if (origin.code() == DataType::kBFloat || cast.code() == DataType::kBFloat) {
// BFloat cast cannot be omitted
return false;
}
if (origin.code() < cast.code()) {
// Loosely have a hiearchy to datatypes
// e.g. int --> uint --> float has increasing range of numbers they can represent
return true;
}
return false;
}
protected:
DFPattern data_;
DFPattern cast1_;
};
/*!
* \brief SimplifyTranspose matches the pattern of consecutive transpose op,
* and merges or cancels them.
*/
class SimplifyTranspose : public DFPatternRewrite {
public:
SimplifyTranspose() {
x_ = IsWildcard();
auto trans1 = IsOp("transpose") || IsOp("layout_transform");
auto trans2 = IsOp("transpose") || IsOp("layout_transform");
pattern_ = trans1({trans2({x_})});
}
Expr Callback(const Expr& pre, const Expr& post,
const Map<DFPattern, Array<Expr>>& node_map) const override {
auto x = node_map[x_][0];
Call trans_call = Downcast<Call>(post);
// Try to fuse any rank changing layout transformations
if (auto layout_trans = FoldRankChangingLayoutTrans(x, trans_call)) {
if (auto attr = layout_trans.value()->attrs.as<LayoutTransformAttrs>()) {
// Prune any trivial layout transformation
if (attr->src_layout == attr->dst_layout) {
return x;
}
}
return layout_trans.value();
}
// Initialize axes
int ndim = Downcast<TensorType>(pre->checked_type())->shape.size();
Array<Integer> axes;
for (int i = 0; i < ndim; ++i) {
axes.push_back(i);
}
// Collect axes changes from the matched pattern, including two consecutive transposes.
std::vector<std::vector<int>> interm_axes;
interm_axes.push_back(GetTransposeAxisOrder(trans_call, ndim));
trans_call = Downcast<Call>(trans_call->args[0]);
interm_axes.push_back(GetTransposeAxisOrder(trans_call, ndim));
// Calculate the final axes in reverse order (from root to output)
auto it = interm_axes.rbegin();
while (it != interm_axes.rend()) {
auto interm = *it;
Array<Integer> new_axes;
for (int i = 0; i < ndim; ++i) {
new_axes.push_back(axes[interm[i]]);
}
axes = new_axes;
it++;
}
// Check if the transpose is still required
bool need_transpose = false;
for (int i = 0; i < ndim; ++i) {
if (axes[i] != i) {
need_transpose = true;
break;
}
}
if (need_transpose) {
return MakeTranspose(x, axes);
}
return x;
}
String PermuteLayout(const String& layout, std::vector<int> axes_order) const {
std::string new_layout{};
std::string old_layout{layout};
ICHECK_EQ(axes_order.size(), layout.size())
<< "Number of axes must match the number of named axes in the layout to permute: length("
<< old_layout << ") != " << axes_order.size();
std::stringstream order;
for (auto axis : axes_order) {
new_layout += old_layout[axis];
order << axis << ", ";
}
DLOG(INFO) << "Using transpose axes order {" << order.str()
<< "} to permute layout: " << old_layout << " to " << new_layout;
return new_layout;
}
struct RankChangingLayoutDescriptor {
Layout src_layout;
Layout dst_layout;
// Either a rank changing layout transform or a transpose
Call other_transform;
};
std::unique_ptr<RankChangingLayoutDescriptor> GetRankChangeDescriptor(const Call& call) const {
std::unique_ptr<RankChangingLayoutDescriptor> desc{nullptr};
if (auto attr = call->attrs.as<LayoutTransformAttrs>()) {
if (attr->src_layout.length() != attr->dst_layout.length()) {
desc = std::make_unique<RankChangingLayoutDescriptor>();
desc->src_layout = Layout(attr->src_layout);
desc->dst_layout = Layout(attr->dst_layout);
desc->other_transform = Downcast<Call>(call->args[0]);
}
}
if (auto attr = Downcast<Call>(call->args[0])->attrs.as<LayoutTransformAttrs>()) {
if (attr->src_layout.length() != attr->dst_layout.length()) {
if (!desc) {
desc = std::make_unique<RankChangingLayoutDescriptor>();
desc->src_layout = Layout(attr->src_layout);
desc->dst_layout = Layout(attr->dst_layout);
desc->other_transform = call;
} else {
ICHECK(desc->src_layout->name == attr->dst_layout)
<< "Back-to-back layout transforms must have the same intermediate layout: "
<< desc->src_layout->name << " != " << attr->dst_layout;
desc->src_layout = Layout(attr->src_layout);
}
}
}
return desc;
}
/*
* \brief Fuse call and it's argument into a single layout_transform operator
* when either call or it's argument is a rang changing layout_transform, e.g.,
*
* Simplify
*
* [N, H, W, C] -> Transpose -> [N, C, H, W] -> LayoutTrans -> [N, C, H, W, 4c]
*
* to,
*
* [N, H, W, C] -> LayoutTrans -> [N, C, H, W, 4c].
*
* \param The input expression to the matched pattern
* \param The pattern root; the second of two consecutive Transpose/LayoutTransform ops
*/
Optional<Call> FoldRankChangingLayoutTrans(const Expr& data, const Call& call) const {
// Check to see if either the first or second call in matched pattern
// is a rank changing layout transform. If so, return a descriptor containing
// the layouts and any additional transpose or layout transform op.
auto desc = GetRankChangeDescriptor(call);
if (desc == nullptr) {
// No rank changing layout transform
return Optional<Call>{nullptr};
}
Optional<Expr> output_layout_trans;
// Fuse a rank increasing layout transform and a preceeding transpose
if (desc->src_layout->axes.size() < desc->dst_layout->axes.size()) {
auto axes = GetTransposeAxisOrder(desc->other_transform, desc->src_layout->axes.size());
// Calculate the reverse axis order and apply to the source layout
std::vector<int> inverse(axes.size());
for (size_t i = 0; i < axes.size(); i++) {
inverse[axes[i]] = i;
}
String new_layout = PermuteLayout(desc->src_layout->name, inverse);
output_layout_trans = MakeLayoutTransform(data, new_layout, desc->dst_layout->name);
// Fuse a rank descreasing layout transform followed by a transpose
} else if (desc->src_layout->axes.size() > desc->dst_layout->axes.size()) {
auto axes = GetTransposeAxisOrder(desc->other_transform, desc->dst_layout->axes.size());
String new_layout = PermuteLayout(desc->dst_layout->name, axes);
output_layout_trans = MakeLayoutTransform(data, desc->src_layout->name, new_layout);
// Fuse two back-to-back layout transformations which change rank
} else if (desc->other_transform->attrs.as<LayoutTransformAttrs>()) {
output_layout_trans =
MakeLayoutTransform(data, desc->src_layout->name, desc->dst_layout->name);
}
return Downcast<Call>(output_layout_trans);
}
std::vector<int> GetTransposeAxisOrder(const Call& call, int ndim) const {
std::vector<int> attr_axes;
if (auto attr = call->attrs.as<TransposeAttrs>()) {
if (attr->axes.defined()) {
for (int i = 0; i < ndim; ++i) {
int64_t axis = attr->axes[i].IntValue();
axis += (axis < 0) ? ndim : 0;
attr_axes.push_back(axis);
}
} else {
// Empty axes means reverse
for (int i = ndim - 1; i >= 0; --i) {
attr_axes.push_back(i);
}
}
} else if (auto attr = call->attrs.as<LayoutTransformAttrs>()) {
Layout src_layout(attr->src_layout);
Layout dst_layout(attr->dst_layout);
for (int i = 0; i < ndim; ++i) {
attr_axes.push_back(src_layout.IndexOf(dst_layout[i]));
}
} else {
CHECK(false) << "Expected transpose or layout_transform, but got "
<< Downcast<Op>(call->op)->name;
}
return std::move(attr_axes);
}
private:
/*! \brief Pattern input */
DFPattern x_;
};
/*!
* \brief FullElementwise finds full like ops followed by broadcasting ops, and eliminates
* the full op by directly passing the fill value into the broadcasting op.
*/
class FullElementwise : public DFPatternRewrite {
public:
FullElementwise() {
x_ = IsWildcard();
data_ = IsWildcard();
value_ = IsConstant();
full_ = IsOp("full")({value_}) || IsOp("full_like")({data_, value_});
ones_ = IsOp("ones")({}) || IsOp("ones_like")({data_});
zeros_ = IsOp("zeros")({}) || IsOp("zeros_like")({data_});
Map<String, ObjectRef> attrs;
attrs.Set("TOpPattern", Integer(static_cast<int>(kBroadcast)));
DFPattern op = IsWildcard().HasAttr(attrs);
DFPattern full = full_ || ones_ || zeros_;
pattern_ = op({full, x_}) || op({x_, full});
}
Expr Callback(const Expr& pre, const Expr& post,
const Map<DFPattern, Array<Expr>>& node_map) const override {
const CallNode* call = pre.as<CallNode>();
ICHECK(call);
Type pre_type = pre->checked_type_;
ICHECK(pre_type.as<TensorTypeNode>());
auto dtype = pre_type.as<TensorTypeNode>()->dtype;
auto x = node_map[x_][0];
bool is_left = post.as<CallNode>()->args[1] == x;
Type x_type;
if (is_left) {
x_type = call->args[1]->checked_type_;
} else {
x_type = call->args[0]->checked_type_;
}
if (StructuralEqual()(x_type, pre_type)) {
Expr value;
if (node_map.count(full_)) {
value = node_map[value_][0];
ICHECK(IsConstScalar(value));
} else if (node_map.count(ones_)) {
value = MakeConstantScalar(dtype, 1);
} else if (node_map.count(zeros_)) {
value = MakeConstantScalar(dtype, 0);
} else {
ICHECK(false) << "Didn't find a full op while matching full + elementwise";
}
if (is_left) {
return Call(call->op, {value, x}, call->attrs, call->type_args, call->span);
} else {
return Call(call->op, {x, value}, call->attrs, call->type_args, call->span);
}
}
return post;
}
private:
/*! \brief binary argument */
DFPattern x_;
/*! \brief data ops get shape from */
DFPattern data_;
/*! \brief constant input */
DFPattern value_;
/*! \brief full op */
DFPattern full_;
/*! \brief ones op */
DFPattern ones_;
/*! \brief zeros op */
DFPattern zeros_;
};
/*!
* \brief Converts `*_like` operators to their explicit shape equivalent (e.g. `zeros_like(x, y)` to
* `zeros(x, y.shape)`), when the target shape is concrete. This removes unnecessary dependencies
* and can enable more opportunities for operator fusion.
*/
class ConcretizeLikeRewrite : public DFPatternRewrite {
public:
explicit ConcretizeLikeRewrite(const Op& op) {
ICHECK(op->num_inputs == 1 || op->num_inputs == 2)
<< "ConcretizeLike does not handle operators that aren't unary or binary, got: " << op;
like_pat_ = IsWildcard();
data_pat_ = IsWildcard();
if (op->num_inputs == 1) {
pattern_ = IsExpr(op)({like_pat_});
} else {
pattern_ = IsExpr(op)({data_pat_, like_pat_});
}
}
virtual bool Check(const Expr& pre, const Expr& post,
const Map<DFPattern, Array<Expr>>& node_map) const {
const CallNode* call_node = pre.as<CallNode>();
ICHECK(call_node);
if (!call_node->checked_type().as<TensorTypeNode>()) {
return false;
}
return true;
}
virtual Expr Concretize(const Map<DFPattern, Array<Expr>>& node_map, Array<Integer> shape,
DataType dtype) const = 0;
Expr Callback(const Expr& pre, const Expr& post,
const Map<DFPattern, Array<Expr>>& node_map) const override {
if (!Check(pre, post, node_map)) {
return post;
}
const TensorTypeNode* like_ty = pre->checked_type().as<TensorTypeNode>();
Array<Integer> cshape;
for (const auto& dim : like_ty->shape) {
if (const auto* imm = dim.as<IntImmNode>()) {
cshape.push_back(Integer(GetRef<IntImm>(imm)));
} else {
// shape is not static, don't concretize
return post;
}
}
return Concretize(node_map, cshape, like_ty->dtype);
}
protected:
DFPattern data_pat_;
DFPattern like_pat_;
};
class ConcretizeZerosLikeRewrite : public ConcretizeLikeRewrite {
public:
ConcretizeZerosLikeRewrite() : ConcretizeLikeRewrite(Op::Get("zeros_like")) {}
Expr Concretize(const Map<DFPattern, Array<Expr>>& node_map, Array<Integer> shape,
DataType dtype) const override {
return MakeZeros(shape, dtype);
}
};
class ConcretizeOnesLikeRewrite : public ConcretizeLikeRewrite {
public:
ConcretizeOnesLikeRewrite() : ConcretizeLikeRewrite(Op::Get("ones_like")) {}
Expr Concretize(const Map<DFPattern, Array<Expr>>& node_map, Array<Integer> shape,
DataType dtype) const override {
return MakeOnes(shape, dtype);
}
};
class ConcretizeFullLikeRewrite : public ConcretizeLikeRewrite {
public:
ConcretizeFullLikeRewrite() : ConcretizeLikeRewrite(Op::Get("full_like")) {}
Expr Concretize(const Map<DFPattern, Array<Expr>>& node_map, Array<Integer> shape,
DataType dtype) const override {
// `like_pat_` here is `fill_value`
return MakeFull(node_map[like_pat_][0], shape, dtype);
}
};
class ConcretizeReshapeLikeRewrite : public ConcretizeLikeRewrite {
public:
ConcretizeReshapeLikeRewrite() : ConcretizeLikeRewrite(Op::Get("reshape_like")) {}
Expr Concretize(const Map<DFPattern, Array<Expr>>& node_map, Array<Integer> shape,
DataType dtype) const override {
return MakeReshape(node_map[data_pat_][0], shape);
}
};
class ConcretizeCollapseSumLikeRewrite : public ConcretizeLikeRewrite {
public:
ConcretizeCollapseSumLikeRewrite() : ConcretizeLikeRewrite(Op::Get("collapse_sum_like")) {}
Expr Concretize(const Map<DFPattern, Array<Expr>>& node_map, Array<Integer> shape,
DataType dtype) const override {
ICHECK_LE(shape.size(), std::numeric_limits<int64_t>::max());
static const Op& op = Op::Get("collapse_sum_to");
auto attrs = make_object<InitOpAttrs>();
attrs->shape = shape;
std::vector<int64_t> s;
std::transform(shape.begin(), shape.end(), std::back_inserter(s),
[](Integer i) { return i.IntValue(); });
auto cshape = MakeConstantTensor(DataType::Int(32), {static_cast<int64_t>(shape.size())}, s);
return Call(op, {node_map[data_pat_][0], cshape}, Attrs(attrs));
}
};
class ConcretizeBroadcastToLikeRewrite : public ConcretizeLikeRewrite {
public:
ConcretizeBroadcastToLikeRewrite() : ConcretizeLikeRewrite(Op::Get("broadcast_to_like")) {}
Expr Concretize(const Map<DFPattern, Array<Expr>>& node_map, Array<Integer> shape,
DataType dtype) const override {
return MakeBroadCastTo(node_map[data_pat_][0], shape);
}
};
/*! \brief Eliminates expressions that are equivalent to identity. */
class EliminateIdentityRewrite : public DFPatternRewrite {
public:
EliminateIdentityRewrite() {
x_ = IsWildcard();
const_ = IsConstant();
DFPattern add_op = IsOp("add");
DFPattern mul_op = IsOp("multiply");
DFPattern zeros_expr = IsOp("zeros")({}) || IsOp("zeros_like")({IsWildcard()}) || const_;
DFPattern ones_expr = IsOp("ones")({}) || IsOp("ones_like")({IsWildcard()}) || const_;
// add and multiply are commutative so we don't need another pattern for reversed args
DFPattern add_id = add_op({x_, zeros_expr});
DFPattern mul_id = mul_op({x_, ones_expr});
DFPattern sub_id = IsOp("subtract")({x_, zeros_expr});
DFPattern div_id = IsOp("divide")({x_, ones_expr});
pattern_ = add_id || mul_id || sub_id || div_id;
}
bool CheckConstant(const OpNode* op, const ConstantNode* constant) const {
if (!IsScalar(GetRef<Expr>(constant))) {
return false;
}
auto value = TryToScalar(constant->data, 0);
if (!value) {
// unsupported dtype
return false;
}
if (op->name == "add" || op->name == "subtract") {
return value.value() == 0.0;
} else if (op->name == "multiply" || op->name == "divide") {
return value.value() == 1.0;
}
return false;
}
Expr Callback(const Expr& pre, const Expr& post,
const Map<DFPattern, Array<Expr>>& node_map) const override {
const CallNode* call = pre.as<CallNode>();
ICHECK(call);
Type pre_type = pre->checked_type_;
ICHECK(pre_type.as<TensorTypeNode>());
auto x = node_map[x_][0];
bool is_left = post.as<CallNode>()->args[1] == x;
Type x_type;
if (is_left) {
x_type = call->args[1]->checked_type_;
} else {
x_type = call->args[0]->checked_type_;
}
if (node_map.count(const_)) {
// the other argument is a Constant in this case
const ConstantNode* constant = node_map[const_][0].as<ConstantNode>();
const OpNode* op = call->op.as<OpNode>();
ICHECK(constant);
ICHECK(op);
if (!CheckConstant(op, constant)) {
return post;
}
}
if (StructuralEqual()(x_type, pre_type)) {
return x;
}
return post;
}
private:
DFPattern x_;
DFPattern const_;
};
/*! \brief Make two consecutive add able to be constant_folded.
* This pattern matching supports commutative property for addition.
*/
class SimplifyConsecutiveAdd : public DFPatternRewrite {
public:
SimplifyConsecutiveAdd() {
x_ = IsWildcard();
const1_ = IsConstant();
const2_ = IsConstant();
DFPattern add_op = IsOp("add");
pattern_ = add_op({add_op({x_, const1_}), const2_});
}
Expr Callback(const Expr& pre, const Expr& post,
const Map<DFPattern, Array<Expr>>& node_map) const override {
const CallNode* call = pre.as<CallNode>();
auto x = node_map[x_][0];
auto c1 = node_map[const1_][0];
auto c2 = node_map[const2_][0];
auto pre_call = call;
// Find the next add call.
if (pre_call->args[1].as<ConstantNode>()) {
pre_call = pre_call->args[0].as<CallNode>();
} else {
pre_call = pre_call->args[1].as<CallNode>();
}
// Do nothing if both inputs are not constants as they will be constant folded already.
if (pre_call->args[0].as<ConstantNode>() && pre_call->args[1].as<ConstantNode>()) {
return post;
} else {
auto add_res = Call(call->op, {c1, c2});
return Call(call->op, {x, add_res});
}
return post;
}
private:
DFPattern x_;
DFPattern const1_;
DFPattern const2_;
};
class SimplifyRSqrt : public DFPatternRewrite {
public:
SimplifyRSqrt() {
x_ = IsWildcard();
numerator_ = IsWildcard();
auto sqrt = IsOp("sqrt");
pattern_ = IsOp("divide")({numerator_, sqrt({x_})});
}
Expr Callback(const Expr& pre, const Expr& post,
const Map<DFPattern, Array<Expr>>& node_map) const override {
static const Op& op = Op::Get("rsqrt");
auto x = node_map[x_][0];
auto numerator = node_map[numerator_][0];
return Call(Op::Get("multiply"), {numerator, Call(op, {x})});
}
private:
/*! \brief Pattern input */
DFPattern x_;
DFPattern numerator_;
};
Expr SimplifyExpr(const Expr& expr, const IRModule& mod) {
// the rewrites will be applied in the given order, and repeated until fixed point
DFPatternRewriteComposer composer;
composer.AddRewrite<ConcretizeZerosLikeRewrite>();
composer.AddRewrite<ConcretizeOnesLikeRewrite>();
composer.AddRewrite<ConcretizeFullLikeRewrite>();
composer.AddRewrite<ConcretizeReshapeLikeRewrite>();
composer.AddRewrite<ConcretizeCollapseSumLikeRewrite>();
composer.AddRewrite<ConcretizeBroadcastToLikeRewrite>();
composer.AddRewrite<SimplifyRSqrt>();
composer.AddRewrite<EliminateIdentityRewrite>();
composer.AddRewrite<SimplifyReshape>();
composer.AddRewrite<SimplifyTranspose>();
composer.AddRewrite<SimplifySameCast>();
composer.AddRewrite<SimplifyConsecutiveCast>();
composer.AddRewrite<FullElementwise>();
composer.AddRewrite<SimplifyConsecutiveAdd>();
return RewritePatterns(composer.MakeCallbacks(), expr, mod);
}
namespace transform {
Pass SimplifyExpr() {
runtime::TypedPackedFunc<Function(Function, IRModule, PassContext)> pass_func =
[=](Function f, IRModule m, PassContext pc) {
return Downcast<Function>(SimplifyExpr(f, m));
};
return CreateFunctionPass(pass_func, 0, "SimplifyExpr", {"InferType"});
}
TVM_REGISTER_GLOBAL("relay._transform.SimplifyExpr").set_body_typed(SimplifyExpr);
} // namespace transform
} // namespace relay
} // namespace tvm