blob: bb2f06fa4c77c026b18a1fb5d58ad9a4f1c46fd0 [file] [log] [blame]
Jonathan Strandbergd2afc512021-03-19 10:31:18 +01001#!/usr/bin/env python3
2
3#
4# Copyright (c) 2021 Arm Limited. All rights reserved.
5#
6# SPDX-License-Identifier: Apache-2.0
7#
8# Licensed under the Apache License, Version 2.0 (the License); you may
9# not use this file except in compliance with the License.
10# You may obtain a copy of the License at
11#
12# www.apache.org/licenses/LICENSE-2.0
13#
14# Unless required by applicable law or agreed to in writing, software
15# distributed under the License is distributed on an AS IS BASIS, WITHOUT
16# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
17# See the License for the specific language governing permissions and
18# limitations under the License.
19#
20
21import argparse
22import multiprocessing
23import numpy
24import os
25import pathlib
26import re
27import shutil
28import subprocess
29import sys
30
31os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3'
Kristofer Jonssonffbd8e72021-06-15 17:51:58 +020032from tensorflow.lite.python.interpreter import Interpreter, OpResolverType
Jonathan Strandbergd2afc512021-03-19 10:31:18 +010033
34CORE_PLATFORM_PATH = pathlib.Path(__file__).resolve().parents[1]
35
36def run_cmd(cmd, **kwargs):
37 # str() is called to handle pathlib.Path objects
38 cmd_str = " ".join([str(arg) for arg in cmd])
39 print(f"Running command: {cmd_str}")
40 return subprocess.run(cmd, check=True, **kwargs)
41
Nir Ekhauz3c505ca2021-06-06 14:57:50 +030042def build_core_platform(output_folder, target, toolchain, memory_model, memory_arena, pmu):
Jonathan Strandbergd2afc512021-03-19 10:31:18 +010043 build_folder = output_folder/"model"/"build"
44 cmake_cmd = ["cmake",
45 CORE_PLATFORM_PATH/"targets"/target,
46 f"-B{build_folder}",
47 f"-DCMAKE_TOOLCHAIN_FILE={CORE_PLATFORM_PATH/'cmake'/'toolchain'/(toolchain + '.cmake')}",
Nir Ekhauz3c505ca2021-06-06 14:57:50 +030048 f"-DBAREMETAL_PATH={output_folder}",
49 f"-DMEMORY_MODEL={memory_model}",
50 f"-DMEMORY_ARENA={memory_arena}"]
Nir Ekhauz1af67f72021-09-13 16:56:39 +030051 if pmu:
52 for i in range(len(pmu)):
53 cmake_cmd += [f"-DETHOSU_PMU_EVENT_{i}={pmu[i]}"]
Jonathan Strandbergd2afc512021-03-19 10:31:18 +010054
55 run_cmd(cmake_cmd)
56
Kristofer Jonssonffbd8e72021-06-15 17:51:58 +020057 make_cmd = ["make", "-C", build_folder, f"-j{multiprocessing.cpu_count()}", "baremetal_custom"]
Jonathan Strandbergd2afc512021-03-19 10:31:18 +010058 run_cmd(make_cmd)
59
60def generate_reference_data(output_folder, non_optimized_model_path, input_path, expected_output_path):
Kristofer Jonssonffbd8e72021-06-15 17:51:58 +020061 interpreter = Interpreter(model_path=str(non_optimized_model_path.resolve()), experimental_op_resolver_type=OpResolverType.BUILTIN_REF)
Jonathan Strandbergd2afc512021-03-19 10:31:18 +010062
63 interpreter.allocate_tensors()
64 input_detail = interpreter.get_input_details()[0]
65 output_detail = interpreter.get_output_details()[0]
66
67 input_data = None
68 if input_path is None:
69 # Randomly generate input data
70 dtype = input_detail["dtype"]
71 if dtype is numpy.float32:
72 rand = numpy.random.default_rng()
73 input_data = rand.random(size=input_detail["shape"], dtype=numpy.float32)
74 else:
75 input_data = numpy.random.randint(low=numpy.iinfo(dtype).min, high=numpy.iinfo(dtype).max, size=input_detail["shape"], dtype=dtype)
76 else:
77 # Load user provided input data
78 input_data = numpy.load(input_path)
79
80 output_data = None
81 if expected_output_path is None:
82 # Run the network with input_data to get reference output
83 interpreter.set_tensor(input_detail["index"], input_data)
84 interpreter.invoke()
85 output_data = interpreter.get_tensor(output_detail["index"])
86 else:
87 # Load user provided output data
88 output_data = numpy.load(expected_output_path)
89
90 network_input_path = output_folder/"ref_input.bin"
91 network_output_path = output_folder/"ref_output.bin"
92
93 with network_input_path.open("wb") as fp:
94 fp.write(input_data.tobytes())
95 with network_output_path.open("wb") as fp:
96 fp.write(output_data.tobytes())
97
98 output_folder = pathlib.Path(output_folder)
99 dump_c_header(network_input_path, output_folder/"input.h", "inputData", "input_data_sec", 4)
100 dump_c_header(network_output_path, output_folder/"output.h", "expectedOutputData", "expected_output_data_sec", 4)
101
102def dump_c_header(input_path, output_path, array_name, section, alignment, extra_data=""):
103 byte_array = []
104 with open(input_path, "rb") as fp:
105 byte_string = fp.read()
106 byte_array = [f"0x{format(byte, '02x')}" for byte in byte_string]
107
108 last = byte_array[-1]
109 byte_array = [byte + "," for byte in byte_array[:-1]] + [last]
110
111 byte_array = [" " + byte if idx % 12 == 0 else byte
112 for idx, byte in enumerate(byte_array)]
113
114 byte_array = [byte + "\n" if (idx + 1) % 12 == 0 else byte + " "
115 for idx, byte in enumerate(byte_array)]
116
117 with open(output_path, "w") as carray:
118 header = f"uint8_t {array_name}[] __attribute__((section(\"{section}\"), aligned({alignment}))) = {{\n"
119 carray.write(extra_data)
120 carray.write(header)
121 carray.write("".join(byte_array))
122 carray.write("\n};\n")
123
124def optimize_network(output_folder, network_path, accelerator_conf):
125 vela_cmd = ["vela",
126 network_path,
127 "--output-dir", output_folder,
128 "--accelerator-config", accelerator_conf]
129 res = run_cmd(vela_cmd)
130 optimized_model_path = output_folder/(network_path.stem + "_vela.tflite")
131 model_name = network_path.stem
132 dump_c_header(optimized_model_path, output_folder/"model.h", "networkModelData", "network_model_sec", 16, extra_data=f"const char *modelName=\"{model_name}\";\n")
133
134def run_model(output_folder):
135 build_folder = output_folder/"model"/"build"
136 model_cmd = ["ctest", "-V", "-R", "^baremetal_custom$" ]
137 res = run_cmd(model_cmd, cwd=build_folder)
138
139def main():
140 target_mapping = {
141 "corstone-300": "ethos-u55-128"
142 }
143 parser = argparse.ArgumentParser()
144 parser.add_argument("-o", "--output-folder", type=pathlib.Path, default="output", help="Output folder for build and generated files")
145 parser.add_argument("--network-path", type=pathlib.Path, required=True, help="Path to .tflite file")
146 parser.add_argument("--target", choices=target_mapping, default="corstone-300", help=f"Configure target")
147 parser.add_argument("--toolchain", choices=["armclang", "arm-none-eabi-gcc"], default="armclang", help=f"Configure toolchain")
Nir Ekhauz3c505ca2021-06-06 14:57:50 +0300148 parser.add_argument("--memory_model", choices=["sram", "dram"], default="dram", help=f"Configure memory_model")
149 parser.add_argument("--memory_arena", choices=["sram", "dram"], default="sram", help=f"Configure memory_arena")
150 parser.add_argument("--pmu", type=int, action='append', help="PMU Event Counters")
Jonathan Strandbergd2afc512021-03-19 10:31:18 +0100151 parser.add_argument("--custom-input", type=pathlib.Path, help="Custom input to network")
152 parser.add_argument("--custom-output", type=pathlib.Path, help="Custom expected output data for network")
153
154 args = parser.parse_args()
Jonathan Strandbergd2afc512021-03-19 10:31:18 +0100155 args.output_folder.mkdir(exist_ok=True)
156
157 try:
158 optimize_network(args.output_folder, args.network_path, target_mapping[args.target])
159 generate_reference_data(args.output_folder, args.network_path, args.custom_input, args.custom_output)
Nir Ekhauz3c505ca2021-06-06 14:57:50 +0300160 build_core_platform(args.output_folder, args.target, args.toolchain, args.memory_model, args.memory_arena, args.pmu)
Jonathan Strandbergd2afc512021-03-19 10:31:18 +0100161 run_model(args.output_folder)
162 except subprocess.CalledProcessError as err:
163 print(f"Command: '{err.cmd}' failed", file=sys.stderr)
164 return 1
165 return 0
166
167if __name__ == "__main__":
168 sys.exit(main())