This is an automated email from the ASF dual-hosted git repository.
lunderberg pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new 7789b248fb [Unity][Transform] Handle symbolic variables in LambdaLift
(#16411)
7789b248fb is described below
commit 7789b248fbe0776fc29efdc0d772a0deeb95f8a3
Author: Eric Lunderberg <[email protected]>
AuthorDate: Tue Jan 23 10:09:34 2024 -0600
[Unity][Transform] Handle symbolic variables in LambdaLift (#16411)
* [Unity][Transform] Handle symbolic variables in LambdaLift
Prior to this commit, symbolic variables used by a lambda function
would be duplicated between the caller and the lifted-out function.
In addition, shape inference within the lifted-out function was
performed without access to the symbolic variables, resulting in
unnecessary fallback from `R.Tensor([m, n])` to `R.Tensor(ndim=2)`.
This commit updates the `LambdaLift` transform to handle symbolic
variables. All symbolic variables have unique definitions across the
resulting `IRModule`, and shape inference in the lifted-out function
is aware of symbolic variables that have been exposed to it.
* Cleanup based on review comments
---
src/relax/transform/lambda_lift.cc | 381 +++++++++++------------
tests/python/relax/test_transform_lambda_lift.py | 150 +++++++--
2 files changed, 307 insertions(+), 224 deletions(-)
diff --git a/src/relax/transform/lambda_lift.cc
b/src/relax/transform/lambda_lift.cc
index c7caeab055..16bd8bfc91 100644
--- a/src/relax/transform/lambda_lift.cc
+++ b/src/relax/transform/lambda_lift.cc
@@ -236,95 +236,24 @@ class LambdaLifter : public ExprMutator {
using ExprMutator::VisitExpr_;
- void VisitBinding_(const VarBindingNode* binding) final {
- bool is_lambda = binding->value->IsInstance<FunctionNode>();
- if (is_lambda) {
- recur_vars_.push_back(binding->var);
- }
-
- Expr new_value = this->VisitExpr(binding->value);
+ void VisitBinding_(const VarBindingNode* binding, const FunctionNode*
func_node) final {
+ auto cache = current_lambda_var_;
+ current_lambda_var_ = binding->var;
- if (new_value->struct_info_.defined() &&
- !new_value->struct_info_.same_as(binding->var->struct_info_)) {
- binding->var->struct_info_ = GetStructInfo(new_value);
- binding->var->checked_type_ = new_value->checked_type_;
- }
- if (new_value.same_as(binding->value)) {
- builder_->EmitNormalized(GetRef<VarBinding>(binding));
- } else {
- builder_->EmitNormalized(VarBinding(binding->var, new_value));
- }
- if (is_lambda) {
- recur_vars_.pop_back();
+ auto new_value = VisitExpr(binding->value);
+ if (!rebind_map_.count(binding->var)) {
+ ReEmitBinding(binding, new_value);
}
- }
-
- Expr VisitExpr_(const CallNode* call_node) final {
- auto call = Downcast<Call>(ExprMutator::VisitExpr_(call_node));
- if (const auto* var_node = call_node->op.as<VarNode>()) {
- auto var = GetRef<Var>(var_node);
- bool has_closure = HasClosure(var);
- auto val = builder_->LookupBinding(var);
- if (const auto* fsinfo_node =
GetStructInfo(var).as<FuncStructInfoNode>()) {
- auto fsinfo = GetRef<FuncStructInfo>(fsinfo_node);
- if (!GetStructInfo(call).same_as(fsinfo)) {
- call->struct_info_ = fsinfo->ret;
- call->checked_type_ = GetStaticType(fsinfo->ret);
- }
- }
- // Call "relax.invoke_closure" to invoke closure
- Var clo_arg = var;
- if (has_closure && val->IsInstance<CallNode>()) {
- if (this->var_remap_.find(var->vid) != this->var_remap_.end()) {
- clo_arg = this->var_remap_.at(var->vid);
- }
- // if the original op was pure, we should use invoke_pure_closure
- Call orig_call = Downcast<Call>(val);
- bool purity;
- if (orig_call->op.as<OpNode>()) {
- auto orig_op = Downcast<Op>(orig_call->op);
- static const auto& purity_map = Op::GetAttrMap<Bool>("FPurity");
- purity = purity_map.count(orig_op) && purity_map[orig_op]->value;
- } else {
- purity = GetStructInfoAs<FuncStructInfoNode>(orig_call->op)->purity;
- }
-
- return Call(purity ? invoke_pure_closure_op_ : invoke_closure_op_,
- {clo_arg, Tuple(call_node->args)}, {},
- {GetStructInfo(GetRef<Expr>(call_node))});
- }
- auto it = lambda_map_.find(var);
- if (it != lambda_map_.end()) {
- // flatten nested call, e.g. call(y)(x) -> call(x, y))
- Array<relay::Expr> new_args;
- Array<StructInfo> params;
- for (const auto arg : call->args) {
- new_args.push_back(arg);
- params.push_back(StructInfoFromType(arg->checked_type()));
- }
- if (const auto* nest_call = it->second.as<CallNode>()) {
- // Update the StructInfo accordingly
- for (const auto arg : nest_call->args) {
- new_args.push_back(arg);
- params.push_back(StructInfoFromType(arg->checked_type()));
- }
- StructInfo new_func_sinfo;
- if (const auto* fsinfo =
GetStructInfo(nest_call->op).as<FuncStructInfoNode>()) {
- auto func_sinfo = GetRef<FuncStructInfo>(fsinfo);
- new_func_sinfo = FuncStructInfo(params, func_sinfo->ret);
- }
- nest_call->op->struct_info_ = new_func_sinfo;
- nest_call->op->checked_type_ = GetStaticType(new_func_sinfo);
- return Call(nest_call->op, new_args, call_node->attrs,
call_node->sinfo_args);
- }
- return Call(it->second, call->args, call_node->attrs,
call_node->sinfo_args);
- }
- }
- return std::move(call);
+ current_lambda_var_ = cache;
}
Expr VisitExpr_(const FunctionNode* func_node) final {
+ if (!current_lambda_var_) {
+ // Early bail-out for top-level functions
+ return ExprMutator::VisitExpr_(func_node);
+ }
+
auto func = GetRef<Function>(func_node);
String lift_func_name = [&]() {
@@ -336,20 +265,19 @@ class LambdaLifter : public ExprMutator {
return it->second;
}();
- auto global = GlobalVar(lift_func_name);
- Array<Var> free_vars = FreeVars(func);
Array<Var> captured_vars;
-
- Array<Var> typed_captured_vars;
- bool recursive = false;
- for (const auto& var : free_vars) {
- if (!recur_vars_.empty() && var == recur_vars_.back()) {
- recursive = true;
+ bool is_recursive = false;
+ bool is_closure = false;
+ for (const auto& var : FreeVars(func)) {
+ if (var.same_as(current_lambda_var_)) {
+ is_recursive = true;
} else {
+ is_closure = true;
captured_vars.push_back(var);
}
}
+ Array<Var> typed_captured_vars;
Map<Var, Expr> rebinding_map;
for (auto free_var : captured_vars) {
Var var = Var(free_var->name_hint(), GetStructInfo(free_var),
free_var->span);
@@ -357,155 +285,207 @@ class LambdaLifter : public ExprMutator {
rebinding_map.Set(free_var, var);
}
- // recursive call
- if (recursive) {
- if (!captured_vars.empty()) {
- Array<Expr> fvs;
- for (auto fv : captured_vars) {
- fvs.push_back(fv);
- }
- // it is required by block_blocker, will be updated later
- UpdateStructInfo(global, GetStructInfo(recur_vars_.back()));
- lambda_map_.emplace(recur_vars_.back(), Call(global, fvs));
- } else {
- if (recur_vars_.size() > 0) {
- lambda_map_.emplace(recur_vars_.back(), global);
- }
- }
+ tvm::Array<Var> lifted_func_params =
+ func_node->params.Map([this](Var var) { return VisitVarDef(var); });
+ for (const auto& var : typed_captured_vars) {
+ lifted_func_params.push_back(var);
}
- tvm::Array<Var> params;
- bool all_params_unchanged = true;
- for (Var param : func_node->params) {
- Var new_param = this->VisitVarDef(param);
- params.push_back(new_param);
- all_params_unchanged &= param.same_as(new_param);
+ auto gvar_lifted_func = GlobalVar(lift_func_name);
+ {
+ auto func_sinfo = Downcast<FuncStructInfo>(func_node->struct_info_);
+ if (is_closure) {
+ func_sinfo = FuncStructInfo(lifted_func_params.Map(GetStructInfo),
func_sinfo->ret,
+ func_sinfo->purity);
+ }
+ UpdateStructInfo(gvar_lifted_func, func_sinfo);
}
- Expr body = this->VisitWithNewScope(func_node->body);
- Expr visited_func;
+ Expr body = func_node->body;
- if (all_params_unchanged && body.same_as(func_node->body)) {
- visited_func = GetRef<Expr>(func_node);
- } else if (const auto& body_sinfo =
MatchStructInfo<ObjectStructInfo>(body)) {
- visited_func =
- Function(params, body, body_sinfo.value(), func_node->is_pure,
func_node->attrs);
- } else {
- visited_func =
- Function(params, body, func_node->ret_struct_info,
func_node->is_pure, func_node->attrs);
+ // Defining the rewrite rule prior to visiting the body, so that
+ // recursive closures can be updated.
+ if (is_recursive && is_closure) {
+ nested_closure_map_.emplace(
+ current_lambda_var_.value(),
+ Call(gvar_lifted_func, captured_vars.Map([](Var var) -> Expr {
return var; })));
}
- auto new_func = Downcast<Function>(visited_func);
- Function lifted_func;
- bool is_closure = IsClosure(captured_vars);
if (!is_closure) {
- lifted_func = Function(
- /*params=*/new_func->params,
- /*body=*/new_func->body,
- /*ret_struct_info=*/new_func->ret_struct_info,
- /*is_pure=*/new_func->is_pure,
- /*attrs=*/new_func->attrs,
- /*span=*/new_func->span);
- } else {
- // Flatten the Closure
- std::vector<Var> closure_params;
- closure_params.reserve(func->params.size() + typed_captured_vars.size());
- for (size_t i = 0; i < func->params.size(); ++i) {
- closure_params.emplace_back(func->params[i]);
- }
- for (size_t i = 0; i < typed_captured_vars.size(); ++i) {
- closure_params.emplace_back(typed_captured_vars[i]);
- }
+ rebind_map_.emplace(current_lambda_var_.value(), gvar_lifted_func);
+ }
- lifted_func = Function(/*params=*/closure_params,
- /*body=*/Bind(new_func->body, rebinding_map),
- /*ret_struct_info=*/new_func->ret_struct_info,
- /*is_pure=*/new_func->is_pure,
- /*attrs=*/new_func->attrs,
- /*span=*/func->span);
+ body = this->VisitWithNewScope(body, lifted_func_params);
+ StructInfo ret_struct_info = GetStructInfo(body);
+ body = Bind(body, rebinding_map);
- for (Var param : closure_params) {
- CHECK(param->checked_type_.defined())
- << "relax.Function requires params to contain checked_type_";
- }
+ Function lifted_func;
+ if (lifted_func_params.same_as(func_node->params) &&
body.same_as(func_node->body) &&
+ ret_struct_info.same_as(func_node->ret_struct_info)) {
+ lifted_func = GetRef<Function>(func_node);
+ } else {
+ lifted_func =
+ Function(lifted_func_params, body, ret_struct_info,
func_node->is_pure, func_node->attrs);
+ }
+
+ for (Var param : lifted_func->params) {
+ CHECK(param->checked_type_.defined())
+ << "relax.Function requires all parameters to contain checked_type_.
"
+ << "However, parameter " << param << " with struct info " <<
param->struct_info_
+ << " has no checked type";
}
ICHECK(lifted_func.defined());
+ if (is_closure || IsClosure(lifted_func)) {
+ closures_.insert(gvar_lifted_func);
+ }
+
// Add the lifted function to the module.
- global->struct_info_ = GetStructInfo(lifted_func);
- global->checked_type_ = lifted_func->checked_type_;
- builder_->UpdateFunction(global, lifted_func);
+ lifted_func = CopyWithNewVars(lifted_func);
+ gvar_lifted_func->struct_info_ = GetStructInfo(lifted_func);
+ gvar_lifted_func->checked_type_ = lifted_func->checked_type_;
- if (!is_closure) {
- return std::move(global);
- } else {
+ builder_->UpdateFunction(gvar_lifted_func, lifted_func);
+
+ Expr callable_value = gvar_lifted_func;
+ if (is_closure) {
// If we need to allocate a closure,
// we pass the variables in its environment here.
- Array<Expr> fvs;
- for (auto fv : captured_vars) {
- fvs.push_back(fv);
- }
+ Tuple arg_tuple(captured_vars.Map([](Var var) -> Expr { return var; }));
// Call make_closure intrinsic
- return Call(make_closure_op_, {global, Tuple(fvs)}, {}, {});
+ callable_value = Call(make_closure_op_, {gvar_lifted_func, arg_tuple},
{}, {});
}
+
+ return callable_value;
}
- bool HasClosure(const Var& var) {
- auto val = builder_->LookupBinding(var);
- if (const auto* value = val.as<GlobalVarNode>()) {
- IRModule ctx_mod = builder_->GetContextIRModule();
- ICHECK(ctx_mod->functions.size() > 0);
- BaseFunc func = ctx_mod->Lookup(GetRef<GlobalVar>(value));
- if (const auto* func_node = func.as<FunctionNode>()) {
- if (const auto* call_node = func_node->body.as<CallNode>()) {
- if (call_node->op == make_closure_op_) {
- return true;
- }
- } else if (const auto* seq_expr_node =
func_node->body.as<SeqExprNode>()) {
- // the return var points to a make_closure intrinsic
- if (const auto* var = seq_expr_node->body.as<VarNode>()) {
- return HasClosure(GetRef<Var>(var));
+ Expr VisitExpr_(const CallNode* call_node) final {
+ auto call = GetRef<Call>(call_node);
+
+ auto orig_sinfo = Downcast<StructInfo>(call->struct_info_);
+
+ if (auto opt_var = call->op.as<Var>()) {
+ auto var = opt_var.value();
+
+ // Call "relax.invoke_closure" to invoke closure
+
+ if (IsClosure(var) && builder_->LookupBinding(var).as<CallNode>()) {
+ // if the original op was pure, we should use invoke_pure_closure
+ Call orig_call = Downcast<Call>(builder_->LookupBinding(var));
+ bool is_pure = [&]() -> bool {
+ if (auto op = orig_call->op.as<Op>()) {
+ static const auto& purity_map = Op::GetAttrMap<Bool>("FPurity");
+ return purity_map.get(op.value(), Bool(false))->value;
+ } else if (const auto* func_sinfo =
+ orig_call->op->struct_info_.as<FuncStructInfoNode>())
{
+ return func_sinfo->purity;
+ } else {
+ LOG(FATAL) << "Could not determine purity of call to " <<
orig_call->op
+ << ", as it is neither a tvm::Op (type = \"" <<
orig_call->op->GetTypeKey()
+ << "\"), "
+ << "nor is is annotated with FuncStructInfo (sinfo = "
+ << orig_call->op->struct_info_ << ")";
}
- }
+ }();
+
+ auto prev = call;
+ call = Call(is_pure ? invoke_pure_closure_op_ : invoke_closure_op_,
+ {var, Tuple(call->args)}, {}, {orig_sinfo});
}
- } else if (const auto* func_node = val.as<FunctionNode>()) {
- if (const auto* call_node = func_node->body.as<CallNode>()) {
- if (call_node->op == make_closure_op_) {
- return true;
+ }
+
+ if (auto opt_var = call->op.as<Var>()) {
+ auto var = opt_var.value();
+ if (auto it = nested_closure_map_.find(var); it !=
nested_closure_map_.end()) {
+ Call nested_call = it->second;
+
+ Array<relay::Expr> new_args = call->args;
+ for (const auto arg : nested_call->args) {
+ new_args.push_back(arg);
}
+
+ auto prev = call;
+ call = Call(nested_call->op, new_args, call->attrs, call->sinfo_args);
+ }
+ }
+
+ return ExprMutator::VisitExpr_(call.get());
+ }
+
+ Expr VisitExpr_(const VarNode* op) override {
+ auto var = GetRef<Var>(op);
+ if (auto it = rebind_map_.find(var); it != rebind_map_.end()) {
+ return it->second;
+ }
+ return ExprMutator::VisitExpr_(op);
+ }
+
+ bool IsClosure(Expr val) {
+ if (auto opt_var = val.as<Var>()) {
+ if (closures_.count(opt_var.value())) {
+ return true;
}
- } else if (const auto* call_node = val.as<relax::CallNode>()) {
+ if (auto bound_value = builder_->LookupBinding(opt_var.value())) {
+ val = bound_value.value();
+ }
+ }
+
+ if (const auto* call_node = val.as<relax::CallNode>()) {
// recursive call
auto op = call_node->op;
- if (make_closure_op_ == op) {
+ if (auto local_var = op.as<Var>()) {
+ return IsClosure(local_var.value());
+ } else if (auto global_var = op.as<GlobalVar>()) {
+ return IsClosure(global_var.value());
+ } else {
+ return make_closure_op_ == op;
+ }
+
+ } else if (const auto* global_var = val.as<GlobalVarNode>()) {
+ if (closures_.count(GetRef<GlobalVar>(global_var))) {
return true;
}
- if (const auto* lv = op.as<VarNode>()) {
- return HasClosure(GetRef<Var>(lv));
+ IRModule ctx_mod = builder_->GetContextIRModule();
+ ICHECK(ctx_mod->functions.size() > 0);
+ BaseFunc func = ctx_mod->Lookup(GetRef<GlobalVar>(global_var));
+ const auto* func_node = func.as<FunctionNode>();
+ if (func_node) {
+ return IsClosure(func_node->body);
+ } else {
+ return false;
}
+
+ } else if (const auto* func_node = val.as<FunctionNode>()) {
+ return IsClosure(func_node->body);
+
+ } else if (const auto* seq_node = val.as<SeqExprNode>()) {
+ return IsClosure(seq_node->body);
+
+ } else {
+ return false;
}
- return false;
}
- bool IsClosure(const Array<Var>& captured_vars) { return
captured_vars.size() > 0; }
-
IRModule Lift() {
auto glob_funcs = mod_->functions;
- for (auto pair : glob_funcs) {
- if (auto* n = pair.second.as<FunctionNode>()) {
- auto func = GetRef<Function>(n);
- func = Function(func->params, VisitExpr(func->body),
func->ret_struct_info, func->is_pure,
- func->attrs);
- builder_->UpdateFunction(pair.first, func);
+ for (auto [gvar, base_func] : glob_funcs) {
+ if (auto opt = base_func.as<Function>()) {
+ // Must visit the function itself, and not just the function
+ // body, to ensure that EraseToWellDefined recognized symbolic
+ // variables that are exposed by the function signature.
+ auto func = Downcast<Function>(VisitExpr(opt.value()));
+ builder_->UpdateFunction(gvar, func);
}
}
return builder_->GetContextIRModule();
}
private:
- std::unordered_map<Var, Expr, ObjectPtrHash, ObjectPtrEqual> lambda_map_;
- Array<Var> recur_vars_;
+ std::unordered_map<Var, Call, ObjectPtrHash, ObjectPtrEqual>
nested_closure_map_;
+ std::unordered_map<Var, Expr, ObjectPtrHash, ObjectPtrEqual> rebind_map_;
+ std::unordered_set<Variant<GlobalVar, Var>, ObjectPtrHash, ObjectPtrEqual>
closures_;
+ Optional<Var> current_lambda_var_ = NullOpt;
IRModule mod_;
std::unordered_map<const FunctionNode*, String> lifted_names_;
@@ -519,9 +499,8 @@ class LambdaLifter : public ExprMutator {
namespace transform {
Pass LambdaLift() {
- runtime::TypedPackedFunc<IRModule(IRModule, PassContext)> pass_func =
- [=](IRModule m, PassContext pc) { return relax::LambdaLifter(m).Lift();
};
- return CreateModulePass(pass_func, 1, "LambdaLift", {});
+ auto pass_func = [=](IRModule mod, PassContext pc) { return
relax::LambdaLifter(mod).Lift(); };
+ return tvm::transform::CreateModulePass(pass_func, 1, "LambdaLift", {});
}
TVM_REGISTER_GLOBAL("relax.transform.LambdaLift").set_body_typed(LambdaLift);
diff --git a/tests/python/relax/test_transform_lambda_lift.py
b/tests/python/relax/test_transform_lambda_lift.py
index 8f3daa06e2..f30afdae84 100644
--- a/tests/python/relax/test_transform_lambda_lift.py
+++ b/tests/python/relax/test_transform_lambda_lift.py
@@ -42,7 +42,7 @@ def test_basic():
"""Functions can be listed from local bindings to the IRModule"""
# the target IRModule
- @tvm.script.ir_module
+ @I.ir_module
class Expected:
@R.function(private=True)
def main_inner(
@@ -55,11 +55,10 @@ def test_basic():
def main(
x1: R.Tensor((10, 5), "float32"), y1: R.Tensor((10, 5), "float32")
) -> R.Tensor((10, 5), "float32"):
- inner = Expected.main_inner
- gv1: R.Tensor((10, 5), "float32") = inner(x1, y1)
+ gv1: R.Tensor((10, 5), "float32") = Expected.main_inner(x1, y1)
return gv1
- @tvm.script.ir_module
+ @I.ir_module
class Before:
@R.function
def main(
@@ -84,18 +83,54 @@ def test_basic():
_check_save_roundtrip(after)
+def test_input_module_is_unmodified():
+ """The input module may not be modified
+
+ If the output requires new StructInfo, it must create a new relax
+ variable. It must not update the struct info of an existing relax
+ variable, as that variable may be used by another IRModule.
+ """
+
+ @I.ir_module
+ class Before:
+ @R.function
+ def main(
+ x: R.Tensor((2, 3), "float32"), y: R.Tensor((2, 3), "float32")
+ ) -> R.Tensor((2, 3), "float32"):
+ @R.function
+ def outer_func(
+ c1: R.Tensor((2, 3), "float32")
+ ) -> R.Callable((R.Tensor((2, 3), "float32"),), R.Tensor((2, 3),
"float32")):
+ @R.function
+ def inner_func(x1: R.Tensor((2, 3), "float32")) ->
R.Tensor((2, 3), "float32"):
+ s: R.Tensor((2, 3), "float32") = R.add(x1, c1)
+ return s
+
+ return inner_func
+
+ in_call = outer_func(x)
+ res = in_call(y)
+ return res
+
+ before = Before
+ copy_of_before = tvm.ir.load_json(tvm.ir.save_json(before))
+
+ transform.LambdaLift()(before)
+
+ tvm.ir.assert_structural_equal(before, copy_of_before)
+
+
def test_closure():
"""Lifting functions may require producing closures"""
# the expected IRModule
- @tvm.script.ir_module
+ @I.ir_module
class Expected:
@R.function
def main(
x: R.Tensor((2, 3), "float32"), y: R.Tensor((2, 3), "float32")
) -> R.Tensor((2, 3), "float32"):
- outer_func = Expected.main_outer_func
- in_call = outer_func(x)
+ in_call = Expected.main_outer_func(x)
res = R.invoke_pure_closure(
in_call, (y,), sinfo_args=(R.Tensor((2, 3), dtype="float32"))
)
@@ -112,7 +147,7 @@ def test_closure():
return inner_func
# IRModule to perform Lambda Lifting
- @tvm.script.ir_module
+ @I.ir_module
class Before:
@R.function
def main(
@@ -144,7 +179,7 @@ def test_recursive():
"""The lifted function may be recursively defined"""
# the expected IRModule
- @tvm.script.ir_module
+ @I.ir_module
class Expected:
@R.function(private=True)
def main_while_loop(
@@ -174,7 +209,7 @@ def test_recursive():
return gv
# the IRModule to apply lambda lifting
- @tvm.script.ir_module
+ @I.ir_module
class Before:
@R.function
def main(x: R.Tensor((2, 3), "float32")) -> R.Tensor:
@@ -218,22 +253,20 @@ def test_multi_func():
"""
# expected IRModule
- @tvm.script.ir_module
+ @I.ir_module
class Expected:
@R.function
def glob_func_1(
x1: R.Tensor((10, 5), "float32"), y1: R.Tensor((10, 5), "float32")
) -> R.Tensor(None, "float32", ndim=2):
- inner = Expected.glob_func_1_inner
- gv1: R.Tensor((10, 5), "float32") = inner(x1, y1)
+ gv1: R.Tensor((10, 5), "float32") = Expected.glob_func_1_inner(x1,
y1)
return gv1
@R.function
def glob_func_2(
x11: R.Tensor((10, 5), "float32"), y11: R.Tensor((10, 5),
"float32")
) -> R.Tensor(None, "float32", ndim=2):
- inner = Expected.glob_func_2_inner
- gv11: R.Tensor((10, 5), "float32") = inner(x11, y11)
+ gv11: R.Tensor((10, 5), "float32") =
Expected.glob_func_2_inner(x11, y11)
return gv11
@R.function(private=True)
@@ -251,7 +284,7 @@ def test_multi_func():
return s1
# the IRModule to apply lambda lifting
- @tvm.script.ir_module
+ @I.ir_module
class Before:
@R.function
def glob_func_1(
@@ -291,7 +324,7 @@ def test_multi_func():
def test_no_local_func():
- @tvm.script.ir_module
+ @I.ir_module
class Before:
@T.prim_func
def sub(
@@ -318,7 +351,7 @@ def test_no_local_func():
def test_impure_function():
- @tvm.script.ir_module
+ @I.ir_module
class Expected:
@R.function(pure=False, private=True)
def main_inner() -> R.Tuple:
@@ -327,11 +360,10 @@ def test_impure_function():
@R.function(pure=False)
def main(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
- inner = Expected.main_inner
- gv1 = inner()
+ gv1 = Expected.main_inner()
return x
- @tvm.script.ir_module
+ @I.ir_module
class Before:
@R.function(pure=False)
def main(x: R.Tensor((), "int32")) -> R.Tensor((), "int32"):
@@ -385,8 +417,7 @@ def test_lambda_function_with_same_name_as_global():
def main(
x1: R.Tensor((10, 5), "float32"), y1: R.Tensor((10, 5), "float32")
) -> R.Tensor((10, 5), "float32"):
- inner = Expected.main_inner_0
- gv1: R.Tensor((10, 5), "float32") = inner(x1, y1)
+ gv1: R.Tensor((10, 5), "float32") = Expected.main_inner_0(x1, y1)
return gv1
@R.function(private=True)
@@ -404,5 +435,78 @@ def test_lambda_function_with_same_name_as_global():
assert_structural_equal(Expected, after)
+def test_symbolic_variable_defined_by_inner_func():
+ @I.ir_module
+ class Before:
+ @R.function
+ def main(
+ x1: R.Tensor((10, 5), "float32"), y1: R.Tensor((10, 5), "float32")
+ ) -> R.Tensor((10, 5), "float32"):
+ @R.function
+ def inner(x2: R.Tensor(("n", "m"), "float32"), y2: R.Tensor(("n",
"m"), "float32")):
+ sum_inner = R.add(x2, y2)
+ return sum_inner
+
+ sum_main = inner(x1, y1)
+ return sum_main
+
+ @I.ir_module
+ class Expected:
+ @R.function
+ def main(
+ x1: R.Tensor((10, 5), "float32"), y1: R.Tensor((10, 5), "float32")
+ ) -> R.Tensor((10, 5), "float32"):
+ sum_main = Expected.main_inner(x1, y1)
+ return sum_main
+
+ @R.function(private=True)
+ def main_inner(
+ x2: R.Tensor(("n", "m"), "float32"), y2: R.Tensor(("n", "m"),
"float32")
+ ) -> R.Tensor(("n", "m"), "float32"):
+ sum_inner = R.add(x2, y2)
+ return sum_inner
+
+ After = transform.LambdaLift()(Before)
+ assert_structural_equal(Expected, After)
+
+
+def test_symbolic_variable_defined_by_outer_func():
+ @I.ir_module
+ class Before:
+ @R.function
+ def main(
+ x1: R.Tensor(("n", "m"), "float32"), y1: R.Tensor(("n", "m"),
"float32")
+ ) -> R.Tensor(("n", "m"), "float32"):
+ n = T.int64()
+ m = T.int64()
+
+ @R.function
+ def inner(x2: R.Tensor((n, m), "float32"), y2: R.Tensor((n, m),
"float32")):
+ sum_inner = R.add(x2, y2)
+ return sum_inner
+
+ sum_main = inner(x1, y1)
+ return sum_main
+
+ @I.ir_module
+ class Expected:
+ @R.function
+ def main(
+ x1: R.Tensor(("n", "m"), "float32"), y1: R.Tensor(("n", "m"),
"float32")
+ ) -> R.Tensor(("n", "m"), "float32"):
+ sum_main = Expected.main_inner(x1, y1)
+ return sum_main
+
+ @R.function(private=True)
+ def main_inner(
+ x2: R.Tensor(("n", "m"), "float32"), y2: R.Tensor(("n", "m"),
"float32")
+ ) -> R.Tensor(("n", "m"), "float32"):
+ sum_inner = R.add(x2, y2)
+ return sum_inner
+
+ After = transform.LambdaLift()(Before)
+ assert_structural_equal(Expected, After)
+
+
if __name__ == "__main__":
tvm.testing.main()