Skip to content

Commit

Permalink
[Runtime] MISRA-C compliant TVM runtime (apache#3934)
Browse files Browse the repository at this point in the history
* implement of MISRA-C compliant TVM runtime;

* working on bundle_deploy_c demo

* move header files into include dir

* fix compatibility issues

* fix compatibility issues

* resolve most of the warnings and errros

* implement c_backend_api

* introduce bridge

* working well

* move to header files and bundle.c into src/runtime/crt

* clean up

* satisfy linter

* clean up

* test with the cat image

* remove synset

* refactoring

* refactoring

* refactoring

* initial crt_runtime_api.c

* improved compatibility with g++

* using exposed API in c_runtime_api.h

* call from c_runtime_api.h

* clean up

* lint

* merge into apps/bundle_deploy directory

Change-Id: I51904db81b8589e65d107d8ca77b47452e3812b5

* make the demo runs in ci

Change-Id: I2c24f8b592508833d3555311c2b24d1931f19385

* address review comments

Change-Id: I027ddff15c31fb4da0bd0e461427dce619de1f93

* release

Change-Id: I5ad5bb8426468aac9fc8d074e56ddea358a7fd91

* fix ci testing

Change-Id: Ic2e82fb3051b6c254ef32a964f976b61e3e5fe4d

* add test case for misra c runtime

Change-Id: Ie0dfd0ade6be4665b4384db7d260a6c69b35010f

* fread files in testing to avoid calling xxd

Change-Id: Ie7fbc16b4b0b9509918d986a841f443900813bef
  • Loading branch information
liangfu authored and Trevor Morris committed Apr 16, 2020
1 parent 333a037 commit 45a4d8a
Show file tree
Hide file tree
Showing 19 changed files with 2,328 additions and 48 deletions.
68 changes: 54 additions & 14 deletions apps/bundle_deploy/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -17,40 +17,80 @@

# Makefile Example to bundle TVM modules.

# Setup build environment
TVM_ROOT=$(shell cd ../..; pwd)
DMLC_CORE=${TVM_ROOT}/3rdparty/dmlc-core
PKG_CFLAGS = -std=c++14 -O2 -fPIC\
-I${TVM_ROOT}/include\
-I${DMLC_CORE}/include\
PKG_CXXFLAGS = -std=c++14 -O2 -fPIC \
-I${TVM_ROOT}/include \
-I${DMLC_CORE}/include \
-I${TVM_ROOT}/3rdparty/dlpack/include
PKG_CFLAGS = -std=c99 -O2 -fPIC \
-I${TVM_ROOT}/include \
-I${DMLC_CORE}/include \
-I${TVM_ROOT}/3rdparty/dlpack/include

PKG_LDFLAGS = -pthread

build_dir := build

test: $(build_dir)/demo $(build_dir)/bundle.so
$(build_dir)/demo $(build_dir)/bundle.so
demo: $(build_dir)/demo $(build_dir)/bundle.so $(build_dir)/bundle_c.so $(build_dir)/cat.bin
TVM_NUM_THREADS=1 $(build_dir)/demo $(build_dir)/bundle.so $(build_dir)/cat.bin
TVM_NUM_THREADS=1 $(build_dir)/demo $(build_dir)/bundle_c.so $(build_dir)/cat.bin

test: $(build_dir)/test $(build_dir)/test_bundle.so $(build_dir)/test_bundle_c.so $(build_dir)/test_data.bin $(build_dir)/test_output.bin
TVM_NUM_THREADS=1 $(build_dir)/test $(build_dir)/test_bundle.so $(build_dir)/test_data.bin $(build_dir)/test_output.bin $(build_dir)/test_graph.json $(build_dir)/test_params.bin
TVM_NUM_THREADS=1 $(build_dir)/test $(build_dir)/test_bundle_c.so $(build_dir)/test_data.bin $(build_dir)/test_output.bin $(build_dir)/test_graph.json $(build_dir)/test_params.bin

$(build_dir)/demo: demo.cc ${build_dir}/graph.json.c ${build_dir}/params.bin.c
@mkdir -p $(@D)
g++ $(PKG_CXXFLAGS) -o $@ demo.cc -ldl

$(build_dir)/demo: demo.cc
$(build_dir)/test: test.cc ${build_dir}/test_graph.json ${build_dir}/test_params.bin
@mkdir -p $(@D)
$(CXX) $(PKG_CFLAGS) -o $@ $^ -ldl
g++ $(PKG_CXXFLAGS) -o $@ test.cc -ldl

# Serialize our graph.json file.
$(build_dir)/graph.json.cc: $(build_dir)/graph.json
$(build_dir)/graph.json.c: $(build_dir)/graph.json
xxd -i $^ > $@

# Serialize our params.bin file.
$(build_dir)/params.bin.cc: $(build_dir)/params.bin
$(build_dir)/params.bin.c: $(build_dir)/params.bin
xxd -i $^ > $@

$(build_dir)/model.o $(build_dir)/graph.json $(build_dir)/params.bin: build_model.py
# # Serialize our test_graph.json file.
# $(build_dir)/test_graph.json.c: $(build_dir)/test_graph.json
# xxd -i $^ > $@
#
# # Serialize our test_params.bin file.
# $(build_dir)/test_params.bin.c: $(build_dir)/test_params.bin
# xxd -i $^ > $@

$(build_dir)/model.o $(build_dir)/graph.json $(build_dir)/params.bin $(build_dir)/cat.bin: build_model.py
python3 $< -o $(build_dir)

# Build our bundle against the serialized bundle.cc API, the runtime.cc API, and
$(build_dir)/test_model.o $(build_dir)/test_graph.json $(build_dir)/test_params.bin $(build_dir)/test_data.bin $(build_dir)/test_output.bin: build_model.py
python3 $< -o $(build_dir) --test

# Build our bundle against the serialized bundle.c API, the runtime.cc API, and
# the serialized graph.json and params.bin
$(build_dir)/bundle.so: bundle.cc runtime.cc $(build_dir)/model.o $(build_dir)/graph.json.cc $(build_dir)/params.bin.cc
$(build_dir)/bundle.so: bundle.cc runtime.cc $(build_dir)/model.o
@mkdir -p $(@D)
$(CXX) -shared $(PKG_CFLAGS) -fvisibility=hidden -o $@ $^ $(PKG_LDFLAGS)
g++ -shared $(PKG_CXXFLAGS) -fvisibility=hidden -o $@ $^ $(PKG_LDFLAGS)

$(build_dir)/bundle_c.so: bundle.c runtime.c $(build_dir)/model.o
@mkdir -p $(@D)
gcc -shared $(PKG_CFLAGS) -fvisibility=hidden -o $@ $^ $(PKG_LDFLAGS)

$(build_dir)/test_bundle.so: bundle.cc runtime.cc $(build_dir)/test_model.o
@mkdir -p $(@D)
g++ -shared $(PKG_CXXFLAGS) -fvisibility=hidden -o $@ $^ $(PKG_LDFLAGS)

$(build_dir)/test_bundle_c.so: bundle.c runtime.c $(build_dir)/test_model.o
@mkdir -p $(@D)
gcc -shared $(PKG_CFLAGS) -fvisibility=hidden -o $@ $^ $(PKG_LDFLAGS)

clean:
rm -r $(build_dir)
rm -rf $(build_dir)/bundle.so $(build_dir)/bundle_c.so $(build_dir)/test_bundle.so $(build_dir)/test_bundle_c.so

cleanall:
rm -rf $(build_dir)
9 changes: 5 additions & 4 deletions apps/bundle_deploy/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,10 @@ make demo
This will:

- Download the mobilenet0.25 model from the MXNet Gluon Model Zoo
- Compile the model with NNVM
- Compile the model with Relay
- Build a `bundle.so` shared object containing the model specification and
parameters
- Build a `demo` executable that `dlopen`'s `bundle.so`, instantiates the
contained graph runtime, and invokes the `GraphRuntime::Run` function on a
random input, then prints the output tensor to `stderr`.
- Build a `demo` executable that `dlopen`'s `bundle.so` (or `bundle_c.so` in
terms of the MISRA-C runtime), instantiates the contained graph runtime,
and invokes the `GraphRuntime::Run` function on a cat image, then prints
the output results.
75 changes: 66 additions & 9 deletions apps/bundle_deploy/build_model.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,15 +22,9 @@
import tvm
from tvm import te
import logging
import json


