Jonathan Strandberg | d2afc51 | 2021-03-19 10:31:18 +0100 | [diff] [blame] | 1 | #!/usr/bin/env python3 |
| 2 | |
| 3 | # |
Davide Grohmann | 5508acb | 2022-08-08 17:12:08 +0200 | [diff] [blame] | 4 | # Copyright (c) 2021-2022 Arm Limited. |
Jonathan Strandberg | d2afc51 | 2021-03-19 10:31:18 +0100 | [diff] [blame] | 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 | |
| 21 | import argparse |
| 22 | import multiprocessing |
| 23 | import numpy |
| 24 | import os |
| 25 | import pathlib |
| 26 | import re |
| 27 | import shutil |
| 28 | import subprocess |
| 29 | import sys |
| 30 | |
| 31 | os.environ['TF_CPP_MIN_LOG_LEVEL'] = '3' |
Kristofer Jonsson | ffbd8e7 | 2021-06-15 17:51:58 +0200 | [diff] [blame] | 32 | from tensorflow.lite.python.interpreter import Interpreter, OpResolverType |
Jonathan Strandberg | d2afc51 | 2021-03-19 10:31:18 +0100 | [diff] [blame] | 33 | |
| 34 | CORE_PLATFORM_PATH = pathlib.Path(__file__).resolve().parents[1] |
| 35 | |
| 36 | def 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 Ekhauz | a58edd8 | 2021-10-04 12:21:17 +0300 | [diff] [blame] | 42 | def ta_parse_raw(ta_raw): |
| 43 | ta_parsed = [-1, -1] |
| 44 | if ta_raw: |
| 45 | for v in ta_raw: |
| 46 | index = v[0] |
| 47 | value = v[1] |
| 48 | if (index > 1): |
| 49 | raise Exception("Illegal index value - Should be '0' or '1'") |
| 50 | (ta_parsed)[index] = value |
Davide Grohmann | 5508acb | 2022-08-08 17:12:08 +0200 | [diff] [blame] | 51 | |
Nir Ekhauz | a58edd8 | 2021-10-04 12:21:17 +0300 | [diff] [blame] | 52 | return ta_parsed |
| 53 | |
| 54 | def build_core_platform(output_folder, target, toolchain, memory_model, memory_arena, pmu, |
| 55 | ta_maxr, ta_maxw, ta_maxrw, ta_rlatency, ta_wlatency, |
| 56 | ta_pulse_on, ta_pulse_off, ta_bwcap, ta_perfctrl, ta_perfcnt, |
| 57 | ta_mode, ta_histbin, ta_histcnt): |
Jonathan Strandberg | d2afc51 | 2021-03-19 10:31:18 +0100 | [diff] [blame] | 58 | build_folder = output_folder/"model"/"build" |
Nir Ekhauz | a58edd8 | 2021-10-04 12:21:17 +0300 | [diff] [blame] | 59 | maxr = ta_parse_raw(ta_maxr) |
| 60 | maxw = ta_parse_raw(ta_maxw) |
| 61 | maxrw = ta_parse_raw(ta_maxrw) |
| 62 | rlatency = ta_parse_raw(ta_rlatency) |
| 63 | wlatency = ta_parse_raw(ta_wlatency) |
| 64 | pulse_on = ta_parse_raw(ta_pulse_on) |
| 65 | pulse_off = ta_parse_raw(ta_pulse_off) |
| 66 | bwcap = ta_parse_raw(ta_bwcap) |
| 67 | perfctrl = ta_parse_raw(ta_perfctrl) |
| 68 | perfcnt = ta_parse_raw(ta_perfcnt) |
| 69 | mode = ta_parse_raw(ta_mode) |
| 70 | histbin = ta_parse_raw(ta_histbin) |
| 71 | histcnt = ta_parse_raw(ta_histcnt) |
Jonathan Strandberg | d2afc51 | 2021-03-19 10:31:18 +0100 | [diff] [blame] | 72 | cmake_cmd = ["cmake", |
| 73 | CORE_PLATFORM_PATH/"targets"/target, |
| 74 | f"-B{build_folder}", |
| 75 | f"-DCMAKE_TOOLCHAIN_FILE={CORE_PLATFORM_PATH/'cmake'/'toolchain'/(toolchain + '.cmake')}", |
Nir Ekhauz | 3c505ca | 2021-06-06 14:57:50 +0300 | [diff] [blame] | 76 | f"-DBAREMETAL_PATH={output_folder}", |
| 77 | f"-DMEMORY_MODEL={memory_model}", |
Nir Ekhauz | a58edd8 | 2021-10-04 12:21:17 +0300 | [diff] [blame] | 78 | f"-DMEMORY_ARENA={memory_arena}", |
| 79 | f"-DETHOSU_TA_MAXR_0={maxr[0]}", |
| 80 | f"-DETHOSU_TA_MAXR_1={maxr[1]}", |
| 81 | f"-DETHOSU_TA_MAXW_0={maxw[0]}", |
| 82 | f"-DETHOSU_TA_MAXW_1={maxw[1]}", |
| 83 | f"-DETHOSU_TA_MAXRW_0={maxrw[0]}", |
| 84 | f"-DETHOSU_TA_MAXRW_1={maxrw[1]}", |
| 85 | f"-DETHOSU_TA_RLATENCY_0={rlatency[0]}", |
| 86 | f"-DETHOSU_TA_RLATENCY_1={rlatency[1]}", |
| 87 | f"-DETHOSU_TA_WLATENCY_0={wlatency[0]}", |
| 88 | f"-DETHOSU_TA_WLATENCY_1={wlatency[1]}", |
| 89 | f"-DETHOSU_TA_PULSE_ON_0={pulse_on[0]}", |
| 90 | f"-DETHOSU_TA_PULSE_ON_1={pulse_on[1]}", |
| 91 | f"-DETHOSU_TA_PULSE_OFF_0={pulse_off[0]}", |
| 92 | f"-DETHOSU_TA_PULSE_OFF_1={pulse_off[1]}", |
| 93 | f"-DETHOSU_TA_BWCAP_0={bwcap[0]}", |
| 94 | f"-DETHOSU_TA_BWCAP_1={bwcap[1]}", |
| 95 | f"-DETHOSU_TA_PERFCTRL_0={perfctrl[0]}", |
| 96 | f"-DETHOSU_TA_PERFCTRL_1={perfctrl[1]}", |
| 97 | f"-DETHOSU_TA_PERFCNT_0={perfcnt[0]}", |
| 98 | f"-DETHOSU_TA_PERFCNT_1={perfcnt[1]}", |
| 99 | f"-DETHOSU_TA_MODE_0={mode[0]}", |
| 100 | f"-DETHOSU_TA_MODE_1={mode[1]}", |
| 101 | f"-DETHOSU_TA_HISTBIN_0={histbin[0]}", |
| 102 | f"-DETHOSU_TA_HISTBIN_1={histbin[1]}", |
| 103 | f"-DETHOSU_TA_HISTCNT_0={histcnt[0]}", |
| 104 | f"-DETHOSU_TA_HISTCNT_1={histcnt[1]}"] |
| 105 | |
Nir Ekhauz | 1af67f7 | 2021-09-13 16:56:39 +0300 | [diff] [blame] | 106 | if pmu: |
| 107 | for i in range(len(pmu)): |
| 108 | cmake_cmd += [f"-DETHOSU_PMU_EVENT_{i}={pmu[i]}"] |
Jonathan Strandberg | d2afc51 | 2021-03-19 10:31:18 +0100 | [diff] [blame] | 109 | run_cmd(cmake_cmd) |
| 110 | |
Kristofer Jonsson | ffbd8e7 | 2021-06-15 17:51:58 +0200 | [diff] [blame] | 111 | make_cmd = ["make", "-C", build_folder, f"-j{multiprocessing.cpu_count()}", "baremetal_custom"] |
Jonathan Strandberg | d2afc51 | 2021-03-19 10:31:18 +0100 | [diff] [blame] | 112 | run_cmd(make_cmd) |
| 113 | |
| 114 | def generate_reference_data(output_folder, non_optimized_model_path, input_path, expected_output_path): |
Kristofer Jonsson | ffbd8e7 | 2021-06-15 17:51:58 +0200 | [diff] [blame] | 115 | interpreter = Interpreter(model_path=str(non_optimized_model_path.resolve()), experimental_op_resolver_type=OpResolverType.BUILTIN_REF) |
Jonathan Strandberg | d2afc51 | 2021-03-19 10:31:18 +0100 | [diff] [blame] | 116 | |
| 117 | interpreter.allocate_tensors() |
| 118 | input_detail = interpreter.get_input_details()[0] |
| 119 | output_detail = interpreter.get_output_details()[0] |
| 120 | |
| 121 | input_data = None |
| 122 | if input_path is None: |
| 123 | # Randomly generate input data |
| 124 | dtype = input_detail["dtype"] |
| 125 | if dtype is numpy.float32: |
| 126 | rand = numpy.random.default_rng() |
| 127 | input_data = rand.random(size=input_detail["shape"], dtype=numpy.float32) |
| 128 | else: |
| 129 | input_data = numpy.random.randint(low=numpy.iinfo(dtype).min, high=numpy.iinfo(dtype).max, size=input_detail["shape"], dtype=dtype) |
| 130 | else: |
| 131 | # Load user provided input data |
| 132 | input_data = numpy.load(input_path) |
| 133 | |
| 134 | output_data = None |
| 135 | if expected_output_path is None: |
| 136 | # Run the network with input_data to get reference output |
| 137 | interpreter.set_tensor(input_detail["index"], input_data) |
| 138 | interpreter.invoke() |
| 139 | output_data = interpreter.get_tensor(output_detail["index"]) |
| 140 | else: |
| 141 | # Load user provided output data |
| 142 | output_data = numpy.load(expected_output_path) |
| 143 | |
| 144 | network_input_path = output_folder/"ref_input.bin" |
| 145 | network_output_path = output_folder/"ref_output.bin" |
| 146 | |
| 147 | with network_input_path.open("wb") as fp: |
| 148 | fp.write(input_data.tobytes()) |
| 149 | with network_output_path.open("wb") as fp: |
| 150 | fp.write(output_data.tobytes()) |
| 151 | |
| 152 | output_folder = pathlib.Path(output_folder) |
| 153 | dump_c_header(network_input_path, output_folder/"input.h", "inputData", "input_data_sec", 4) |
| 154 | dump_c_header(network_output_path, output_folder/"output.h", "expectedOutputData", "expected_output_data_sec", 4) |
| 155 | |
| 156 | def dump_c_header(input_path, output_path, array_name, section, alignment, extra_data=""): |
| 157 | byte_array = [] |
| 158 | with open(input_path, "rb") as fp: |
| 159 | byte_string = fp.read() |
| 160 | byte_array = [f"0x{format(byte, '02x')}" for byte in byte_string] |
| 161 | |
| 162 | last = byte_array[-1] |
| 163 | byte_array = [byte + "," for byte in byte_array[:-1]] + [last] |
| 164 | |
| 165 | byte_array = [" " + byte if idx % 12 == 0 else byte |
| 166 | for idx, byte in enumerate(byte_array)] |
| 167 | |
| 168 | byte_array = [byte + "\n" if (idx + 1) % 12 == 0 else byte + " " |
| 169 | for idx, byte in enumerate(byte_array)] |
| 170 | |
| 171 | with open(output_path, "w") as carray: |
| 172 | header = f"uint8_t {array_name}[] __attribute__((section(\"{section}\"), aligned({alignment}))) = {{\n" |
| 173 | carray.write(extra_data) |
| 174 | carray.write(header) |
| 175 | carray.write("".join(byte_array)) |
| 176 | carray.write("\n};\n") |
| 177 | |
| 178 | def optimize_network(output_folder, network_path, accelerator_conf): |
| 179 | vela_cmd = ["vela", |
| 180 | network_path, |
| 181 | "--output-dir", output_folder, |
| 182 | "--accelerator-config", accelerator_conf] |
| 183 | res = run_cmd(vela_cmd) |
| 184 | optimized_model_path = output_folder/(network_path.stem + "_vela.tflite") |
| 185 | model_name = network_path.stem |
Davide Grohmann | 5508acb | 2022-08-08 17:12:08 +0200 | [diff] [blame] | 186 | dump_c_header(optimized_model_path, output_folder/"model.h", "networkModelData", "network_model_sec", 16, extra_data=f""" |
| 187 | #include <stddef.h> |
| 188 | |
| 189 | const size_t tensorArenaSize = 2000000; |
| 190 | const char* modelName = \"{model_name}\"; |
| 191 | """) |
Jonathan Strandberg | d2afc51 | 2021-03-19 10:31:18 +0100 | [diff] [blame] | 192 | |
| 193 | def run_model(output_folder): |
| 194 | build_folder = output_folder/"model"/"build" |
| 195 | model_cmd = ["ctest", "-V", "-R", "^baremetal_custom$" ] |
| 196 | res = run_cmd(model_cmd, cwd=build_folder) |
| 197 | |
| 198 | def main(): |
| 199 | target_mapping = { |
| 200 | "corstone-300": "ethos-u55-128" |
| 201 | } |
| 202 | parser = argparse.ArgumentParser() |
| 203 | parser.add_argument("-o", "--output-folder", type=pathlib.Path, default="output", help="Output folder for build and generated files") |
| 204 | parser.add_argument("--network-path", type=pathlib.Path, required=True, help="Path to .tflite file") |
| 205 | parser.add_argument("--target", choices=target_mapping, default="corstone-300", help=f"Configure target") |
| 206 | parser.add_argument("--toolchain", choices=["armclang", "arm-none-eabi-gcc"], default="armclang", help=f"Configure toolchain") |
Nir Ekhauz | 3c505ca | 2021-06-06 14:57:50 +0300 | [diff] [blame] | 207 | parser.add_argument("--memory_model", choices=["sram", "dram"], default="dram", help=f"Configure memory_model") |
| 208 | parser.add_argument("--memory_arena", choices=["sram", "dram"], default="sram", help=f"Configure memory_arena") |
| 209 | parser.add_argument("--pmu", type=int, action='append', help="PMU Event Counters") |
Jonathan Strandberg | d2afc51 | 2021-03-19 10:31:18 +0100 | [diff] [blame] | 210 | parser.add_argument("--custom-input", type=pathlib.Path, help="Custom input to network") |
| 211 | parser.add_argument("--custom-output", type=pathlib.Path, help="Custom expected output data for network") |
Nir Ekhauz | a58edd8 | 2021-10-04 12:21:17 +0300 | [diff] [blame] | 212 | parser.add_argument("--ta-maxr", type=int, nargs=2, action='append', help="Max no. of pending reads") |
| 213 | parser.add_argument("--ta-maxw", type=int, nargs=2, action='append', help="Max no. of pending writes") |
| 214 | parser.add_argument("--ta-maxrw", type=int, nargs=2, action='append', help="Max no. of pending reads+writes") |
| 215 | parser.add_argument("--ta-rlatency", type=int, nargs=2, action='append', help="Minimum latency (clock cycles) from AVALID to RVALID") |
| 216 | parser.add_argument("--ta-wlatency", type=int, nargs=2, action='append', help="Minimum latency (clock cycles) from WVALID&WLAST to BVALID") |
| 217 | parser.add_argument("--ta-pulse_on", type=int, nargs=2, action='append', help="No. of cycles addresses let through (0-65535)") |
| 218 | parser.add_argument("--ta-pulse_off", type=int, nargs=2, action='append', help="No. of cycles addresses blocked (0-65535)") |
| 219 | parser.add_argument("--ta-bwcap", type=int, nargs=2, action='append', help="Max no. of 64-bit words transfered per pulse cycle 0=infinite") |
| 220 | parser.add_argument("--ta-perfctrl", type=int, nargs=2, action='append', help="selecting an event for event counter 0=default") |
| 221 | parser.add_argument("--ta-perfcnt", type=int, nargs=2, action='append', help="event counter") |
| 222 | parser.add_argument("--ta-mode", type=int, nargs=2, action='append', help="Max no. of pending reads") |
| 223 | parser.add_argument("--ta-histbin", type=int, nargs=2, action='append', help="Controlls which histogram bin (0-15) that should be accessed by HISTCNT") |
| 224 | parser.add_argument("--ta-histcnt", type=int, nargs=2, action='append', help="Read/write the selected histogram bin") |
Jonathan Strandberg | d2afc51 | 2021-03-19 10:31:18 +0100 | [diff] [blame] | 225 | |
| 226 | args = parser.parse_args() |
Jonathan Strandberg | d2afc51 | 2021-03-19 10:31:18 +0100 | [diff] [blame] | 227 | args.output_folder.mkdir(exist_ok=True) |
| 228 | |
| 229 | try: |
| 230 | optimize_network(args.output_folder, args.network_path, target_mapping[args.target]) |
| 231 | generate_reference_data(args.output_folder, args.network_path, args.custom_input, args.custom_output) |
Nir Ekhauz | a58edd8 | 2021-10-04 12:21:17 +0300 | [diff] [blame] | 232 | build_core_platform(args.output_folder, args.target, args.toolchain, args.memory_model, args.memory_arena, args.pmu, |
| 233 | args.ta_maxr, args.ta_maxw, args.ta_maxrw, args.ta_rlatency, args.ta_wlatency, |
| 234 | args.ta_pulse_on, args.ta_pulse_off, args.ta_bwcap, args.ta_perfctrl, args.ta_perfcnt, |
| 235 | args.ta_mode, args.ta_histbin, args.ta_histcnt) |
Jonathan Strandberg | d2afc51 | 2021-03-19 10:31:18 +0100 | [diff] [blame] | 236 | run_model(args.output_folder) |
| 237 | except subprocess.CalledProcessError as err: |
| 238 | print(f"Command: '{err.cmd}' failed", file=sys.stderr) |
| 239 | return 1 |
| 240 | return 0 |
| 241 | |
| 242 | if __name__ == "__main__": |
| 243 | sys.exit(main()) |