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

[µTVM] Use standalone_crt build tree for all µTVM builds #7333

Merged
merged 15 commits into from
Feb 12, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
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
8 changes: 4 additions & 4 deletions cmake/modules/StandaloneCrt.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -45,12 +45,14 @@ if(USE_MICRO)
"src/runtime/crt/common *.c -> src/runtime/crt/common"
"src/runtime/crt/graph_runtime *.c -> src/runtime/crt/graph_runtime"
"src/runtime/crt/graph_runtime_module *.c -> src/runtime/crt/graph_runtime_module"
"src/runtime/crt/host crt_config.h -> src/runtime/crt/host"
"src/runtime/crt/host crt_config.h -> template/host"
"src/runtime/crt/host *.cc -> template/host"
"src/runtime/crt/memory *.c -> src/runtime/crt/memory"
"src/runtime/crt/utvm_rpc_common *.cc -> src/runtime/crt/utvm_rpc_common"
"src/runtime/crt/utvm_rpc_server *.cc -> src/runtime/crt/utvm_rpc_server"
"src/runtime/minrpc *.h -> src/runtime/minrpc"
"src/support generic_arena.h -> src/support"
"src/runtime/crt crt_config-template.h -> template"
)

set(standalone_crt_base "${CMAKE_CURRENT_BINARY_DIR}/standalone_crt")
Expand Down Expand Up @@ -101,9 +103,7 @@ if(USE_MICRO)
endforeach()