def main():
logging.basicConfig(level=logging.INFO)

parser = argparse.ArgumentParser()
parser.add_argument('-o', '--out-dir', default='.')
opts = parser.parse_args()

def build_module(opts):
dshape = (1, 3, 224, 224)
from mxnet.gluon.model_zoo.vision import get_model
block = get_model('mobilenet0.25', pretrained=True)
Expand All @@ -53,6 +47,69 @@ def main():
with open(os.path.join(build_dir, 'params.bin'), 'wb') as f_params:
f_params.write(relay.save_param_dict(params))

def build_test_module(opts):
import numpy as np

x = relay.var('x', shape=(10, 5))
y = relay.var('y', shape=(1, 5))
z = relay.add(x, y)
func = relay.Function([x, y], z)
x_data = np.random.rand(10, 5).astype('float32')
y_data = np.random.rand(1, 5).astype('float32')
params = {"y": y_data}
graph, lib, params = relay.build(
tvm.IRModule.from_expr(func), "llvm --system-lib", params=params)

build_dir = os.path.abspath(opts.out_dir)
if not os.path.isdir(build_dir):
os.makedirs(build_dir)

lib.save(os.path.join(build_dir, 'test_model.o'))
with open(os.path.join(build_dir, 'test_graph.json'), 'w') as f_graph_json:
f_graph_json.write(graph)
with open(os.path.join(build_dir, 'test_params.bin'), 'wb') as f_params:
f_params.write(relay.save_param_dict(params))
with open(os.path.join(build_dir, "test_data.bin"), "wb") as fp:
fp.write(x_data.astype(np.float32).tobytes())
x_output = x_data + y_data
with open(os.path.join(build_dir, "test_output.bin"), "wb") as fp:
fp.write(x_output.astype(np.float32).tobytes())

