Generate C++ entry points#925
Conversation
|
(The aim is to switch these generated wrappers to use |
awf
left a comment
There was a problem hiding this comment.
Yes, looking forward to the version with torch tensor.
| if len(arg_types) == 1 and arg_types[0].is_tuple: | ||
| arg_types = arg_types[0].children | ||
|
|
||
| cpp_declaration = "{ret} {fname}({args})".format( |
There was a problem hiding this comment.
I think this will be more readable as an f string.
There was a problem hiding this comment.
Hmm, let me see if I agree with myself:
args=", ".join(f"{ks_cpp_type(arg_type)} arg{i}" for i, arg_type in enumerate(arg_types));
cpp_declaration = f"{ks_cpp_type(return_type)} {cpp_function_name}({args})"I think I do.
0a3123d to
2792d8c
Compare
|
(I've rebased this onto master in order to use |
a90a4af to
c73aa2c
Compare
|
In order to merge with #877, I've included the following code in each of the embedded C++ examples: It's not really possible to generate this code automatically (replicating what this PR does when compiling a ks file), because it's hard to generate the code without knowing the function signatures. But if we want to reproduce what these wrapper functions should look like, it's always possible to run one of the ks benchmarks, and then copy the wrapper functions from the generated C++ file. |
347a11e to
e6a2ef1
Compare
e6a2ef1 to
b92b82f
Compare
f61b21c to
8afc0c3
Compare
b92b82f to
4da90f2
Compare
toelli-msft
left a comment
There was a problem hiding this comment.
This seems fine. Generating the wrappers ourselves seems to afford more flexibility than doing so with C++ template trickery.
On the other hand there's a bunch of code that duplicates Cgen (see the N.B. comment). An alternative would be to have Cgen output a ks::without_allocator::... function for every function in the module. For example, if my .ks file defines f : Integer -> Integer then as well as Cgen generating ks::Integer ks::f(allocator *, ks::Integer) it could also generate ks::Integer ks::without_allocator::f(ks::Integer). That way we don't need any special logic on the Python side.
What do you think?
| if (g_logging) {{ | ||
| std::cerr << "{ks_function_name}("{args_streamed} << ") =" << std::endl; | ||
| auto ret = {cpp_call}; | ||
| std::cerr << ret << std::endl; |
There was a problem hiding this comment.
I wonder how useful this is if ret is a size one million tensor.
There was a problem hiding this comment.
Ah, maybe this code is past its sell-by date: it's originally from #762, when we needed to debug issues to do with python object lifetimes. So, it was intended for debugging rather than routine logging. Though we're making changes in this area at the moment so maybe it's worth keeping a little while longer.
| arg_types = [arg.type_ for arg in decl.args] | ||
| if len(arg_types) == 1 and arg_types[0].is_tuple: | ||
| arg_types = arg_types[0].children | ||
| arg_names = [f"arg{i}" for i in range(len(arg_types))] | ||
|
|
||
| args = ", ".join( | ||
| f"{ks_cpp_type(arg_type)} {arg_name}" | ||
| for arg_type, arg_name in zip(arg_types, arg_names) |
There was a problem hiding this comment.
I'm always uneasy with zip because I wonder whether trailing elements are being silently discarded. Perhaps the following instead?
arg_types_names = ((arg.type_, f"arg{i}") for (i, arg) in enumerate(decl.args))
...
for arg_type, arg_name in arg_types_names(Even inlining arg_types_names would probably be fine.)
There was a problem hiding this comment.
I like this but need to do something about the other uses of arg_names in this function. I've pushed a change with one way of doing this - what do you think?
There was a problem hiding this comment.
Thanks, that version looks good. I think if I were doing it I would do
arg_types_names = [(arg.type_, f"arg{i}") for (i, arg) in enumerate(decl.args)]
...
for arg_type, arg_name in arg_types_names
...
arg_names = [arg_name for (_, arg_name) in arg_types_names]but that's not necessarily to everyone's taste.
There was a problem hiding this comment.
I think the first line has to be arg_types = ... because we need to do de-oneargification as well. (I probably should write the function multiargify rather than inlining it here, but I don't think that solves the problem.) I tried enumerate(multiargify(...)) but that seemed to be doing too much in one line of code. Anyway, it's looking a bit better now - happy to come back to this but I'll merge here.
| scalar_type_to_cpp_map = { | ||
| "Integer": "ks::Integer", | ||
| "Float": "ks::Float", | ||
| "Bool": "ks::Bool", | ||
| "String": "std::string", | ||
| } | ||
|
|
||
|
|
||
| def ks_cpp_type(t): | ||
| if t.is_scalar: | ||
| return scalar_type_to_cpp_map[t.kind] | ||
| elif t.is_tuple: | ||
| return ( | ||
| "ks::Tuple<" | ||
| + ", ".join(ks_cpp_type(child) for child in t.tuple_elems()) | ||
| + ">" | ||
| ) | ||
| elif t.is_tensor: | ||
| return f"ks::tensor<{t.tensor_rank}, {ks_cpp_type(t.tensor_elem_type)}>" | ||
| else: | ||
| raise ValueError(f'Unable to generate C++ type for "{t}"') |
There was a problem hiding this comment.
N.B. Overlaps with Cgen
As things stand in this PR, I think you're right that this would fit better in ksc. I'm not sure whether this will remain true when we add more functionality to the wrappers. The next PR (#931, currently draft) changes the wrappers to take If we wanted ksc to handle all of the binding code (including the declaration of the pybind11 module), that seems more consistent to me. But then we're probably committing to having ksc know how to generate bindings for all of the languages that we support (PyTorch, pure Python, and others that might be added in future). I'd suggest we wait to see what these wrappers look like in their final version, as there are plenty of issues left to resolve. I don't think the amount of duplication with ksc is going to grow much further. |
Makes sense, thanks! |
Draft PR: this would currently break the embedded C++ examples added in #877. I'd prefer to merge #877 first and do the fixes here, unless there is still much more work to do on #877.Now merged with #877.
The purpose of this PR is to reimplement the functionality currently provided by the function
with_ks_allocator. That is, for each ks function that we wish to provide python bindings for, we produce a second C++ function which wraps it. This second C++ function is the one that actually gets used by the pybind module. Previously the wrapper function was defined using C++ template trickery inwith_ks_allocator; this PR instead generates code for the wrapper in python.Example code produced for relu3: