From ac20b73800bee92052c5cde63aa893e1b7191ca2 Mon Sep 17 00:00:00 2001 From: Andrew Luo Date: Mon, 28 Jun 2021 15:27:00 -0700 Subject: [PATCH 1/9] go to callable class --- python/tvm/autotvm/measure/measure_methods.py | 43 ++++++++++++++----- 1 file changed, 33 insertions(+), 10 deletions(-) diff --git a/python/tvm/autotvm/measure/measure_methods.py b/python/tvm/autotvm/measure/measure_methods.py index f41795fb0810..6e6c61b37313 100644 --- a/python/tvm/autotvm/measure/measure_methods.py +++ b/python/tvm/autotvm/measure/measure_methods.py @@ -24,29 +24,29 @@ import contextlib import logging -import shutil import os +import shutil +import tempfile import threading import time import typing -from random import getrandbits from collections import namedtuple -import tempfile +from random import getrandbits import tvm._ffi import tvm.ir.transform -from tvm import nd, rpc as _rpc -from tvm.error import TVMError +from tvm import nd +from tvm import rpc as _rpc +from tvm.contrib import ndk, nvcc, stackvm, tar from tvm.driver import build -from tvm.contrib import nvcc, ndk, tar, stackvm +from tvm.error import TVMError from tvm.target import Target -from ..utils import get_const_tuple from ..env import AutotvmGlobalScope from ..task.space import InstantiationError - -from .measure import MeasureResult, MeasureErrorNo, Builder, Runner +from ..utils import get_const_tuple from .local_executor import LocalExecutor +from .measure import Builder, MeasureErrorNo, MeasureResult, Runner logger = logging.getLogger("autotvm") @@ -393,8 +393,8 @@ def __init__( def set_task(self, task): # pylint: disable=import-outside-toplevel - from ...rpc.tracker import Tracker from ...rpc.server import Server + from ...rpc.tracker import Tracker self.task = task tracker = Tracker(port=9000, port_end=10000, silent=True) @@ -605,6 +605,28 @@ def run_through_rpc( return MeasureResult(costs, errno, tstamp - tic + build_result.time_cost, tstamp) +class default_module_loader: + def __init__(self, pre_load_function=None) -> None: + self.pre_load_function = pre_load_function + + @contextlib.contextmanager + def __call__(self, remote_kwargs, build_result): + remote = request_remote(**remote_kwargs) + if self.pre_load_function is not None: + self.pre_load_function(remote, build_result) + + remote.upload(build_result.filename) + try: + yield remote, remote.load_module(os.path.split(build_result.filename)[1]) + + finally: + # clean up remote files + remote.remove(build_result.filename) + remote.remove(os.path.splitext(build_result.filename)[0] + ".so") + remote.remove("") + + +''' def default_module_loader(pre_load_function=None): """Returns a default function that can be passed as module_loader to run_through_rpc. @@ -637,6 +659,7 @@ def default_module_loader_mgr(remote_kwargs, build_result): remote.remove("") return default_module_loader_mgr +''' def request_remote(device_key, host=None, port=None, priority=1, timeout=60): From 926d394fba82ea972de727f0151aaa0dc3b18e33 Mon Sep 17 00:00:00 2001 From: Andrew Luo Date: Mon, 28 Jun 2021 15:37:05 -0700 Subject: [PATCH 2/9] add some documentation and naming --- python/tvm/autotvm/measure/measure_methods.py | 25 ++++--------------- 1 file changed, 5 insertions(+), 20 deletions(-) diff --git a/python/tvm/autotvm/measure/measure_methods.py b/python/tvm/autotvm/measure/measure_methods.py index 6e6c61b37313..e6172e095e95 100644 --- a/python/tvm/autotvm/measure/measure_methods.py +++ b/python/tvm/autotvm/measure/measure_methods.py @@ -605,7 +605,9 @@ def run_through_rpc( return MeasureResult(costs, errno, tstamp - tic + build_result.time_cost, tstamp) -class default_module_loader: +class DefaultModuleLoader: + """See default_module_loader(). A pickleable emulation of the original function closure.""" + def __init__(self, pre_load_function=None) -> None: self.pre_load_function = pre_load_function @@ -626,7 +628,6 @@ def __call__(self, remote_kwargs, build_result): remote.remove("") -''' def default_module_loader(pre_load_function=None): """Returns a default function that can be passed as module_loader to run_through_rpc. @@ -642,24 +643,8 @@ def default_module_loader(pre_load_function=None): A function that can be passed as module_loader to run_through_rpc. """ - @contextlib.contextmanager - def default_module_loader_mgr(remote_kwargs, build_result): - remote = request_remote(**remote_kwargs) - if pre_load_function is not None: - pre_load_function(remote, build_result) - - remote.upload(build_result.filename) - try: - yield remote, remote.load_module(os.path.split(build_result.filename)[1]) - - finally: - # clean up remote files - remote.remove(build_result.filename) - remote.remove(os.path.splitext(build_result.filename)[0] + ".so") - remote.remove("") - - return default_module_loader_mgr -''' + # This was a function with a closure before but that couldn't be pickled! + return DefaultModuleLoader(pre_load_function) def request_remote(device_key, host=None, port=None, priority=1, timeout=60): From d9183c5a57c28b3ec6b43cf22127aba713662cec Mon Sep 17 00:00:00 2001 From: Andrew Luo Date: Mon, 28 Jun 2021 15:41:31 -0700 Subject: [PATCH 3/9] extend comment --- python/tvm/autotvm/measure/measure_methods.py | 1 + 1 file changed, 1 insertion(+) diff --git a/python/tvm/autotvm/measure/measure_methods.py b/python/tvm/autotvm/measure/measure_methods.py index e6172e095e95..2b6137809d76 100644 --- a/python/tvm/autotvm/measure/measure_methods.py +++ b/python/tvm/autotvm/measure/measure_methods.py @@ -644,6 +644,7 @@ def default_module_loader(pre_load_function=None): """ # This was a function with a closure before but that couldn't be pickled! + # We need pickle to work for using python's multiprocessing on some platforms. return DefaultModuleLoader(pre_load_function) From 657e056a92575dbb8ec60235364abce28d9ab5dc Mon Sep 17 00:00:00 2001 From: Andrew Luo Date: Mon, 28 Jun 2021 16:37:06 -0700 Subject: [PATCH 4/9] manually do logic to avoid bug with pointer comparison --- python/tvm/target/target.py | 10 ++++++---- 1 file changed, 6 insertions(+), 4 deletions(-) diff --git a/python/tvm/target/target.py b/python/tvm/target/target.py index be39a6f6bd25..b752cfc8bfb7 100644 --- a/python/tvm/target/target.py +++ b/python/tvm/target/target.py @@ -15,14 +15,15 @@ # specific language governing permissions and limitations # under the License. """Target data structure.""" +import json import os import re -import json import warnings -import tvm._ffi -from tvm.runtime import Object +import tvm._ffi from tvm._ffi import register_func as _register_func +from tvm.runtime import Object + from . import _ffi_api @@ -197,7 +198,8 @@ def check_and_update_host_consist(target, host=None, target_is_dict_key=True): new_target[tgt] = mod target = new_target else: - target = Target(target, host) + target = Target(target) + target.host = host host = target.host return target, host From f349778dc8d727cba1f2b093f5375db5a1953055 Mon Sep 17 00:00:00 2001 From: Andrew Luo Date: Tue, 29 Jun 2021 10:41:49 -0700 Subject: [PATCH 5/9] revert changes to light change, correct comment' --- python/tvm/autotvm/measure/measure_methods.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/python/tvm/autotvm/measure/measure_methods.py b/python/tvm/autotvm/measure/measure_methods.py index 2b6137809d76..3de25cb6100b 100644 --- a/python/tvm/autotvm/measure/measure_methods.py +++ b/python/tvm/autotvm/measure/measure_methods.py @@ -639,8 +639,8 @@ def default_module_loader(pre_load_function=None): Returns ------- - ModuleLoader : - A function that can be passed as module_loader to run_through_rpc. + DefaultModuleLoader : + A callable that can be passed as module_loader to run_through_rpc. """ # This was a function with a closure before but that couldn't be pickled! From 9ef46f696a572c8bfb8a1e2005e3915dc3ea463c Mon Sep 17 00:00:00 2001 From: Andrew Luo Date: Tue, 29 Jun 2021 13:42:00 -0700 Subject: [PATCH 6/9] more principled change, but also kind of hacky --- python/tvm/target/target.py | 3 +-- src/target/target.cc | 13 ++++++++++++- tests/python/unittest/test_target_target.py | 11 ++++++----- 3 files changed, 19 insertions(+), 8 deletions(-) diff --git a/python/tvm/target/target.py b/python/tvm/target/target.py index b752cfc8bfb7..40569908f9d9 100644 --- a/python/tvm/target/target.py +++ b/python/tvm/target/target.py @@ -198,8 +198,7 @@ def check_and_update_host_consist(target, host=None, target_is_dict_key=True): new_target[tgt] = mod target = new_target else: - target = Target(target) - target.host = host + target = Target(target, host) host = target.host return target, host diff --git a/src/target/target.cc b/src/target/target.cc index 546a3596297a..afcaec5c4e3c 100644 --- a/src/target/target.cc +++ b/src/target/target.cc @@ -418,8 +418,19 @@ Target::Target(const Map& config) { Target::Target(Target target, Target host) { ObjectPtr n = make_object(*target.get()); - CHECK(!n->host.defined() || n->host.same_as(host)) + + // It's ok to add a host to target with a defined host if it's the exact same target. + // We do a check if target's host and the the parameter host are the same pointers. + // However, for multiprocessing in python some methods involve serializing and + // reserializing objects which breaks pointer equality. Therefore we need + // a method of seeing if the two objects are similar. This does not exist + // so we see if the human readable strings of the targets are the same. + CHECK(!n->host.defined() || n->host.same_as(host) || + + // TODO (AndrewZhaoLuo): create a deep equality for TargetNodes + n->host.as()->str() == host.as()->str()) << "ValueError: Adding a host to a target whose host field has been defined"; + // add target host into host field n->host = std::move(host); data_ = std::move(n); diff --git a/tests/python/unittest/test_target_target.py b/tests/python/unittest/test_target_target.py index 3ed275800bd2..0d5378d10dc4 100644 --- a/tests/python/unittest/test_target_target.py +++ b/tests/python/unittest/test_target_target.py @@ -16,9 +16,10 @@ # under the License. import json import sys + import pytest import tvm -from tvm.target import cuda, rocm, mali, intel_graphics, arm_cpu, vta, bifrost, Target +from tvm.target import Target, arm_cpu, bifrost, cuda, intel_graphics, mali, rocm, vta @tvm.target.generic_func @@ -240,10 +241,10 @@ def test_target_host_merge_1(): def test_target_host_merge_2(): - with pytest.raises( - ValueError, match="Adding a host to a target whose host field has been defined" - ): - tvm.target.Target(tvm.target.Target("cuda --host llvm"), tvm.target.Target("llvm")) + """Test picking the same host is ok.""" + tgt = tvm.target.Target(tvm.target.Target("cuda --host llvm"), tvm.target.Target("llvm")) + assert tgt.kind.name == "cuda" + assert tgt.host.kind.name == "llvm" @pytest.mark.skip(reason="Causing infinite loop because of pytest and handle issue") From e8d05fdf3accd709c6a63b007ed7e484c8117b4a Mon Sep 17 00:00:00 2001 From: Andrew Luo Date: Tue, 29 Jun 2021 14:00:30 -0700 Subject: [PATCH 7/9] test other tuning methods --- tests/python/integration/test_tuning.py | 107 ++++++++++++++---------- 1 file changed, 63 insertions(+), 44 deletions(-) diff --git a/tests/python/integration/test_tuning.py b/tests/python/integration/test_tuning.py index 55c8e5643c71..225259f7fa26 100644 --- a/tests/python/integration/test_tuning.py +++ b/tests/python/integration/test_tuning.py @@ -18,22 +18,19 @@ Test the tuner """ import logging +import multiprocessing as mp import sys import textwrap import time import pytest - import tvm import tvm.relay -from tvm import te - -from tvm import autotvm +import tvm.testing +from tvm import autotvm, te from tvm.autotvm.tuner import RandomTuner from tvm.target import Target -import tvm.testing - def setup_module(): @autotvm.template("testing/conv2d_no_batching") @@ -140,62 +137,84 @@ def get_sample_task(target=tvm.target.cuda(), target_host=None): return task, target +def run_test_with_all_multiprocessing(func, *args, **kwargs): + """Check all multiprocessing methods work for the tuning test. + + In the past fork() had the most support at detriment to spawn() and forkserver(). + As fork() is unavailable or unsafe on some platforms it is good to check all + available methods. + """ + for multiprocessing_method in mp.get_all_start_methods(): + old_start_method = mp.get_start_method() + try: + mp.set_start_method(multiprocessing_method, force=True) + func(*args, **kwargs) + finally: + mp.set_start_method(old_start_method, force=True) + + @tvm.testing.parametrize_targets("cuda", "opencl") def test_tuning_gpu(target, dev): - # init task - task, target = get_sample_task(target, None) - logging.info("task config space: %s", task.config_space) + def runner(target, dev): + # init task + task, target = get_sample_task(target, None) + logging.info("task config space: %s", task.config_space) - measure_option = autotvm.measure_option(autotvm.LocalBuilder(), autotvm.LocalRunner()) + measure_option = autotvm.measure_option(autotvm.LocalBuilder(), autotvm.LocalRunner()) - results = [] + results = [] - tuner = RandomTuner(task) - tuner.tune( - n_trial=20, - measure_option=measure_option, - callbacks=(lambda _tuner, _inputs, rs: results.extend(rs),), - ) + tuner = RandomTuner(task) + tuner.tune( + n_trial=20, + measure_option=measure_option, + callbacks=(lambda _tuner, _inputs, rs: results.extend(rs),), + ) + + assert len(results) == 20 - assert len(results) == 20 + successful_results = [r for r in results if r.error_no == autotvm.MeasureErrorNo.NO_ERROR] + assert len(successful_results) > 0, f"No successful tuning runs: {results!r}" - successful_results = [r for r in results if r.error_no == autotvm.MeasureErrorNo.NO_ERROR] - assert len(successful_results) > 0, f"No successful tuning runs: {results!r}" + run_test_with_all_multiprocessing(runner, target, dev) def test_tuning_cpu(): - ir_mod = tvm.parser.fromtext( - textwrap.dedent( + def runner(): + ir_mod = tvm.parser.fromtext( + textwrap.dedent( + """ + #[version = "0.0.5"] + def @main(%a : Tensor[(1, 3, 32, 32), float32], %b : Tensor[(3, 3, 5, 5), float32]) { + nn.conv2d(%a, %b, data_layout="NCHW", kernel_layout="OIHW") + } """ - #[version = "0.0.5"] - def @main(%a : Tensor[(1, 3, 32, 32), float32], %b : Tensor[(3, 3, 5, 5), float32]) { - nn.conv2d(%a, %b, data_layout="NCHW", kernel_layout="OIHW") - } - """ + ) ) - ) - tasks = autotvm.task.relay_integration.extract_from_program( - ir_mod, {}, tvm.target.create("llvm") - ) - assert len(tasks) == 1, f"Extracted != 1 task from program: {tasks!r}" + tasks = autotvm.task.relay_integration.extract_from_program( + ir_mod, {}, tvm.target.create("llvm") + ) + assert len(tasks) == 1, f"Extracted != 1 task from program: {tasks!r}" - task = tasks[0] + task = tasks[0] - measure_option = autotvm.measure_option(autotvm.LocalBuilder(), autotvm.LocalRunner()) + measure_option = autotvm.measure_option(autotvm.LocalBuilder(), autotvm.LocalRunner()) - results = [] + results = [] - tuner = RandomTuner(task) - tuner.tune( - n_trial=20, - measure_option=measure_option, - callbacks=(lambda _tuner, _inputs, rs: results.extend(rs),), - ) + tuner = RandomTuner(task) + tuner.tune( + n_trial=20, + measure_option=measure_option, + callbacks=(lambda _tuner, _inputs, rs: results.extend(rs),), + ) + + assert len(results) == 20 - assert len(results) == 20 + successful_results = [r for r in results if r.error_no == autotvm.MeasureErrorNo.NO_ERROR] + assert len(successful_results) > 0, f"No successful tuning runs: {results!r}" - successful_results = [r for r in results if r.error_no == autotvm.MeasureErrorNo.NO_ERROR] - assert len(successful_results) > 0, f"No successful tuning runs: {results!r}" + run_test_with_all_multiprocessing(runner) if __name__ == "__main__": From 678f85a0421fbe27fa859d6e976e2433810e1b44 Mon Sep 17 00:00:00 2001 From: Andrew Luo Date: Tue, 29 Jun 2021 14:48:39 -0700 Subject: [PATCH 8/9] remove check; --- src/target/target.cc | 14 -------------- tests/python/unittest/test_target_target.py | 11 ----------- 2 files changed, 25 deletions(-) diff --git a/src/target/target.cc b/src/target/target.cc index afcaec5c4e3c..df810185784e 100644 --- a/src/target/target.cc +++ b/src/target/target.cc @@ -418,20 +418,6 @@ Target::Target(const Map& config) { Target::Target(Target target, Target host) { ObjectPtr n = make_object(*target.get()); - - // It's ok to add a host to target with a defined host if it's the exact same target. - // We do a check if target's host and the the parameter host are the same pointers. - // However, for multiprocessing in python some methods involve serializing and - // reserializing objects which breaks pointer equality. Therefore we need - // a method of seeing if the two objects are similar. This does not exist - // so we see if the human readable strings of the targets are the same. - CHECK(!n->host.defined() || n->host.same_as(host) || - - // TODO (AndrewZhaoLuo): create a deep equality for TargetNodes - n->host.as()->str() == host.as()->str()) - << "ValueError: Adding a host to a target whose host field has been defined"; - - // add target host into host field n->host = std::move(host); data_ = std::move(n); } diff --git a/tests/python/unittest/test_target_target.py b/tests/python/unittest/test_target_target.py index 0d5378d10dc4..bb3aa9e86267 100644 --- a/tests/python/unittest/test_target_target.py +++ b/tests/python/unittest/test_target_target.py @@ -211,17 +211,6 @@ def test_target_host_single_string_with_tag(): assert tgt.host.attrs["registers_per_block"] == 32768 -def test_target_host_warning(): - """ - Confirm that constructing a target with invalid - attributes fails as expected. - """ - with pytest.raises( - ValueError, match="Adding a host to a target whose host field has been defined" - ): - tvm.target.Target("cuda --host nvidia/jetson-nano", "llvm") - - def test_target_host_merge_0(): tgt = tvm.target.Target(tvm.target.Target("cuda --host nvidia/jetson-nano"), None) assert tgt.kind.name == "cuda" From 8d5d738dccc60048d07b150b660666e62fb9a2d8 Mon Sep 17 00:00:00 2001 From: Andrew Luo Date: Tue, 29 Jun 2021 14:52:29 -0700 Subject: [PATCH 9/9] jostle CI