def build_inputs(opts):
from tvm.contrib import download
from PIL import Image
import numpy as np

build_dir = os.path.abspath(opts.out_dir)

# Download test image
image_url = 'https://homes.cs.washington.edu/~moreau/media/vta/cat.jpg'
image_fn = os.path.join(build_dir, "cat.png")
download.download(image_url, image_fn)
image = Image.open(image_fn).resize((224, 224))

def transform_image(image):
image = np.array(image) - np.array([123., 117., 104.])
image /= np.array([58.395, 57.12, 57.375])
image = image.transpose((2, 0, 1))
image = image[np.newaxis, :]
return image

x = transform_image(image)
print('x', x.shape)
with open(os.path.join(build_dir, "cat.bin"), "wb") as fp:
fp.write(x.astype(np.float32).tobytes())

if __name__ == '__main__':
main()
logging.basicConfig(level=logging.INFO)

parser = argparse.ArgumentParser()
parser.add_argument('-o', '--out-dir', default='.')
parser.add_argument('-t', '--test', action='store_true')
opts = parser.parse_args()

if opts.test:
build_test_module(opts)
else:
build_module(opts)
build_inputs(opts)
89 changes: 89 additions & 0 deletions apps/bundle_deploy/bundle.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,89 @@
/*
* 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.
*/

#include <tvm/runtime/c_runtime_api.h>
#include <stdio.h>
#include <stdlib.h>

/*! \brief macro to do C API call */
#define TVM_CCALL(func) \
do { \
int ret = (func); \
if (ret != 0) { \
fprintf(stderr, "%s: %d: error: %s\n", __FILE__, __LINE__, TVMGetLastError()); \
exit(ret); \
} \
} while (0)

