From 01e1d5abb1b119b61beea83bf646ec73b13ea95e Mon Sep 17 00:00:00 2001 From: Zhi Chen Date: Mon, 10 Feb 2020 21:21:29 +0000 Subject: [PATCH 1/5] move vm.py under runtime and adt to runtime.container.py --- python/tvm/autotvm/task/relay_integration.py | 3 +- python/tvm/container.py | 55 +----------------- python/tvm/relay/__init__.py | 2 - python/tvm/relay/backend/interpreter.py | 2 +- python/tvm/relay/build_module.py | 4 +- python/tvm/relay/testing/py_converter.py | 4 +- python/tvm/{relay/backend => runtime}/_vm.py | 0 python/tvm/runtime/container.py | 56 +++++++++++++++++++ .../{relay/backend => runtime}/profiler_vm.py | 0 python/tvm/{relay/backend => runtime}/vm.py | 14 ++--- src/runtime/container.cc | 10 ++-- .../frontend/tensorflow/test_forward.py | 2 +- .../python/relay/benchmarking/benchmark_vm.py | 3 +- tests/python/relay/test_adt.py | 2 +- .../python/relay/test_backend_interpreter.py | 3 +- tests/python/relay/test_external_codegen.py | 8 +-- .../python/relay/test_pass_partition_graph.py | 9 +-- tests/python/relay/test_py_converter.py | 2 +- tests/python/relay/test_vm.py | 14 ++--- tests/python/relay/test_vm_serialization.py | 2 +- tests/python/unittest/test_container.py | 2 +- .../unittest/test_runtime_vm_profiler.py | 8 ++- 22 files changed, 106 insertions(+), 99 deletions(-) rename python/tvm/{relay/backend => runtime}/_vm.py (100%) rename python/tvm/{relay/backend => runtime}/profiler_vm.py (100%) rename python/tvm/{relay/backend => runtime}/vm.py (98%) diff --git a/python/tvm/autotvm/task/relay_integration.py b/python/tvm/autotvm/task/relay_integration.py index 7471ca3d6c8f..2f60be63833e 100644 --- a/python/tvm/autotvm/task/relay_integration.py +++ b/python/tvm/autotvm/task/relay_integration.py @@ -39,6 +39,7 @@ def _lower(mod, """ # pylint: disable=import-outside-toplevel from tvm import relay + from tvm import runtime from tvm.relay.backend import graph_runtime_codegen if hasattr(target, 'device_name') and target.device_name == "vta": @@ -49,7 +50,7 @@ def _lower(mod, grc = graph_runtime_codegen.GraphRuntimeCodegen(None, target) grc.codegen(mod["main"]) # default case - compiler = relay.vm.VMCompiler() + compiler = runtime.vm.VMCompiler() if params: compiler.set_params(params) compiler.lower(mod, target=target) diff --git a/python/tvm/container.py b/python/tvm/container.py index 3019ad2368fd..111257eec1fb 100644 --- a/python/tvm/container.py +++ b/python/tvm/container.py @@ -17,7 +17,7 @@ """Container data structures used in TVM DSL.""" import tvm._ffi -from tvm.runtime import Object, ObjectTypes +from tvm.runtime import Object from tvm.runtime.container import getitem_helper from tvm.runtime import _ffi_node_api from . import _api_internal @@ -104,56 +104,3 @@ class LoweredFunc(Object): MixedFunc = 0 HostFunc = 1 DeviceFunc = 2 - - -@tvm._ffi.register_object("vm.ADT") -class ADT(Object): - """Algebatic data type(ADT) object. - - Parameters - ---------- - tag : int - The tag of ADT. - - fields : list[Object] or tuple[Object] - The source tuple. - """ - def __init__(self, tag, fields): - for f in fields: - assert isinstance(f, ObjectTypes), "Expect object or " \ - "tvm NDArray type, but received : {0}".format(type(f)) - self.__init_handle_by_constructor__(_ADT, tag, *fields) - - @property - def tag(self): - return _GetADTTag(self) - - def __getitem__(self, idx): - return getitem_helper( - self, _GetADTFields, len(self), idx) - - def __len__(self): - return _GetADTSize(self) - - -def tuple_object(fields=None): - """Create a ADT object from source tuple. - - Parameters - ---------- - fields : list[Object] or tuple[Object] - The source tuple. - - Returns - ------- - ret : ADT - The created object. - """ - fields = fields if fields else [] - for f in fields: - assert isinstance(f, ObjectTypes), "Expect object or tvm " \ - "NDArray type, but received : {0}".format(type(f)) - return _Tuple(*fields) - - -tvm._ffi._init_api("tvm.container") diff --git a/python/tvm/relay/__init__.py b/python/tvm/relay/__init__.py index 2432ec31cfe5..904c0702916a 100644 --- a/python/tvm/relay/__init__.py +++ b/python/tvm/relay/__init__.py @@ -36,8 +36,6 @@ from . import debug from . import param_dict from . import feature -from .backend import vm -from .backend import profiler_vm # Root operators from .op import Op diff --git a/python/tvm/relay/backend/interpreter.py b/python/tvm/relay/backend/interpreter.py index 3759bc9950af..58596ec4f247 100644 --- a/python/tvm/relay/backend/interpreter.py +++ b/python/tvm/relay/backend/interpreter.py @@ -20,7 +20,7 @@ import numpy as np -from tvm import container +from tvm.runtime import container from . import _backend from .. import _make, analysis, transform from .. import module diff --git a/python/tvm/relay/build_module.py b/python/tvm/relay/build_module.py index ea7a4cacfc60..8e70b2840b53 100644 --- a/python/tvm/relay/build_module.py +++ b/python/tvm/relay/build_module.py @@ -22,6 +22,7 @@ import numpy as np from tvm import expr as tvm_expr +from tvm.runtime import vm from .. import nd as _nd, target as _target, autotvm from ..contrib import graph_runtime as _graph_rt from . import _build_module @@ -29,7 +30,6 @@ from . import expr as _expr from .module import Module as _Module from .backend import interpreter as _interpreter -from .backend.vm import VMExecutor def _update_target(target): target = target if target else _target.current_target() @@ -408,5 +408,5 @@ def create_executor(kind="debug", if kind == "graph": return GraphExecutor(mod, ctx, target) if kind == "vm": - return VMExecutor(mod, ctx, target) + return vm.VMExecutor(mod, ctx, target) raise RuntimeError("unknown execution strategy: {0}".format(kind)) diff --git a/python/tvm/relay/testing/py_converter.py b/python/tvm/relay/testing/py_converter.py index 72b835dddee7..e2825f815b67 100644 --- a/python/tvm/relay/testing/py_converter.py +++ b/python/tvm/relay/testing/py_converter.py @@ -32,15 +32,15 @@ # import numpy # import tvm # from tvm import relay -# from tvm import import container as _container # from tvm import nd +# from tvm.runtime import import container as _container # from tvm.relay.backend.interpreter import RefValue, ConstructorValue PROLOGUE = [ ast.Import([alias('numpy', None)]), ast.Import([alias('tvm', None)]), ast.ImportFrom('tvm', [alias('relay', None)], 0), ast.ImportFrom('tvm', [alias('nd', None)], 0), - ast.ImportFrom('tvm', [alias('container', '_container')], + ast.ImportFrom('tvm.runtime', [alias('container', '_container')], 0), ast.ImportFrom('tvm.relay.backend.interpreter', [alias('RefValue', None), diff --git a/python/tvm/relay/backend/_vm.py b/python/tvm/runtime/_vm.py similarity index 100% rename from python/tvm/relay/backend/_vm.py rename to python/tvm/runtime/_vm.py diff --git a/python/tvm/runtime/container.py b/python/tvm/runtime/container.py index 196291fd325a..02a082affc77 100644 --- a/python/tvm/runtime/container.py +++ b/python/tvm/runtime/container.py @@ -15,6 +15,9 @@ # specific language governing permissions and limitations # under the License. """Runtime container structures.""" +import tvm._ffi + +from tvm.runtime import Object, ObjectTypes def getitem_helper(obj, elem_getter, length, idx): """Helper function to implement a pythonic getitem function. @@ -54,3 +57,56 @@ def getitem_helper(obj, elem_getter, length, idx): if idx < 0: idx += length return elem_getter(obj, idx) + + +@tvm._ffi.register_object("vm.ADT") +class ADT(Object): + """Algebatic data type(ADT) object. + + Parameters + ---------- + tag : int + The tag of ADT. + + fields : list[Object] or tuple[Object] + The source tuple. + """ + def __init__(self, tag, fields): + for f in fields: + assert isinstance(f, ObjectTypes), "Expect object or " \ + "tvm NDArray type, but received : {0}".format(type(f)) + self.__init_handle_by_constructor__(_ADT, tag, *fields) + + @property + def tag(self): + return _GetADTTag(self) + + def __getitem__(self, idx): + return getitem_helper( + self, _GetADTFields, len(self), idx) + + def __len__(self): + return _GetADTSize(self) + + +def tuple_object(fields=None): + """Create a ADT object from source tuple. + + Parameters + ---------- + fields : list[Object] or tuple[Object] + The source tuple. + + Returns + ------- + ret : ADT + The created object. + """ + fields = fields if fields else [] + for f in fields: + assert isinstance(f, ObjectTypes), "Expect object or tvm " \ + "NDArray type, but received : {0}".format(type(f)) + return _Tuple(*fields) + + +tvm._ffi._init_api("tvm.runtime.container") diff --git a/python/tvm/relay/backend/profiler_vm.py b/python/tvm/runtime/profiler_vm.py similarity index 100% rename from python/tvm/relay/backend/profiler_vm.py rename to python/tvm/runtime/profiler_vm.py diff --git a/python/tvm/relay/backend/vm.py b/python/tvm/runtime/vm.py similarity index 98% rename from python/tvm/relay/backend/vm.py rename to python/tvm/runtime/vm.py index 67afe0945ccc..7a709635a34d 100644 --- a/python/tvm/relay/backend/vm.py +++ b/python/tvm/runtime/vm.py @@ -24,13 +24,13 @@ import tvm import tvm.runtime.ndarray as _nd -from tvm.runtime import Object -from tvm import autotvm, container +from tvm.runtime import Object, container +from tvm import autotvm from tvm.relay import expr as _expr from tvm._ffi.runtime_ctypes import TVMByteArray from tvm._ffi import base as _base +from tvm.relay.backend.interpreter import Executor from . import _vm -from .interpreter import Executor def _convert(arg, cargs): if isinstance(arg, _expr.Constant): @@ -117,7 +117,7 @@ def save(self): # create a Relay VM. ctx = tvm.cpu() target = "llvm" - executable = relay.vm.compile(mod, target) + executable = tvm.runtime.vm.compile(mod, target) code, lib = executable.save() # save and load the code and lib file. tmp = tvm.contrib.util.tempdir() @@ -128,10 +128,10 @@ def save(self): loaded_lib = tvm.runtime.load_module(path_lib) loaded_code = bytearray(open(tmp.relpath("code.ro"), "rb").read()) # deserialize. - des_exec = relay.vm.Executable.load_exec(loaded_code, loaded_code) + des_exec = tvm.runtime.vm.Executable.load_exec(loaded_code, loaded_code) # execute the deserialized executable. x_data = np.random.rand(10, 10).astype('float32') - des_vm = relay.vm.VirtualMachine(des_exec) + des_vm = tvm.runtime.vm.VirtualMachine(des_exec) des_vm.init(ctx) res = des_vm.run(x_data) print(res.asnumpy()) @@ -556,7 +556,7 @@ class VMExecutor(Executor): Useful interface for experimentation and debugging the VM can also be used directly from the API. - supported by `tvm.relay.vm`. + supported by `tvm.runtime.vm`. Parameters ---------- diff --git a/src/runtime/container.cc b/src/runtime/container.cc index cd426482d285..f54ae6cf9c23 100644 --- a/src/runtime/container.cc +++ b/src/runtime/container.cc @@ -32,14 +32,14 @@ namespace runtime { using namespace vm; -TVM_REGISTER_GLOBAL("container._GetADTTag") +TVM_REGISTER_GLOBAL("runtime.container._GetADTTag") .set_body([](TVMArgs args, TVMRetValue* rv) { ObjectRef obj = args[0]; const auto& adt = Downcast(obj); *rv = static_cast(adt.tag()); }); -TVM_REGISTER_GLOBAL("container._GetADTSize") +TVM_REGISTER_GLOBAL("runtime.container._GetADTSize") .set_body([](TVMArgs args, TVMRetValue* rv) { ObjectRef obj = args[0]; const auto& adt = Downcast(obj); @@ -47,7 +47,7 @@ TVM_REGISTER_GLOBAL("container._GetADTSize") }); -TVM_REGISTER_GLOBAL("container._GetADTFields") +TVM_REGISTER_GLOBAL("runtime.container._GetADTFields") .set_body([](TVMArgs args, TVMRetValue* rv) { ObjectRef obj = args[0]; int idx = args[1]; @@ -56,7 +56,7 @@ TVM_REGISTER_GLOBAL("container._GetADTFields") *rv = adt[idx]; }); -TVM_REGISTER_GLOBAL("container._Tuple") +TVM_REGISTER_GLOBAL("runtime.container._Tuple") .set_body([](TVMArgs args, TVMRetValue* rv) { std::vector fields; for (auto i = 0; i < args.size(); ++i) { @@ -65,7 +65,7 @@ TVM_REGISTER_GLOBAL("container._Tuple") *rv = ADT::Tuple(fields); }); -TVM_REGISTER_GLOBAL("container._ADT") +TVM_REGISTER_GLOBAL("runtime.container._ADT") .set_body([](TVMArgs args, TVMRetValue* rv) { int itag = args[0]; size_t tag = static_cast(itag); diff --git a/tests/python/frontend/tensorflow/test_forward.py b/tests/python/frontend/tensorflow/test_forward.py index 830c3f6ea995..2340bd4e6318 100644 --- a/tests/python/frontend/tensorflow/test_forward.py +++ b/tests/python/frontend/tensorflow/test_forward.py @@ -62,7 +62,7 @@ def convert_to_list(x): def vmobj_to_list(o): if isinstance(o, tvm.nd.NDArray): return [o.asnumpy().tolist()] - elif isinstance(o, tvm.container.ADT): + elif isinstance(o, tvm.runtime.container.ADT): result = [] for f in o: result.extend(vmobj_to_list(f)) diff --git a/tests/python/relay/benchmarking/benchmark_vm.py b/tests/python/relay/benchmarking/benchmark_vm.py index 3513832184ce..df7eb185da74 100644 --- a/tests/python/relay/benchmarking/benchmark_vm.py +++ b/tests/python/relay/benchmarking/benchmark_vm.py @@ -19,7 +19,8 @@ import tvm from tvm.contrib import graph_runtime -from tvm import relay, container +from tvm import relay +from tvm.runtime import container from tvm.relay import testing from tvm.relay import vm diff --git a/tests/python/relay/test_adt.py b/tests/python/relay/test_adt.py index 00c07b928b1a..81594c0d04ed 100644 --- a/tests/python/relay/test_adt.py +++ b/tests/python/relay/test_adt.py @@ -117,7 +117,7 @@ def tree_to_dict(t): def vmobj_to_list(o, dtype="float32"): if isinstance(o, tvm.nd.NDArray): return [o.asnumpy().tolist()] - elif isinstance(o, tvm.container.ADT): + elif isinstance(o, tvm.runtime.container.ADT): if len(o) == 0: tensor_nil = p.get_var("tensor_nil", dtype=dtype) if tensor_nil.tag == o.tag: diff --git a/tests/python/relay/test_backend_interpreter.py b/tests/python/relay/test_backend_interpreter.py index 11a9e05b3e7f..28906f19ea3a 100644 --- a/tests/python/relay/test_backend_interpreter.py +++ b/tests/python/relay/test_backend_interpreter.py @@ -18,7 +18,8 @@ import tvm import tvm.testing from tvm import nd -from tvm import relay, container +from tvm import relay +from tvm.runtime import container from tvm.relay.backend.interpreter import RefValue, ConstructorValue from tvm.relay.scope_builder import ScopeBuilder from tvm.relay import testing, create_executor diff --git a/tests/python/relay/test_external_codegen.py b/tests/python/relay/test_external_codegen.py index 13193fc87e07..fb0b5894a43f 100644 --- a/tests/python/relay/test_external_codegen.py +++ b/tests/python/relay/test_external_codegen.py @@ -18,12 +18,12 @@ import os import sys import numpy as np -import pytest import tvm import tvm.relay.testing import tvm.relay.transform from tvm import relay +from tvm import runtime from tvm.contrib import util def check_result(mod, map_inputs, out_shape, result, tol=1e-5, target="llvm", @@ -49,11 +49,11 @@ def update_lib(lib): def check_vm_result(): with relay.build_config(opt_level=3, disabled_pass=["AlterOpLayout"]): - exe = relay.vm.compile(mod, target=target) + exe = runtime.vm.compile(mod, target=target) code, lib = exe.save() lib = update_lib(lib) - exe = relay.vm.Executable.load_exec(code, lib) - vm = relay.vm.VirtualMachine(exe) + exe = runtime.vm.Executable.load_exec(code, lib) + vm = runtime.vm.VirtualMachine(exe) vm.init(ctx) out = vm.run(**map_inputs) tvm.testing.assert_allclose(out.asnumpy(), result, rtol=tol, atol=tol) diff --git a/tests/python/relay/test_pass_partition_graph.py b/tests/python/relay/test_pass_partition_graph.py index 75d3c932f05a..28ba3d115d5c 100644 --- a/tests/python/relay/test_pass_partition_graph.py +++ b/tests/python/relay/test_pass_partition_graph.py @@ -24,6 +24,7 @@ import tvm.relay.testing import tvm.relay.transform as transform from tvm import relay +from tvm import runtime from tvm.contrib import util from tvm.relay.annotation import compiler_begin, compiler_end from tvm.relay.expr_functor import ExprMutator @@ -182,17 +183,17 @@ def update_lib(lib): lib_name = 'lib.so' lib_path = tmp_path.relpath(lib_name) lib.export_library(lib_path, fcompile=False, **kwargs) - lib = tvm.runtime.load_module(lib_path) + lib = runtime.load_module(lib_path) return lib def check_vm_result(): with relay.build_config(opt_level=3, disabled_pass=["AlterOpLayout"]): - exe = relay.vm.compile(mod, target=target, params=params) + exe = runtime.vm.compile(mod, target=target, params=params) code, lib = exe.save() lib = update_lib(lib) - exe = relay.vm.Executable.load_exec(code, lib) - vm = relay.vm.VirtualMachine(exe) + exe = runtime.vm.Executable.load_exec(code, lib) + vm = runtime.vm.VirtualMachine(exe) vm.init(ctx) out = vm.run(**map_inputs) tvm.testing.assert_allclose(out.asnumpy(), result, rtol=tol, atol=tol) diff --git a/tests/python/relay/test_py_converter.py b/tests/python/relay/test_py_converter.py index 76aa697a2aab..e46b6d41eeb5 100644 --- a/tests/python/relay/test_py_converter.py +++ b/tests/python/relay/test_py_converter.py @@ -19,7 +19,7 @@ from tvm import relay from tvm.relay.testing import to_python, run_as_python from tvm.relay.prelude import Prelude -from tvm.container import ADT +from tvm.runtime.container import ADT from tvm.relay.backend.interpreter import RefValue, ConstructorValue # helper: uses a dummy let binding to sequence a list diff --git a/tests/python/relay/test_vm.py b/tests/python/relay/test_vm.py index 9ea939ce9c83..86cc221c2606 100644 --- a/tests/python/relay/test_vm.py +++ b/tests/python/relay/test_vm.py @@ -14,16 +14,16 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -import os +import numpy as np +import pytest import tvm -import numpy as np +from tvm import runtime from tvm import relay from tvm.relay.scope_builder import ScopeBuilder from tvm.relay.testing.config import ctx_list from tvm.relay.prelude import Prelude from tvm.relay import testing -import pytest def check_result(args, expected_result, mod=None): """ @@ -51,15 +51,15 @@ def veval(f, *args, ctx=tvm.cpu(), target="llvm"): else: assert isinstance(f, relay.Module), "expected expression or module" mod = f - exe = relay.vm.compile(mod, target) - vm = relay.vm.VirtualMachine(exe) + exe = runtime.vm.compile(mod, target) + vm = runtime.vm.VirtualMachine(exe) vm.init(ctx) return vm.invoke("main", *args) def vmobj_to_list(o): if isinstance(o, tvm.nd.NDArray): return [o.asnumpy().tolist()] - elif isinstance(o, tvm.container.ADT): + elif isinstance(o, tvm.runtime.container.ADT): result = [] for f in o: result.extend(vmobj_to_list(f)) @@ -573,7 +573,7 @@ def test_add_op_broadcast(): def test_vm_optimize(): mod, params = testing.resnet.get_workload(batch_size=1, num_layers=18) - comp = relay.backend.vm.VMCompiler() + comp = runtime.vm.VMCompiler() opt_mod, _ = comp.optimize(mod, "llvm", params) if __name__ == "__main__": diff --git a/tests/python/relay/test_vm_serialization.py b/tests/python/relay/test_vm_serialization.py index 6f4e09a393ff..cc06857b129f 100644 --- a/tests/python/relay/test_vm_serialization.py +++ b/tests/python/relay/test_vm_serialization.py @@ -19,9 +19,9 @@ import numpy as np import tvm +from tvm.runtime import vm as _vm from tvm import relay from tvm.relay.module import Module as rly_module -from tvm.relay import vm as _vm from tvm.relay.scope_builder import ScopeBuilder from tvm.relay.prelude import Prelude from tvm.contrib import util diff --git a/tests/python/unittest/test_container.py b/tests/python/unittest/test_container.py index 7bdab82d7a65..5ed6e04e0b45 100644 --- a/tests/python/unittest/test_container.py +++ b/tests/python/unittest/test_container.py @@ -18,7 +18,7 @@ import numpy as np import tvm from tvm import nd, relay -from tvm import container as _container +from tvm.runtime import container as _container def test_adt_constructor(): diff --git a/tests/python/unittest/test_runtime_vm_profiler.py b/tests/python/unittest/test_runtime_vm_profiler.py index b7bbe2f64c96..cc125a1e5ea6 100644 --- a/tests/python/unittest/test_runtime_vm_profiler.py +++ b/tests/python/unittest/test_runtime_vm_profiler.py @@ -19,6 +19,8 @@ import numpy as np import pytest +from tvm.runtime import vm as _vm +from tvm.runtime import profiler_vm from tvm import relay from tvm.relay.testing import resnet @@ -26,10 +28,10 @@ def test_basic(): mod, params = resnet.get_workload() target = 'llvm' ctx = tvm.cpu() - if not relay.profiler_vm.enabled(): + if not profiler_vm.enabled(): return - exe = relay.vm.compile(mod, target, params=params) - vm = relay.profiler_vm.VirtualMachineProfiler(exe) + exe = _vm.compile(mod, target, params=params) + vm = profiler_vm.VirtualMachineProfiler(exe) vm.init(ctx) data = np.random.rand(1, 3, 224, 224).astype('float32') From a31b23ed6c2c853d2091187dc36e3f27c421d619 Mon Sep 17 00:00:00 2001 From: Zhi Chen Date: Mon, 10 Feb 2020 23:01:21 +0000 Subject: [PATCH 2/5] separate compilation and runtime vm --- python/tvm/autotvm/task/relay_integration.py | 3 +- python/tvm/relay/__init__.py | 1 + python/tvm/relay/backend/_vm.py | 21 ++ python/tvm/relay/backend/vm.py | 258 ++++++++++++++++++ python/tvm/relay/build_module.py | 4 +- python/tvm/runtime/_vm.py | 2 +- python/tvm/runtime/vm.py | 190 +------------ src/runtime/vm/executable.cc | 10 +- src/runtime/vm/vm.cc | 2 +- .../python/relay/benchmarking/benchmark_vm.py | 3 +- tests/python/relay/test_external_codegen.py | 2 +- .../python/relay/test_pass_partition_graph.py | 2 +- tests/python/relay/test_vm.py | 4 +- tests/python/relay/test_vm_serialization.py | 5 +- .../unittest/test_runtime_vm_profiler.py | 7 +- 15 files changed, 301 insertions(+), 213 deletions(-) create mode 100644 python/tvm/relay/backend/_vm.py create mode 100644 python/tvm/relay/backend/vm.py diff --git a/python/tvm/autotvm/task/relay_integration.py b/python/tvm/autotvm/task/relay_integration.py index 2f60be63833e..7471ca3d6c8f 100644 --- a/python/tvm/autotvm/task/relay_integration.py +++ b/python/tvm/autotvm/task/relay_integration.py @@ -39,7 +39,6 @@ def _lower(mod, """ # pylint: disable=import-outside-toplevel from tvm import relay - from tvm import runtime from tvm.relay.backend import graph_runtime_codegen if hasattr(target, 'device_name') and target.device_name == "vta": @@ -50,7 +49,7 @@ def _lower(mod, grc = graph_runtime_codegen.GraphRuntimeCodegen(None, target) grc.codegen(mod["main"]) # default case - compiler = runtime.vm.VMCompiler() + compiler = relay.vm.VMCompiler() if params: compiler.set_params(params) compiler.lower(mod, target=target) diff --git a/python/tvm/relay/__init__.py b/python/tvm/relay/__init__.py index 904c0702916a..25956b4656fd 100644 --- a/python/tvm/relay/__init__.py +++ b/python/tvm/relay/__init__.py @@ -36,6 +36,7 @@ from . import debug from . import param_dict from . import feature +from .backend import vm # Root operators from .op import Op diff --git a/python/tvm/relay/backend/_vm.py b/python/tvm/relay/backend/_vm.py new file mode 100644 index 000000000000..cffbbdccde5a --- /dev/null +++ b/python/tvm/relay/backend/_vm.py @@ -0,0 +1,21 @@ +# 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. +"""The Relay virtual machine FFI namespace. +""" +import tvm._ffi + +tvm._ffi._init_api("relay._vm", __name__) diff --git a/python/tvm/relay/backend/vm.py b/python/tvm/relay/backend/vm.py new file mode 100644 index 000000000000..1c635a40de66 --- /dev/null +++ b/python/tvm/relay/backend/vm.py @@ -0,0 +1,258 @@ +# License .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. +# pylint: disable=no-else-return, unidiomatic-typecheck, undefined-variable, invalid-name, redefined-builtin +""" +The Relay Virtual Machine. + +Implements a Python interface to compiling and executing on the Relay VM. +""" +import numpy as np + +import tvm +import tvm.runtime.ndarray as _nd +import tvm.runtime.vm as vm_rt +from tvm import autotvm +from tvm.relay import expr as _expr +from tvm.relay.backend.interpreter import Executor +from . import _vm + + +def compile(mod, target=None, target_host=None, params=None): + """Compile the module to VM executable. A helper function for VMCompiler. + + 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 + ------- + exec : tvm.runtime.vmf.Executable + The VM executable that contains both library code and bytecode. + """ + compiler = VMCompiler() + if params: + compiler.set_params(params) + compiler.lower(mod, target, target_host) + compiler.codegen() + return compiler.get_exec() + + +class VMCompiler(object): + """Compiler that compiles Relay module to VM executable.""" + def __init__(self): + self.mod = _vm._VMCompiler() + self._lower = self.mod["lower"] + self._codegen = self.mod["codegen"] + self._get_exec = self.mod["get_executable"] + self._set_params_func = self.mod["set_params"] + self._get_params_func = self.mod["get_params"] + self._optimize = self.mod["optimize"] + + def set_params(self, params): + """Set constant parameters for the model. + + Parameters + ---------- + params : dict of str to NDArray + Input parameters to the graph that do not change + during inference time. Used for constant folding. + """ + 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 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 lower(self, mod, target=None, target_host=None): + """Lower the module to VM bytecode. + + 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. + """ + target = self._update_target(target) + target_host = self._update_target_host(target, target_host) + tophub_context = self._tophub_context(target) + with tophub_context: + self._lower(mod, target, target_host) + + def codegen(self): + """Generate the kernel library.""" + self._codegen() + + def optimize(self, mod, target=None, params=None): + """Helper method that optimizes a Relay module via VM. + + Parameters + ---------- + mod : relay.Module + + target : str, :any:`tvm.target.Target`, or dict of str (i.e. + device/context name) to str/tvm.target.Target, optional + + params : dict of str to NDArray + Input parameters to the graph that do not change + during inference time. Used for constant folding. + + Returns + ------- + mod : relay.Module + The optimized relay module. + + params : dict + The parameters of the final module. + """ + target = self._update_target(target) + if params: + self.set_params(params) + return self._optimize(mod, target), self.get_params() + + def get_exec(self): + """Get the VM executable. + + Returns + ------- + exec : tvm.runtime.vm.Executable + The VM executable that contains both library code and bytecode. + """ + return vm_rt.Executable(self._get_exec()) + + 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 _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.runtime.enabled("llvm") else "stackvm" + if isinstance(target_host, str): + target_host = tvm.target.create(target_host) + return target_host + + def _tophub_context(self, target): + """Get the autotvm context.""" + # 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 + + +class VMExecutor(Executor): + """ + An implementation of the executor interface for + the Relay VM. + + Useful interface for experimentation and debugging + the VM can also be used directly from the API. + supported by `tvm.runtime.vm`. + + Parameters + ---------- + mod : :py:class:`~tvm.relay.module.Module` + The module to support the execution. + + ctx : :py:class:`~tvm.TVMContext` + The runtime context to run the code on. + + target : :py:class:`Target` + The target option to build the function. + """ + def __init__(self, mod, ctx, target): + if mod is None: + raise RuntimeError("Must provide module to get VM executor.") + self.mod = mod + self.ctx = ctx + self.target = target + self.executable = compile(mod, target) + self.vm = vm_rt.VirtualMachine(self.executable) + self.vm.init(ctx) + + def _make_executor(self, expr=None): + main = self.mod["main"] + + def _vm_wrapper(*args, **kwargs): + args = self._convert_args(main, args, kwargs) + return self.vm.run(*args) + + return _vm_wrapper diff --git a/python/tvm/relay/build_module.py b/python/tvm/relay/build_module.py index 8e70b2840b53..ea7a4cacfc60 100644 --- a/python/tvm/relay/build_module.py +++ b/python/tvm/relay/build_module.py @@ -22,7 +22,6 @@ import numpy as np from tvm import expr as tvm_expr -from tvm.runtime import vm from .. import nd as _nd, target as _target, autotvm from ..contrib import graph_runtime as _graph_rt from . import _build_module @@ -30,6 +29,7 @@ from . import expr as _expr from .module import Module as _Module from .backend import interpreter as _interpreter +from .backend.vm import VMExecutor def _update_target(target): target = target if target else _target.current_target() @@ -408,5 +408,5 @@ def create_executor(kind="debug", if kind == "graph": return GraphExecutor(mod, ctx, target) if kind == "vm": - return vm.VMExecutor(mod, ctx, target) + return VMExecutor(mod, ctx, target) raise RuntimeError("unknown execution strategy: {0}".format(kind)) diff --git a/python/tvm/runtime/_vm.py b/python/tvm/runtime/_vm.py index cffbbdccde5a..939d3e12c93c 100644 --- a/python/tvm/runtime/_vm.py +++ b/python/tvm/runtime/_vm.py @@ -18,4 +18,4 @@ """ import tvm._ffi -tvm._ffi._init_api("relay._vm", __name__) +tvm._ffi._init_api("runtime._vm", __name__) diff --git a/python/tvm/runtime/vm.py b/python/tvm/runtime/vm.py index 7a709635a34d..aa2ea0f586b1 100644 --- a/python/tvm/runtime/vm.py +++ b/python/tvm/runtime/vm.py @@ -23,9 +23,7 @@ import numpy as np import tvm -import tvm.runtime.ndarray as _nd from tvm.runtime import Object, container -from tvm import autotvm from tvm.relay import expr as _expr from tvm._ffi.runtime_ctypes import TVMByteArray from tvm._ffi import base as _base @@ -117,7 +115,7 @@ def save(self): # create a Relay VM. ctx = tvm.cpu() target = "llvm" - executable = tvm.runtime.vm.compile(mod, target) + executable = relay.vm.compile(mod, target) code, lib = executable.save() # save and load the code and lib file. tmp = tvm.contrib.util.tempdir() @@ -363,192 +361,6 @@ def run(self, *args, **kwargs): return self.invoke("main", *args, **kwargs) -def compile(mod, target=None, target_host=None, params=None): - """Compile the module to VM executable. A helper function for VMCompiler. - - 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 - ------- - exec : Executable - The VM executable that contains both library code and bytecode. - """ - compiler = VMCompiler() - if params: - compiler.set_params(params) - compiler.lower(mod, target, target_host) - compiler.codegen() - return compiler.get_exec() - - -class VMCompiler(object): - """Compiler that compiles Relay module to VM executable.""" - def __init__(self): - self.mod = _vm._VMCompiler() - self._lower = self.mod["lower"] - self._codegen = self.mod["codegen"] - self._get_exec = self.mod["get_executable"] - self._set_params_func = self.mod["set_params"] - self._get_params_func = self.mod["get_params"] - self._optimize = self.mod["optimize"] - - def set_params(self, params): - """Set constant parameters for the model. - - Parameters - ---------- - params : dict of str to NDArray - Input parameters to the graph that do not change - during inference time. Used for constant folding. - """ - 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 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 lower(self, mod, target=None, target_host=None): - """Lower the module to VM bytecode. - - 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. - """ - target = self._update_target(target) - target_host = self._update_target_host(target, target_host) - tophub_context = self._tophub_context(target) - with tophub_context: - self._lower(mod, target, target_host) - - def codegen(self): - """Generate the kernel library.""" - self._codegen() - - def optimize(self, mod, target=None, params=None): - """Helper method that optimizes a Relay module via VM. - - Parameters - ---------- - mod : relay.Module - - target : str, :any:`tvm.target.Target`, or dict of str (i.e. - device/context name) to str/tvm.target.Target, optional - - params : dict of str to NDArray - Input parameters to the graph that do not change - during inference time. Used for constant folding. - - Returns - ------- - mod : relay.Module - The optimized relay module. - - params : dict - The parameters of the final module. - """ - target = self._update_target(target) - if params: - self.set_params(params) - return self._optimize(mod, target), self.get_params() - - def get_exec(self): - """Get the VM executable. - - Returns - ------- - exec : Executable - The VM executable that contains both library code and bytecode. - """ - return Executable(self._get_exec()) - - 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 _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.runtime.enabled("llvm") else "stackvm" - if isinstance(target_host, str): - target_host = tvm.target.create(target_host) - return target_host - - def _tophub_context(self, target): - """Get the autotvm context.""" - # 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 - class VMExecutor(Executor): """ An implementation of the executor interface for diff --git a/src/runtime/vm/executable.cc b/src/runtime/vm/executable.cc index bd650665e196..624bf9d4c242 100644 --- a/src/runtime/vm/executable.cc +++ b/src/runtime/vm/executable.cc @@ -738,7 +738,7 @@ void Executable::LoadCodeSection(dmlc::Stream* strm) { } } -TVM_REGISTER_GLOBAL("relay._vm.GetNumOfGlobals") +TVM_REGISTER_GLOBAL("runtime._vm.GetNumOfGlobals") .set_body([](TVMArgs args, TVMRetValue* rv) { runtime::Module mod = args[0]; const auto* exec = dynamic_cast(mod.operator->()); @@ -746,7 +746,7 @@ TVM_REGISTER_GLOBAL("relay._vm.GetNumOfGlobals") *rv = static_cast(exec->global_map.size()); }); -TVM_REGISTER_GLOBAL("relay._vm.GetGlobalFields") +TVM_REGISTER_GLOBAL("runtime._vm.GetGlobalFields") .set_body([](TVMArgs args, TVMRetValue* rv) { runtime::Module mod = args[0]; const auto* exec = dynamic_cast(mod.operator->()); @@ -763,7 +763,7 @@ TVM_REGISTER_GLOBAL("relay._vm.GetGlobalFields") *rv = globals[idx].first; }); -TVM_REGISTER_GLOBAL("relay._vm.GetNumOfPrimitives") +TVM_REGISTER_GLOBAL("runtime._vm.GetNumOfPrimitives") .set_body([](TVMArgs args, TVMRetValue* rv) { runtime::Module mod = args[0]; const auto* exec = dynamic_cast(mod.operator->()); @@ -772,7 +772,7 @@ TVM_REGISTER_GLOBAL("relay._vm.GetNumOfPrimitives") }); -TVM_REGISTER_GLOBAL("relay._vm.GetPrimitiveFields") +TVM_REGISTER_GLOBAL("runtime._vm.GetPrimitiveFields") .set_body([](TVMArgs args, TVMRetValue* rv) { runtime::Module mod = args[0]; const auto* exec = dynamic_cast(mod.operator->()); @@ -789,7 +789,7 @@ TVM_REGISTER_GLOBAL("relay._vm.GetPrimitiveFields") } }); -TVM_REGISTER_GLOBAL("relay._vm.Load_Executable") +TVM_REGISTER_GLOBAL("runtime._vm.Load_Executable") .set_body_typed([]( std::string code, runtime::Module lib) { diff --git a/src/runtime/vm/vm.cc b/src/runtime/vm/vm.cc index 84a3e26fb7f9..ced16d799509 100644 --- a/src/runtime/vm/vm.cc +++ b/src/runtime/vm/vm.cc @@ -1057,7 +1057,7 @@ runtime::Module CreateVirtualMachine(const Executable* exec) { return runtime::Module(vm); } -TVM_REGISTER_GLOBAL("relay._vm._VirtualMachine") +TVM_REGISTER_GLOBAL("runtime._vm._VirtualMachine") .set_body([](TVMArgs args, TVMRetValue* rv) { runtime::Module mod = args[0]; const auto* exec = dynamic_cast(mod.operator->()); diff --git a/tests/python/relay/benchmarking/benchmark_vm.py b/tests/python/relay/benchmarking/benchmark_vm.py index df7eb185da74..55d788756b5c 100644 --- a/tests/python/relay/benchmarking/benchmark_vm.py +++ b/tests/python/relay/benchmarking/benchmark_vm.py @@ -21,6 +21,7 @@ from tvm.contrib import graph_runtime from tvm import relay from tvm.runtime import container +from tvm.runtime import vm as vm_rt from tvm.relay import testing from tvm.relay import vm @@ -59,7 +60,7 @@ def get_vm_output(mod, data, params, target, ctx, dtype='float32', number=2, repeat=20): with relay.build_config(opt_level=3): exe = vm.compile(mod, target, params=params) - rly_vm = vm.VirtualMachine(exe) + rly_vm = vm_rt.VirtualMachine(exe) rly_vm.init(ctx) result = rly_vm.run(data) diff --git a/tests/python/relay/test_external_codegen.py b/tests/python/relay/test_external_codegen.py index fb0b5894a43f..17585835c178 100644 --- a/tests/python/relay/test_external_codegen.py +++ b/tests/python/relay/test_external_codegen.py @@ -49,7 +49,7 @@ def update_lib(lib): def check_vm_result(): with relay.build_config(opt_level=3, disabled_pass=["AlterOpLayout"]): - exe = runtime.vm.compile(mod, target=target) + exe = relay.vm.compile(mod, target=target) code, lib = exe.save() lib = update_lib(lib) exe = runtime.vm.Executable.load_exec(code, lib) diff --git a/tests/python/relay/test_pass_partition_graph.py b/tests/python/relay/test_pass_partition_graph.py index 28ba3d115d5c..869dba82bea4 100644 --- a/tests/python/relay/test_pass_partition_graph.py +++ b/tests/python/relay/test_pass_partition_graph.py @@ -189,7 +189,7 @@ def update_lib(lib): def check_vm_result(): with relay.build_config(opt_level=3, disabled_pass=["AlterOpLayout"]): - exe = runtime.vm.compile(mod, target=target, params=params) + exe = relay.vm.compile(mod, target=target, params=params) code, lib = exe.save() lib = update_lib(lib) exe = runtime.vm.Executable.load_exec(code, lib) diff --git a/tests/python/relay/test_vm.py b/tests/python/relay/test_vm.py index 86cc221c2606..cd8a875f5bf7 100644 --- a/tests/python/relay/test_vm.py +++ b/tests/python/relay/test_vm.py @@ -51,7 +51,7 @@ def veval(f, *args, ctx=tvm.cpu(), target="llvm"): else: assert isinstance(f, relay.Module), "expected expression or module" mod = f - exe = runtime.vm.compile(mod, target) + exe = relay.vm.compile(mod, target) vm = runtime.vm.VirtualMachine(exe) vm.init(ctx) return vm.invoke("main", *args) @@ -573,7 +573,7 @@ def test_add_op_broadcast(): def test_vm_optimize(): mod, params = testing.resnet.get_workload(batch_size=1, num_layers=18) - comp = runtime.vm.VMCompiler() + comp = relay.vm.VMCompiler() opt_mod, _ = comp.optimize(mod, "llvm", params) if __name__ == "__main__": diff --git a/tests/python/relay/test_vm_serialization.py b/tests/python/relay/test_vm_serialization.py index cc06857b129f..515baa2ef6ce 100644 --- a/tests/python/relay/test_vm_serialization.py +++ b/tests/python/relay/test_vm_serialization.py @@ -20,6 +20,7 @@ import tvm from tvm.runtime import vm as _vm +from tvm.relay import vm as rly_vm from tvm import relay from tvm.relay.module import Module as rly_module from tvm.relay.scope_builder import ScopeBuilder @@ -31,11 +32,11 @@ def create_exec(f, target="llvm", params=None): if isinstance(f, relay.Expr): mod = relay.Module() mod["main"] = f - executable = _vm.compile(mod, target=target, params=params) + executable = rly_vm.compile(mod, target=target, params=params) return executable else: assert isinstance(f, relay.Module), "expected mod as relay.Module" - executable = _vm.compile(f, target=target, params=params) + executable = rly_vm.compile(f, target=target, params=params) return executable diff --git a/tests/python/unittest/test_runtime_vm_profiler.py b/tests/python/unittest/test_runtime_vm_profiler.py index cc125a1e5ea6..6de94f18f7e0 100644 --- a/tests/python/unittest/test_runtime_vm_profiler.py +++ b/tests/python/unittest/test_runtime_vm_profiler.py @@ -14,12 +14,7 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. -import os import tvm -import numpy as np - -import pytest -from tvm.runtime import vm as _vm from tvm.runtime import profiler_vm from tvm import relay from tvm.relay.testing import resnet @@ -30,7 +25,7 @@ def test_basic(): ctx = tvm.cpu() if not profiler_vm.enabled(): return - exe = _vm.compile(mod, target, params=params) + exe = relay.vm.compile(mod, target, params=params) vm = profiler_vm.VirtualMachineProfiler(exe) vm.init(ctx) From 2b921335f9a718ef7c15c196fe72289150a17613 Mon Sep 17 00:00:00 2001 From: Zhi Chen Date: Tue, 11 Feb 2020 00:17:59 +0000 Subject: [PATCH 3/5] use _ffi_api --- python/tvm/relay/backend/vm.py | 2 +- python/tvm/runtime/_vm.py | 21 ------ python/tvm/runtime/profiler_vm.py | 7 +- python/tvm/runtime/vm.py | 64 +++---------------- src/runtime/vm/executable.cc | 10 +-- src/runtime/vm/profiler/vm.cc | 2 +- src/runtime/vm/vm.cc | 2 +- .../unittest/test_runtime_vm_profiler.py | 2 + 8 files changed, 24 insertions(+), 86 deletions(-) delete mode 100644 python/tvm/runtime/_vm.py diff --git a/python/tvm/relay/backend/vm.py b/python/tvm/relay/backend/vm.py index 1c635a40de66..b61fafda1d1d 100644 --- a/python/tvm/relay/backend/vm.py +++ b/python/tvm/relay/backend/vm.py @@ -59,7 +59,7 @@ def compile(mod, target=None, target_host=None, params=None): Returns ------- - exec : tvm.runtime.vmf.Executable + exec : tvm.runtime.vm.Executable The VM executable that contains both library code and bytecode. """ compiler = VMCompiler() diff --git a/python/tvm/runtime/_vm.py b/python/tvm/runtime/_vm.py deleted file mode 100644 index 939d3e12c93c..000000000000 --- a/python/tvm/runtime/_vm.py +++ /dev/null @@ -1,21 +0,0 @@ -# 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. -"""The Relay virtual machine FFI namespace. -""" -import tvm._ffi - -tvm._ffi._init_api("runtime._vm", __name__) diff --git a/python/tvm/runtime/profiler_vm.py b/python/tvm/runtime/profiler_vm.py index fa0326edb95b..11cedd5fa200 100644 --- a/python/tvm/runtime/profiler_vm.py +++ b/python/tvm/runtime/profiler_vm.py @@ -20,18 +20,19 @@ Provides extra APIs for profiling vm execution. """ -from . import vm, _vm +from tvm.runtime import _ffi_api +from . import vm def enabled(): """Whether vm profiler is enabled.""" - return hasattr(_vm, "_VirtualMachineDebug") + return hasattr(_ffi_api, "_VirtualMachineDebug") class VirtualMachineProfiler(vm.VirtualMachine): """Relay profile VM runtime.""" def __init__(self, mod): super(VirtualMachineProfiler, self).__init__(mod) m = mod.module if isinstance(mod, vm.Executable) else mod - self.mod = _vm._VirtualMachineDebug(m) + self.mod = _ffi_api._VirtualMachineDebug(m) self._init = self.mod["init"] self._invoke = self.mod["invoke"] self._get_stat = self.mod["get_stat"] diff --git a/python/tvm/runtime/vm.py b/python/tvm/runtime/vm.py index aa2ea0f586b1..b6de7f36f42a 100644 --- a/python/tvm/runtime/vm.py +++ b/python/tvm/runtime/vm.py @@ -16,24 +16,20 @@ # under the License. # pylint: disable=no-else-return, unidiomatic-typecheck, undefined-variable, invalid-name, redefined-builtin """ -The Relay Virtual Machine. +The Relay Virtual Machine runtime. -Implements a Python interface to compiling and executing on the Relay VM. +Implements a Python interface to executing the compiled VM object. """ import numpy as np import tvm from tvm.runtime import Object, container -from tvm.relay import expr as _expr +from tvm.runtime import _ffi_api from tvm._ffi.runtime_ctypes import TVMByteArray from tvm._ffi import base as _base -from tvm.relay.backend.interpreter import Executor -from . import _vm def _convert(arg, cargs): - if isinstance(arg, _expr.Constant): - cargs.append(arg.data) - elif isinstance(arg, Object): + if isinstance(arg, Object): cargs.append(arg) elif isinstance(arg, np.ndarray): nd_arr = tvm.nd.array(arg, ctx=tvm.cpu(0)) @@ -163,7 +159,7 @@ def load_exec(bytecode, lib): raise TypeError("lib is expected to be the type of tvm.runtime.Module" + ", but received {}".format(type(lib))) - return Executable(_vm.Load_Executable(bytecode, lib)) + return Executable(_ffi_api.Load_Executable(bytecode, lib)) @property def lib(self): @@ -197,9 +193,9 @@ def primitive_ops(self): The list of primitive ops. """ ret = [] - num_primitives = _vm.GetNumOfPrimitives(self.module) + num_primitives = _ffi_api.GetNumOfPrimitives(self.module) for i in range(num_primitives): - ret.append(_vm.GetPrimitiveFields(self.module, i)) + ret.append(_ffi_api.GetPrimitiveFields(self.module, i)) return ret @property @@ -240,9 +236,9 @@ def globals(self): The globals contained in the executable. """ ret = [] - num_globals = _vm.GetNumOfGlobals(self.module) + num_globals = _ffi_api.GetNumOfGlobals(self.module) for i in range(num_globals): - ret.append(_vm.GetGlobalFields(self.module, i)) + ret.append(_ffi_api.GetGlobalFields(self.module, i)) return ret @property @@ -272,7 +268,7 @@ def __init__(self, mod): raise TypeError("mod is expected to be the type of Executable or " + "tvm.Module, but received {}".format(type(mod))) m = mod.module if isinstance(mod, Executable) else mod - self.mod = _vm._VirtualMachine(m) + self.mod = _ffi_api._VirtualMachine(m) self._exec = mod self._init = self.mod["init"] self._invoke = self.mod["invoke"] @@ -359,43 +355,3 @@ def run(self, *args, **kwargs): The output. """ return self.invoke("main", *args, **kwargs) - - -class VMExecutor(Executor): - """ - An implementation of the executor interface for - the Relay VM. - - Useful interface for experimentation and debugging - the VM can also be used directly from the API. - supported by `tvm.runtime.vm`. - - Parameters - ---------- - mod : :py:class:`~tvm.relay.module.Module` - The module to support the execution. - - ctx : :py:class:`~tvm.TVMContext` - The runtime context to run the code on. - - target : :py:class:`Target` - The target option to build the function. - """ - def __init__(self, mod, ctx, target): - if mod is None: - raise RuntimeError("Must provide module to get VM executor.") - self.mod = mod - self.ctx = ctx - self.target = target - self.executable = compile(mod, target) - self.vm = VirtualMachine(self.executable) - self.vm.init(ctx) - - def _make_executor(self, expr=None): - main = self.mod["main"] - - def _vm_wrapper(*args, **kwargs): - args = self._convert_args(main, args, kwargs) - return self.vm.run(*args) - - return _vm_wrapper diff --git a/src/runtime/vm/executable.cc b/src/runtime/vm/executable.cc index 624bf9d4c242..c2036da46e09 100644 --- a/src/runtime/vm/executable.cc +++ b/src/runtime/vm/executable.cc @@ -738,7 +738,7 @@ void Executable::LoadCodeSection(dmlc::Stream* strm) { } } -TVM_REGISTER_GLOBAL("runtime._vm.GetNumOfGlobals") +TVM_REGISTER_GLOBAL("runtime.GetNumOfGlobals") .set_body([](TVMArgs args, TVMRetValue* rv) { runtime::Module mod = args[0]; const auto* exec = dynamic_cast(mod.operator->()); @@ -746,7 +746,7 @@ TVM_REGISTER_GLOBAL("runtime._vm.GetNumOfGlobals") *rv = static_cast(exec->global_map.size()); }); -TVM_REGISTER_GLOBAL("runtime._vm.GetGlobalFields") +TVM_REGISTER_GLOBAL("runtime.GetGlobalFields") .set_body([](TVMArgs args, TVMRetValue* rv) { runtime::Module mod = args[0]; const auto* exec = dynamic_cast(mod.operator->()); @@ -763,7 +763,7 @@ TVM_REGISTER_GLOBAL("runtime._vm.GetGlobalFields") *rv = globals[idx].first; }); -TVM_REGISTER_GLOBAL("runtime._vm.GetNumOfPrimitives") +TVM_REGISTER_GLOBAL("runtime.GetNumOfPrimitives") .set_body([](TVMArgs args, TVMRetValue* rv) { runtime::Module mod = args[0]; const auto* exec = dynamic_cast(mod.operator->()); @@ -772,7 +772,7 @@ TVM_REGISTER_GLOBAL("runtime._vm.GetNumOfPrimitives") }); -TVM_REGISTER_GLOBAL("runtime._vm.GetPrimitiveFields") +TVM_REGISTER_GLOBAL("runtime.GetPrimitiveFields") .set_body([](TVMArgs args, TVMRetValue* rv) { runtime::Module mod = args[0]; const auto* exec = dynamic_cast(mod.operator->()); @@ -789,7 +789,7 @@ TVM_REGISTER_GLOBAL("runtime._vm.GetPrimitiveFields") } }); -TVM_REGISTER_GLOBAL("runtime._vm.Load_Executable") +TVM_REGISTER_GLOBAL("runtime.Load_Executable") .set_body_typed([]( std::string code, runtime::Module lib) { diff --git a/src/runtime/vm/profiler/vm.cc b/src/runtime/vm/profiler/vm.cc index 3b7b7aa8e73e..4dac66e50a82 100644 --- a/src/runtime/vm/profiler/vm.cc +++ b/src/runtime/vm/profiler/vm.cc @@ -133,7 +133,7 @@ runtime::Module CreateVirtualMachineDebug(const Executable* exec) { return runtime::Module(vm); } -TVM_REGISTER_GLOBAL("relay._vm._VirtualMachineDebug") +TVM_REGISTER_GLOBAL("runtime._VirtualMachineDebug") .set_body([](TVMArgs args, TVMRetValue* rv) { runtime::Module mod = args[0]; const auto* exec = dynamic_cast(mod.operator->()); diff --git a/src/runtime/vm/vm.cc b/src/runtime/vm/vm.cc index ced16d799509..af3a86cac17f 100644 --- a/src/runtime/vm/vm.cc +++ b/src/runtime/vm/vm.cc @@ -1057,7 +1057,7 @@ runtime::Module CreateVirtualMachine(const Executable* exec) { return runtime::Module(vm); } -TVM_REGISTER_GLOBAL("runtime._vm._VirtualMachine") +TVM_REGISTER_GLOBAL("runtime._VirtualMachine") .set_body([](TVMArgs args, TVMRetValue* rv) { runtime::Module mod = args[0]; const auto* exec = dynamic_cast(mod.operator->()); diff --git a/tests/python/unittest/test_runtime_vm_profiler.py b/tests/python/unittest/test_runtime_vm_profiler.py index 6de94f18f7e0..849a9ef3f823 100644 --- a/tests/python/unittest/test_runtime_vm_profiler.py +++ b/tests/python/unittest/test_runtime_vm_profiler.py @@ -14,6 +14,8 @@ # KIND, either express or implied. See the License for the # specific language governing permissions and limitations # under the License. +import numpy as np + import tvm from tvm.runtime import profiler_vm from tvm import relay From 558770b1abf0ebdb384cd895ab99fe035ab9eeae Mon Sep 17 00:00:00 2001 From: Zhi Chen Date: Tue, 11 Feb 2020 02:49:56 +0000 Subject: [PATCH 4/5] address comments --- python/tvm/runtime/vm.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/python/tvm/runtime/vm.py b/python/tvm/runtime/vm.py index b6de7f36f42a..a0039082f52f 100644 --- a/python/tvm/runtime/vm.py +++ b/python/tvm/runtime/vm.py @@ -23,10 +23,10 @@ import numpy as np import tvm -from tvm.runtime import Object, container -from tvm.runtime import _ffi_api from tvm._ffi.runtime_ctypes import TVMByteArray from tvm._ffi import base as _base +from .object import Object +from . import _ffi_api, container def _convert(arg, cargs): if isinstance(arg, Object): @@ -167,7 +167,7 @@ def lib(self): Returns ------- - ret : :py:class:`~tvm.Module` + ret : :py:class:`~tvm.runtime.Module` The runtime module that contains hardware dependent code. """ return self._get_lib() From 61e4c9dc27a835409db1b4cd0cb68c51671e9319 Mon Sep 17 00:00:00 2001 From: Zhi Chen Date: Tue, 11 Feb 2020 17:43:11 +0000 Subject: [PATCH 5/5] doc style, nd to NDArrary --- python/tvm/runtime/vm.py | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/python/tvm/runtime/vm.py b/python/tvm/runtime/vm.py index a0039082f52f..211bee32ed3c 100644 --- a/python/tvm/runtime/vm.py +++ b/python/tvm/runtime/vm.py @@ -34,7 +34,7 @@ def _convert(arg, cargs): elif isinstance(arg, np.ndarray): nd_arr = tvm.nd.array(arg, ctx=tvm.cpu(0)) cargs.append(nd_arr) - elif isinstance(arg, tvm.nd.NDArray): + elif isinstance(arg, tvm.runtime.NDArray): cargs.append(arg) elif isinstance(arg, (tuple, list)): field_args = [] @@ -88,11 +88,14 @@ def save(self): The returned code is organized with the following sections in order. - Global section. This section contains the globals used by the virtual machine. + - Constant section. This section is used to store the constant pool of a virtual machine. + - Primitive name section. This section is introduced to accommodate the list of primitive operator names that will be invoked by the virtual machine. + - Code section. The VM functions, including bytecode, are sitting in this section. @@ -211,10 +214,15 @@ def bytecode(self): ----- The bytecode is in the following format: func_name reg_file_size num_instructions + param1 param2 ... paramM + instruction1 + instruction2 + ... + instructionN Each instruction is printed in the following format: @@ -266,7 +274,7 @@ class VirtualMachine(object): def __init__(self, mod): if not isinstance(mod, (Executable, tvm.runtime.Module)): raise TypeError("mod is expected to be the type of Executable or " + - "tvm.Module, but received {}".format(type(mod))) + "tvm.runtime.Module, but received {}".format(type(mod))) m = mod.module if isinstance(mod, Executable) else mod self.mod = _ffi_api._VirtualMachine(m) self._exec = mod @@ -293,10 +301,10 @@ def set_input(self, func_name, *args, **kwargs): func_name : str The name of the function. - args : list[NDArray] or list[np.ndarray] + args : list[tvm.runtime.NDArray] or list[np.ndarray] The arguments to the function. - kwargs: dict of str to NDArray or np.ndarray + kwargs: dict of str to tvm.runtime.NDArray or np.ndarray Named arguments to the function. """ if kwargs: @@ -323,10 +331,10 @@ def invoke(self, func_name, *args, **kwargs): func_name : str The name of the function. - args : list[NDArray] or list[np.ndarray] + args : list[tvm.runtime.NDArray] or list[np.ndarray] The arguments to the function. - kwargs: dict of str to NDArray or np.ndarray + kwargs: dict of str to tvm.runtime.NDArray or np.ndarray Named arguments to the function. Returns @@ -343,10 +351,10 @@ def run(self, *args, **kwargs): Parameters ---------- - args : list[NDArray] or list[np.ndarray] + args : list[tvm.runtime.NDArray] or list[np.ndarray] The arguments to the function. - kwargs: dict of str to NDArray or np.ndarray + kwargs: dict of str to tvm.runtime.NDArray or np.ndarray Named arguments to the function. Returns