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 python binding to new JSON target construction. #6315

Merged
merged 5 commits into from
Aug 21, 2020
Merged
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 39 additions & 7 deletions python/tvm/target/target.py
Original file line number Diff line number Diff line change
@@ -347,13 +347,43 @@ def create_llvm(llvm_args):
return _ffi_api.TargetCreate('hexagon', *args_list)


def create(target_str):
def create(target):
"""Get a target given target string.

Parameters
----------
target_str : str
The target string.
target : str or dict
The target string or configuration dictionary.
When using a dictionary to configure target, the
possible values are:
{
kind : str (required)
Which codegen path to use, for example 'llvm' or 'cuda'.
keys : List of str (optional)
A set of strategies that can be dispatched to. When using
"kind=opencl" for example, one could set keys to ["mali", "opencl", "gpu"].
device : str (optional)
A single key that corresponds to the actual device being run on.
This will be effectively appended to the keys.
libs : List of str (optional)
The set of external libraries to use. For example ['cblas', 'mkl'].
system-lib : bool (optional)
If True, build a module that contains self registered functions.
Useful for environments where dynamic loading like dlopen is banned.
mcpu : str (optional)
The specific cpu being run on. Serves only as an annotation.
model : str (optional)
An annotation indicating what model a workload came from.
runtime : str (optional)
An annotation indicating which runtime to use with a workload.
mtriple : str (optional)
The llvm triplet describing the target, for example "arm64-linux-android".
mattr : List of str (optional)
The llvm features to compile with, for example ["+avx512f", "+mmx"].
mfloat-abi : str (optional)
An llvm setting that is one of 'hard' or 'soft' indicating whether to use
hardware or software floating-point operations.
}

Returns
-------
@@ -364,9 +394,11 @@ def create(target_str):
----
See the note on :py:mod:`tvm.target` on target string format.
"""
if isinstance(target_str, Target):
if isinstance(target, Target):
return target_str
if not isinstance(target_str, str):
raise ValueError("target_str has to be string type")
if isinstance(target, dict):
return _ffi_api.TargetFromConfig(target)
if isinstance(target, str):
return _ffi_api.TargetFromString(target)
Copy link
Member

Choose a reason for hiding this comment

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

Shall we also do some guessing work as well for strings? If it is a string but seems like a JSON, we can use json.loads instead.

Suggested change
if isinstance(target, str):
return _ffi_api.TargetFromString(target)
if isinstance(target, str):
if target.startswith("{"):
return _ffi_api.TargetFromConfig(json.loads(target))
else:
return _ffi_api.TargetFromString(target)

Copy link
Contributor

Choose a reason for hiding this comment

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

I would prefer to support the following use case as well:

target = target.create('./target.json') # Input is a string file path.

This case could be supported by using os to check if the string is a file path or not.

In addition, if we would like to support the JSON string, I personally don't think checking the first character is a proper approach, as you may have spaces or something else. A better way should be just trying to parse the string with JSON parser first and use string parser instead if the JSON parser throws an exception.

Copy link
Member

Choose a reason for hiding this comment

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

Yes, I think we should try to find a unified approach to create target from

  1. file
  2. json string
  3. json-like dict
  4. legacy raw string
  5. tag

Copy link
Member

@tqchen tqchen Aug 20, 2020

Choose a reason for hiding this comment

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

Given that it is one line to open and read the file. I think we can just allow it takes in string and not support the file path feature(because supporting both could be confusing). It also helps to avoid conflcits(e.g. what if there is a file with the same name).

target = target.create(open("target.json").read()) is not as bad

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've added a try except block to see if the input string can be parsed to json. We now can accept traditional target string, python dicts, or json strings. Let me know we need other options like tag (not sure what this means).


return _ffi_api.TargetFromString(target_str)
raise ValueError("target has to be a string or dictionary.")
4 changes: 3 additions & 1 deletion src/target/target.cc
Original file line number Diff line number Diff line change
@@ -410,7 +410,7 @@ Target Target::FromConfig(const Map<String, ObjectRef>& config_dict) {
const auto* cfg_keys = config[kKeys].as<ArrayNode>();
CHECK(cfg_keys != nullptr)
<< "AttributeError: Expect type of field 'keys' is an Array, but get: "
<< config[kTag]->GetTypeKey();
<< config[kKeys]->GetTypeKey();
for (const ObjectRef& e : *cfg_keys) {
const auto* key = e.as<StringObj>();
CHECK(key != nullptr) << "AttributeError: Expect 'keys' to be an array of strings, but it "
@@ -525,6 +525,8 @@ TVM_REGISTER_GLOBAL("target.GetCurrentTarget").set_body_typed(Target::Current);

TVM_REGISTER_GLOBAL("target.TargetFromString").set_body_typed(Target::Create);

TVM_REGISTER_GLOBAL("target.TargetFromConfig").set_body_typed(Target::FromConfig);

TVM_STATIC_IR_FUNCTOR(ReprPrinter, vtable)
.set_dispatch<TargetNode>([](const ObjectRef& node, ReprPrinter* p) {
auto* op = static_cast<const TargetNode*>(node.get());
24 changes: 24 additions & 0 deletions tests/python/unittest/test_target_target.py
Original file line number Diff line number Diff line change
@@ -80,7 +80,31 @@ def test_target_create():
assert tgt is not None


def test_target_config():
"""
Test that constructing a target from a dictionary works.
"""
target_config = {
Copy link
Contributor

Choose a reason for hiding this comment

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

Should also test map-type attributes.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Can you expand on this a little? Do you mean add testing that confirms it fails when invalid attribute types are provided?

Copy link
Contributor

Choose a reason for hiding this comment

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

I just meant we should also test the attribute which type is a map. Since one purpose of testing Python binding is to make sure we can correctly pass supported data structures to the C++ container. Specifically, the Python version of this test: https://github.com/apache/incubator-tvm/blob/master/tests/cpp/target_test.cc#L121

Copy link
Contributor Author

@jwfromm jwfromm Aug 20, 2020

Choose a reason for hiding this comment

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

Ah I see. It's a little strange to do this since none of the supported options are maps. But I could add a test case that passes a map and then confirm it fails like the cpp version you linked.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added an analogous test in the latest commit. Let me know if this is what you were looking for.

Copy link
Contributor

Choose a reason for hiding this comment

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

I saw the map test you added, but it is intented to fail so I'm not sure if it is sufficient.
@junrushao1994 could you comment?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Just to be clear, there are no valid arguments that have type map. The tests you linked in target_test.cc also are intentionally failing.

'kind': 'llvm',
'keys': ['arm_cpu', 'cpu'],
'device': 'arm_cpu',
'libs': ['cblas'],
'system-lib': True,
'mfloat-abi': 'hard',
'mattr': ['+neon', '-avx512f'],
}
target = tvm.target.create(target_config)
assert target.kind.name == 'llvm'
assert all([key in target.keys for key in ['arm_cpu', 'cpu']])
assert target.device_name == 'arm_cpu'
assert target.libs == ['cblas']
assert 'system-lib' in str(target)
assert target.attrs['mfloat-abi'] == 'hard'
assert all([attr in target.attrs['mattr'] for attr in ['+neon', '-avx512f']])


if __name__ == "__main__":
test_target_dispatch()
test_target_string_parse()
test_target_create()
test_target_config()