TVM_DLL void * tvm_runtime_create(const char * json_data,
const char * params_data,
const uint64_t params_size) {
int64_t device_type = kDLCPU;
int64_t device_id = 0;

TVMByteArray params;
params.data = params_data;
params.size = params_size;

TVMContext ctx;
ctx.device_type = (DLDeviceType)device_type;
ctx.device_id = device_id;

// declare pointers
TVMModuleHandle (*SystemLibraryCreate)();
TVMModuleHandle (*TVMGraphRuntimeCreate)(const char *, const TVMModuleHandle, const TVMContext *);
int (*TVMGraphRuntime_LoadParams)(TVMModuleHandle, const char *, const uint32_t);

// get pointers
TVM_CCALL(TVMFuncGetGlobal("runtime.SystemLib", (TVMFunctionHandle*)&SystemLibraryCreate));
TVM_CCALL(TVMFuncGetGlobal("tvm.graph_runtime.create", (TVMFunctionHandle*)&TVMGraphRuntimeCreate));

// run modules
TVMModuleHandle mod_syslib = SystemLibraryCreate();
TVMModuleHandle mod = TVMGraphRuntimeCreate(json_data, mod_syslib, &ctx);
TVM_CCALL(TVMModGetFunction(mod, "load_params", 0, (TVMFunctionHandle*)&TVMGraphRuntime_LoadParams));
TVMGraphRuntime_LoadParams(mod, params.data, params.size);

return mod;
}

TVM_DLL void tvm_runtime_destroy(void * runtime) {
void (*TVMGraphRuntimeRelease)(TVMModuleHandle *);
TVM_CCALL(TVMFuncGetGlobal("tvm.graph_runtime.release", (TVMFunctionHandle*)&TVMGraphRuntimeRelease));
TVMGraphRuntimeRelease(&runtime);
}

TVM_DLL void tvm_runtime_set_input(void * runtime, const char * name, DLTensor * tensor) {
void (*TVMGraphRuntime_SetInput)(TVMModuleHandle, const char *, DLTensor*);
TVM_CCALL(TVMFuncGetGlobal("tvm.graph_runtime.set_input", (TVMFunctionHandle*)&TVMGraphRuntime_SetInput));
TVMGraphRuntime_SetInput(runtime, name, tensor);
}

TVM_DLL void tvm_runtime_run(void * runtime) {
void (*TVMGraphRuntime_Run)(TVMModuleHandle runtime);
TVM_CCALL(TVMFuncGetGlobal("tvm.graph_runtime.run", (TVMFunctionHandle*)&TVMGraphRuntime_Run));
TVMGraphRuntime_Run(runtime);
}

TVM_DLL void tvm_runtime_get_output(void * runtime, int32_t index, DLTensor * tensor) {
int (*TVMGraphRuntime_GetOutput)(TVMModuleHandle, const int32_t, DLTensor *);
TVM_CCALL(TVMFuncGetGlobal("tvm.graph_runtime.get_output", (TVMFunctionHandle*)&TVMGraphRuntime_GetOutput));
TVMGraphRuntime_GetOutput(runtime, index, tensor);
}

10 changes: 4 additions & 6 deletions apps/bundle_deploy/bundle.cc
Original file line number Diff line number Diff line change
Expand Up @@ -21,16 +21,14 @@
#include <tvm/runtime/c_runtime_api.h>
#include <tvm/runtime/registry.h>

extern unsigned char build_graph_json[];
extern unsigned int build_graph_json_len;
extern unsigned char build_params_bin[];
extern unsigned int build_params_bin_len;

#define TVM_BUNDLE_FUNCTION __attribute__((visibility("default")))

extern "C" {

TVM_BUNDLE_FUNCTION void *tvm_runtime_create() {
TVM_BUNDLE_FUNCTION void *tvm_runtime_create(const char * build_graph_json,
const char * build_params_bin,
const uint64_t build_params_bin_len) {
const int build_graph_json_len = strlen(build_graph_json);
const std::string json_data(&build_graph_json[0],
&build_graph_json[0] + build_graph_json_len);
tvm::runtime::Module mod_syslib =
Expand Down
Loading

0 comments on commit 45a4d8a

Please sign in to comment.