set(make_common_args
"DLPACK_INCLUDE_DIR=${CMAKE_SOURCE_DIR}/3rdparty/dlpack/include"
"TVM_INCLUDE_DIR=${CMAKE_CURRENT_BINARY_DIR}/standalone_crt/include"
"CRT_CONFIG=src/runtime/crt/host/crt_config.h"
"CRT_CONFIG=template/host/crt_config.h"
"BUILD_DIR=${host_build_dir_abspath}"
"EXTRA_CFLAGS=-fPIC"
"EXTRA_CXXFLAGS=-fPIC"
Expand Down
4 changes: 2 additions & 2 deletions python/tvm/micro/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,8 @@
"""MicroTVM module for bare-metal backends"""

from .artifact import Artifact
from .build import build_static_runtime, default_options, TVM_ROOT_DIR
from .build import CRT_ROOT_DIR, Workspace
from .build import build_static_runtime, default_options, get_standalone_crt_dir
from .build import get_standalone_crt_lib, Workspace
from .compiler import Compiler, DefaultCompiler, Flasher
from .debugger import GdbRemoteDebugger
from .micro_library import MicroLibrary
Expand Down
174 changes: 118 additions & 56 deletions python/tvm/micro/build.py
Original file line number Diff line number Diff line change
Expand Up @@ -21,9 +21,11 @@
import logging
import os
import re
import typing
from tvm.contrib import utils

from .micro_library import MicroLibrary
from .._ffi import libinfo


_LOG = logging.getLogger(__name__)
Expand Down Expand Up @@ -55,15 +57,62 @@ def path(self):
CRT_RUNTIME_LIB_NAMES = ["utvm_rpc_server", "utvm_rpc_common", "common"]


TVM_ROOT_DIR = os.path.realpath(os.path.join(os.path.dirname(__file__), "..", "..", ".."))
STANDALONE_CRT_DIR = None
Copy link
Contributor

Choose a reason for hiding this comment

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

Im not too sure whether we need this ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

do you mean that it's fine to just do the search every time?

Copy link
Contributor

Choose a reason for hiding this comment

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

oh you want to cache/memoize the function ... if thats the case, you might want to consider options to do that such using the memoize decorator, functools caches or maybe we can move variable inside of the function as it is not used by anyother ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

we can, though functools.cache is new in 3.9 and lru_cache seems a bit complex. i think this way might almost be easier to debug. is there a different stdlib thing i'm missing?

Copy link
Contributor

Choose a reason for hiding this comment

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

Hmm there is one other option you might want to consider -- you make it attr of the function -- thats python's way of having static variables. Anyway, Im not very fussed about it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

ok--I may opt to leave this as it's passing CI and there's a bunch of other pressing work.



CRT_ROOT_DIR = os.path.join(TVM_ROOT_DIR, "src", "runtime", "crt")
class CrtNotFoundError(Exception):
"""Raised when the standalone CRT dirtree cannot be found."""


RUNTIME_LIB_SRC_DIRS = [os.path.join(CRT_ROOT_DIR, n) for n in CRT_RUNTIME_LIB_NAMES] + [
os.path.join(TVM_ROOT_DIR, "3rdparty/libcrc/src")
]
def get_standalone_crt_dir() -> str:
"""Find the standalone_crt directory.

Though the C runtime source lives in the tvm tree, it is intended to be distributed with any
binary build of TVM. This source tree is intended to be integrated into user projects to run
models targeted with --runtime=c.

Returns
-------
str :
The path to the standalone_crt
"""
global STANDALONE_CRT_DIR
if STANDALONE_CRT_DIR is None:
Copy link
Contributor

Choose a reason for hiding this comment

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

Again on the same question as above, however this kind of makes me think do we need something like an environment variable ? Maybe PassContext is a better option for that ? What do you think ?

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 think that ultimately these are going to become data dependencies of the tlcpack wheel and we don't have a standard facility to figure out where the data deps go. I think we should make such a thing in both Python and CMake and then it would reduce this function to: if not isdir(f'{data_dir}/standalone_crt'): raise CrtNotFoundError().

Copy link
Contributor

Choose a reason for hiding this comment

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

Ack

for path in libinfo.find_lib_path():
crt_path = os.path.join(os.path.dirname(path), "standalone_crt")
if os.path.isdir(crt_path):
STANDALONE_CRT_DIR = crt_path
break

else:
raise CrtNotFoundError()

return STANDALONE_CRT_DIR


def get_standalone_crt_lib(name: str) -> str:
"""Find a source library directory in the standalone_crt.

The standalone C runtime is split into various libraries (one per directory underneath
src/runtime/crt). This convenience function returns the full path to one of those libraries
located in get_standalone_crt_dir().

Parameters
----------
name : str
Name of the library subdirectory underneath src/runtime/crt.

Returns
-------
str :
The full path to the the library.
"""
return os.path.join(get_standalone_crt_dir(), "src", "runtime", "crt", name)


def get_runtime_libs() -> str:
"""Return abspath to all CRT directories which contain source (i.e. not header) files."""
return [get_standalone_crt_lib(n) for n in CRT_RUNTIME_LIB_NAMES]


RUNTIME_SRC_REGEX = re.compile(r"^.*\.cc?$", re.IGNORECASE)
Expand All @@ -72,52 +121,73 @@ def path(self):
_COMMON_CFLAGS = ["-Wall", "-Werror"]


_CRT_DEFAULT_OPTIONS = {
"cflags": ["-std=c11"] + _COMMON_CFLAGS,
"ccflags": ["-std=c++11"] + _COMMON_CFLAGS,
"ldflags": ["-std=c++11"],
"include_dirs": [
f"{TVM_ROOT_DIR}/include",
f"{TVM_ROOT_DIR}/3rdparty/dlpack/include",
f"{TVM_ROOT_DIR}/3rdparty/libcrc/include",
f"{TVM_ROOT_DIR}/3rdparty/dmlc-core/include",
Copy link
Contributor

Choose a reason for hiding this comment

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

[Clarification] do we not need them anymore or do they go inside build/crt/include ?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

they go in build/standalone_crt/include according to cmake/modules/StandaloneCrt.cmake.

Copy link
Contributor

Choose a reason for hiding this comment

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

Alright!

f"{CRT_ROOT_DIR}/include",
],
}
def _build_default_compiler_options(standalone_crt_dir: typing.Optional[str] = None) -> str:
"""Return a dict containing base compile flags for the CRT under gcc common to .

Parameters
----------
standalone_crt_dir : Optional[str]
If given, the path to the standalone_crt
"""
if standalone_crt_dir is None:
standalone_crt_dir = get_standalone_crt_dir()
return {
"cflags": ["-std=c11"] + _COMMON_CFLAGS,
"ccflags": ["-std=c++11"] + _COMMON_CFLAGS,
"ldflags": ["-std=c++11"],
"include_dirs": [os.path.join(standalone_crt_dir, "include")],
}

_CRT_GENERATED_LIB_OPTIONS = copy.copy(_CRT_DEFAULT_OPTIONS)

def default_options(crt_config_include_dir, standalone_crt_dir=None):
"""Return default opts passed to Compile commands.

Parameters
----------
crt_config_include_dir : str
Path to a directory containing crt_config.h for the target. This will be appended
to the include path for cflags and ccflags.
standalone_crt_dir : Optional[str]

Returns
-------
Dict :
A dictionary containing 3 subkeys, each whose value is _build_default_compiler_options()
plus additional customization.
- "bin_opts" - passed as "options" to Compiler.binary() when building MicroBinary.
- "lib_opts" - passed as "options" to Compiler.library() when building bundled CRT
libraries (or otherwise, non-generated libraries).
- "generated_lib_opts" - passed as "options" to Compiler.library() when building the
generated library.
"""
bin_opts = _build_default_compiler_options(standalone_crt_dir)
bin_opts["include_dirs"].append(crt_config_include_dir)

# Disable due to limitation in the TVM C codegen, which generates lots of local variable
# declarations at the top of generated code without caring whether they're used.
# Example:
# void* arg0 = (((TVMValue*)args)[0].v_handle);
# int32_t arg0_code = ((int32_t*)arg_type_ids)[(0)];
_CRT_GENERATED_LIB_OPTIONS["cflags"].append("-Wno-unused-variable")
_CRT_GENERATED_LIB_OPTIONS["ccflags"].append("-Wno-unused-variable")
lib_opts = _build_default_compiler_options(standalone_crt_dir)
lib_opts["cflags"] = ["-Wno-error=incompatible-pointer-types"]
Copy link
Contributor

Choose a reason for hiding this comment

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

[Clarification] why is this needed suddenly ?

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 think it's on line 110 at main--I don't think it's a change here?

Copy link
Contributor

Choose a reason for hiding this comment

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

Oh sorry I missed that!

lib_opts["include_dirs"].append(crt_config_include_dir)

generated_lib_opts = copy.copy(lib_opts)

# Many TVM-intrinsic operators (i.e. expf, in particular)
_CRT_GENERATED_LIB_OPTIONS["cflags"].append("-fno-builtin")
# Disable due to limitation in the TVM C codegen, which generates lots of local variable
# declarations at the top of generated code without caring whether they're used.
# Example:
# void* arg0 = (((TVMValue*)args)[0].v_handle);
# int32_t arg0_code = ((int32_t*)arg_type_ids)[(0)];
generated_lib_opts["cflags"].append("-Wno-unused-variable")
generated_lib_opts["ccflags"].append("-Wno-unused-variable")

# Many TVM-intrinsic operators (i.e. expf, in particular)
generated_lib_opts["cflags"].append("-fno-builtin")

def default_options(target_include_dir):
"""Return default opts passed to Compile commands."""
bin_opts = copy.deepcopy(_CRT_DEFAULT_OPTIONS)
bin_opts["include_dirs"].append(target_include_dir)
lib_opts = copy.deepcopy(bin_opts)
lib_opts["cflags"] = ["-Wno-error=incompatible-pointer-types"]
return {"bin_opts": bin_opts, "lib_opts": lib_opts}
return {"bin_opts": bin_opts, "lib_opts": lib_opts, "generated_lib_opts": generated_lib_opts}


def build_static_runtime(
workspace,
compiler,
module,
lib_opts=None,
bin_opts=None,
generated_lib_opts=None,
compiler_options,
extra_libs=None,
):
"""Build the on-device runtime, statically linking the given modules.
Expand All @@ -130,15 +200,11 @@ def build_static_runtime(
module : IRModule
Module to statically link.

lib_opts : Optional[dict]
The `options` parameter passed to compiler.library().

bin_opts : Optional[dict]
The `options` parameter passed to compiler.binary().

generated_lib_opts : Optional[dict]
The `options` parameter passed to compiler.library() when compiling the generated TVM C
source module.
compiler_options : dict
The return value of tvm.micro.default_options(), with any keys overridden to inject
compiler options specific to this build. If not given, tvm.micro.default_options() is
used. This dict contains the `options` parameter passed to Compiler.library() and
Compiler.binary() at various stages in the compilation process.

extra_libs : Optional[List[MicroLibrary|str]]
If specified, extra libraries to be compiled into the binary. If a MicroLibrary, it is
Expand All @@ -151,18 +217,12 @@ def build_static_runtime(
MicroBinary :
The compiled runtime.
"""
lib_opts = _CRT_DEFAULT_OPTIONS if lib_opts is None else lib_opts
bin_opts = _CRT_DEFAULT_OPTIONS if bin_opts is None else bin_opts
generated_lib_opts = (
_CRT_GENERATED_LIB_OPTIONS if generated_lib_opts is None else generated_lib_opts
)

mod_build_dir = workspace.relpath(os.path.join("build", "module"))
os.makedirs(mod_build_dir)
mod_src_dir = workspace.relpath(os.path.join("src", "module"))

libs = []
for mod_or_src_dir in (extra_libs or []) + RUNTIME_LIB_SRC_DIRS:
for mod_or_src_dir in (extra_libs or []) + get_runtime_libs():
if isinstance(mod_or_src_dir, MicroLibrary):
libs.append(mod_or_src_dir)
continue
Expand All @@ -177,18 +237,20 @@ def build_static_runtime(
if RUNTIME_SRC_REGEX.match(p):
lib_srcs.append(os.path.join(lib_src_dir, p))

libs.append(compiler.library(lib_build_dir, lib_srcs, lib_opts))
libs.append(compiler.library(lib_build_dir, lib_srcs, compiler_options["lib_opts"]))

mod_src_dir = workspace.relpath(os.path.join("src", "module"))
os.makedirs(mod_src_dir)
libs.append(
module.export_library(
mod_build_dir,
workspace_dir=mod_src_dir,
fcompile=lambda bdir, srcs, **kwargs: compiler.library(bdir, srcs, generated_lib_opts),
fcompile=lambda bdir, srcs, **kwargs: compiler.library(
bdir, srcs, compiler_options["generated_lib_opts"]
),
)
)

runtime_build_dir = workspace.relpath(f"build/runtime")
os.makedirs(runtime_build_dir)
return compiler.binary(runtime_build_dir, libs, bin_opts)
return compiler.binary(runtime_build_dir, libs, compiler_options["bin_opts"])
5 changes: 3 additions & 2 deletions python/tvm/micro/compiler.py
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,6 @@
import subprocess

import tvm.target
from . import build
from . import class_factory
from . import debugger
from . import transport
Expand Down Expand Up @@ -291,7 +290,9 @@ def binary(self, output, objects, options=None, link_main=True, main_options=Non
args.extend(["-g", "-o", output_abspath])

if link_main:
host_main_srcs = glob.glob(os.path.join(build.CRT_ROOT_DIR, "host", "*.cc"))
host_main_srcs = glob.glob(
os.path.join(tvm.micro.get_standalone_crt_dir(), "template", "host", "*.cc")
)
if main_options:
main_lib = self.library(os.path.join(output, "host"), host_main_srcs, main_options)
for lib_name in main_lib.library_files:
Expand Down
3 changes: 1 addition & 2 deletions tests/micro/qemu/test_zephyr.py
Original file line number Diff line number Diff line change
Expand Up @@ -92,8 +92,7 @@ def _make_session(model, target, zephyr_board, west_cmd, mod):
workspace,
compiler,
mod,
lib_opts=opts["lib_opts"],
bin_opts=opts["bin_opts"],
opts,
)
if os.path.exists(prev_build):
os.unlink(prev_build)
Expand Down
13 changes: 5 additions & 8 deletions tests/python/unittest/test_crt.py
Original file line number Diff line number Diff line change
Expand Up @@ -50,18 +50,15 @@ def _make_sess_from_op(workspace, op_name, sched, arg_bufs):

def _make_session(workspace, mod):
compiler = tvm.micro.DefaultCompiler(target=TARGET)
opts = tvm.micro.default_options(os.path.join(tvm.micro.CRT_ROOT_DIR, "host"))
opts = tvm.micro.default_options(
os.path.join(tvm.micro.get_standalone_crt_dir(), "template", "host")
)
micro_binary = tvm.micro.build_static_runtime(
# the x86 compiler *expects* you to give the exact same dictionary for both
# lib_opts and bin_opts. so the library compiler is mutating lib_opts and
# the binary compiler is expecting those mutations to be in bin_opts.
# TODO(weberlo) fix this very bizarre behavior
workspace,
compiler,
mod,
lib_opts=opts["bin_opts"],
bin_opts=opts["bin_opts"],
extra_libs=[os.path.join(tvm.micro.build.CRT_ROOT_DIR, "memory")],
opts,
extra_libs=[tvm.micro.get_standalone_crt_lib("memory")],
)

flasher_kw = {
Expand Down
13 changes: 5 additions & 8 deletions tests/python/unittest/test_link_params.py
Original file line number Diff line number Diff line change
Expand Up @@ -354,21 +354,18 @@ def test_crt_link_params():

workspace = tvm.micro.Workspace()
compiler = tvm.micro.DefaultCompiler(target=target)
opts = tvm.micro.default_options(os.path.join(tvm.micro.CRT_ROOT_DIR, "host"))
opts = tvm.micro.default_options(
os.path.join(tvm.micro.get_standalone_crt_dir(), "template", "host")
)
opts["bin_opts"]["ldflags"].append("-DTVM_HOST_USE_GRAPH_RUNTIME_MODULE")

micro_binary = tvm.micro.build_static_runtime(
# the x86 compiler *expects* you to give the exact same dictionary for both
# lib_opts and bin_opts. so the library compiler is mutating lib_opts and
# the binary compiler is expecting those mutations to be in bin_opts.
# TODO(weberlo) fix this very bizarre behavior
workspace,
compiler,
lib,
lib_opts=opts["bin_opts"],
bin_opts=opts["bin_opts"],
compiler_options=opts,
extra_libs=[
os.path.join(tvm.micro.CRT_ROOT_DIR, m)
tvm.micro.get_standalone_crt_lib(m)
for m in ("memory", "graph_runtime_module", "graph_runtime")
],
)
Expand Down
5 changes: 5 additions & 0 deletions tests/scripts/task_ci_setup.sh
Original file line number Diff line number Diff line change
Expand Up @@ -31,3 +31,8 @@ set -o pipefail
echo "Addtiional setup in" ${CI_IMAGE_NAME}

python3 -m pip install --user tlcpack-sphinx-addon==0.1.4 synr==0.2.1

# Rebuild standalone_crt in build/ tree. This file is not currently archived by pack_lib() in
# Jenkinsfile. We expect config.cmake to be present from pack_lib().
# TODO(areusch): Make pack_lib() pack all the data dependencies of TVM.
(cd build && cmake .. && make standalone_crt)
Loading