Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Target] Add target host field for target specification #7462

Merged
merged 11 commits into from
Feb 23, 2021
26 changes: 24 additions & 2 deletions include/tvm/target/target.h
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,8 @@ class TargetNode : public Object {
public:
/*! \brief The kind of the target device */
TargetKind kind;
/*! \brief Target host information of the target device */
Optional<ObjectRef> host;
zxybazh marked this conversation as resolved.
Show resolved Hide resolved
/*! \brief Tag of the the target, can be empty */
String tag;
/*! \brief Keys for this target */
Expand All @@ -64,6 +66,7 @@ class TargetNode : public Object {
v->Visit("tag", &tag);
v->Visit("keys", &keys);
v->Visit("attrs", &attrs);
v->Visit("host", &host);
}

/*!
Expand Down Expand Up @@ -122,14 +125,28 @@ class Target : public ObjectRef {
TVM_DLL explicit Target(std::nullptr_t) { data_ = nullptr; }
/*!
* \brief Construct a Target given a string
* \param tag_or_config_or_target_str the string to parse
* \param tag_or_config_or_target_str the string to parse for target
*/
TVM_DLL explicit Target(const String& tag_or_config_or_target_str);
/*!
* \brief Construct a Target given a string
* \param tag_or_config_or_target_str the string to parse for target
* \param host_tag_or_config_or_host_str the string to parse for target host
*/
TVM_DLL explicit Target(const String& tag_or_config_or_target_str,
const String& host_tag_or_config_or_host_str);
/*!
* \brief Construct a Target using a JSON-like configuration
* \param config The JSON-like configuration
* \param config The JSON-like configuration for target
*/
TVM_DLL explicit Target(const Map<String, ObjectRef>& config);
/*!
* \brief Construct a Target using a JSON-like configuration
* \param config The JSON-like configuration for target
* \param host_config The JSON-like configuration for target host
*/
TVM_DLL explicit Target(const Map<String, ObjectRef>& config,
const Map<String, ObjectRef>& host_config);
zxybazh marked this conversation as resolved.
Show resolved Hide resolved
/*!
* \brief Get the current target context from thread local storage.
* \param allow_not_defined If the context stack is empty and this is set to true, an
Expand All @@ -138,6 +155,11 @@ class Target : public ObjectRef {
* \return The target that is the current context. The target may not be defined if
* allow_not_defined is true.
*/
TVM_DLL explicit Target(Target target, Target host);
/*!
* \brief Construct a Target given target and host
* \return The Target with given target and host context information
*/
TVM_DLL static tvm::Target Current(bool allow_not_defined = true);

TVM_DEFINE_OBJECT_REF_METHODS(Target, ObjectRef, TargetNode);
Expand Down
18 changes: 16 additions & 2 deletions python/tvm/target/target.py
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ class Target(Object):
- :py:func:`tvm.target.intel_graphics` create Intel Graphics target
"""

def __init__(self, tag_or_str_or_dict):
def __init__(self, tag_or_str_or_dict, host_tag_or_str_or_dict=None):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the API, I would almost expect Target(target_host, sub_target_0, sub_target_1, ...)

what would be the future path towards supporting multiple sub-targets? would there be a target_host object which contains all the sub-targets?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In terms of sub-targets, I think it will be better to converge to composite targets instead, where we can specify a list of targets as --devices: https://github.com/apache/tvm/blob/main/src/target/target_kind.cc#L311-L313.

We can help create a short cut to construct the composite kind, like adding a static function

@staticmethod
def composite(target, devices):

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

for the API, I would almost expect Target(target_host, sub_target_0, sub_target_1, ...)

In terms of API, that would be very good indeed!

If internally we would convert that to represent the required composite target, with specific dictionary representing all the internal data structures, etc., that's implementation details the we care about, but the end-user, interested in compiling a model shouldn't.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@areusch as a follow-up to this, we've recently updated tvmc to accept something similar - in terms of user facing syntax to express "targets" for compilation - to what you present here.

Check it out on #7304 for code and https://discuss.tvm.apache.org/t/byoc-partitioning-support-on-tvmc/8901 for discussion.

"""Construct a TVM target object from
1) Raw target string
2) Target config dict
Expand Down Expand Up @@ -86,10 +86,24 @@ def __init__(self, tag_or_str_or_dict):
mfloat-abi : str (optional)
An llvm setting that is one of 'hard' or 'soft' indicating whether to use
hardware or software floating-point operations.
host : Union[str, Dict[str, Any]] (optional)
Description for target host. Can be recursive. Similar to tag_or_str_or_dict.
host_tag_or_str_or_dict : Union[str, Dict[str, Any]]
zxybazh marked this conversation as resolved.
Show resolved Hide resolved
Similar to tag_or_str_or_dict but for target host. Can be one of a literal
target host string, a json string describing a configuration, or a dictionary of
configuration options. When using a dictionary or json string to configure target,
the possible values are same as tag_or_str_or_dict.
"""
if not isinstance(tag_or_str_or_dict, (dict, str, Target)):
raise ValueError("target has to be a string or dictionary.")
self.__init_handle_by_constructor__(_ffi_api.Target, tag_or_str_or_dict)
if host_tag_or_str_or_dict is not None:
if not isinstance(host_tag_or_str_or_dict, (dict, str, Target)):
raise ValueError("target host has to be a string or dictionary.")
self.__init_handle_by_constructor__(
_ffi_api.Target, tag_or_str_or_dict, host_tag_or_str_or_dict
)
zxybazh marked this conversation as resolved.
Show resolved Hide resolved
else:
self.__init_handle_by_constructor__(_ffi_api.Target, tag_or_str_or_dict)

def __enter__(self):
_ffi_api.TargetEnterScope(self)
Expand Down
35 changes: 35 additions & 0 deletions src/target/target.cc
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,13 @@ Target::Target(const String& tag_or_config_or_target_str) {
data_ = std::move(target);
}

Target::Target(const String& tag_or_config_or_target_str,
const String& host_tag_or_config_or_host_str) {
ObjectPtr<TargetNode> n = make_object<TargetNode>(
*Target(Target(tag_or_config_or_target_str), Target(host_tag_or_config_or_host_str)).get());
data_ = std::move(n);
}

Target::Target(const Map<String, ObjectRef>& config) {
ObjectPtr<Object> target;
try {
Expand All @@ -373,6 +380,21 @@ Target::Target(const Map<String, ObjectRef>& config) {
data_ = std::move(target);
}

Target::Target(const Map<String, ObjectRef>& config, const Map<String, ObjectRef>& host_config) {
ObjectPtr<TargetNode> n =
make_object<TargetNode>(*Target(Target(config), Target(host_config)).get());
data_ = std::move(n);
}

Target::Target(Target target, Target host) {
ObjectPtr<TargetNode> n = make_object<TargetNode>(*target.get());
if (n->host.defined())
throw dmlc::Error(": Error when adding target host to target already with host");
zxybazh marked this conversation as resolved.
Show resolved Hide resolved
// add target host into host field
n->host = std::move(host);
data_ = std::move(n);
}

std::vector<std::string> TargetNode::GetKeys() const {
std::vector<std::string> result;
for (auto& expr : keys) {
Expand Down Expand Up @@ -456,6 +478,11 @@ void TargetInternal::ConstructorDispatcher(TVMArgs args, TVMRetValue* rv) {
<< runtime::ArgTypeCode2Str(arg.type_code());
}
return;
} else if (args.num_args == 2) {
const auto &argt = args[0], &argh = args[1];
auto func = PackedFunc(ConstructorDispatcher);
*rv = Target(func(argt).AsObjectRef<Target>(), func(argh).AsObjectRef<Target>());
zxybazh marked this conversation as resolved.
Show resolved Hide resolved
return;
}
LOG(FATAL) << "ValueError: Invalid number of arguments. Expect 1, but gets: " << args.num_args;
zxybazh marked this conversation as resolved.
Show resolved Hide resolved
}
Expand Down Expand Up @@ -527,6 +554,7 @@ ObjectPtr<Object> TargetInternal::FromConfig(std::unordered_map<String, ObjectRe
const String kTag = "tag";
const String kKeys = "keys";
const String kDeviceName = "device";
const String kHost = "host";
ObjectPtr<TargetNode> target = make_object<TargetNode>();
// parse 'kind'
if (config.count(kKind)) {
Expand Down Expand Up @@ -599,6 +627,13 @@ ObjectPtr<Object> TargetInternal::FromConfig(std::unordered_map<String, ObjectRe
throw dmlc::Error(": Error when parsing target[\"" + key + "\"]" + e.what());
}
}
// parse host
if (config.count(kHost)) {
target->host = PackedFunc(ConstructorDispatcher)(config[kHost]).AsObjectRef<Target>();
config.erase(kHost);
} else {
target->host = NullOpt;
}
// set default attribute values if they do not exist
for (const auto& kv : target->kind->key2default_) {
if (!attrs.count(kv.first)) {
Expand Down
24 changes: 20 additions & 4 deletions src/target/target_kind.cc
Original file line number Diff line number Diff line change
Expand Up @@ -219,6 +219,7 @@ TVM_REGISTER_TARGET_KIND("llvm", kDLCPU)
.add_attr_option<Bool>("system-lib")
.add_attr_option<String>("runtime")
.add_attr_option<Bool>("link-params", Bool(false))
.add_attr_option<Target>("host")
zxybazh marked this conversation as resolved.
Show resolved Hide resolved
.set_default_keys({"cpu"});

TVM_REGISTER_TARGET_KIND("c", kDLCPU)
Expand All @@ -227,6 +228,7 @@ TVM_REGISTER_TARGET_KIND("c", kDLCPU)
.add_attr_option<String>("runtime")
.add_attr_option<String>("mcpu")
.add_attr_option<String>("march")
.add_attr_option<Target>("host")
.set_default_keys({"cpu"});

TVM_REGISTER_TARGET_KIND("cuda", kDLGPU)
Expand All @@ -238,6 +240,7 @@ TVM_REGISTER_TARGET_KIND("cuda", kDLGPU)
.add_attr_option<Integer>("shared_memory_per_block")
.add_attr_option<Integer>("registers_per_block")
.add_attr_option<Integer>("max_threads_per_block")
.add_attr_option<Target>("host")
.set_default_keys({"cuda", "gpu"});

TVM_REGISTER_TARGET_KIND("nvptx", kDLGPU)
Expand All @@ -246,6 +249,7 @@ TVM_REGISTER_TARGET_KIND("nvptx", kDLGPU)
.add_attr_option<Bool>("system-lib")
.add_attr_option<Integer>("max_num_threads", Integer(1024))
.add_attr_option<Integer>("thread_warp_size", Integer(32))
.add_attr_option<Target>("host")
.set_default_keys({"cuda", "gpu"})
.set_attrs_preprocessor(UpdateNVPTXAttrs);

Expand All @@ -255,40 +259,48 @@ TVM_REGISTER_TARGET_KIND("rocm", kDLROCM)
.add_attr_option<Bool>("system-lib")
.add_attr_option<Integer>("max_num_threads", Integer(256))
.add_attr_option<Integer>("thread_warp_size", Integer(64))
.add_attr_option<Target>("host")
.set_default_keys({"rocm", "gpu"})
.set_attrs_preprocessor(UpdateROCmAttrs);

TVM_REGISTER_TARGET_KIND("opencl", kDLOpenCL)
.add_attr_option<Bool>("system-lib")
.add_attr_option<Integer>("max_num_threads", Integer(256))
.add_attr_option<Integer>("thread_warp_size")
.add_attr_option<Target>("host")
.set_default_keys({"opencl", "gpu"});

TVM_REGISTER_TARGET_KIND("metal", kDLMetal)
.add_attr_option<Bool>("system-lib")
.add_attr_option<Integer>("max_num_threads", Integer(256))
.add_attr_option<Target>("host")
.set_default_keys({"metal", "gpu"});

TVM_REGISTER_TARGET_KIND("vulkan", kDLVulkan)
.add_attr_option<Bool>("system-lib")
.add_attr_option<Integer>("max_num_threads", Integer(256))
.add_attr_option<Target>("host")
.set_default_keys({"vulkan", "gpu"});

TVM_REGISTER_TARGET_KIND("webgpu", kDLWebGPU)
.add_attr_option<Bool>("system-lib")
.add_attr_option<Integer>("max_num_threads", Integer(256))
.add_attr_option<Target>("host")
.set_default_keys({"webgpu", "gpu"});

TVM_REGISTER_TARGET_KIND("sdaccel", kDLOpenCL)
.add_attr_option<Bool>("system-lib")
.add_attr_option<Target>("host")
.set_default_keys({"sdaccel", "hls"});

TVM_REGISTER_TARGET_KIND("aocl", kDLAOCL)
.add_attr_option<Bool>("system-lib")
.add_attr_option<Target>("host")
.set_default_keys({"aocl", "hls"});

TVM_REGISTER_TARGET_KIND("aocl_sw_emu", kDLAOCL)
.add_attr_option<Bool>("system-lib")
.add_attr_option<Target>("host")
.set_default_keys({"aocl", "hls"});

TVM_REGISTER_TARGET_KIND("hexagon", kDLHexagon)
Expand All @@ -297,19 +309,23 @@ TVM_REGISTER_TARGET_KIND("hexagon", kDLHexagon)
.add_attr_option<String>("mtriple")
.add_attr_option<Bool>("system-lib")
.add_attr_option<Array<String>>("llvm-options")
.add_attr_option<Target>("host")
.set_default_keys({"hexagon"});

TVM_REGISTER_TARGET_KIND("stackvm", kDLCPU) // line break
.add_attr_option<Bool>("system-lib");
.add_attr_option<Bool>("system-lib")
.add_attr_option<Target>("host");

TVM_REGISTER_TARGET_KIND("ext_dev", kDLExtDev) // line break
.add_attr_option<Bool>("system-lib");
.add_attr_option<Bool>("system-lib")
.add_attr_option<Target>("host");

TVM_REGISTER_TARGET_KIND("hybrid", kDLCPU) // line break
.add_attr_option<Bool>("system-lib");
.add_attr_option<Bool>("system-lib")
.add_attr_option<Target>("host");

TVM_REGISTER_TARGET_KIND("composite", kDLCPU)
.add_attr_option<Target>("target_host")
.add_attr_option<Target>("host")
.add_attr_option<Array<Target>>("devices");

/********** Registry **********/
Expand Down
49 changes: 42 additions & 7 deletions tests/python/unittest/test_target_target.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,7 @@
# under the License.
import json
import tvm
import pytest
from tvm import te
from tvm.target import cuda, rocm, mali, intel_graphics, arm_cpu, vta, bifrost, hexagon

Expand Down Expand Up @@ -113,18 +114,14 @@ def test_config_map():
attributes fails as expected.
"""
target_config = {"kind": "llvm", "libs": {"a": "b", "c": "d"}}
failed = False
try:
with pytest.raises(ValueError):
tvm.target.Target(target_config)
except ValueError:
failed = True
assert failed


