blob: 079b790e74c0100b5c349aae24fcbd425c24bb43 [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 lazy_gradient_init.cc
*
* \brief Lazily instantiate 0-filled or 1-filled tensors.
* This pass should be used after reverse-mode ad so that gradient tensors
* are not instantiated until after the forward pass.
*
* This pass delays or removes memory allocation by converting tensors into
* GradCell, an algebraic data type defined in gradient.rly.
*
* This will delay or decrease memory usage. All calls to
* ones, ones_like, zeros, zeros_like will call the One or Zero constructor
* of GradCell, which will not instantiate in memory until needed. All other cases result
* in using the Raw constructor which means the tensor is instantiated in memory.
*
* It also overloads + and * operation which can increase performance when doing
* operations involving tensors with values of only 0 or 1.
*
* Note: this pass can only be used with functions where the input/output types are
* a combination of TupleTypes and TensorTypes
*
* This pass optimizes 6 ops:
* - add
* - multiply
* - ones
* - ones_like
* - zeros
* - zeros_like
*
* This pass makes use of three visitor. The most important one visits the entire function,
* one is used for wrap inputs and one to unwrap outputs.
*
* For example:
* fn: TensorType[(10,10), float32] -> TensorType[(10,10), float32]
*
* After this pass
* fn: GradCell[TensorType[(10,10), float32]] -> GradCell[TensorType[(10,10), float32]]
*
* Thus, it is necessary to wrap this outer function so that the input/output types remain the same
*/
#include <tvm/ir/type_functor.h>
#include <tvm/node/structural_equal.h>
#include <tvm/relay/analysis.h>
#include <tvm/relay/expr_functor.h>
#include <tvm/relay/feature.h>
#include <tvm/relay/transform.h>
#include "let_list.h"
namespace tvm {
namespace relay {
class LazyGradientInitializer : public ExprMutator, public TypeMutator {
public:
explicit LazyGradientInitializer(IRModule module) : module_(module) {
module_->ImportFromStd("gradient.rly");
}
Expr WrapExpr(const Var& var, const Type& type, LetList* ll) {
if (type.as<TensorTypeNode>()) {
return Call(module_->GetConstructor("GradCell", "Raw"), {var}, Attrs(), {type});
} else if (auto* type_anno = type.as<TupleTypeNode>()) {
tvm::Array<Expr> fields;
for (size_t i = 0; i < type_anno->fields.size(); i++) {
const Type& t = type_anno->fields[i];
fields.push_back(WrapExpr(ll->Push(TupleGetItem(var, i)), t, ll));
}
Expr tuple = Tuple(fields);
return tuple;
}
return var;
}
Expr UnwrapExpr(const Var& var, const Type& type, LetList* ll) {
if (auto* type_call = type.as<TypeCallNode>()) {
if (type_call->func.same_as(module_->GetGlobalTypeVar("GradCell"))) {
return Call(module_->GetGlobalVar("FromGradCell"), {var});
}
return var;
} else if (auto* type_anno = type.as<TupleTypeNode>()) {
tvm::Array<Expr> fields;
for (size_t i = 0; i < type_anno->fields.size(); i++) {
const Type& t = type_anno->fields[i];
fields.push_back(UnwrapExpr(ll->Push(TupleGetItem(var, i)), t, ll));
}
Expr tuple = Tuple(fields);
return tuple;
}
return var;
}
// Turn off memo for constant node.
Expr VisitExpr(const Expr& e) final {
if (e.as<ConstantNode>()) {
return ExprFunctor::VisitExpr(e);
} else {
return ExprMutator::VisitExpr(e);
}
}
/*!
* \brief apply LazyGradientInit transformation and wrap function
* so that function type stays the same
*
* input/output types should only be a combination of TupleTypes and TensorTypes
*/
Expr Transform(const Expr& e) {
auto* f = e.as<FunctionNode>();
auto* transformed = this->Mutate(e).as<FunctionNode>();
ICHECK(f);
ICHECK(transformed);
if (e.same_as(GetRef<Function>(transformed))) {
return GetRef<Function>(transformed);
}
auto tensorOutput = LetList::With([&](LetList* ll) {
// wrap inputs of Tensor type using InputVisitor class
tvm::Array<Expr> args;
for (const Var& var : f->params) {
args.push_back(WrapExpr(var, var->checked_type(), ll));
}
Expr transformedExpr = Call(GetRef<Function>(transformed), args);
// unwrap outputs of GradCell type into Tensor type using OutputVisitor class
return UnwrapExpr(ll->Push(transformedExpr), transformed->ret_type, ll);
});
return Function(f->params, tensorOutput, f->ret_type, Array<TypeVar>());
}
Expr VisitExpr_(const ConstantNode* op) final {
return Call(module_->GetConstructor("GradCell", "Raw"), {GetRef<Constant>(op)}, Attrs(),
{op->checked_type()});
}
Expr VisitExpr_(const CallNode* call_node) final {
if (auto* op = (call_node->op).as<OpNode>()) {
Expr op_expr = GetRef<Op>(op);
if (op_expr == Op::Get("add")) {
return CallGradCellFunction(call_node, module_->GetGlobalVar("AddGradCell"));
}
if (op_expr == Op::Get("multiply")) {
return CallGradCellFunction(call_node, module_->GetGlobalVar("MultiplyGradCell"));
}
if (op_expr == Op::Get("ones") || op_expr == Op::Get("zeros")) {
// ones and zeros need TensorType input
Expr result = CallPrimitiveOp(call_node);
Expr func = Function({}, result, {call_node->checked_type()}, Array<TypeVar>());
// call appropriate GradCell constructor
std::string constructor_name = op_expr == Op::Get("ones") ? "One" : "Zero";
return Call(module_->GetConstructor("GradCell", constructor_name), {func}, Attrs(),
{call_node->checked_type()});
}
if (op_expr == Op::Get("ones_like") || op_expr == Op::Get("zeros_like")) {
// ones_like and zeros_like need TensorType input
Expr result = CallPrimitiveOp(call_node);
// fn() -> T, function returns result of operation
Expr func = Function({}, result, {call_node->checked_type()}, Array<TypeVar>());
// call appropriate GradCell constructor
std::string constructor_name = op_expr == Op::Get("ones_like") ? "One" : "Zero";
return Call(module_->GetConstructor("GradCell", "One"), {func}, Attrs(),
{call_node->checked_type()});
}
// handle all other ops
Expr result = CallPrimitiveOp(call_node);
// wrap result with Raw constructor
return Call(module_->GetConstructor("GradCell", "Raw"), {result}, Attrs(),
{call_node->checked_type()});
}
// not an op
return ExprMutator::VisitExpr_(call_node);
}
Type VisitType(const Type& t) final { return TypeMutator::VisitType(t); }
Type VisitType_(const TensorTypeNode* op) {
GlobalTypeVar gradCell = module_->GetGlobalTypeVar("GradCell");
tvm::Array<Type> args;
args.push_back(GetRef<TensorType>(op));
return TypeCall(gradCell, args);
}
private:
// Module
IRModule module_;
/*!
* \brief Convert call_node to add/multiply op to use overloaded functions for GradCell type
*/
Expr CallGradCellFunction(const CallNode* call_node, GlobalVar overloaded_op) {
// can only use overloaded functions if 2 arguments of same type
if (call_node->args.size() != 2 ||
!tvm::StructuralEqual()(call_node->args[0]->checked_type(),
call_node->args[1]->checked_type())) {
Expr result = CallPrimitiveOp(call_node);
return Call(module_->GetConstructor("GradCell", "Raw"), {result}, Attrs(),
{call_node->checked_type()});
}
tvm::Array<Expr> args;
// create "fallback" function for overloaded function
Type paramType = call_node->args[0]->checked_type();
tvm::Array<Var> params = {Var("lhs", paramType), Var("rhs", paramType)};
// use primitive op in this case
Expr callOp = Call(call_node->op, {params[0], params[1]});
Expr func = Function(params, callOp, paramType, Array<TypeVar>());
// pass "fallback" function and tensors as arguments
args.push_back(func);
for (Expr expr : call_node->args) {
args.push_back(VisitExpr(expr));
}
// return new call to overloaded function
return Call(overloaded_op, args, Attrs(), {paramType});
}
/*!
* \brief Convert calls to other ops by converting args into TensorType
* \return call expr returning result of op
*/
Expr CallPrimitiveOp(const CallNode* call_node) {
const auto fromFunc = module_->GetGlobalVar("FromGradCell");
tvm::Array<Expr> args;
// use FromGradCell to convert args to Tensor
for (Expr expr : call_node->args) {
args.push_back(Call(fromFunc, {VisitExpr(expr)}, Attrs(), {expr->checked_type()}));
}
// result of operation
return Call(call_node->op, args, call_node->attrs);
}
};
Expr LazyGradientInit(const Expr& e, IRModule mod) {
CheckFeature(e, mod, FeatureSet::All() - fGraph);
auto ret = LazyGradientInitializer(mod).Transform(e);
CheckFeature(ret, mod, FeatureSet::All() - fGraph);
return ret;
}
namespace transform {
Pass LazyGradientInit() {
runtime::TypedPackedFunc<Function(Function, IRModule, PassContext)> pass_func =
[=](Function f, IRModule m, PassContext pc) {
return Downcast<Function>(LazyGradientInit(f, m));
};
return CreateFunctionPass(pass_func, 2, "LazyGradientInit", {});
}
TVM_REGISTER_GLOBAL("relay._transform.LazyGradientInit").set_body_typed(LazyGradientInit);
} // namespace transform
} // namespace relay
} // namespace tvm