From 50b93ce82e719f7f1fa5e617dddbf405eb96ebb3 Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Mon, 7 Oct 2019 12:20:18 -0700 Subject: [PATCH 01/12] [Relay][VM] Fix constant folding issue in VM compiler 1. allow pass params when compile a module 2. enhance profiler robustness --- python/tvm/relay/backend/profiler_vm.py | 17 +++- python/tvm/relay/backend/vm.py | 99 +++++++++++++-------- src/relay/backend/vm/compiler.cc | 56 +++++++++++- src/relay/backend/vm/compiler.h | 22 ++++- src/runtime/vm/profiler/vm.cc | 5 ++ tests/python/relay/test_vm_serialization.py | 8 +- 6 files changed, 160 insertions(+), 47 deletions(-) diff --git a/python/tvm/relay/backend/profiler_vm.py b/python/tvm/relay/backend/profiler_vm.py index 3adbecaa2531..77bb87d80413 100644 --- a/python/tvm/relay/backend/profiler_vm.py +++ b/python/tvm/relay/backend/profiler_vm.py @@ -49,8 +49,9 @@ def __init__(self): self.mod = _vm._VMCompilerProfiler() self._compile = self.mod["compile"] self._get_vm = self.mod["get_vm"] + self._set_params_func = self.mod["set_params"] - def compile(self, mod, target=None, target_host=None): + def compile(self, mod, target=None, target_host=None, params=None): """ Parameters ---------- @@ -71,13 +72,25 @@ def compile(self, mod, target=None, target_host=None): By default, llvm is used if it is enabled, otherwise a stackvm intepreter is used. + params : dict of str to NDArray + Input parameters to the graph that do not change + during inference time. Used for constant folding. + Returns ------- vm : VirtualMachineProfiler The profile VM runtime. """ target = _update_target(target) - self._compile(mod, target, target_host) + target_host = self.update_target_host(target, target_host) + + if params: + self.set_params(params) + + tophub_context = self.tophub_context(target) + + with tophub_context: + self._compile(mod, target, target_host) return VirtualMachineProfiler(self._get_vm()) class VirtualMachineProfiler(vm.VirtualMachine): diff --git a/python/tvm/relay/backend/vm.py b/python/tvm/relay/backend/vm.py index a6cb91c2dfde..99b964f91a43 100644 --- a/python/tvm/relay/backend/vm.py +++ b/python/tvm/relay/backend/vm.py @@ -25,30 +25,11 @@ import tvm from tvm import autotvm from tvm._ffi.runtime_ctypes import TVMByteArray +from tvm.relay import expr as _expr from . import _vm from . import vmobj as _obj from .interpreter import Executor - -def _update_target(target): - target = target if target else tvm.target.current_target() - if target is None: - raise ValueError("Target is not set in env or passed as argument.") - - tgts = {} - if isinstance(target, (str, tvm.target.Target)): - dev_type = tvm.expr.IntImm("int32", tvm.nd.context(str(target)).device_type) - tgts[dev_type] = tvm.target.create(target) - elif isinstance(target, dict): - for dev, tgt in target.items(): - dev_type = tvm.expr.IntImm("int32", tvm.nd.context(dev).device_type) - tgts[dev_type] = tvm.target.create(tgt) - else: - raise TypeError("target is expected to be str, tvm.target.Target, " + - "or dict of str to str/tvm.target.Target, but received " + - "{}".format(type(target))) - return tgts - def _convert(arg, cargs): if isinstance(arg, (np.ndarray, tvm.nd.NDArray)): cargs.append(_obj.tensor_object(arg)) @@ -150,8 +131,58 @@ def __init__(self): self.mod = _vm._VMCompiler() self._compile = self.mod["compile"] self._get_vm = self.mod["get_vm"] + self._set_params_func = self.mod["set_params"] + + def set_params(self, params): + """Set constant parameters for the model""" + inputs = {} + for name, param in params.items(): + if isinstance(param, np.ndarray): + param = _nd.array(param) + inputs[name] = _expr.const(param) + self._set_params_func(inputs) + + def update_target(self, target): + """Update target""" + target = target if target else tvm.target.current_target() + if target is None: + raise ValueError("Target is not set in env or passed as argument.") + tgts = {} + if isinstance(target, (str, tvm.target.Target)): + dev_type = tvm.expr.IntImm("int32", tvm.nd.context(str(target)).device_type) + tgts[dev_type] = tvm.target.create(target) + elif isinstance(target, dict): + for dev, tgt in target.items(): + dev_type = tvm.expr.IntImm("int32", tvm.nd.context(dev).device_type) + tgts[dev_type] = tvm.target.create(tgt) + else: + raise TypeError("target is expected to be str, tvm.target.Target, " + + "or dict of str to str/tvm.target.Target, but received " + + "{}".format(type(target))) + return tgts - def compile(self, mod, target=None, target_host=None): + def update_target_host(self, target, target_host): + """Update target host""" + target_host = None if target_host == "" else target_host + if not target_host: + for device_type, tgt in target.items(): + if device_type.value == tvm.nd.cpu(0).device_type: + target_host = tgt + break + if not target_host: + target_host = "llvm" if tvm.module.enabled("llvm") else "stackvm" + return tvm.target.create(target_host) + + def tophub_context(self, target): + # If current dispatch context is fallback context (the default root context), + # then load pre-tuned parameters from TopHub + if isinstance(autotvm.DispatchContext.current, autotvm.FallbackContext): + tophub_context = autotvm.tophub.context(list(target.values())) + else: + tophub_context = autotvm.util.EmptyContext() + return tophub_context + + def compile(self, mod, target=None, target_host=None, params=None): """ Parameters ---------- @@ -172,28 +203,22 @@ def compile(self, mod, target=None, target_host=None): By default, llvm is used if it is enabled, otherwise a stackvm intepreter is used. + params : dict of str to NDArray + Input parameters to the graph that do not change + during inference time. Used for constant folding. + Returns ------- vm : VirtualMachine The VM runtime. """ - target = _update_target(target) - target_host = None if target_host == "" else target_host - if not target_host: - for device_type, tgt in target.items(): - if device_type.value == tvm.nd.cpu(0).device_type: - target_host = tgt - break - if not target_host: - target_host = "llvm" if tvm.module.enabled("llvm") else "stackvm" - target_host = tvm.target.create(target_host) + target = self.update_target(target) + target_host = self.update_target_host(target, target_host) - # If current dispatch context is fallback context (the default root context), - # then load pre-tuned parameters from TopHub - if isinstance(autotvm.DispatchContext.current, autotvm.FallbackContext): - tophub_context = autotvm.tophub.context(list(target.values())) - else: - tophub_context = autotvm.util.EmptyContext() + if params: + self.set_params(params) + + tophub_context = self.tophub_context(target) with tophub_context: self._compile(mod, target, target_host) diff --git a/src/relay/backend/vm/compiler.cc b/src/relay/backend/vm/compiler.cc index 49079fbc107e..c88843206029 100644 --- a/src/relay/backend/vm/compiler.cc +++ b/src/relay/backend/vm/compiler.cc @@ -780,23 +780,73 @@ PackedFunc VMCompiler::GetFunction(const std::string& name, if (name == "compile") { return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) { CHECK_EQ(args.num_args, 3); - this->Compile(args[0], args[1], args[2]); + Module mod = args[0]; + this->Compile(mod, args[1], args[2]); }); } else if (name == "get_vm") { return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) { *rv = runtime::Module(vm_); }); + } else if (name == "set_params") { + return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) { + Map params = args[0]; + for (const auto& kv : params) { + this->SetParam(kv.first, kv.second->data); + } + }); } else { LOG(FATAL) << "Unknown packed function: " << name; return PackedFunc([sptr_to_self, name](TVMArgs args, TVMRetValue* rv) {}); } } -void VMCompiler::Compile(const Module& mod_ref, +void VMCompiler::SetParam(const std::string& name, runtime::NDArray data_in) { + params_[name] = data_in; +} + +relay::Function VMCompiler::BindParamsByName( + relay::Function func, + const std::unordered_map& params) { + std::unordered_map name_dict; + std::unordered_set repeat_var; + for (auto arg : func->params) { + const auto &name = arg->name_hint(); + if (name_dict.count(name)) { + repeat_var.insert(arg); + } else { + name_dict[name] = arg; + } + } + std::unordered_map bind_dict; + for (auto &kv : params) { + if (name_dict.count(kv.first) == 0) { + continue; + } + auto arg = name_dict.at(kv.first); + if (repeat_var.count(arg)) { + LOG(FATAL) << "Multiple args in the function have name " << kv.first; + } + bind_dict[arg] = ConstantNode::make(kv.second); + } + Expr bound_expr = relay::Bind(func, bind_dict); + Function ret = Downcast(bound_expr); + CHECK(ret.defined()) + << "The returning type is expected to be a Relay Function." + << "\n"; + return ret; +} + + +void VMCompiler::Compile(Module mod, const TargetsMap& targets, const tvm::Target& target_host) { CHECK_EQ(targets.size(), 1) << "Currently VM compiler doesn't support heterogeneous compilation"; + if (params_.size()) { + auto f = BindParamsByName(mod->Lookup("main"), params_); + auto gvar = mod->GetGlobalVar("main"); + mod->Add(gvar, f); + } InitVM(); targets_ = targets; @@ -804,7 +854,7 @@ void VMCompiler::Compile(const Module& mod_ref, // Run some optimizations first, this code should // be moved to pass manager. - context_.module = OptimizeModule(mod_ref, targets_); + context_.module = OptimizeModule(mod, targets_); // Populate the global map. // diff --git a/src/relay/backend/vm/compiler.h b/src/relay/backend/vm/compiler.h index 14a5035b20dc..1738112cdfdf 100644 --- a/src/relay/backend/vm/compiler.h +++ b/src/relay/backend/vm/compiler.h @@ -100,11 +100,29 @@ class VMCompiler : public runtime::ModuleNode { vm_ = std::make_shared(); } - void Compile(const Module& mod_ref, + /*! + * \brief Set the parameters + * + * \param name name of parameter + * \param data_in input DLTensor + */ + void SetParam(const std::string& name, runtime::NDArray data_in); + + void Compile(Module mod, const TargetsMap& targets, const tvm::Target& target_host); protected: + /*! + * \brief Bind params to function by using name + * \param func Relay function + * \param params params dict + * \return relay::Function + */ + relay::Function BindParamsByName( + relay::Function func, + const std::unordered_map& params); + Module OptimizeModule(const Module& mod, const TargetsMap& targets); void PopulateGlobalMap(); @@ -120,6 +138,8 @@ class VMCompiler : public runtime::ModuleNode { VMCompilerContext context_; /*! \brief Compiled virtual machine. */ std::shared_ptr vm_; + /*! \brief parameters */ + std::unordered_map params_; }; } // namespace vm diff --git a/src/runtime/vm/profiler/vm.cc b/src/runtime/vm/profiler/vm.cc index 1d3ac836925a..5f59f6ed7f48 100644 --- a/src/runtime/vm/profiler/vm.cc +++ b/src/runtime/vm/profiler/vm.cc @@ -98,6 +98,11 @@ void VirtualMachineDebug::InvokePacked(Index packed_index, Index output_size, const std::vector& args) { auto ctx = VirtualMachine::GetParamsContext(); + // warmup + VirtualMachine::InvokePacked(packed_index, func, arg_count, output_size, + args); + TVMSynchronize(ctx.device_type, ctx.device_id, nullptr); + auto op_begin = std::chrono::high_resolution_clock::now(); VirtualMachine::InvokePacked(packed_index, func, arg_count, output_size, args); diff --git a/tests/python/relay/test_vm_serialization.py b/tests/python/relay/test_vm_serialization.py index a32ec2768540..7bf91ce355b5 100644 --- a/tests/python/relay/test_vm_serialization.py +++ b/tests/python/relay/test_vm_serialization.py @@ -28,18 +28,18 @@ from tvm.contrib import util from tvm.relay import testing -def create_vm(f, ctx=tvm.cpu(), target="llvm"): +def create_vm(f, ctx=tvm.cpu(), target="llvm", params=None): if isinstance(f, relay.Expr): mod = relay.Module() mod["main"] = f compiler = relay.vm.VMCompiler() - vm = compiler.compile(mod, target) + vm = compiler.compile(mod, target=target, params=params) vm.init(ctx) return vm else: assert isinstance(f, relay.Module), "expected mod as relay.Module" compiler = relay.vm.VMCompiler() - vm = compiler.compile(f, target) + vm = compiler.compile(f, target=target, params=params) vm.init(ctx) return vm @@ -61,7 +61,7 @@ def get_vm_output(mod, data, params, target, ctx, dtype='float32'): return result.asnumpy().astype(dtype) def get_serialized_output(mod, data, params, target, ctx, dtype='float32'): - vm = create_vm(mod, ctx, target) + vm = create_vm(mod, ctx, target, params=params) ser = serializer.Serializer(vm) code, lib = ser.serialize() deser = deserializer.Deserializer(code, lib) From 1647d16ba46dd52d36415a754eaeb2c56a11e4bd Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Tue, 8 Oct 2019 12:22:25 -0700 Subject: [PATCH 02/12] remove dead code --- python/tvm/relay/backend/profiler_vm.py | 21 +-------------------- 1 file changed, 1 insertion(+), 20 deletions(-) diff --git a/python/tvm/relay/backend/profiler_vm.py b/python/tvm/relay/backend/profiler_vm.py index 77bb87d80413..2ef03ea570e8 100644 --- a/python/tvm/relay/backend/profiler_vm.py +++ b/python/tvm/relay/backend/profiler_vm.py @@ -23,25 +23,6 @@ import tvm from . import vm, _vm -def _update_target(target): - target = target if target else tvm.target.current_target() - if target is None: - raise ValueError("Target is not set in env or passed as argument.") - - tgts = {} - if isinstance(target, (str, tvm.target.Target)): - dev_type = tvm.expr.IntImm("int32", tvm.nd.context(str(target)).device_type) - tgts[dev_type] = tvm.target.create(target) - elif isinstance(target, dict): - for dev, tgt in target.items(): - dev_type = tvm.expr.IntImm("int32", tvm.nd.context(dev).device_type) - tgts[dev_type] = tvm.target.create(tgt) - else: - raise TypeError("target is expected to be str, tvm.target.Target, " + - "or dict of str to str/tvm.target.Target, but received " + - "{}".format(type(target))) - return tgts - class VMCompilerProfiler(vm.VMCompiler): """Build Relay module to run on VM runtime.""" def __init__(self): @@ -81,7 +62,7 @@ def compile(self, mod, target=None, target_host=None, params=None): vm : VirtualMachineProfiler The profile VM runtime. """ - target = _update_target(target) + target = self.update_target(target) target_host = self.update_target_host(target, target_host) if params: From ffd16df9a2edc64a46eb3f4b10b49136cda036e4 Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Tue, 8 Oct 2019 12:26:20 -0700 Subject: [PATCH 03/12] fix lint --- python/tvm/relay/backend/profiler_vm.py | 1 - 1 file changed, 1 deletion(-) diff --git a/python/tvm/relay/backend/profiler_vm.py b/python/tvm/relay/backend/profiler_vm.py index 2ef03ea570e8..b987b335df8f 100644 --- a/python/tvm/relay/backend/profiler_vm.py +++ b/python/tvm/relay/backend/profiler_vm.py @@ -20,7 +20,6 @@ Provides extra APIs for profiling vm execution. """ -import tvm from . import vm, _vm class VMCompilerProfiler(vm.VMCompiler): From cbbb692b8a5386ddc27d4649933a50c463d4a63d Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Tue, 8 Oct 2019 19:44:58 -0700 Subject: [PATCH 04/12] add get_params --- python/tvm/relay/backend/profiler_vm.py | 7 ++++++- python/tvm/relay/backend/vm.py | 16 ++++++++++++++-- src/relay/backend/vm/compiler.cc | 20 ++++++++++++++++++++ src/relay/backend/vm/compiler.h | 11 +++++++++++ 4 files changed, 51 insertions(+), 3 deletions(-) diff --git a/python/tvm/relay/backend/profiler_vm.py b/python/tvm/relay/backend/profiler_vm.py index b987b335df8f..e42a0fc4e057 100644 --- a/python/tvm/relay/backend/profiler_vm.py +++ b/python/tvm/relay/backend/profiler_vm.py @@ -30,6 +30,7 @@ def __init__(self): self._compile = self.mod["compile"] self._get_vm = self.mod["get_vm"] self._set_params_func = self.mod["set_params"] + self._get_params_func = self.mod["get_params"] def compile(self, mod, target=None, target_host=None, params=None): """ @@ -60,6 +61,9 @@ def compile(self, mod, target=None, target_host=None, params=None): ------- vm : VirtualMachineProfiler The profile VM runtime. + + params : dict + The parameters of the final graph. """ target = self.update_target(target) target_host = self.update_target_host(target, target_host) @@ -71,7 +75,8 @@ def compile(self, mod, target=None, target_host=None, params=None): with tophub_context: self._compile(mod, target, target_host) - return VirtualMachineProfiler(self._get_vm()) + params = self.get_params() + return VirtualMachineProfiler(self._get_vm()), params class VirtualMachineProfiler(vm.VirtualMachine): """Relay profile VM runtime.""" diff --git a/python/tvm/relay/backend/vm.py b/python/tvm/relay/backend/vm.py index 99b964f91a43..4e231cd30122 100644 --- a/python/tvm/relay/backend/vm.py +++ b/python/tvm/relay/backend/vm.py @@ -132,6 +132,7 @@ def __init__(self): self._compile = self.mod["compile"] self._get_vm = self.mod["get_vm"] self._set_params_func = self.mod["set_params"] + self._get_params_func = self.mod["get_params"] def set_params(self, params): """Set constant parameters for the model""" @@ -182,6 +183,14 @@ def tophub_context(self, target): tophub_context = autotvm.util.EmptyContext() return tophub_context + def get_params(self): + """Return the updated weights.""" + params = self._get_params_func() + ret = {} + for key, value in params.items(): + ret[key] = value.data + return ret + def compile(self, mod, target=None, target_host=None, params=None): """ Parameters @@ -211,6 +220,9 @@ def compile(self, mod, target=None, target_host=None, params=None): ------- vm : VirtualMachine The VM runtime. + + params : dict + The parameters of the final graph. """ target = self.update_target(target) target_host = self.update_target_host(target, target_host) @@ -222,8 +234,8 @@ def compile(self, mod, target=None, target_host=None, params=None): with tophub_context: self._compile(mod, target, target_host) - return VirtualMachine(self._get_vm()) - + params = self.get_params() + return VirtualMachine(self._get_vm()), params class VMExecutor(Executor): """ diff --git a/src/relay/backend/vm/compiler.cc b/src/relay/backend/vm/compiler.cc index c88843206029..e1922f56e1c8 100644 --- a/src/relay/backend/vm/compiler.cc +++ b/src/relay/backend/vm/compiler.cc @@ -266,6 +266,8 @@ class VMFunctionCompiler : ExprFunctor { void VisitExpr_(const ConstantNode* const_node) { size_t konst_idx = context_->constants.size(); + std::string name = "p" + std::to_string(konst_idx); + context_->constant_indices[name] = konst_idx; context_->constants.push_back(const_node->data); Emit(Instruction::LoadConst(konst_idx, NewRegister())); } @@ -395,6 +397,8 @@ class VMFunctionCompiler : ExprFunctor { } } size_t konst_idx = context_->constants.size(); + std::string name = "p" + std::to_string(konst_idx); + context_->constant_indices[name] = konst_idx; context_->constants.push_back(shape_tensor); Emit(Instruction::LoadConst(konst_idx, NewRegister())); return last_register_; @@ -794,12 +798,24 @@ PackedFunc VMCompiler::GetFunction(const std::string& name, this->SetParam(kv.first, kv.second->data); } }); + } else if (name == "get_params") { + return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) { + *rv = this->GetParams(); + }); } else { LOG(FATAL) << "Unknown packed function: " << name; return PackedFunc([sptr_to_self, name](TVMArgs args, TVMRetValue* rv) {}); } } +Map VMCompiler::GetParams() { + Map ret; + for (const auto& kv : out_params_) { + ret.Set(kv.first, ConstantNode::make(kv.second)); + } + return ret; +} + void VMCompiler::SetParam(const std::string& name, runtime::NDArray data_in) { params_[name] = data_in; } @@ -887,6 +903,10 @@ void VMCompiler::Compile(Module mod, for (auto data : context_.constants) { vm_->constants.push_back(Object::Tensor(data)); } + // populate output parameters + for (auto p : context_.constant_indices) { + out_params_[p.first] = context_.constants[p.second]; + } LibraryCodegen(); diff --git a/src/relay/backend/vm/compiler.h b/src/relay/backend/vm/compiler.h index 1738112cdfdf..6e169b87c21f 100644 --- a/src/relay/backend/vm/compiler.h +++ b/src/relay/backend/vm/compiler.h @@ -72,6 +72,8 @@ struct VMCompilerContext { TagMap tag_map; // Map from global var to a unique integer GlobalMap global_map; + // Map from name to constant index + std::unordered_map constant_indices; // List of constants std::vector constants; // List of cached functions @@ -100,6 +102,13 @@ class VMCompiler : public runtime::ModuleNode { vm_ = std::make_shared(); } + /*! + * \brief Get params dictionary + * + * \return Map params dictionary + */ + Map GetParams(); + /*! * \brief Set the parameters * @@ -140,6 +149,8 @@ class VMCompiler : public runtime::ModuleNode { std::shared_ptr vm_; /*! \brief parameters */ std::unordered_map params_; + /*! \brief Output parameters */ + std::unordered_map out_params_; }; } // namespace vm From 5e00db9a43abce3d55408fd1424f8b7da5af4407 Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Tue, 8 Oct 2019 21:03:36 -0700 Subject: [PATCH 05/12] fix test --- python/tvm/relay/backend/vm.py | 2 +- tests/python/relay/test_vm.py | 6 +++--- tests/python/relay/test_vm_serialization.py | 4 ++-- 3 files changed, 6 insertions(+), 6 deletions(-) diff --git a/python/tvm/relay/backend/vm.py b/python/tvm/relay/backend/vm.py index 4e231cd30122..f16750ec590b 100644 --- a/python/tvm/relay/backend/vm.py +++ b/python/tvm/relay/backend/vm.py @@ -264,7 +264,7 @@ def __init__(self, mod, ctx, target): self.ctx = ctx self.target = target compiler = VMCompiler() - self.vm = compiler.compile(mod, target) + self.vm, _ = compiler.compile(mod, target) self.vm.init(ctx) def _make_executor(self, expr=None): diff --git a/tests/python/relay/test_vm.py b/tests/python/relay/test_vm.py index f60c53317407..42ad8fdb516d 100644 --- a/tests/python/relay/test_vm.py +++ b/tests/python/relay/test_vm.py @@ -48,14 +48,14 @@ def veval(f, *args, ctx=tvm.cpu(), target="llvm"): mod = relay.Module() mod["main"] = f compiler = relay.vm.VMCompiler() - vm = compiler.compile(mod, target) + vm, _ = compiler.compile(mod, target) vm.init(tvm.cpu()) return vm.invoke("main", *args) else: assert isinstance(f, relay.Module), "expected expression or module" mod = f compiler = relay.vm.VMCompiler() - vm = compiler.compile(mod, target) + vm, _ = compiler.compile(mod, target) vm.init(tvm.cpu()) ret = vm.invoke("main", *args) return ret @@ -583,7 +583,7 @@ def test_set_params(): y = relay.nn.bias_add(relay.nn.dense(x, w), b) mod["main"] = relay.Function([x, w, b], y) compiler = relay.vm.VMCompiler() - vm = compiler.compile(mod, 'llvm') + vm, params = compiler.compile(mod, 'llvm') vm.init(tvm.cpu()) x_np = np.random.uniform(size=(10, 5)).astype('float32') diff --git a/tests/python/relay/test_vm_serialization.py b/tests/python/relay/test_vm_serialization.py index 7bf91ce355b5..e3a9e4f591b6 100644 --- a/tests/python/relay/test_vm_serialization.py +++ b/tests/python/relay/test_vm_serialization.py @@ -33,13 +33,13 @@ def create_vm(f, ctx=tvm.cpu(), target="llvm", params=None): mod = relay.Module() mod["main"] = f compiler = relay.vm.VMCompiler() - vm = compiler.compile(mod, target=target, params=params) + vm, _ = compiler.compile(mod, target=target, params=params) vm.init(ctx) return vm else: assert isinstance(f, relay.Module), "expected mod as relay.Module" compiler = relay.vm.VMCompiler() - vm = compiler.compile(f, target=target, params=params) + vm, _ = compiler.compile(f, target=target, params=params) vm.init(ctx) return vm From 7a7234ec07a6b13bbda7814f0743a494291abb2e Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Wed, 9 Oct 2019 10:19:13 -0700 Subject: [PATCH 06/12] don't pass params back --- python/tvm/relay/backend/profiler_vm.py | 5 +---- python/tvm/relay/backend/vm.py | 7 ++----- tests/python/relay/test_vm.py | 6 +++--- tests/python/relay/test_vm_serialization.py | 4 ++-- 4 files changed, 8 insertions(+), 14 deletions(-) diff --git a/python/tvm/relay/backend/profiler_vm.py b/python/tvm/relay/backend/profiler_vm.py index e42a0fc4e057..6b2e150e1c36 100644 --- a/python/tvm/relay/backend/profiler_vm.py +++ b/python/tvm/relay/backend/profiler_vm.py @@ -62,8 +62,6 @@ def compile(self, mod, target=None, target_host=None, params=None): vm : VirtualMachineProfiler The profile VM runtime. - params : dict - The parameters of the final graph. """ target = self.update_target(target) target_host = self.update_target_host(target, target_host) @@ -75,8 +73,7 @@ def compile(self, mod, target=None, target_host=None, params=None): with tophub_context: self._compile(mod, target, target_host) - params = self.get_params() - return VirtualMachineProfiler(self._get_vm()), params + return VirtualMachineProfiler(self._get_vm()) class VirtualMachineProfiler(vm.VirtualMachine): """Relay profile VM runtime.""" diff --git a/python/tvm/relay/backend/vm.py b/python/tvm/relay/backend/vm.py index f16750ec590b..6f61280f562e 100644 --- a/python/tvm/relay/backend/vm.py +++ b/python/tvm/relay/backend/vm.py @@ -221,8 +221,6 @@ def compile(self, mod, target=None, target_host=None, params=None): vm : VirtualMachine The VM runtime. - params : dict - The parameters of the final graph. """ target = self.update_target(target) target_host = self.update_target_host(target, target_host) @@ -234,8 +232,7 @@ def compile(self, mod, target=None, target_host=None, params=None): with tophub_context: self._compile(mod, target, target_host) - params = self.get_params() - return VirtualMachine(self._get_vm()), params + return VirtualMachine(self._get_vm()) class VMExecutor(Executor): """ @@ -264,7 +261,7 @@ def __init__(self, mod, ctx, target): self.ctx = ctx self.target = target compiler = VMCompiler() - self.vm, _ = compiler.compile(mod, target) + self.vm = compiler.compile(mod, target) self.vm.init(ctx) def _make_executor(self, expr=None): diff --git a/tests/python/relay/test_vm.py b/tests/python/relay/test_vm.py index 42ad8fdb516d..f60c53317407 100644 --- a/tests/python/relay/test_vm.py +++ b/tests/python/relay/test_vm.py @@ -48,14 +48,14 @@ def veval(f, *args, ctx=tvm.cpu(), target="llvm"): mod = relay.Module() mod["main"] = f compiler = relay.vm.VMCompiler() - vm, _ = compiler.compile(mod, target) + vm = compiler.compile(mod, target) vm.init(tvm.cpu()) return vm.invoke("main", *args) else: assert isinstance(f, relay.Module), "expected expression or module" mod = f compiler = relay.vm.VMCompiler() - vm, _ = compiler.compile(mod, target) + vm = compiler.compile(mod, target) vm.init(tvm.cpu()) ret = vm.invoke("main", *args) return ret @@ -583,7 +583,7 @@ def test_set_params(): y = relay.nn.bias_add(relay.nn.dense(x, w), b) mod["main"] = relay.Function([x, w, b], y) compiler = relay.vm.VMCompiler() - vm, params = compiler.compile(mod, 'llvm') + vm = compiler.compile(mod, 'llvm') vm.init(tvm.cpu()) x_np = np.random.uniform(size=(10, 5)).astype('float32') diff --git a/tests/python/relay/test_vm_serialization.py b/tests/python/relay/test_vm_serialization.py index e3a9e4f591b6..7bf91ce355b5 100644 --- a/tests/python/relay/test_vm_serialization.py +++ b/tests/python/relay/test_vm_serialization.py @@ -33,13 +33,13 @@ def create_vm(f, ctx=tvm.cpu(), target="llvm", params=None): mod = relay.Module() mod["main"] = f compiler = relay.vm.VMCompiler() - vm, _ = compiler.compile(mod, target=target, params=params) + vm = compiler.compile(mod, target=target, params=params) vm.init(ctx) return vm else: assert isinstance(f, relay.Module), "expected mod as relay.Module" compiler = relay.vm.VMCompiler() - vm, _ = compiler.compile(f, target=target, params=params) + vm = compiler.compile(f, target=target, params=params) vm.init(ctx) return vm From a0a4c9735a6fb0df0cb9f47a8ce58d91f25f1c51 Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Wed, 9 Oct 2019 10:29:50 -0700 Subject: [PATCH 07/12] remove get_params --- python/tvm/relay/backend/profiler_vm.py | 1 - python/tvm/relay/backend/vm.py | 9 --------- src/relay/backend/vm/compiler.cc | 16 ---------------- src/relay/backend/vm/compiler.h | 9 --------- 4 files changed, 35 deletions(-) diff --git a/python/tvm/relay/backend/profiler_vm.py b/python/tvm/relay/backend/profiler_vm.py index 6b2e150e1c36..2537c82711e0 100644 --- a/python/tvm/relay/backend/profiler_vm.py +++ b/python/tvm/relay/backend/profiler_vm.py @@ -30,7 +30,6 @@ def __init__(self): self._compile = self.mod["compile"] self._get_vm = self.mod["get_vm"] self._set_params_func = self.mod["set_params"] - self._get_params_func = self.mod["get_params"] def compile(self, mod, target=None, target_host=None, params=None): """ diff --git a/python/tvm/relay/backend/vm.py b/python/tvm/relay/backend/vm.py index 6f61280f562e..a878e3f0979c 100644 --- a/python/tvm/relay/backend/vm.py +++ b/python/tvm/relay/backend/vm.py @@ -132,7 +132,6 @@ def __init__(self): self._compile = self.mod["compile"] self._get_vm = self.mod["get_vm"] self._set_params_func = self.mod["set_params"] - self._get_params_func = self.mod["get_params"] def set_params(self, params): """Set constant parameters for the model""" @@ -183,14 +182,6 @@ def tophub_context(self, target): tophub_context = autotvm.util.EmptyContext() return tophub_context - def get_params(self): - """Return the updated weights.""" - params = self._get_params_func() - ret = {} - for key, value in params.items(): - ret[key] = value.data - return ret - def compile(self, mod, target=None, target_host=None, params=None): """ Parameters diff --git a/src/relay/backend/vm/compiler.cc b/src/relay/backend/vm/compiler.cc index e1922f56e1c8..d2654a124f6f 100644 --- a/src/relay/backend/vm/compiler.cc +++ b/src/relay/backend/vm/compiler.cc @@ -798,24 +798,12 @@ PackedFunc VMCompiler::GetFunction(const std::string& name, this->SetParam(kv.first, kv.second->data); } }); - } else if (name == "get_params") { - return PackedFunc([sptr_to_self, this](TVMArgs args, TVMRetValue* rv) { - *rv = this->GetParams(); - }); } else { LOG(FATAL) << "Unknown packed function: " << name; return PackedFunc([sptr_to_self, name](TVMArgs args, TVMRetValue* rv) {}); } } -Map VMCompiler::GetParams() { - Map ret; - for (const auto& kv : out_params_) { - ret.Set(kv.first, ConstantNode::make(kv.second)); - } - return ret; -} - void VMCompiler::SetParam(const std::string& name, runtime::NDArray data_in) { params_[name] = data_in; } @@ -903,10 +891,6 @@ void VMCompiler::Compile(Module mod, for (auto data : context_.constants) { vm_->constants.push_back(Object::Tensor(data)); } - // populate output parameters - for (auto p : context_.constant_indices) { - out_params_[p.first] = context_.constants[p.second]; - } LibraryCodegen(); diff --git a/src/relay/backend/vm/compiler.h b/src/relay/backend/vm/compiler.h index 6e169b87c21f..852808edb20c 100644 --- a/src/relay/backend/vm/compiler.h +++ b/src/relay/backend/vm/compiler.h @@ -102,13 +102,6 @@ class VMCompiler : public runtime::ModuleNode { vm_ = std::make_shared(); } - /*! - * \brief Get params dictionary - * - * \return Map params dictionary - */ - Map GetParams(); - /*! * \brief Set the parameters * @@ -149,8 +142,6 @@ class VMCompiler : public runtime::ModuleNode { std::shared_ptr vm_; /*! \brief parameters */ std::unordered_map params_; - /*! \brief Output parameters */ - std::unordered_map out_params_; }; } // namespace vm From 8e2c2b12690320bea23dd839b107d6885cade8c9 Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Wed, 9 Oct 2019 11:35:07 -0700 Subject: [PATCH 08/12] docs --- src/relay/backend/vm/compiler.h | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/src/relay/backend/vm/compiler.h b/src/relay/backend/vm/compiler.h index 852808edb20c..dff1ef7f4569 100644 --- a/src/relay/backend/vm/compiler.h +++ b/src/relay/backend/vm/compiler.h @@ -72,8 +72,6 @@ struct VMCompilerContext { TagMap tag_map; // Map from global var to a unique integer GlobalMap global_map; - // Map from name to constant index - std::unordered_map constant_indices; // List of constants std::vector constants; // List of cached functions @@ -110,6 +108,14 @@ class VMCompiler : public runtime::ModuleNode { */ void SetParam(const std::string& name, runtime::NDArray data_in); + /*! + * \brief Compile functions in a Module + * + * \param mod Relay Module + * \param targets For heterogeneous compilation, it is a dictionary indicating context + to target mapping. For homogeneous compilation, it is a build target. + * \param target_host Host compilation target, if target is device. + */ void Compile(Module mod, const TargetsMap& targets, const tvm::Target& target_host); From def12a90fbf17ce7f8a50ec0039140b4d0ec421f Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Wed, 9 Oct 2019 11:45:39 -0700 Subject: [PATCH 09/12] move compile function to api --- python/tvm/relay/backend/profiler_vm.py | 83 +++++++++--------- python/tvm/relay/backend/vm.py | 87 +++++++++---------- tests/python/relay/test_vm.py | 9 +- tests/python/relay/test_vm_serialization.py | 6 +- .../unittest/test_runtime_vm_profiler.py | 4 +- 5 files changed, 88 insertions(+), 101 deletions(-) diff --git a/python/tvm/relay/backend/profiler_vm.py b/python/tvm/relay/backend/profiler_vm.py index 2537c82711e0..5c8bf044a647 100644 --- a/python/tvm/relay/backend/profiler_vm.py +++ b/python/tvm/relay/backend/profiler_vm.py @@ -22,6 +22,46 @@ """ from . import vm, _vm +def compile(mod, target=None, target_host=None, params=None): + """ + Parameters + ---------- + mod : relay.Module + The Relay module to build. + + target : str, :any:`tvm.target.Target`, or dict of str(i.e. + device/context name) to str/tvm.target.Target, optional + For heterogeneous compilation, it is a dictionary indicating context + to target mapping. For homogeneous compilation, it is a build target. + + target_host : str or :any:`tvm.target.Target`, optional + Host compilation target, if target is device. + When TVM compiles device specific program such as CUDA, + we also need host(CPU) side code to interact with the driver + to setup the dimensions and parameters correctly. + target_host is used to specify the host side codegen target. + By default, llvm is used if it is enabled, + otherwise a stackvm intepreter is used. + + params : dict of str to NDArray + Input parameters to the graph that do not change + during inference time. Used for constant folding. + + Returns + ------- + vm : VirtualMachineProfiler + The profile VM runtime. + """ + compiler = VMCompilerProfiler() + target = compiler.update_target(target) + target_host = compiler.update_target_host(target, target_host) + if params: + compiler.set_params(params) + tophub_context = compiler.tophub_context(target) + with tophub_context: + compiler._compile(mod, target, target_host) + return VirtualMachineProfiler(compiler._get_vm()) + class VMCompilerProfiler(vm.VMCompiler): """Build Relay module to run on VM runtime.""" def __init__(self): @@ -31,49 +71,6 @@ def __init__(self): self._get_vm = self.mod["get_vm"] self._set_params_func = self.mod["set_params"] - def compile(self, mod, target=None, target_host=None, params=None): - """ - Parameters - ---------- - mod : relay.Module - The Relay module to build. - - target : str, :any:`tvm.target.Target`, or dict of str(i.e. - device/context name) to str/tvm.target.Target, optional - For heterogeneous compilation, it is a dictionary indicating context - to target mapping. For homogeneous compilation, it is a build target. - - target_host : str or :any:`tvm.target.Target`, optional - Host compilation target, if target is device. - When TVM compiles device specific program such as CUDA, - we also need host(CPU) side code to interact with the driver - to setup the dimensions and parameters correctly. - target_host is used to specify the host side codegen target. - By default, llvm is used if it is enabled, - otherwise a stackvm intepreter is used. - - params : dict of str to NDArray - Input parameters to the graph that do not change - during inference time. Used for constant folding. - - Returns - ------- - vm : VirtualMachineProfiler - The profile VM runtime. - - """ - target = self.update_target(target) - target_host = self.update_target_host(target, target_host) - - if params: - self.set_params(params) - - tophub_context = self.tophub_context(target) - - with tophub_context: - self._compile(mod, target, target_host) - return VirtualMachineProfiler(self._get_vm()) - class VirtualMachineProfiler(vm.VirtualMachine): """Relay profile VM runtime.""" def __init__(self, mod): diff --git a/python/tvm/relay/backend/vm.py b/python/tvm/relay/backend/vm.py index a878e3f0979c..5e9f1ccb6a75 100644 --- a/python/tvm/relay/backend/vm.py +++ b/python/tvm/relay/backend/vm.py @@ -125,6 +125,47 @@ def module(self): return self.mod +def compile(mod, target=None, target_host=None, params=None): + """ + Parameters + ---------- + mod : relay.Module + The Relay module to build. + + target : str, :any:`tvm.target.Target`, or dict of str(i.e. + device/context name) to str/tvm.target.Target, optional + For heterogeneous compilation, it is a dictionary indicating context + to target mapping. For homogeneous compilation, it is a build target. + + target_host : str or :any:`tvm.target.Target`, optional + Host compilation target, if target is device. + When TVM compiles device specific program such as CUDA, + we also need host(CPU) side code to interact with the driver + to setup the dimensions and parameters correctly. + target_host is used to specify the host side codegen target. + By default, llvm is used if it is enabled, + otherwise a stackvm intepreter is used. + + params : dict of str to NDArray + Input parameters to the graph that do not change + during inference time. Used for constant folding. + + Returns + ------- + vm : VirtualMachine + The VM runtime. + """ + compiler = VMCompiler() + + target = compiler.update_target(target) + target_host = compiler.update_target_host(target, target_host) + if params: + compiler.set_params(params) + tophub_context = compiler.tophub_context(target) + with tophub_context: + compiler._compile(mod, target, target_host) + return VirtualMachine(compiler._get_vm()) + class VMCompiler(object): """Build Relay module to run on VM runtime.""" def __init__(self): @@ -182,49 +223,6 @@ def tophub_context(self, target): tophub_context = autotvm.util.EmptyContext() return tophub_context - def compile(self, mod, target=None, target_host=None, params=None): - """ - Parameters - ---------- - mod : relay.Module - The Relay module to build. - - target : str, :any:`tvm.target.Target`, or dict of str(i.e. - device/context name) to str/tvm.target.Target, optional - For heterogeneous compilation, it is a dictionary indicating context - to target mapping. For homogeneous compilation, it is a build target. - - target_host : str or :any:`tvm.target.Target`, optional - Host compilation target, if target is device. - When TVM compiles device specific program such as CUDA, - we also need host(CPU) side code to interact with the driver - to setup the dimensions and parameters correctly. - target_host is used to specify the host side codegen target. - By default, llvm is used if it is enabled, - otherwise a stackvm intepreter is used. - - params : dict of str to NDArray - Input parameters to the graph that do not change - during inference time. Used for constant folding. - - Returns - ------- - vm : VirtualMachine - The VM runtime. - - """ - target = self.update_target(target) - target_host = self.update_target_host(target, target_host) - - if params: - self.set_params(params) - - tophub_context = self.tophub_context(target) - - with tophub_context: - self._compile(mod, target, target_host) - return VirtualMachine(self._get_vm()) - class VMExecutor(Executor): """ An implementation of the executor interface for @@ -251,8 +249,7 @@ def __init__(self, mod, ctx, target): self.mod = mod self.ctx = ctx self.target = target - compiler = VMCompiler() - self.vm = compiler.compile(mod, target) + self.vm = compile(mod, target) self.vm.init(ctx) def _make_executor(self, expr=None): diff --git a/tests/python/relay/test_vm.py b/tests/python/relay/test_vm.py index f60c53317407..f643f8ad1f0b 100644 --- a/tests/python/relay/test_vm.py +++ b/tests/python/relay/test_vm.py @@ -47,15 +47,13 @@ def veval(f, *args, ctx=tvm.cpu(), target="llvm"): if isinstance(f, relay.Expr): mod = relay.Module() mod["main"] = f - compiler = relay.vm.VMCompiler() - vm = compiler.compile(mod, target) + vm = relay.vm.compile(mod, target) vm.init(tvm.cpu()) return vm.invoke("main", *args) else: assert isinstance(f, relay.Module), "expected expression or module" mod = f - compiler = relay.vm.VMCompiler() - vm = compiler.compile(mod, target) + vm = relay.vm.compile(mod, target) vm.init(tvm.cpu()) ret = vm.invoke("main", *args) return ret @@ -582,8 +580,7 @@ def test_set_params(): b = relay.var('b', shape=(6,)) y = relay.nn.bias_add(relay.nn.dense(x, w), b) mod["main"] = relay.Function([x, w, b], y) - compiler = relay.vm.VMCompiler() - vm = compiler.compile(mod, 'llvm') + vm = relay.vm.compile(mod, 'llvm') vm.init(tvm.cpu()) x_np = np.random.uniform(size=(10, 5)).astype('float32') diff --git a/tests/python/relay/test_vm_serialization.py b/tests/python/relay/test_vm_serialization.py index 7bf91ce355b5..3a317fc2d111 100644 --- a/tests/python/relay/test_vm_serialization.py +++ b/tests/python/relay/test_vm_serialization.py @@ -32,14 +32,12 @@ def create_vm(f, ctx=tvm.cpu(), target="llvm", params=None): if isinstance(f, relay.Expr): mod = relay.Module() mod["main"] = f - compiler = relay.vm.VMCompiler() - vm = compiler.compile(mod, target=target, params=params) + vm = _vm.compile(mod, target=target, params=params) vm.init(ctx) return vm else: assert isinstance(f, relay.Module), "expected mod as relay.Module" - compiler = relay.vm.VMCompiler() - vm = compiler.compile(f, target=target, params=params) + vm = _vm.compile(f, target=target, params=params) vm.init(ctx) return vm diff --git a/tests/python/unittest/test_runtime_vm_profiler.py b/tests/python/unittest/test_runtime_vm_profiler.py index 4281ccc1d97a..b5ce0ec70e51 100644 --- a/tests/python/unittest/test_runtime_vm_profiler.py +++ b/tests/python/unittest/test_runtime_vm_profiler.py @@ -22,13 +22,11 @@ from tvm import relay from tvm.relay.testing import resnet -@pytest.mark.skip def test_basic(): mod, params = resnet.get_workload() - compiler = relay.profiler_vm.VMCompilerProfiler() target = 'llvm' ctx = tvm.cpu() - vm = compiler.compile(mod, target) + vm = relay.profiler_vm.compile(mod, target) vm.init(ctx) vm.load_params(params) From 7c7ce6f3df23f94f2876815dbbc7beba0803ca03 Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Wed, 9 Oct 2019 12:20:31 -0700 Subject: [PATCH 10/12] compile clashes with builtin name --- python/tvm/relay/backend/profiler_vm.py | 2 +- python/tvm/relay/backend/vm.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/python/tvm/relay/backend/profiler_vm.py b/python/tvm/relay/backend/profiler_vm.py index 5c8bf044a647..8ae3161e0b83 100644 --- a/python/tvm/relay/backend/profiler_vm.py +++ b/python/tvm/relay/backend/profiler_vm.py @@ -14,7 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -# pylint: disable=no-else-return, unidiomatic-typecheck, undefined-variable, invalid-name +# pylint: disable=no-else-return, unidiomatic-typecheck, undefined-variable, invalid-name, redefined-builtin """ The Relay Virtual Machine profiler. diff --git a/python/tvm/relay/backend/vm.py b/python/tvm/relay/backend/vm.py index 5e9f1ccb6a75..e54629dd1344 100644 --- a/python/tvm/relay/backend/vm.py +++ b/python/tvm/relay/backend/vm.py @@ -14,7 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -# pylint: disable=no-else-return, unidiomatic-typecheck, undefined-variable, invalid-name +# pylint: disable=no-else-return, unidiomatic-typecheck, undefined-variable, invalid-name, redefined-builtin """ The Relay Virtual Machine. From dd311886ce808ec9abfe02052c876f5eae89387e Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Wed, 9 Oct 2019 12:47:15 -0700 Subject: [PATCH 11/12] fix compilation error --- src/relay/backend/vm/compiler.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/relay/backend/vm/compiler.cc b/src/relay/backend/vm/compiler.cc index d2654a124f6f..3581629aef9b 100644 --- a/src/relay/backend/vm/compiler.cc +++ b/src/relay/backend/vm/compiler.cc @@ -267,7 +267,6 @@ class VMFunctionCompiler : ExprFunctor { void VisitExpr_(const ConstantNode* const_node) { size_t konst_idx = context_->constants.size(); std::string name = "p" + std::to_string(konst_idx); - context_->constant_indices[name] = konst_idx; context_->constants.push_back(const_node->data); Emit(Instruction::LoadConst(konst_idx, NewRegister())); } @@ -398,7 +397,6 @@ class VMFunctionCompiler : ExprFunctor { } size_t konst_idx = context_->constants.size(); std::string name = "p" + std::to_string(konst_idx); - context_->constant_indices[name] = konst_idx; context_->constants.push_back(shape_tensor); Emit(Instruction::LoadConst(konst_idx, NewRegister())); return last_register_; From 8fd5d7f1fa055bfd696a4b542abbd3ca76381a84 Mon Sep 17 00:00:00 2001 From: Wei Chen Date: Wed, 9 Oct 2019 13:36:11 -0700 Subject: [PATCH 12/12] remove dead code --- src/relay/backend/vm/compiler.cc | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/relay/backend/vm/compiler.cc b/src/relay/backend/vm/compiler.cc index 3581629aef9b..c88843206029 100644 --- a/src/relay/backend/vm/compiler.cc +++ b/src/relay/backend/vm/compiler.cc @@ -266,7 +266,6 @@ class VMFunctionCompiler : ExprFunctor { void VisitExpr_(const ConstantNode* const_node) { size_t konst_idx = context_->constants.size(); - std::string name = "p" + std::to_string(konst_idx); context_->constants.push_back(const_node->data); Emit(Instruction::LoadConst(konst_idx, NewRegister())); } @@ -396,7 +395,6 @@ class VMFunctionCompiler : ExprFunctor { } } size_t konst_idx = context_->constants.size(); - std::string name = "p" + std::to_string(konst_idx); context_->constants.push_back(shape_tensor); Emit(Instruction::LoadConst(konst_idx, NewRegister())); return last_register_;