Skip to content

Commit

Permalink
Introduce --interface-api={c,packed} parameter
Browse files Browse the repository at this point in the history
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 tvm_default_inputs inputs = {
  .input_1 = input_data,
};
struct tvm_default_outputs outputs = {
  .output = output_data,
};
int ret_val = tvm_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 will greatly benefit from name mangling from apache#8014
  • Loading branch information
Mousius committed Jun 18, 2021
1 parent bf3f000 commit 463eda2
Show file tree
Hide file tree
Showing 12 changed files with 563 additions and 210 deletions.
11 changes: 6 additions & 5 deletions apps/microtvm/zephyr/aot_demo/src/main.c
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

#include "input_data.h"
#include "output_data.h"
#include "tvm_default.h"
#include "zephyr_uart.h"

#ifdef CONFIG_ARCH_POSIX
Expand Down Expand Up @@ -194,18 +195,18 @@ void main(void) {
}
TVMLogf("Zephyr AOT Runtime\n");

void* inputs[1] = {
input_data,
struct tvm_default_inputs inputs = {
.input_1 = input_data,
};
void* outputs[1] = {
output_data,
struct tvm_default_outputs outputs = {
.output = output_data,
};

StackMemoryManager_Init(&app_workspace, g_aot_memory, WORKSPACE_SIZE);

double elapsed_time = 0;
TVMPlatformTimerStart();
int ret_val = tvm_runtime_run(&network, inputs, outputs);
int ret_val = tvm_default_run(&inputs, &outputs, NULL, NULL);
TVMPlatformTimerStop(&elapsed_time);

if (ret_val != 0) {
Expand Down
62 changes: 62 additions & 0 deletions python/tvm/micro/interface_api.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
# Licensed to the Apache Software Foundation (ASF) under one
# or more contributor license agreements. See the NOTICE file
# distributed with this work for additional information
# regarding copyright ownership. The ASF licenses this file
# to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance
# with the License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing,
# software distributed under the License is distributed on an
# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
# KIND, either express or implied. See the License for the
# specific language governing permissions and limitations
# under the License.

import os


def _emit_brief(header_file, model_name, description):
header_file.write("/*!\n")
header_file.write(f" * \\brief TVM {model_name} model {description} \n")
header_file.write(" */\n")


def generate_c_interface_header(model_name, inputs, outputs, output_path):
metadata_header = os.path.join(output_path, f"tvm_{model_name}.h")
with open(metadata_header, "w") as header_file:
_emit_brief(header_file, model_name, "input tensors")
header_file.write(f"struct tvm_{model_name}_inputs {{\n")
for input_name in inputs:
header_file.write(f"\tvoid* {input_name};\n")
header_file.write("};\n\n")

_emit_brief(header_file, model_name, "output tensors")
header_file.write(f"struct tvm_{model_name}_outputs {{\n")
for output_name in outputs:
header_file.write(f"\tvoid* {output_name};\n")
header_file.write("};\n\n")

_emit_brief(header_file, model_name, "memory blocks")
header_file.write(f"struct tvm_{model_name}_memory {{\n")
header_file.write("};\n\n")

_emit_brief(header_file, model_name, "device configurations")
header_file.write(f"struct tvm_{model_name}_devices {{\n")
header_file.write("};\n\n")

header_file.write("/*!\n")
header_file.write(f" * \\brief TVM {model_name} model run function \n")
header_file.write(f" * \\param inputs Input tensors for the model \n")
header_file.write(f" * \\param outputs Output tensors for the model \n")
header_file.write(f" * \\param memory Memory blocks for the model to use \n")
header_file.write(f" * \\param devices Devices for the model to use \n")
header_file.write(" */\n")
header_file.write(f"int tvm_{model_name}_run(\n")
header_file.write(f"\tstruct tvm_{model_name}_inputs* inputs,\n")
header_file.write(f"\tstruct tvm_{model_name}_outputs* outputs,\n")
header_file.write(f"\tstruct tvm_{model_name}_memory* memory,\n")
header_file.write(f"\tstruct tvm_{model_name}_devices* devices\n")
header_file.write(");\n")
45 changes: 44 additions & 1 deletion python/tvm/micro/model_library_format.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,9 +23,11 @@
import re
import tarfile

from .interface_api import generate_c_interface_header
from ..contrib import utils
from ..relay.backend import executor_factory
from ..relay import param_dict
from tvm.ir.type import TupleType

# This should be kept identical to runtime::symbol::tvm_module_main
MAIN_FUNC_NAME_STR = "__tvm_main__"
Expand All @@ -46,7 +48,6 @@ def _populate_codegen_dir(mod, codegen_dir: str):
Path to the codegen directory on disk.
"""
dso_modules = mod._collect_dso_modules()
dso_module_handles = [m.handle.value for m in dso_modules]
non_dso_modules = mod._collect_from_import_tree(lambda m: m not in dso_modules)
if non_dso_modules:
raise UnsupportedInModelLibraryFormatError(
Expand Down Expand Up @@ -203,6 +204,42 @@ def _build_function_memory_map(function_metadata):
return ret


def _get_main_relay_func(mod: executor_factory.ExecutorFactoryModule):
main_func = mod.function_metadata[MAIN_FUNC_NAME_STR]
target = list(main_func.relay_primfuncs.keys())[0]
return main_func.relay_primfuncs[target]


def _convert_tuple_to_outputs(ret_type, offset=0):
outputs = []
added_fields = len(ret_type.fields)
for output_index in range(added_fields):
next_output = offset + len(outputs)
if isinstance(ret_type.fields[output_index], TupleType):
outputs.extend(_convert_tuple_to_outputs(ret_type.fields[output_index], next_output))
else:
outputs.append(f"output{next_output}")
return outputs


def _get_inputs_and_outputs_from_module(mod):
main_func = _get_main_relay_func(mod)
inputs = [argument.name_hint for argument in main_func.params]

outputs = ["output"]
if isinstance(main_func.ret_type, TupleType):
outputs = _convert_tuple_to_outputs(main_func.ret_type)

return inputs, outputs


def _should_generate_interface_header(mod):
for _, target in mod.target.items():
if "interface-api" in target.attrs and target.attrs["interface-api"] == "c":
return True
return False


def export_model_library_format(mod: executor_factory.ExecutorFactoryModule, file_name):
"""Export the build artifact in Model Library Format.
Expand Down Expand Up @@ -242,6 +279,12 @@ def export_model_library_format(mod: executor_factory.ExecutorFactoryModule, fil
os.mkdir(codegen_dir_path)
_populate_codegen_dir(mod.lib, codegen_dir_path)

if _should_generate_interface_header(mod):
include_path = os.path.join(codegen_dir_path, "host/include")
os.mkdir(include_path)
inputs, outputs = _get_inputs_and_outputs_from_module(mod)
generate_c_interface_header(mod.libmod_name, inputs, outputs, include_path)

parameters_dir_path = tempdir.relpath("parameters")
os.mkdir(parameters_dir_path)
param_filename = os.path.join(parameters_dir_path, f"{mod.libmod_name}.params")
Expand Down
6 changes: 3 additions & 3 deletions src/relay/backend/aot_executor_codegen.cc
Original file line number Diff line number Diff line change
Expand Up @@ -544,7 +544,7 @@ class AOTExecutorCodegen : public ExprVisitor {
/*! \brief mod */
runtime::Module* mod_;
/*! \brief list of input expressions (i.e., variable passed by the user) */
std::vector<Expr> input_vars_;
std::vector<Var> input_vars_;
/*! \brief input and output variables belonging to the main function signature */
Array<tir::Var> main_signature_;
/*! \brief target device */
Expand Down Expand Up @@ -652,8 +652,8 @@ class AOTExecutorCodegen : public ExprVisitor {
ret.lowered_funcs.Set(target_host_str, IRModule(symbol_map));
}
ret.function_metadata = std::move(function_metadata_);
ret.metadata =
runtime::Metadata(input_vars_.size(), return_sid_.size(), runtime::kTvmExecutorAot);

ret.metadata = runtime::Metadata(input_vars_, return_sid_.size(), runtime::kTvmExecutorAot);
return ret;
}
};
Expand Down
9 changes: 5 additions & 4 deletions src/runtime/meta_data.h
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@

#include <dmlc/io.h>
#include <dmlc/json.h>
#include <tvm/relay/expr.h>
#include <tvm/runtime/executor_info.h>
#include <tvm/runtime/module.h>
#include <tvm/runtime/ndarray.h>
Expand All @@ -46,8 +47,8 @@ namespace runtime {
*/
class MetadataNode : public Object {
public:
/*! \brief number of inputs of the main function */
int num_inputs = 1;
/*! \brief input information for the main function */
Array<tvm::relay::Var> inputs;
/*! \brief number of outputs of the main function */
int num_outputs = 1;
/*! \brief the executor to be used to run the model */
Expand All @@ -63,9 +64,9 @@ class MetadataNode : public Object {
*/
class Metadata : public ObjectRef {
public:
TVM_DLL Metadata(int num_inputs, int num_outputs, String executor) {
TVM_DLL Metadata(Array<tvm::relay::Var> inputs, int num_outputs, String executor) {
auto n = make_object<MetadataNode>();
n->num_inputs = num_inputs;
n->inputs = inputs;
n->num_outputs = num_outputs;
n->executor = executor;
data_ = std::move(n);
Expand Down
70 changes: 54 additions & 16 deletions src/target/source/source_module.cc
Original file line number Diff line number Diff line change
Expand Up @@ -193,24 +193,24 @@ class CSourceCrtMetadataModuleNode : public runtime::ModuleNode {
}

void GenerateEntrypointForUnpackedAPI() {
code_ << "TVM_DLL int32_t " << ::tvm::runtime::symbol::tvm_run_func_prefix << "(";
int total_args = (metadata_->num_inputs + metadata_->num_outputs);
for (int i = 0; i < total_args; ++i) {
code_ << "arg" << i;
code_ << "TVM_DLL int " << ::tvm::runtime::symbol::tvm_run_func_prefix << "(";
unsigned int total_args = (metadata_->inputs.size() + metadata_->num_outputs);
for (unsigned int i = 0; i < total_args; ++i) {
code_ << "void* arg" << i;
if (i + 1 != total_args) {
code_ << ",";
}
}
code_ << ");\n";
code_ << "static int32_t " << ::tvm::runtime::symbol::tvm_module_main;
code_ << "int " << ::tvm::runtime::symbol::tvm_module_main;
code_ << "(void* args, void* type_code, int num_args, void* out_value, void* "
"out_type_code, void* resource_handle) {\n";
code_ << "return " << ::tvm::runtime::symbol::tvm_run_func_prefix << "(";
for (int i = 0; i < metadata_->num_inputs; ++i) {
for (unsigned int i = 0; i < metadata_->inputs.size(); ++i) {
code_ << "((DLTensor*)(((TVMValue*)args)[" << i << "].v_handle))[0].data,";
}
for (int i = 0; i < metadata_->num_outputs; ++i) {
int j = metadata_->num_inputs + i;
int j = metadata_->inputs.size() + i;
code_ << "((DLTensor*)(((TVMValue*)args)[" << j << "].v_handle))[0].data";
if (i + 1 != metadata_->num_outputs) {
code_ << ",";
Expand All @@ -221,33 +221,71 @@ class CSourceCrtMetadataModuleNode : public runtime::ModuleNode {
}

void GenerateEntrypointForPackedAPI() {
code_ << "TVM_DLL int32_t " << ::tvm::runtime::symbol::tvm_run_func_prefix;
code_ << "TVM_DLL int " << ::tvm::runtime::symbol::tvm_run_func_prefix;
code_ << "(void* args, void* type_code, int num_args, void* out_value, void* "
"out_type_code, void* resource_handle);\n";
code_ << "static int32_t " << ::tvm::runtime::symbol::tvm_module_main;
code_ << "int " << ::tvm::runtime::symbol::tvm_module_main;
code_ << "(void* args, void* type_code, int num_args, void* out_value, void* "
"out_type_code, void* resource_handle) {\n";
code_ << "return " << ::tvm::runtime::symbol::tvm_run_func_prefix;
code_ << "(args, type_code, num_args, out_value, out_type_code, resource_handle);\n";
code_ << "}\n";
}

void GenerateCInterfaceEntrypoint() {
code_ << "#include <tvm_default.h>\n";
code_ << "TVM_DLL int32_t " << ::tvm::runtime::symbol::tvm_run_func_prefix << "(";
unsigned int total_args = (metadata_->inputs.size() + metadata_->num_outputs);
for (unsigned int i = 0; i < total_args; ++i) {
code_ << "void* arg" << i;
if (i + 1 != total_args) {
code_ << ",";
}
}
code_ << ");\n";
code_ << "int tvm_default_run(";
code_ << "struct tvm_default_inputs* inputs,"
<< "struct tvm_default_outputs* outputs,"
<< "struct tvm_default_memory* memory,"
<< "struct tvm_default_devices* devices"
<< ") {";
code_ << "return " << ::tvm::runtime::symbol::tvm_run_func_prefix << "(";
for (const auto& input : metadata_->inputs) {
code_ << "inputs->" << input->name_hint() << ",";
}
if (metadata_->num_outputs == 1) {
code_ << "outputs->output";
} else {
for (int i = 0; i < metadata_->num_outputs; ++i) {
code_ << "outputs->output" << i;
if (i + 1 != metadata_->num_outputs) {
code_ << ",";
}
}
}
code_ << ");\n";
code_ << "}\n";
}

void GenerateAOTDescriptor() {
auto unpacked_api = target_->GetAttr<Bool>("unpacked-api").value_or(Bool(false));
auto interface_api = target_->GetAttr<String>("interface-api").value_or(String("packed"));

code_ << "#include \"tvm/runtime/crt/internal/aot_executor/aot_executor.h\"\n";
code_ << "#include \"tvm/runtime/c_runtime_api.h\"\n";
code_ << "#ifdef __cplusplus\n";
code_ << "extern \"C\"\n";
code_ << "#endif\n";
if (target_->GetAttr<Bool>("unpacked-api").value_or(Bool(false))) {
GenerateEntrypointForUnpackedAPI();
if (unpacked_api) {
if (interface_api == "c") {
GenerateCInterfaceEntrypoint();
} else {
GenerateEntrypointForUnpackedAPI();
}
} else {
ICHECK_EQ(interface_api, "packed") << "Packed interface required for packed operators";
GenerateEntrypointForPackedAPI();
}
code_ << "const tvm_model_t network = {\n"
<< " .run_func = &" << ::tvm::runtime::symbol::tvm_module_main << ",\n"
<< " .num_input_tensors = " << metadata_->num_inputs << ",\n"
<< " .num_output_tensors = " << metadata_->num_outputs << ", \n"
<< "};\n";
}

void CreateSource() {
Expand Down
2 changes: 2 additions & 0 deletions src/target/target_kind.cc
Original file line number Diff line number Diff line change
Expand Up @@ -299,6 +299,7 @@ TVM_REGISTER_TARGET_KIND("llvm", kDLCPU)
.add_attr_option<String>("runtime")
.add_attr_option<Bool>("link-params", Bool(false))
.add_attr_option<Bool>("unpacked-api")
.add_attr_option<String>("interface-api")
.set_default_keys({"cpu"});

TVM_REGISTER_TARGET_KIND("c", kDLCPU)
Expand All @@ -310,6 +311,7 @@ TVM_REGISTER_TARGET_KIND("c", kDLCPU)
.add_attr_option<String>("executor")
.add_attr_option<Integer>("workspace-byte-alignment")
.add_attr_option<Bool>("unpacked-api")
.add_attr_option<String>("interface-api")
.set_default_keys({"cpu"});

TVM_REGISTER_TARGET_KIND("cuda", kDLCUDA)
Expand Down
6 changes: 5 additions & 1 deletion tests/micro/zephyr/test_zephyr_aot.py
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@
from tvm.micro.contrib import zephyr
from tvm.contrib import utils
from tvm.contrib.download import download_testdata
from tvm.micro.interface_api import generate_c_interface_header

import conftest

Expand Down Expand Up @@ -181,7 +182,9 @@ def test_tflite(platform, west_cmd, skip_build, tvm_debug):
tflite_model, shape_dict={"input_1": input_shape}, dtype_dict={"input_1 ": "float32"}
)

target = tvm.target.target.micro(model, options=["-link-params=1", "--executor=aot"])
target = tvm.target.target.micro(
model, options=["-link-params=1", "--executor=aot", "--unpacked-api=1", "--interface-api=c"]
)
with tvm.transform.PassContext(opt_level=3, config={"tir.disable_vectorize": True}):
lowered = relay.build(relay_mod, target, params=params)

Expand All @@ -192,6 +195,7 @@ def test_tflite(platform, west_cmd, skip_build, tvm_debug):
)
sample = np.load(sample_path)
model_files_path = os.path.join(runtime_path, "include")
generate_c_interface_header(lowered.libmod_name, ["input_1"], ["output"], model_files_path)
_create_header_file((f"input_data"), sample, model_files_path)
_create_header_file(
"output_data", np.zeros(shape=output_shape, dtype="float32"), model_files_path
Expand Down
3 changes: 2 additions & 1 deletion tests/python/relay/aot/aot_test.mk
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,8 @@ PKG_CFLAGS = ${PKG_COMPILE_OPTS} \
-I$(DMLC_CORE)/include \
-I$(TVM_ROOT)/3rdparty/dlpack/include \
-I$(AOT_ROOT)\
-I$(build_dir)
-I$(build_dir) \
-I$(CODEGEN_ROOT)/host/include

$(ifeq VERBOSE,1)
QUIET ?=
Expand Down
Loading

0 comments on commit 463eda2

Please sign in to comment.