From ef7dabb2b4f67f0c93c793f1d16248a511fafe2d Mon Sep 17 00:00:00 2001 From: Mehrdad Hessar Date: Fri, 6 Jan 2023 16:48:23 -0800 Subject: [PATCH] [microTVM][Zephyr]Add project files for mlperftiny submission (#13690) This PR makes these changes: 1. add source/header files for generating a zephyr project which is compatible with EEMBC runner for MLPerfTiny 2. adjust microtvm_api_server.py and CMakeLists.template to support `mlperftiny` project type 3. adds EEMBC api files from https://github.com/mlcommons/tiny in `thirdparty/tiny`. This pull request was co-authored by @alanmacd, @mkatanbaf, @guberti and @areusch as part of our effort to submit to MLPerfTiny. You can find our submission results here: https://mlcommons.org/en/inference-tiny-10/ --- 3rdparty/mlperftiny/README.md | 2 + .../mlperftiny/api/internally_implemented.cpp | 325 ++++++++++++++++++ .../mlperftiny/api/internally_implemented.h | 62 ++++ .../mlperftiny/api/submitter_implemented.h | 85 +++++ .../template_project/CMakeLists.txt.template | 13 +- .../template_project/microtvm_api_server.py | 9 +- .../template_project/src/mlperftiny/README.md | 20 ++ .../template_project/src/mlperftiny/main.cc | 38 ++ .../src/mlperftiny/submitter_implemented.cc | 218 ++++++++++++ .../src/mlperftiny/tvmruntime.cc | 164 +++++++++ .../src/mlperftiny/tvmruntime.h | 62 ++++ .../src/mlperftiny/zephyr_uart.cc | 89 +++++ .../src/mlperftiny/zephyr_uart.h | 51 +++ cmake/modules/Zephyr.cmake | 3 + 14 files changed, 1139 insertions(+), 2 deletions(-) create mode 100644 3rdparty/mlperftiny/README.md create mode 100644 3rdparty/mlperftiny/api/internally_implemented.cpp create mode 100644 3rdparty/mlperftiny/api/internally_implemented.h create mode 100644 3rdparty/mlperftiny/api/submitter_implemented.h create mode 100644 apps/microtvm/zephyr/template_project/src/mlperftiny/README.md create mode 100644 apps/microtvm/zephyr/template_project/src/mlperftiny/main.cc create mode 100644 apps/microtvm/zephyr/template_project/src/mlperftiny/submitter_implemented.cc create mode 100644 apps/microtvm/zephyr/template_project/src/mlperftiny/tvmruntime.cc create mode 100644 apps/microtvm/zephyr/template_project/src/mlperftiny/tvmruntime.h create mode 100644 apps/microtvm/zephyr/template_project/src/mlperftiny/zephyr_uart.cc create mode 100644 apps/microtvm/zephyr/template_project/src/mlperftiny/zephyr_uart.h diff --git a/3rdparty/mlperftiny/README.md b/3rdparty/mlperftiny/README.md new file mode 100644 index 000000000000..28938e90d744 --- /dev/null +++ b/3rdparty/mlperftiny/README.md @@ -0,0 +1,2 @@ +# MLPerf™ Tiny Benchmark API +This directory includes API files to build a microTVM project that could be tested with EEMBC benchmark runner. API files are captured from the [MLCommons/tiny repository](https://github.com/mlcommons/tiny). diff --git a/3rdparty/mlperftiny/api/internally_implemented.cpp b/3rdparty/mlperftiny/api/internally_implemented.cpp new file mode 100644 index 000000000000..4754d4e267e6 --- /dev/null +++ b/3rdparty/mlperftiny/api/internally_implemented.cpp @@ -0,0 +1,325 @@ +/* +Copyright 2020 EEMBC and The MLPerf Authors. All Rights Reserved. +Licensed 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. +This file is a modified version of the original EEMBC implementation of ee_lib. +The file name has been changed and some functions removed. Malloc has been +replaced by a fixed-size array. +==============================================================================*/ +/// \file +/// \brief Internally-implemented methods required to perform inference. + +#include "internally_implemented.h" + +#include +#include +#include + +#include "submitter_implemented.h" + +// Command buffer (incoming commands from host) +char volatile g_cmd_buf[EE_CMD_SIZE + 1]; +size_t volatile g_cmd_pos = 0u; + +// Generic buffer to db input. +uint8_t gp_buff[MAX_DB_INPUT_SIZE]; +size_t g_buff_size = 0u; +size_t g_buff_pos = 0u; + +/** + * Since the serial port ISR may be connected before the loop is ready, this + * flag turns off the parser until the main routine is ready. + */ +bool g_state_parser_enabled = false; + +/** + * This function assembles a command string from the UART. It should be called + * from the UART ISR for each new character received. When the parser sees the + * termination character, the user-defined th_command_ready() command is called. + * It is up to the application to then dispatch this command outside the ISR + * as soon as possible by calling ee_serial_command_parser_callback(), below. + */ +void ee_serial_callback(char c) { + if (c == EE_CMD_TERMINATOR) { + g_cmd_buf[g_cmd_pos] = (char)0; + th_command_ready(g_cmd_buf); + g_cmd_pos = 0; + } else { + g_cmd_buf[g_cmd_pos] = c; + g_cmd_pos = g_cmd_pos >= EE_CMD_SIZE ? EE_CMD_SIZE : g_cmd_pos + 1; + } +} + +/** + * This is the minimal parser required to test the monitor; profile-specific + * commands are handled by whatever profile is compiled into the firmware. + * + * The most basic commands are: + * + * name Print m-name-NAME, where NAME defines the intent of the f/w + * timestamp Generate a signal used for timestamping by the framework + */ +/*@-mustfreefresh*/ +/*@-nullpass*/ +void ee_serial_command_parser_callback(char *p_command) { + char *tok; + + if (g_state_parser_enabled != true) { + return; + } + + tok = strtok(p_command, EE_CMD_DELIMITER); + + if (strncmp(tok, EE_CMD_NAME, EE_CMD_SIZE) == 0) { + th_printf(EE_MSG_NAME, EE_DEVICE_NAME, TH_VENDOR_NAME_STRING); + } else if (strncmp(tok, EE_CMD_TIMESTAMP, EE_CMD_SIZE) == 0) { + th_timestamp(); + } else if (ee_profile_parse(tok) == EE_ARG_CLAIMED) { + } else { + th_printf(EE_ERR_CMD, tok); + } + + th_printf(EE_MSG_READY); +} + +/** + * Perform the basic setup. + */ +void ee_benchmark_initialize(void) { + th_serialport_initialize(); + th_timestamp_initialize(); + th_final_initialize(); + th_printf(EE_MSG_INIT_DONE); + // Enable the command parser here (the callback is connected) + g_state_parser_enabled = true; + // At this point, the serial monitor should be up and running, + th_printf(EE_MSG_READY); +} + +arg_claimed_t ee_profile_parse(char *command) { + char *p_next; /* strtok already primed from ee_main.c */ + + if (strncmp(command, "profile", EE_CMD_SIZE) == 0) { + th_printf("m-profile-[%s]\r\n", EE_FW_VERSION); + th_printf("m-model-[%s]\r\n", TH_MODEL_VERSION); + } else if (strncmp(command, "help", EE_CMD_SIZE) == 0) { + th_printf("%s\r\n", EE_FW_VERSION); + th_printf("\r\n"); + /* These are the three common functions for all IoTConnect f/w. */ + th_printf("help : Print this information\r\n"); + th_printf("name : Print the name of the device\r\n"); + th_printf("timestsamp : Generate a timetsamp\r\n"); + /* These are profile-specific commands. */ + th_printf("db SUBCMD : Manipulate a generic byte buffer\r\n"); + th_printf(" load N : Allocate N bytes and set load counter\r\n"); + th_printf(" db HH[HH]* : Load 8-bit hex byte(s) until N bytes\r\n"); + th_printf(" print [N=16] [offset=0]\r\n"); + th_printf(" : Print N bytes at offset as hex\r\n"); + th_printf( + "infer N [W=0]: Load input, execute N inferences after W warmup " + "loops\r\n"); + th_printf("results : Return the result fp32 vector\r\n"); + } else if (ee_buffer_parse(command) == EE_ARG_CLAIMED) { + } else if (strncmp(command, "infer", EE_CMD_SIZE) == 0) { + size_t n = 1; + size_t w = 10; + int i; + + /* Check for inference iterations */ + p_next = strtok(NULL, EE_CMD_DELIMITER); + if (p_next) { + i = atoi(p_next); + if (i <= 0) { + th_printf("e-[Inference iterations must be >0]\r\n"); + return EE_ARG_CLAIMED; + } + n = (size_t)i; + /* Check for warmup iterations */ + p_next = strtok(NULL, EE_CMD_DELIMITER); + if (p_next) { + i = atoi(p_next); + if (i < 0) { + th_printf("e-[Inference warmup must be >=0]\r\n"); + return EE_ARG_CLAIMED; + } + w = (size_t)i; + } + } + + ee_infer(n, w); + } else if (strncmp(command, "results", EE_CMD_SIZE) == 0) { + th_results(); + } else { + return EE_ARG_UNCLAIMED; + } + return EE_ARG_CLAIMED; +} + +/** + * Inference without feature engineering. The inpput tensor is expected to + * have been loaded from the buffer via the th_load_tensor() function, which in + * turn was loaded from the interface via `db` commands. + * + * For testing, you can pre-load known-good data into the buffer during the + * th_final_initialize() function. + * + */ +void ee_infer(size_t n, size_t n_warmup) { + th_load_tensor(); /* if necessary */ + th_printf("m-warmup-start-%d\r\n", n_warmup); + while (n_warmup-- > 0) { + th_infer(); /* call the API inference function */ + } + th_printf("m-warmup-done\r\n"); + th_printf("m-infer-start-%d\r\n", n); + th_timestamp(); + th_pre(); + while (n-- > 0) { + th_infer(); /* call the API inference function */ + } + th_post(); + th_timestamp(); + th_printf("m-infer-done\r\n"); + th_results(); +} + +arg_claimed_t ee_buffer_parse(char *p_command) { + char *p_next; + + if (strncmp(p_command, "db", EE_CMD_SIZE) != 0) { + return EE_ARG_UNCLAIMED; + } + + p_next = strtok(NULL, EE_CMD_DELIMITER); + + if (p_next == NULL) { + th_printf("e-[Command 'db' requires a subcommand]\r\n"); + } else if (strncmp(p_next, "load", EE_CMD_SIZE) == 0) { + p_next = strtok(NULL, EE_CMD_DELIMITER); + + if (p_next == NULL) { + th_printf("e-[Command 'db load' requires the # of bytes]\r\n"); + } else { + g_buff_size = (size_t)atoi(p_next); + if (g_buff_size == 0) { + th_printf("e-[Command 'db load' must be >0 bytes]\r\n"); + } else { + g_buff_pos = 0; + if (g_buff_size > MAX_DB_INPUT_SIZE) { + th_printf("Supplied buffer size %d exceeds maximum of %d\n", + g_buff_size, MAX_DB_INPUT_SIZE); + } else { + th_printf("m-[Expecting %d bytes]\r\n", g_buff_size); + } + } + } + } else if (strncmp(p_next, "print", EE_CMD_SIZE) == 0) { + size_t i = 0; + const size_t max = 8; + for (; i < g_buff_size; ++i) { + if ((i + max) % max == 0 || i == 0) { + th_printf("m-buffer-"); + } + /* N.B. Not every `printf` supports the spacing prefix! */ + th_printf("%02x", gp_buff[i]); + if (((i + 1) % max == 0) || ((i + 1) == g_buff_size)) { + th_printf("\r\n"); + } else { + th_printf("-"); + } + } + if (i % max != 0) { + th_printf("\r\n"); + } + } else { + size_t numbytes; + char test[3]; + long res; + + /* Two hexdigits per byte */ + numbytes = th_strnlen(p_next, EE_CMD_SIZE); + + if ((numbytes & 1) != 0) { + th_printf("e-[Insufficent number of hex digits]\r\n"); + return EE_ARG_CLAIMED; + } + test[2] = 0; + for (size_t i = 0; i < numbytes;) { + test[0] = p_next[i++]; + test[1] = p_next[i++]; + res = ee_hexdec(test); + if (res < 0) { + th_printf("e-[Invalid hex digit '%s']\r\n", test); + return EE_ARG_CLAIMED; + } else { + gp_buff[g_buff_pos] = (uint8_t)res; + g_buff_pos++; + if (g_buff_pos == g_buff_size) { + th_printf("m-load-done\r\n"); + /* Disregard the remainder of the digits when done. */ + return EE_ARG_CLAIMED; + } + } + } + } + return EE_ARG_CLAIMED; +} + +/** + * @brief convert a hexidecimal string to a signed long + * will not produce or process negative numbers except + * to signal error. + * + * @param hex without decoration, case insensitive. + * + * @return -1 on error, or result (max (sizeof(long)*8)-1 bits) + * + */ +long ee_hexdec(char *hex) { + char c; + long dec = 0; + long ret = 0; + + while (*hex && ret >= 0) { + c = *hex++; + if (c >= '0' && c <= '9') { + dec = c - '0'; + } else if (c >= 'a' && c <= 'f') { + dec = c - 'a' + 10; + } else if (c >= 'A' && c <= 'F') { + dec = c - 'A' + 10; + } else { + return -1; + } + ret = (ret << 4) + dec; + } + return ret; +} + +/** + * @brief get the buffer resulting from the last db command. Returns length 0 + * if the db command has not been used yet. + * + * @param buffer to fill with bytes from internal buffer filled by db commands. + * @param maximum number of bytes to copy into provided buffer. This is + * typically the length of the provided buffer. + * + * @return number of bytes copied from internal buffer. + * + */ +size_t ee_get_buffer(uint8_t* buffer, size_t max_len) { + int len = max_len < g_buff_pos ? max_len : g_buff_pos; + if (buffer != nullptr) { + memcpy(buffer, gp_buff, len * sizeof(uint8_t)); + } + return len; +} + +uint8_t* ee_get_buffer_pointer() { return gp_buff; } diff --git a/3rdparty/mlperftiny/api/internally_implemented.h b/3rdparty/mlperftiny/api/internally_implemented.h new file mode 100644 index 000000000000..44583173f8f8 --- /dev/null +++ b/3rdparty/mlperftiny/api/internally_implemented.h @@ -0,0 +1,62 @@ +/* +Copyright 2020 EEMBC and The MLPerf Authors. All Rights Reserved. +Licensed 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. +This file is a modified version of the original EEMBC implementation of ee_lib. +The file name has been changed and some functions removed. +==============================================================================*/ + +/// \file +/// \brief Internally-implemented methods required to perform inference. + +#include +#include + +#ifndef MLPERF_TINY_V0_1_API_INTERNALLY_IMPLEMENTED_H_ +#define MLPERF_TINY_V0_1_API_INTERNALLY_IMPLEMENTED_H_ + +#define EE_MONITOR_VERSION "2.2.0" +#define EE_FW_VERSION "ULPMark for tinyML Firmware V0.0.1" + +/* Version 1.0 of the benchmark only supports these models */ +#define EE_MODEL_VERSION_KWS01 "kws01" +#define EE_MODEL_VERSION_VWW01 "vww01" +#define EE_MODEL_VERSION_AD01 "ad01" +#define EE_MODEL_VERSION_IC01 "ic01" + +typedef enum { EE_ARG_CLAIMED, EE_ARG_UNCLAIMED } arg_claimed_t; +typedef enum { EE_STATUS_OK = 0, EE_STATUS_ERROR } ee_status_t; + +#define EE_DEVICE_NAME "dut" + +#define EE_CMD_SIZE 80u +#define EE_CMD_DELIMITER " " +#define EE_CMD_TERMINATOR '%' + +#define EE_CMD_NAME "name" +#define EE_CMD_TIMESTAMP "timestamp" + +#define EE_MSG_READY "m-ready\r\n" +#define EE_MSG_INIT_DONE "m-init-done\r\n" +#define EE_MSG_NAME "m-name-%s-[%s]\r\n" + +#define EE_ERR_CMD "e-[Unknown command: %s]\r\n" + +void ee_serial_callback(char); +void ee_serial_command_parser_callback(char*); +void ee_benchmark_initialize(void); +long ee_hexdec(char*); +void ee_infer(size_t n, size_t n_warmup); +size_t ee_get_buffer(uint8_t* buffer, size_t max_len); +arg_claimed_t ee_buffer_parse(char* command); +arg_claimed_t ee_profile_parse(char* command); +uint8_t* ee_get_buffer_pointer(); + +#endif /* MLPERF_TINY_V0_1_API_INTERNALLY_IMPLEMENTED_H_ */ diff --git a/3rdparty/mlperftiny/api/submitter_implemented.h b/3rdparty/mlperftiny/api/submitter_implemented.h new file mode 100644 index 000000000000..fc03a733c9cb --- /dev/null +++ b/3rdparty/mlperftiny/api/submitter_implemented.h @@ -0,0 +1,85 @@ +/* +Copyright 2020 EEMBC and The MLPerf Authors. All Rights Reserved. +Licensed 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. +This file reflects a modified version of th_lib from EEMBC. All wrapped libc +methods from th_libc.h and all testharness methods from th_lib.h are here. +==============================================================================*/ +/// \file +/// \brief Submitter-implemented methods required to perform inference. +/// \detail All methods with names starting with th_ are to be implemented by +/// the submitter. All basic I/O, inference and timer APIs must be implemented +/// in order for the benchmark to output useful results, but some auxiliary +/// methods default to an empty implementation. These methods are provided to +/// enable submitter optimizations, and are not required for submission. + +#ifndef MLPERF_TINY_V0_1_API_SUBMITTER_IMPLEMENTED_H_ +#define MLPERF_TINY_V0_1_API_SUBMITTER_IMPLEMENTED_H_ + +/// \brief These defines set logging prefixes for test harness integration. +/// \detail This API is designed for performance evaluation only. In order to +/// gather energy measurments we recommend using the EEMBC test suite. +#define EE_MSG_TIMESTAMP "m-lap-us-%lu\r\n" +#define TH_VENDOR_NAME_STRING "unspecified" + +// MAX_DB_INPUT_SIZE defined in CMakeList.txt +#ifndef TH_MODEL_VERSION +// See "internally_implemented.h" for a list +#error "PLease set TH_MODEL_VERSION to one of the EE_MODEL_VERSION_* defines" +// e.g.: to inform the user of model `ic01` use this: +// #define TH_MODEL_VERSION EE_MODEL_VERSION_IC01 +#endif + +// Use this to switch between DUT-direct (perf) & DUT-inderrect (energy) modes +#ifndef EE_CFG_ENERGY_MODE +#define EE_CFG_ENERGY_MODE 0 +#endif + +// This is a visual cue to the user when reviewing logs or plugging an +// unknown device into the system. +#if EE_CFG_ENERGY_MODE == 1 +#define EE_MSG_TIMESTAMP_MODE "m-timestamp-mode-energy\r\n" +#else +#define EE_MSG_TIMESTAMP_MODE "m-timestamp-mode-performance\r\n" +#endif + +#include +#include +#include +#include + +/// \brief required core API +void th_load_tensor(); +void th_results(); +void th_infer(); +void th_timestamp(void); +void th_printf(const char* fmt, ...); +char th_getchar(); + +/// \brief optional API +void th_serialport_initialize(void); +void th_timestamp_initialize(void); +void th_final_initialize(void); +void th_pre(); +void th_post(); +void th_command_ready(char volatile* msg); + +/// \brief libc hooks +int th_strncmp(const char* str1, const char* str2, size_t n); +char* th_strncpy(char* dest, const char* src, size_t n); +size_t th_strnlen(const char* str, size_t maxlen); +char* th_strcat(char* dest, const char* src); +char* th_strtok(/*@null@*/ char* str1, const char* sep); +int th_atoi(const char* str); +void* th_memset(void* b, int c, size_t len); +void* th_memcpy(void* dst, const void* src, size_t n); +int th_vprintf(const char* format, va_list ap); + +#endif // MLPERF_TINY_V0_1_API_SUBMITTER_IMPLEMENTED_H_ diff --git a/apps/microtvm/zephyr/template_project/CMakeLists.txt.template b/apps/microtvm/zephyr/template_project/CMakeLists.txt.template index 9386576c394b..a41d68a134ef 100644 --- a/apps/microtvm/zephyr/template_project/CMakeLists.txt.template +++ b/apps/microtvm/zephyr/template_project/CMakeLists.txt.template @@ -23,6 +23,8 @@ set(ENV{QEMU_BIN_PATH} "${CMAKE_SOURCE_DIR}/qemu-hack") set(QEMU_PIPE CACHE PATH "Path to QEMU pipe") +option(ENERGY_MODE "Enable energy mode for MLPerfTiny tests." 0) + find_package(Zephyr HINTS $ENV{ZEPHYR_BASE}) @@ -70,6 +72,15 @@ target_include_directories(tvm_model PRIVATE ${CMAKE_SOURCE_DIR}/include crt_con target_compile_options(tvm_model PRIVATE -Wno-unused-variable) # TVM-generated code tends to include lots of these. target_link_libraries(app PRIVATE tvm_model) -file(GLOB_RECURSE app_srcs src/**.c) +if(EXISTS ${CMAKE_CURRENT_SOURCE_DIR}/src/api) + zephyr_library_named(tinymlperf_api) + file(GLOB_RECURSE tiny_internal_srcs src/api/*.cpp) + target_sources(tinymlperf_api PRIVATE ${tiny_internal_srcs}) + target_compile_options(tinymlperf_api PRIVATE -Wno-unused-variable) # TVM-generated code tends to include lots of these. + target_link_libraries(app PRIVATE tinymlperf_api) + target_compile_definitions(tinymlperf_api PUBLIC -DEE_CFG_ENERGY_MODE=${ENERGY_MODE}) +endif() + +file(GLOB_RECURSE app_srcs src/**.c src/**.cc) target_sources(app PRIVATE ${app_srcs} ${cmsis_lib_srcs}) target_include_directories(app PRIVATE crt_config ${CMAKE_SOURCE_DIR}/include crt/include ${cmsis_includes}) diff --git a/apps/microtvm/zephyr/template_project/microtvm_api_server.py b/apps/microtvm/zephyr/template_project/microtvm_api_server.py index b0cd21e4adb2..e93918e44844 100644 --- a/apps/microtvm/zephyr/template_project/microtvm_api_server.py +++ b/apps/microtvm/zephyr/template_project/microtvm_api_server.py @@ -448,6 +448,7 @@ def _create_prj_conf( CRT_LIBS_BY_PROJECT_TYPE = { "host_driven": "microtvm_rpc_server microtvm_rpc_common aot_executor_module aot_executor common", "aot_standalone_demo": "memory microtvm_rpc_common common", + "mlperftiny": "memory common", } def _get_platform_version(self, zephyr_base: str) -> float: @@ -623,7 +624,13 @@ def generate_project(self, model_library_format_path, standalone_crt_dir, projec if compile_definitions: flags = compile_definitions for item in flags: - cmake_f.write(f"target_compile_definitions(app PUBLIC {item})\n") + if "MAX_DB_INPUT_SIZE" in item or "TH_MODEL_VERSION" in item: + compile_target = "tinymlperf_api" + else: + compile_target = "app" + cmake_f.write( + f"target_compile_definitions({compile_target} PUBLIC {item})\n" + ) if self._is_fvp(zephyr_board, use_fvp): cmake_f.write(f"target_compile_definitions(app PUBLIC -DFVP=1)\n") diff --git a/apps/microtvm/zephyr/template_project/src/mlperftiny/README.md b/apps/microtvm/zephyr/template_project/src/mlperftiny/README.md new file mode 100644 index 000000000000..c38a1f05bfc6 --- /dev/null +++ b/apps/microtvm/zephyr/template_project/src/mlperftiny/README.md @@ -0,0 +1,20 @@ + + + + + + + + + + + + + + + + + +# MLPerf Tiny Project API +This directory includes source files to build a Zephyr microTVM project to use for benchmarking with EEMBC runner. +This project has been tested with NUCLEO_L4R5ZI and NRF5340DK. diff --git a/apps/microtvm/zephyr/template_project/src/mlperftiny/main.cc b/apps/microtvm/zephyr/template_project/src/mlperftiny/main.cc new file mode 100644 index 000000000000..4c91177062ad --- /dev/null +++ b/apps/microtvm/zephyr/template_project/src/mlperftiny/main.cc @@ -0,0 +1,38 @@ +/* + * 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 "api/internally_implemented.h" +#include "api/submitter_implemented.h" + +int main(int argc, char* argv[]) { +#if NRF_BOARD == 1 + // Set frequency to 128MHz for nrf5340dk_nrf534 by setting the clock divider to 0. + // 0x50005558 is the clock division reg address. + uint32_t* clock_div = (uint32_t*)0x50005558; + *clock_div = 0; +#endif + + ee_benchmark_initialize(); + while (1) { + int c; + c = th_getchar(); + ee_serial_callback(c); + } + return 0; +} diff --git a/apps/microtvm/zephyr/template_project/src/mlperftiny/submitter_implemented.cc b/apps/microtvm/zephyr/template_project/src/mlperftiny/submitter_implemented.cc new file mode 100644 index 000000000000..84baee3072cd --- /dev/null +++ b/apps/microtvm/zephyr/template_project/src/mlperftiny/submitter_implemented.cc @@ -0,0 +1,218 @@ +/* + * 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 "api/submitter_implemented.h" + +#include +#include +#include +#include + +#include "api/internally_implemented.h" +#include "tvmruntime.h" +#include "zephyr_uart.h" + +static void* g_input_data; +#if TARGET_MODEL == 3 // AD +static uint8_t __aligned(4) g_input_data_buffer_aligned[MAX_DB_INPUT_SIZE]; +#endif + +#if EE_CFG_ENERGY_MODE == 1 && NRF_BOARD != 1 +// use GPIO PC6 which is on connector CN7 pin 1 on the nucleo_l4r5zi +static const char* g_gpio_device_name = "GPIOC"; +static const struct device* g_gpio_dev; +static const gpio_pin_t g_gpio_pin = 6; +#endif + +// Implement this method to prepare for inference and preprocess inputs. +// Modified from source +void th_load_tensor() { +#if TARGET_MODEL == 1 // KWS + g_input_data = static_cast(ee_get_buffer_pointer()); +#elif TARGET_MODEL == 2 // VWW + // Converting uint8 to int8 + int8_t* temp_int = reinterpret_cast(ee_get_buffer_pointer()); + for (size_t i = 0; i < MAX_DB_INPUT_SIZE; i++) { + temp_int[i] -= 128; + } + g_input_data = static_cast(temp_int); +#elif TARGET_MODEL == 3 // AD + uint8_t* buffer = ee_get_buffer_pointer(); + memcpy(g_input_data_buffer_aligned, buffer, sizeof(g_input_data_buffer_aligned)); + g_input_data = g_input_data_buffer_aligned; +#elif TARGET_MODEL == 4 // IC + uint8_t* temp_uint = reinterpret_cast(ee_get_buffer_pointer()); + int8_t* temp_int = reinterpret_cast(ee_get_buffer_pointer()); + for (size_t i = 0; i < MAX_DB_INPUT_SIZE; i++) { + if (temp_uint[i] <= 127) + temp_int[i] = ((int8_t)temp_uint[i]) - 128; + else + temp_int[i] = (int8_t)(temp_uint[i] - 128); + } + g_input_data = reinterpret_cast(temp_int); +#else +#error Wrong model +#endif +} + +#if TARGET_MODEL == 3 // model AD +// calculate |output - input| for AD model +static float calculate_result() { + size_t feature_size = g_output_data_len; + float diffsum = 0; + float* input_float = reinterpret_cast(g_input_data); + float* output_float = reinterpret_cast(g_output_data); + + for (size_t i = 0; i < feature_size; i++) { + float diff = output_float[i] - input_float[i]; + diffsum += diff * diff; + } + diffsum /= feature_size; + + return diffsum; +} +#endif + +// Add to this method to return real inference results. +void th_results() { + /** + * The results need to be printed back in exactly this format; if easier + * to just modify this loop than copy to results[] above, do that. + */ +#if TARGET_MODEL == 3 // model AD + th_printf("m-results-[%0.3f]\r\n", calculate_result()); +#else + size_t kCategoryCount = g_output_data_len; + th_printf("m-results-["); + for (size_t i = 0; i < kCategoryCount; i++) { + float converted = static_cast(g_quant_scale * (g_output_data[i] - g_quant_zero)); + // float converted = static_cast(g_output_data[i]); + th_printf("%.3f", converted); + if (i < (kCategoryCount - 1)) { + th_printf(","); + } + } + th_printf("]\r\n"); +#endif +} + +// Implement this method with the logic to perform one inference cycle. +// Modified from source +void th_infer() { TVMInfer(g_input_data); } + +/// \brief optional API. +// Modified from source +void th_final_initialize(void) { TVMRuntimeInit(); } + +void th_pre() {} +void th_post() {} + +void th_command_ready(char volatile* p_command) { + p_command = p_command; + ee_serial_command_parser_callback((char*)p_command); +} + +// th_libc implementations. +int th_strncmp(const char* str1, const char* str2, size_t n) { return strncmp(str1, str2, n); } + +char* th_strncpy(char* dest, const char* src, size_t n) { return strncpy(dest, src, n); } + +size_t th_strnlen(const char* str, size_t maxlen) { return strlen(str); } + +char* th_strcat(char* dest, const char* src) { return strcat(dest, src); } + +char* th_strtok(char* str1, const char* sep) { return strtok(str1, sep); } + +int th_atoi(const char* str) { return atoi(str); } + +void* th_memset(void* b, int c, size_t len) { return memset(b, c, len); } + +void* th_memcpy(void* dst, const void* src, size_t n) { return memcpy(dst, src, n); } + +/* N.B.: Many embedded *printf SDKs do not support all format specifiers. */ +int th_vprintf(const char* format, va_list ap) { return vprintf(format, ap); } + +// Modified from source +void th_printf(const char* p_fmt, ...) { + char buffer[128]; + int size; + va_list args; + va_start(args, p_fmt); + size = TVMPlatformFormatMessage(buffer, 128, p_fmt, args); + va_end(args); + TVMPlatformWriteSerial(buffer, (size_t)size); +} + +// Modified from source +char th_getchar() { return TVMPlatformUartRxRead(); } + +// Modified from source +void th_serialport_initialize(void) { +#if EE_CFG_ENERGY_MODE == 1 && NRF_BOARD != 1 + TVMPlatformUARTInit(9600); +#else + TVMPlatformUARTInit(); +#endif +} + +// Modified from source +void th_timestamp(void) { +#if EE_CFG_ENERGY_MODE == 1 && NRF_BOARD != 1 + /* USER CODE 1 BEGIN */ + /* Step 1. Pull pin low */ + gpio_pin_set(g_gpio_dev, g_gpio_pin, 0); + /* Step 2. Hold low for at least 1us */ + k_busy_wait(1); + /* Step 3. Release driver */ + gpio_pin_set(g_gpio_dev, g_gpio_pin, 1); + /* USER CODE 1 END */ +#else + /* USER CODE 2 BEGIN */ + unsigned long microSeconds = (unsigned long)(k_uptime_get() * 1000LL); + /* USER CODE 2 END */ + /* This message must NOT be changed. */ + th_printf(EE_MSG_TIMESTAMP, microSeconds); +#endif +} + +// Modified from source +void th_timestamp_initialize(void) { + /* USER CODE 1 BEGIN */ + // Setting up BOTH perf and energy here +#if EE_CFG_ENERGY_MODE == 1 && NRF_BOARD != 1 + g_gpio_dev = device_get_binding(g_gpio_device_name); + if (g_gpio_dev == NULL) { + th_printf("GPIO device init failed\r\n"); + return; + } + + int ret = gpio_pin_configure(g_gpio_dev, g_gpio_pin, GPIO_OUTPUT_HIGH); + if (ret < 0) { + th_printf("GPIO pin configure failed\r\n"); + return; + } +#endif + + /* USER CODE 1 END */ + /* This message must NOT be changed. */ + th_printf(EE_MSG_TIMESTAMP_MODE); + /* Always call the timestamp on initialize so that the open-drain output + is set to "1" (so that we catch a falling edge) */ + th_timestamp(); +} diff --git a/apps/microtvm/zephyr/template_project/src/mlperftiny/tvmruntime.cc b/apps/microtvm/zephyr/template_project/src/mlperftiny/tvmruntime.cc new file mode 100644 index 000000000000..b16a1e711f8c --- /dev/null +++ b/apps/microtvm/zephyr/template_project/src/mlperftiny/tvmruntime.cc @@ -0,0 +1,164 @@ +/* + * 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 "tvmruntime.h" + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "output_data.h" +#include "tvmgen_default.h" +#include "zephyr_uart.h" + +#ifdef CONFIG_ARCH_POSIX +#include "posix_board_if.h" +#endif + +// OUT_QUANT_SCALE and OUT_QUANT_ZERO are set in python. +#if TARGET_MODEL == 3 +float* g_output_data = output_data; +#else +int8_t* g_output_data = output_data; +float g_quant_scale = OUT_QUANT_SCALE; +int8_t g_quant_zero = OUT_QUANT_ZERO; +#endif +size_t g_output_data_len = output_data_len; + +// WORKSPACE_SIZE is defined in python +static uint8_t g_aot_memory[WORKSPACE_SIZE]; +tvm_workspace_t app_workspace; + +size_t TVMPlatformFormatMessage(char* out_buf, size_t out_buf_size_bytes, const char* fmt, + va_list args) { + return vsnprintk(out_buf, out_buf_size_bytes, fmt, args); +} + +void TVMLogf(const char* msg, ...) { + char buffer[128]; + int size; + va_list args; + va_start(args, msg); + size = TVMPlatformFormatMessage(buffer, 128, msg, args); + va_end(args); + TVMPlatformWriteSerial(buffer, (size_t)size); +} + +void __attribute__((noreturn)) TVMPlatformAbort(tvm_crt_error_t error) { + TVMLogf("TVMPlatformAbort: %08x\n", error); + sys_reboot(SYS_REBOOT_COLD); + for (;;) + ; +} + +tvm_crt_error_t TVMPlatformMemoryAllocate(size_t num_bytes, DLDevice dev, void** out_ptr) { + return StackMemoryManager_Allocate(&app_workspace, num_bytes, out_ptr); +} + +tvm_crt_error_t TVMPlatformMemoryFree(void* ptr, DLDevice dev) { + return StackMemoryManager_Free(&app_workspace, ptr); +} + +void timer_expiry_function(struct k_timer* timer_id) { return; } + +#ifdef __cplusplus +extern "C" { +#endif +void* TVMBackendAllocWorkspace(int device_type, int device_id, uint64_t nbytes, int dtype_code_hint, + int dtype_bits_hint) { + tvm_crt_error_t err = kTvmErrorNoError; + void* ptr = 0; + DLDevice dev = {(DLDeviceType)device_type, device_id}; + assert(nbytes > 0); + err = TVMPlatformMemoryAllocate(nbytes, dev, &ptr); + CHECK_EQ(err, kTvmErrorNoError, + "TVMBackendAllocWorkspace(%d, %d, %" PRIu64 ", %d, %d) -> %" PRId32, device_type, + device_id, nbytes, dtype_code_hint, dtype_bits_hint, err); + return ptr; +} + +int TVMBackendFreeWorkspace(int device_type, int device_id, void* ptr) { + tvm_crt_error_t err = kTvmErrorNoError; + DLDevice dev = {(DLDeviceType)device_type, device_id}; + err = TVMPlatformMemoryFree(ptr, dev); + CHECK_EQ(err, kTvmErrorNoError, "TVMBackendFreeWorkspace(%d, %d)", device_type, device_id); + return err; +} + +#ifdef __cplusplus +} // extern "C" +#endif + +void TVMRuntimeInit() { StackMemoryManager_Init(&app_workspace, g_aot_memory, WORKSPACE_SIZE); } + +void TVMInfer(void* input_ptr) { + struct tvmgen_default_inputs inputs = { +#if TARGET_MODEL == MODEL_KWS + .input_1 = input_ptr, +#elif TARGET_MODEL == MODEL_IC + .input_1_int8 = input_ptr, +#elif TARGET_MODEL == MODEL_VWW + .input_1_int8 = input_ptr, +#elif TARGET_MODEL == MODEL_AD + .input_1 = input_ptr, +#elif +#error Wrong model. +#endif + }; + + struct tvmgen_default_outputs outputs = { +#if TARGET_MODEL == MODEL_KWS +#if COMPILE_WITH_CMSISNN + .Identity = output_data, +#else + .output = output_data, +#endif +#elif TARGET_MODEL == MODEL_IC + .Identity_int8 = output_data, +#elif TARGET_MODEL == MODEL_VWW + .Identity_int8 = output_data, +#elif TARGET_MODEL == MODEL_AD + .Identity = output_data, +#endif + }; + + int ret_val = tvmgen_default_run(&inputs, &outputs); + if (ret_val != 0) { + TVMLogf("Error: %d\n", ret_val); + } +} + +int8_t QuantizeFloatToInt8(float value, float scale, int zero_point) { + int32_t result = round(value / scale) + zero_point; + if (result < INT8_MIN) { + result = INT8_MIN; + } + if (result > INT8_MAX) { + result = INT8_MAX; + } + return (int8_t)(result); +} diff --git a/apps/microtvm/zephyr/template_project/src/mlperftiny/tvmruntime.h b/apps/microtvm/zephyr/template_project/src/mlperftiny/tvmruntime.h new file mode 100644 index 000000000000..940d64634d59 --- /dev/null +++ b/apps/microtvm/zephyr/template_project/src/mlperftiny/tvmruntime.h @@ -0,0 +1,62 @@ +/* + * 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. + */ + +#ifndef APPS_MICROTVM_ZEPHYR_TEMPLATE_PROJECT_SRC_MLPERFTINY_TVMRUNTIME_H_ +#define APPS_MICROTVM_ZEPHYR_TEMPLATE_PROJECT_SRC_MLPERFTINY_TVMRUNTIME_H_ + +#include +#include +#include + +#define MODEL_KWS 1 +#define MODEL_VWW 2 +#define MODEL_AD 3 +#define MODEL_IC 4 + +extern const unsigned char g_wakeup_sequence[]; +extern size_t g_output_data_len; + +#if TARGET_MODEL == 3 +extern float* g_output_data; +#else +extern int8_t* g_output_data; +#endif + +extern float g_quant_scale; +extern int8_t g_quant_zero; + +/*! + * \brief Initialize TVM runtime. + */ +void TVMRuntimeInit(); + +/*! + * \brief Run TVM inference. + */ +void TVMInfer(void* input_ptr); + +/*! + * \brief Quantize float to int8. + * \param value Input data in float. + * \param scale Quantization scale factor. + * \param zero_point Quantization zero point. + */ +int8_t QuantizeFloatToInt8(float value, float scale, int zero_point); + +#endif /* APPS_MICROTVM_ZEPHYR_TEMPLATE_PROJECT_SRC_MLPERFTINY_TVMRUNTIME_H_ */ diff --git a/apps/microtvm/zephyr/template_project/src/mlperftiny/zephyr_uart.cc b/apps/microtvm/zephyr/template_project/src/mlperftiny/zephyr_uart.cc new file mode 100644 index 000000000000..9880eadd4d9b --- /dev/null +++ b/apps/microtvm/zephyr/template_project/src/mlperftiny/zephyr_uart.cc @@ -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 "zephyr_uart.h" + +#include +#include +#include + +#include "crt_config.h" + +static const struct device* g_microtvm_uart; + +static uint8_t uart_data[8]; + +// UART interrupt callback. +void uart_irq_cb(const struct device* dev, void* user_data) { + while (uart_irq_update(dev) && uart_irq_is_pending(dev)) { + struct ring_buf* rbuf = (struct ring_buf*)user_data; + if (uart_irq_rx_ready(dev) != 0) { + for (;;) { + // Read a small chunk of data from the UART. + int bytes_read = uart_fifo_read(dev, uart_data, sizeof(uart_data)); + if (bytes_read < 0) { + TVMPlatformAbort((tvm_crt_error_t)(0xbeef1)); + } else if (bytes_read == 0) { + break; + } + // Write it into the ring buffer. + int bytes_written = ring_buf_put(rbuf, uart_data, bytes_read); + if (bytes_read != bytes_written) { + TVMPlatformAbort((tvm_crt_error_t)(0xbeef2)); + } + } + } + } +} + +// Initialize the UART receiver. +void uart_rx_init(struct ring_buf* rbuf, const struct device* dev) { + uart_irq_callback_user_data_set(dev, uart_irq_cb, (void*)rbuf); + uart_irq_rx_enable(dev); +} + +// UART read. +char TVMPlatformUartRxRead() { + unsigned char c; + int ret = -1; + while (ret != 0) { + ret = uart_poll_in(g_microtvm_uart, &c); + } + return (char)c; +} + +// UART write. +uint32_t TVMPlatformWriteSerial(const char* data, uint32_t size) { + for (uint32_t i = 0; i < size; i++) { + uart_poll_out(g_microtvm_uart, data[i]); + } + return size; +} + +// Initialize UART. +void TVMPlatformUARTInit(uint32_t baudrate /* = TVM_UART_DEFAULT_BAUDRATE */) { + // Claim console device. + g_microtvm_uart = device_get_binding(DT_LABEL(DT_CHOSEN(zephyr_console))); + const struct uart_config config = {.baudrate = baudrate, + .parity = UART_CFG_PARITY_NONE, + .stop_bits = UART_CFG_STOP_BITS_1, + .data_bits = UART_CFG_DATA_BITS_8, + .flow_ctrl = UART_CFG_FLOW_CTRL_NONE}; + uart_configure(g_microtvm_uart, &config); +} diff --git a/apps/microtvm/zephyr/template_project/src/mlperftiny/zephyr_uart.h b/apps/microtvm/zephyr/template_project/src/mlperftiny/zephyr_uart.h new file mode 100644 index 000000000000..f10cf0262224 --- /dev/null +++ b/apps/microtvm/zephyr/template_project/src/mlperftiny/zephyr_uart.h @@ -0,0 +1,51 @@ +/* + * 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. + */ + +#ifndef APPS_MICROTVM_ZEPHYR_TEMPLATE_PROJECT_SRC_MLPERFTINY_ZEPHYR_UART_H_ +#define APPS_MICROTVM_ZEPHYR_TEMPLATE_PROJECT_SRC_MLPERFTINY_ZEPHYR_UART_H_ + +#include + +#define TVM_UART_DEFAULT_BAUDRATE 115200 + +/*! + * \brief Read Uart Rx buffer. + * \param data Pointer to read data. + * \param data_size_bytes Read request size in bytes. + * + * \return Number of data read in bytes. + */ +char TVMPlatformUartRxRead(); + +/*! + * \brief Write data in serial. + * \param data Pointer to data to write. + * \param size Size of data in bytes. + * + * \return Number of write in bytes. + */ +uint32_t TVMPlatformWriteSerial(const char* data, uint32_t size); + +/*! + * \brief Initialize Uart. + * \param baudrate Desired UART baudrate. + */ +void TVMPlatformUARTInit(uint32_t baudrate = TVM_UART_DEFAULT_BAUDRATE); + +#endif /* APPS_MICROTVM_ZEPHYR_TEMPLATE_PROJECT_SRC_MLPERFTINY_ZEPHYR_UART_H_ */ diff --git a/cmake/modules/Zephyr.cmake b/cmake/modules/Zephyr.cmake index 644675dcf871..1f506f5132fd 100644 --- a/cmake/modules/Zephyr.cmake +++ b/cmake/modules/Zephyr.cmake @@ -27,6 +27,9 @@ if(USE_MICRO) "apps/microtvm/zephyr/template_project/src/aot_standalone_demo *.h -> zephyr/src/aot_standalone_demo" "apps/microtvm/zephyr/template_project/src/host_driven *.c -> zephyr/src/host_driven" "apps/microtvm/zephyr/template_project/src/host_driven *.h -> zephyr/src/host_driven" + "apps/microtvm/zephyr/template_project/src/mlperftiny *.cc -> zephyr/src/mlperftiny" + "apps/microtvm/zephyr/template_project/src/mlperftiny *.h -> zephyr/src/mlperftiny" + "3rdparty/mlperftiny/api * -> zephyr/src/mlperftiny/api" "apps/microtvm/zephyr/template_project/fvp-hack * -> zephyr/fvp-hack" "apps/microtvm/zephyr/template_project/qemu-hack * -> zephyr/qemu-hack" "apps/microtvm/zephyr/template_project/app-overlay * -> zephyr/app-overlay"