Skip to content

Commit 49d5879

Browse files
authored
Introduce --interface-api={c,packed} parameter (#8280)
* Introduce --interface-api={c,packed} parameter This introduces structures generated to provide a documented and stable user friendly interface to a TVM generated model, as can be seen in the AOT demo application: ``` struct tvmgen_default_inputs inputs = { .input_1 = input_data, }; struct tvmgen_default_outputs outputs = { .output = output_data, }; int ret_val = tvmgen_default_run(&inputs, &outputs, NULL, NULL); ``` To facilitate this, some other changes are included: * Removed dependency on `aot_executor.{c,h}` in tests, pending the discussion in the interface RFC as to whether we keep them. * Moved creation of test DLTensor's into the AOT test utils, in future this can be replaced by loading via the Python API or otherwise * Introduce `parametrize_aot_options` which can be used to test permutations of AOT which work together - for now this filters C interface and packed operators * Updated demo application to generate the header for demonstration purposes, we should consider porting the demo application to Model Library Format and using the toolchain in the Zephyr App via CMake instead? This patch builds upon the improvements @giuseros made to AOT testing and name mangling from #8014 * Tweak metadata variable description and MLF target loop * Remove direct usage of `relay::Var` in meta_data.h This looks like the only place that could be causing the Windows CI failures, so trying removing the additional header in meta_data.h * Linting fix * Post-rebase files fixing These tests were somehow transmuted in transit, I've updated them to the most recent variant of the test helpers. * Strip back interface API to just inputs and outputs This removes any speculative structures from the generated code and cleans up some of the documentation. * Add header guards and tweak documentation
1 parent 47a5a3a commit 49d5879

File tree

13 files changed

+637
-218
lines changed

13 files changed

+637
-218
lines changed

apps/microtvm/zephyr/aot_demo/src/main.c

Lines changed: 6 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
#include "input_data.h"
3434
#include "output_data.h"
35+
#include "tvmgen_default.h"
3536
#include "zephyr_uart.h"
3637

3738
#ifdef CONFIG_ARCH_POSIX
@@ -194,18 +195,18 @@ void main(void) {
194195
}
195196
TVMLogf("Zephyr AOT Runtime\n");
196197

197-
void* inputs[1] = {
198-
input_data,
198+
struct tvmgen_default_inputs inputs = {
199+
.input_1 = input_data,
199200
};
200-
void* outputs[1] = {
201-
output_data,
201+
struct tvmgen_default_outputs outputs = {
202+
.output = output_data,
202203
};
203204

204205
StackMemoryManager_Init(&app_workspace, g_aot_memory, WORKSPACE_SIZE);
205206

206207
double elapsed_time = 0;
207208
TVMPlatformTimerStart();
208-
int ret_val = tvm_runtime_run(&tvmgen_default_network, inputs, outputs);
209+
int ret_val = tvmgen_default_run(&inputs, &outputs);
209210
TVMPlatformTimerStop(&elapsed_time);
210211

211212
if (ret_val != 0) {

include/tvm/runtime/module.h

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -230,8 +230,10 @@ constexpr const char* tvm_module_main = "__tvm_main__";
230230
constexpr const char* tvm_param_prefix = "__tvm_param__";
231231
/*! \brief A PackedFunc that looks up linked parameters by storage_id. */
232232
constexpr const char* tvm_lookup_linked_param = "_lookup_linked_param";
233-
/*! \brief The main AOT executor function */
233+
/*! \brief The main AOT executor function generated from TIR */
234234
constexpr const char* tvm_run_func_suffix = "run_model";
235+
/*! \brief Model entrypoint generated as an interface to the AOT function outside of TIR */
236+
constexpr const char* tvm_entrypoint_suffix = "run";
235237
} // namespace symbol
236238

237239
// implementations of inline functions.

python/tvm/micro/interface_api.py

Lines changed: 79 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,79 @@
1+
# Licensed to the Apache Software Foundation (ASF) under one
2+
# or more contributor license agreements. See the NOTICE file
3+
# distributed with this work for additional information
4+
# regarding copyright ownership. The ASF licenses this file
5+
# to you under the Apache License, Version 2.0 (the
6+
# "License"); you may not use this file except in compliance
7+
# with the License. You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing,
12+
# software distributed under the License is distributed on an
13+
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
# KIND, either express or implied. See the License for the
15+
# specific language governing permissions and limitations
16+
# under the License.
17+
18+
"""Defines functions for generating a C interface header"""
19+
20+
import os
21+
22+
from tvm.relay.backend.utils import mangle_module_name
23+
24+
25+
def _emit_brief(header_file, module_name, description):
26+
header_file.write("/*!\n")
27+
header_file.write(f' * \\brief {description} for TVM module "{module_name}" \n')
28+
header_file.write(" */\n")
29+
30+
31+
def generate_c_interface_header(module_name, inputs, outputs, output_path):
32+
"""Generates a C interface header for a given modules inputs and outputs
33+
34+
Parameters
35+
----------
36+
module_name : str
37+
Name of the module to be used in defining structs and naming the header
38+
inputs : list[str]
39+
List of module input names to be placed in generated structs
40+
outputs : list[str]
41+
List of module output names to be placed in generated structs
42+
output_path : str
43+
Path to the output folder to generate the header into
44+
"""
45+
46+
mangled_name = mangle_module_name(module_name)
47+
metadata_header = os.path.join(output_path, f"{mangled_name}.h")
48+
with open(metadata_header, "w") as header_file:
49+
header_file.write(
50+
"#include <stdint.h>\n"
51+
f"#ifndef {mangled_name.upper()}_H_\n"
52+
f"#define {mangled_name.upper()}_H_\n"
53+
)
54+
55+
_emit_brief(header_file, module_name, "Input tensor pointers")
56+
header_file.write(f"struct {mangled_name}_inputs {{\n")
57+
for input_name in inputs:
58+
header_file.write(f" void* {input_name};\n")
59+
header_file.write("};\n\n")
60+
61+
_emit_brief(header_file, module_name, "Output tensor pointers")
62+
header_file.write(f"struct {mangled_name}_outputs {{\n")
63+
for output_name in outputs:
64+
header_file.write(f" void* {output_name};\n")
65+
header_file.write("};\n\n")
66+
67+
header_file.write(
68+
"/*!\n"
69+
f' * \\brief entrypoint function for TVM module "{module_name}"\n'
70+
" * \\param inputs Input tensors for the module \n"
71+
" * \\param outputs Output tensors for the module \n"
72+
" */\n"
73+
f"int32_t {mangled_name}_run(\n"
74+
f" struct {mangled_name}_inputs* inputs,\n"
75+
f" struct {mangled_name}_outputs* outputs\n"
76+
");\n"
77+
)
78+
79+
header_file.write(f"#endif // {mangled_name.upper()}_H_\n")

python/tvm/micro/model_library_format.py

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,9 @@
2525
import tarfile
2626
import typing
2727

28+
from tvm.ir.type import TupleType
2829
from .._ffi import get_global_func
30+
from .interface_api import generate_c_interface_header
2931
from ..contrib import utils
3032
from ..driver import build_module
3133
from ..runtime import ndarray as _nd
@@ -55,7 +57,6 @@ def _populate_codegen_dir(mod, codegen_dir: str, module_name: str = None):
5557
5658
"""
5759
dso_modules = mod._collect_dso_modules()
58-
dso_module_handles = [m.handle.value for m in dso_modules]
5960
non_dso_modules = mod._collect_from_import_tree(lambda m: m not in dso_modules)
6061
if non_dso_modules:
6162
raise UnsupportedInModelLibraryFormatError(
@@ -213,6 +214,39 @@ def _build_function_memory_map(function_metadata):
213214
return ret
214215

215216

217+
def _get_main_relay_func(mod: executor_factory.ExecutorFactoryModule):
218+
main_func = mod.function_metadata[MAIN_FUNC_NAME_STR]
219+
target = list(main_func.relay_primfuncs.keys())[0]
220+
return main_func.relay_primfuncs[target]
221+
222+
223+
def _convert_tuple_to_outputs(ret_type, offset=0):
224+
outputs = []
225+
added_fields = len(ret_type.fields)
226+
for output_index in range(added_fields):
227+
next_output = offset + len(outputs)
228+
if isinstance(ret_type.fields[output_index], TupleType):
229+
outputs.extend(_convert_tuple_to_outputs(ret_type.fields[output_index], next_output))
230+
else:
231+
outputs.append(f"output{next_output}")
232+
return outputs
233+
234+
235+
def _get_inputs_and_outputs_from_module(mod):
236+
main_func = _get_main_relay_func(mod)
237+
inputs = [argument.name_hint for argument in main_func.params]
238+
239+
outputs = ["output"]
240+
if isinstance(main_func.ret_type, TupleType):
241+
outputs = _convert_tuple_to_outputs(main_func.ret_type)
242+
243+
return inputs, outputs
244+
245+
246+
def _should_generate_interface_header(mod):
247+
return any(target.attrs.get("interface-api") == "c" for target in mod.target.values())
248+
249+
216250
def _make_tar(source_dir, tar_file_path):
217251
"""Build a tar file from source_dir."""
218252
with tarfile.open(tar_file_path, "w") as tar_f:
@@ -260,6 +294,12 @@ def _export_graph_model_library_format(
260294
codegen_dir.mkdir()
261295
_populate_codegen_dir(mod.lib, codegen_dir, mod.libmod_name)
262296

297+
if _should_generate_interface_header(mod):
298+
include_path = codegen_dir / "host" / "include"
299+
include_path.mkdir()
300+
inputs, outputs = _get_inputs_and_outputs_from_module(mod)
301+
generate_c_interface_header(mod.libmod_name, inputs, outputs, include_path)
302+
263303
parameters_dir = tempdir / "parameters"
264304
parameters_dir.mkdir()
265305
param_filename = parameters_dir / f"{mod.libmod_name}.params"

src/relay/backend/aot_executor_codegen.cc

Lines changed: 7 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -650,7 +650,7 @@ class AOTExecutorCodegen : public ExprVisitor {
650650
/*! \brief mod */
651651
runtime::Module* mod_;
652652
/*! \brief list of input expressions (i.e., variable passed by the user) */
653-
std::vector<Expr> input_vars_;
653+
std::vector<Var> input_vars_;
654654
/*! \brief input and output variables belonging to the main function signature */
655655
Array<tir::Var> main_signature_;
656656
/*! \brief target device */
@@ -782,8 +782,12 @@ class AOTExecutorCodegen : public ExprVisitor {
782782
ret.lowered_funcs.Set(target_host_str, mod_run);
783783
}
784784
ret.function_metadata = std::move(function_metadata_);
785-
ret.metadata = runtime::Metadata(input_vars_.size(), return_sid_.size(),
786-
runtime::kTvmExecutorAot, mod_name);
785+
786+
std::vector<String> input_var_names(input_vars_.size());
787+
std::transform(input_vars_.begin(), input_vars_.end(), input_var_names.begin(),
788+
[](Var input_var) -> String { return input_var->name_hint(); });
789+
ret.metadata =
790+
runtime::Metadata(input_var_names, return_sid_.size(), runtime::kTvmExecutorAot, mod_name);
787791
return ret;
788792
}
789793
};

src/runtime/meta_data.h

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -54,8 +54,8 @@ inline String get_name_mangled(const String& module_name, const String& name) {
5454
*/
5555
class MetadataNode : public Object {
5656
public:
57-
/*! \brief number of inputs of the main function */
58-
int num_inputs = 1;
57+
/*! \brief input information for the main function */
58+
Array<String> inputs;
5959
/*! \brief number of outputs of the main function */
6060
int num_outputs = 1;
6161
/*! \brief the executor to be used to run the model */
@@ -73,9 +73,9 @@ class MetadataNode : public Object {
7373
*/
7474
class Metadata : public ObjectRef {
7575
public:
76-
TVM_DLL Metadata(int num_inputs, int num_outputs, String executor, String mod_name) {
76+
TVM_DLL Metadata(Array<String> inputs, int num_outputs, String executor, String mod_name) {
7777
auto n = make_object<MetadataNode>();
78-
n->num_inputs = num_inputs;
78+
n->inputs = inputs;
7979
n->num_outputs = num_outputs;
8080
n->executor = executor;
8181
n->mod_name = mod_name;

src/target/source/source_module.cc

Lines changed: 68 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -192,25 +192,26 @@ class CSourceCrtMetadataModuleNode : public runtime::ModuleNode {
192192
<< "}\n";
193193
}
194194

195-
void GenerateEntrypointForUnpackedAPI(const std::string& run_func) {
195+
void GenerateEntrypointForUnpackedAPI(const std::string& entrypoint_name,
196+
const std::string& run_func) {
196197
code_ << "TVM_DLL int32_t " << run_func << "(";
197-
int total_args = (metadata_->num_inputs + metadata_->num_outputs);
198-
for (int i = 0; i < total_args; ++i) {
199-
code_ << "arg" << i;
198+
unsigned int total_args = (metadata_->inputs.size() + metadata_->num_outputs);
199+
for (unsigned int i = 0; i < total_args; ++i) {
200+
code_ << "void* arg" << i;
200201
if (i + 1 != total_args) {
201202
code_ << ",";
202203
}
203204
}
204205
code_ << ");\n";
205-
code_ << "static int32_t " << ::tvm::runtime::symbol::tvm_module_main;
206+
code_ << "int32_t " << entrypoint_name;
206207
code_ << "(void* args, void* type_code, int num_args, void* out_value, void* "
207208
"out_type_code, void* resource_handle) {\n";
208209
code_ << "return " << run_func << "(";
209-
for (int i = 0; i < metadata_->num_inputs; ++i) {
210+
for (unsigned int i = 0; i < metadata_->inputs.size(); ++i) {
210211
code_ << "((DLTensor*)(((TVMValue*)args)[" << i << "].v_handle))[0].data,";
211212
}
212213
for (int i = 0; i < metadata_->num_outputs; ++i) {
213-
int j = metadata_->num_inputs + i;
214+
int j = metadata_->inputs.size() + i;
214215
code_ << "((DLTensor*)(((TVMValue*)args)[" << j << "].v_handle))[0].data";
215216
if (i + 1 != metadata_->num_outputs) {
216217
code_ << ",";
@@ -220,37 +221,83 @@ class CSourceCrtMetadataModuleNode : public runtime::ModuleNode {
220221
code_ << "}\n";
221222
}
222223

223-
void GenerateEntrypointForPackedAPI(const std::string& run_func) {
224+
void GenerateEntrypointForPackedAPI(const std::string& entrypoint_name,
225+
const std::string& run_func) {
224226
code_ << "TVM_DLL int32_t " << run_func;
225227
code_ << "(void* args, void* type_code, int num_args, void* out_value, void* "
226228
"out_type_code, void* resource_handle);\n";
227-
code_ << "static int32_t " << ::tvm::runtime::symbol::tvm_module_main;
229+
code_ << "int32_t " << entrypoint_name;
228230
code_ << "(void* args, void* type_code, int num_args, void* out_value, void* "
229231
"out_type_code, void* resource_handle) {\n";
230232
code_ << "return " << run_func;
231233
code_ << "(args, type_code, num_args, out_value, out_type_code, resource_handle);\n";
232234
code_ << "}\n";
233235
}
234236

237+
void GenerateCInterfaceEntrypoint(const std::string& entrypoint_name, const std::string& run_func,
238+
const std::string& mod_name) {
239+
code_ << "#include <" << mod_name << ".h>\n";
240+
code_ << "TVM_DLL int32_t " << run_func << "(";
241+
unsigned int total_args = (metadata_->inputs.size() + metadata_->num_outputs);
242+
for (unsigned int i = 0; i < total_args; ++i) {
243+
code_ << "void* arg" << i;
244+
if (i + 1 != total_args) {
245+
code_ << ",";
246+
}
247+
}
248+
code_ << ");\n";
249+
code_ << "int32_t " << entrypoint_name << "(";
250+
code_ << "struct " << runtime::get_name_mangled(mod_name, "inputs") << "* inputs,"
251+
<< "struct " << runtime::get_name_mangled(mod_name, "outputs") << "* outputs"
252+
<< ") {";
253+
code_ << "return " << run_func << "(";
254+
for (const auto& input : metadata_->inputs) {
255+
code_ << "inputs->" << input << ",";
256+
}
257+
if (metadata_->num_outputs == 1) {
258+
code_ << "outputs->output";
259+
} else {
260+
for (int i = 0; i < metadata_->num_outputs; ++i) {
261+
code_ << "outputs->output" << i;
262+
if (i + 1 != metadata_->num_outputs) {
263+
code_ << ",";
264+
}
265+
}
266+
}
267+
code_ << ");\n";
268+
code_ << "}\n";
269+
}
270+
235271
void GenerateAOTDescriptor() {
236-
const std::string run_func = ::tvm::runtime::symbol::tvm_run_func_suffix;
237-
const std::string run_func_mangled = runtime::get_name_mangled(metadata_->mod_name, run_func);
272+
const std::string run_func_suffix = ::tvm::runtime::symbol::tvm_run_func_suffix;
273+
const std::string tvm_entrypoint_suffix = ::tvm::runtime::symbol::tvm_entrypoint_suffix;
274+
const std::string run_func_mangled =
275+
runtime::get_name_mangled(metadata_->mod_name, run_func_suffix);
276+
const std::string entrypoint_mangled =
277+
runtime::get_name_mangled(metadata_->mod_name, tvm_entrypoint_suffix);
238278
const std::string network_mangled = runtime::get_name_mangled(metadata_->mod_name, "network");
239-
code_ << "#include \"tvm/runtime/crt/internal/aot_executor/aot_executor.h\"\n";
279+
auto unpacked_api = target_->GetAttr<Bool>("unpacked-api").value_or(Bool(false));
280+
auto interface_api = target_->GetAttr<String>("interface-api").value_or(String("packed"));
281+
240282
code_ << "#include \"tvm/runtime/c_runtime_api.h\"\n";
241283
code_ << "#ifdef __cplusplus\n";
242-
code_ << "extern \"C\"\n";
284+
code_ << "extern \"C\" {\n";
243285
code_ << "#endif\n";
244-
if (target_->GetAttr<Bool>("unpacked-api").value_or(Bool(false))) {
245-
GenerateEntrypointForUnpackedAPI(run_func_mangled);
286+
287+
if (unpacked_api) {
288+
if (interface_api == "c") {
289+
GenerateCInterfaceEntrypoint(entrypoint_mangled, run_func_mangled, metadata_->mod_name);
290+
} else {
291+
GenerateEntrypointForUnpackedAPI(entrypoint_mangled, run_func_mangled);
292+
}
246293
} else {
247-
GenerateEntrypointForPackedAPI(run_func_mangled);
294+
ICHECK_EQ(interface_api, "packed") << "Packed interface required for packed operators";
295+
GenerateEntrypointForPackedAPI(entrypoint_mangled, run_func_mangled);
248296
}
249-
code_ << "const tvm_model_t " << network_mangled << " = {\n"
250-
<< " .run_func = &" << ::tvm::runtime::symbol::tvm_module_main << ",\n"
251-
<< " .num_input_tensors = " << metadata_->num_inputs << ",\n"
252-
<< " .num_output_tensors = " << metadata_->num_outputs << ", \n"
253-
<< "};\n";
297+
298+
code_ << "#ifdef __cplusplus\n";
299+
code_ << "}\n";
300+
code_ << "#endif\n";
254301
}
255302

256303
void CreateSource() {

src/target/target_kind.cc

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -299,6 +299,7 @@ TVM_REGISTER_TARGET_KIND("llvm", kDLCPU)
299299
.add_attr_option<String>("runtime")
300300
.add_attr_option<Bool>("link-params", Bool(false))
301301
.add_attr_option<Bool>("unpacked-api")
302+
.add_attr_option<String>("interface-api")
302303
.set_default_keys({"cpu"});
303304

304305
TVM_REGISTER_TARGET_KIND("c", kDLCPU)
@@ -310,6 +311,7 @@ TVM_REGISTER_TARGET_KIND("c", kDLCPU)
310311
.add_attr_option<String>("executor")
311312
.add_attr_option<Integer>("workspace-byte-alignment")
312313
.add_attr_option<Bool>("unpacked-api")
314+
.add_attr_option<String>("interface-api")
313315
.set_default_keys({"cpu"});
314316

315317
TVM_REGISTER_TARGET_KIND("cuda", kDLCUDA)

0 commit comments

Comments
 (0)