Files
apache--tvm/python/tvm/micro/model_library_format.py
T
Manupa Karunaratne c0690496af Improved MLF to contain workspace info (#7938)
* Improved MLF to contain workspace info

Added functionality to calculate workspace, io and constant
memory required by each primfunc and main function. Moreover,
the workspace information required by each primfunc and main
is reported in metadata.json in the Model Library Format(MLF).
- added functionality to record tir and relay primfuncs
- added tests for model_library_format changes

Change-Id: Ib4a8b787345aa35f8a1645e8a648fad84de37bce

* Improved MLF to contain workspace info

* disable AoT for now
* addressing comments

Change-Id: I5f041ec461b02dac6ea9c96ea50eb400d55eef53

* Improved MLF to contain workspace info

* addressed comments
* added aot executor support

Change-Id: I9b54a7939d8ccb3c6ce0454f0fe62866ac66eb5c

* Improved MLF to contain workspace info

* removed redundant utils.py

Change-Id: I256dd88fab31a595bf9509bd1c4ab59b0c145b1e

* Improved MLF to contain workspace info

* removed redundant ffi api

Change-Id: I9ad6795aa839edfdfd05b902d4531fb0a20e894d
2021-05-07 14:54:40 -07:00

263 lines
9.4 KiB
Python

# 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.
"""Defines functions for exporting to Model Library Format."""
import datetime
import json
import os
import re
import tarfile
from ..contrib import utils
from ..relay.backend import executor_factory
from ..relay import param_dict
# This should be kept identical to runtime::symbol::tvm_module_main
MAIN_FUNC_NAME_STR = "__tvm_main__"
class UnsupportedInModelLibraryFormatError(Exception):
"""Raised when export_model_library_format does not support the given Module tree."""
def _populate_codegen_dir(mod, codegen_dir: str):
"""Populate the codegen sub-directory as part of a Model Library Format export.
Parameters
----------
mod : tvm.runtime.Module
Module which should be written to codegen_dir.
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(
f"Don't know how to export non-c or non-llvm modules; found: {non_dso_modules!r}"
)
mod_indices = {"lib": 0, "src": 0}
host_codegen_dir = os.path.join(codegen_dir, "host")
for dso_mod in dso_modules:
if dso_mod.type_key == "c":
index = mod_indices["src"]
mod_indices["src"] += 1
parent_dir = os.path.join(host_codegen_dir, "src")
file_name = os.path.join(parent_dir, f"lib{index}.c")
elif dso_mod.type_key == "llvm":
index = mod_indices["lib"]
mod_indices["lib"] += 1
parent_dir = os.path.join(host_codegen_dir, "lib")
file_name = os.path.join(parent_dir, f"lib{index}.o")
else:
assert (
False
), f"do not expect module with type_key={mod.type_key} from _collect_dso_modules"
if not os.path.exists(parent_dir):
os.makedirs(parent_dir)
dso_mod.save(file_name)
def _build_memory_map(mod):
ret = dict()
if isinstance(mod, executor_factory.GraphExecutorFactoryModule):
ret["sids"] = _build_sid_map(mod.graph_json)
ret["functions"] = _build_function_memory_map(mod.function_metadata)
return ret
def _build_sid_map(graph_json):
"""Build a simpler storage id info map from graph JSON.
Parameters
----------
graph_json : str
String representation of the graph_json created from tvm.relay.build().
Returns
-------
list :
A list with one entry per storage id describing that memory.
"""
graph = json.loads(graph_json)
seen_storage_ids = set()
memory_map = []
for node_id, storage_id in enumerate(graph["attrs"]["storage_id"][1]):
if storage_id in seen_storage_ids:
continue
seen_storage_ids.add(storage_id)
num_elements = 1
for dim in graph["attrs"]["shape"][1][storage_id]:
num_elements *= dim
dltype = graph["attrs"]["dltype"][1][storage_id]
m = re.match(r"^[a-zA-Z]+([0-9]+)$", dltype)
assert m, f"Exported graph contains unknown dltype {dltype}"
elem_bits = int(m.group(1))
map_entry = {
"storage_id": storage_id,
"size_bytes": (num_elements * elem_bits + 7) // 8,
}
if node_id in graph["arg_nodes"]:
map_entry["input_binding"] = graph["nodes"][node_id]["name"]
memory_map.append(map_entry)
return memory_map
def _build_function_memory_map(function_metadata):
"""Build a simple map that shows how much workspace is required to execute
each primitive function. The main_func describes how much memory is required
to execute the main control code.
Parameters
----------
function_metadata : Map<String, FunctionInfo>
This contains all the compiled metadata on a function basis
Returns
-------
dict :
This will have two entries:
1.) A list with one entry per function describing local memory it is using.
2.) A global memory requirement if all functions are executed sequentially
"""
device_max_workspace = dict()
main_func_metadata = function_metadata[MAIN_FUNC_NAME_STR]
num_targets = len(main_func_metadata.workspace_sizes.items())
func_entries = []
target_local_entries = dict()
for i in range(num_targets):
target = main_func_metadata.workspace_sizes.items()[i][0]
device_max_workspace[target] = 0
for func_name, finfo in function_metadata.items():
if func_name == MAIN_FUNC_NAME_STR:
continue
target_local_entries[func_name] = list()
for func_name, finfo in function_metadata.items():
if func_name == MAIN_FUNC_NAME_STR:
continue
assert len(finfo.constant_sizes.items()) == num_targets
assert len(finfo.io_sizes.items()) == num_targets
target = finfo.workspace_sizes.items()[i][0]
workspace_size = finfo.workspace_sizes.items()[i][1]
target_entry = {
"device": int(target.kind.device_type),
"workspace_size_bytes": int(workspace_size),
}
target_local_entries[func_name].append(target_entry)
if workspace_size > device_max_workspace[target]:
device_max_workspace[target] = workspace_size
for func_name, target_entries_ in target_local_entries.items():
func_entry = {
"function_name": str(func_name),
"workspace": target_entries_,
}
func_entries.append(func_entry)
target_main_entries = list()
for i in range(num_targets):
target = main_func_metadata.workspace_sizes.items()[i][0]
main_func_local_workspace = main_func_metadata.workspace_sizes.items()[i][1]
main_func_constants = main_func_metadata.constant_sizes.items()[i][1]
main_func_io = main_func_metadata.io_sizes.items()[i][1]
target_main_entries.append(
{
"device": int(target.kind.device_type),
"workspace_size_bytes": int(device_max_workspace[target])
+ int(main_func_local_workspace),
"constants_size_bytes": int(main_func_constants),
"io_size_bytes": int(main_func_io),
}
)
ret = {
"operator_functions": func_entries,
"main": target_main_entries,
}
return ret
def export_model_library_format(mod: executor_factory.ExecutorFactoryModule, file_name):
"""Export the build artifact in Model Library Format.
This function creates a .tar archive containing the build artifacts in a standardized
layout. It's intended to allow downstream automation to build TVM artifacts against the C
runtime.
Parameters
----------
mod : tvm.relay.backend.executor_factory.ExecutorFactoryModule
The return value of tvm.relay.build, which will be exported into Model Library Format.
file_name : str
Path to the .tar archive to generate.
"""
tempdir = utils.tempdir()
is_aot = isinstance(mod, executor_factory.AOTExecutorFactoryModule)
runtime = ["aot"] if is_aot else ["graph"]
metadata = {
"version": 2,
"model_name": mod.libmod_name,
"export_datetime": datetime.datetime.now().strftime("%Y-%m-%d %H:%M:%SZ"),
"memory": _build_memory_map(mod),
"target": {int(k): str(v) for k, v in mod.target.items()},
"runtimes": runtime,
}
with open(tempdir.relpath("metadata.json"), "w") as json_f:
json.dump(metadata, json_f, indent=2, sort_keys=True)
codegen_dir_path = tempdir.relpath("codegen")
os.mkdir(codegen_dir_path)
_populate_codegen_dir(mod.lib, codegen_dir_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")
with open(param_filename, "wb") as f:
f.write(param_dict.save_param_dict(mod.params))
with open(tempdir.relpath("relay.txt"), "w") as f:
f.write(str(mod.ir_mod))
if not is_aot:
graph_config_dir_path = tempdir.relpath(os.path.join("runtime-config", "graph"))
os.makedirs(graph_config_dir_path)
with open(os.path.join(graph_config_dir_path, "graph.json"), "w") as f:
f.write(mod.get_executor_config())
with tarfile.open(file_name, "w") as tar_f:
def reset(tarinfo):
tarinfo.uid = tarinfo.gid = 0
tarinfo.uname = tarinfo.gname = "root"
return tarinfo
tar_f.add(tempdir.temp_dir, arcname=".", filter=reset)