def test_composite_target():
tgt = tvm.target.Target("composite --target_host=llvm --devices=cuda,opencl")
tgt = tvm.target.Target("composite --host=llvm --devices=cuda,opencl")
assert tgt.kind.name == "composite"
assert tgt.attrs["target_host"].kind.name == "llvm"
assert tgt.attrs["host"].kind.name == "llvm"
assert len(tgt.attrs["devices"]) == 2
cuda_device, opencl_device = tgt.attrs["devices"]
assert cuda_device.kind.name == "cuda"
Expand Down Expand Up @@ -158,6 +155,44 @@ def test_list_kinds():
assert all(isinstance(target_name, str) for target_name in targets)


def test_target_host_tags():
tgt = tvm.target.Target("nvidia/jetson-nano", "nvidia/geforce-rtx-2080-ti")
assert tgt.kind.name == "cuda"
assert tgt.attrs["arch"] == "sm_53"
assert tgt.attrs["shared_memory_per_block"] == 49152
assert tgt.attrs["max_threads_per_block"] == 1024
assert tgt.attrs["thread_warp_size"] == 32
assert tgt.attrs["registers_per_block"] == 32768
assert tgt.host.kind.name == "cuda"
assert tgt.host.attrs["arch"] == "sm_75"
assert tgt.host.attrs["shared_memory_per_block"] == 49152
assert tgt.host.attrs["max_threads_per_block"] == 1024
assert tgt.host.attrs["thread_warp_size"] == 32
assert tgt.host.attrs["registers_per_block"] == 65536


def test_target_host_tag_dict():
zxybazh marked this conversation as resolved.
Show resolved Hide resolved
tgt = tvm.target.Target("nvidia/jetson-nano", {"kind": "llvm"})
assert tgt.kind.name == "cuda"
assert tgt.attrs["arch"] == "sm_53"
assert tgt.attrs["shared_memory_per_block"] == 49152
assert tgt.attrs["max_threads_per_block"] == 1024
assert tgt.attrs["thread_warp_size"] == 32
assert tgt.attrs["registers_per_block"] == 32768
assert tgt.host.kind.name == "llvm"


def test_target_host_single_dict():
tgt = tvm.target.Target({"kind": "llvm", "host": "nvidia/jetson-nano"})
assert tgt.kind.name == "llvm"
assert tgt.host.kind.name == "cuda"
assert tgt.host.attrs["arch"] == "sm_53"
assert tgt.host.attrs["shared_memory_per_block"] == 49152
assert tgt.host.attrs["max_threads_per_block"] == 1024
assert tgt.host.attrs["thread_warp_size"] == 32
assert tgt.host.attrs["registers_per_block"] == 32768


if __name__ == "__main__":
test_target_dispatch()
test_target_string_parse()
Expand Down