diff --git a/.ci/CI.Dockerfile b/.ci/CI.Dockerfile new file mode 100644 index 0000000000000000000000000000000000000000..6c06d665ee94b4b7b1662bcebb202d0fc2f5d8cf --- /dev/null +++ b/.ci/CI.Dockerfile @@ -0,0 +1,38 @@ +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its +# affiliates +# SPDX-License-Identifier: Apache-2.0 +# +# 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. + +FROM ubuntu:22.04 + +ENV DEBIAN_FRONTEND=noninteractive + +RUN apt-get update && \ + apt-get install -y \ + make \ + git \ + gcc \ + g++ \ + python3.10 \ + python3-pip \ + python3.10-dev \ + python3.10-venv \ + unzip \ + curl \ + wget \ + gpg \ + libsndfile1 \ + sudo \ + telnet \ + && rm -rf /var/lib/apt/lists/* diff --git a/.ci/build_and_test.yml b/.ci/build_and_test.yml new file mode 100644 index 0000000000000000000000000000000000000000..5e6ae62b5cf361fcf165addf610b605f9f827064 --- /dev/null +++ b/.ci/build_and_test.yml @@ -0,0 +1,62 @@ +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited +# and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 +# +# 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. + +variables: + BUILD_LOG_LEVEL: "LOG_LEVEL_INFO" + EVAL_KIT_DIR: "$CI_PROJECT_DIR" + IMAGE: "$CI_REGISTRY_IMAGE:latest" + +build_and_test: + stage: build_and_test + tags: [$RUNNER_TAG] + image: $IMAGE + rules: + - if: $CI_COMMIT_BRANCH || $CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "web" + parallel: + matrix: + - TARGET_PLATFORM: native + CMAKE_BUILD_TYPE: [Debug, Release] + TOOLCHAIN_FILE: native-gcc + RUNNER_TAG: [arm64-shared, amd64-shared] + before_script: + - git submodule update --init --recursive + - python3 ./set_up_default_resources.py + script: + - echo "Building with PLATFORM=$TARGET_PLATFORM, TYPE=$CMAKE_BUILD_TYPE, TOOLCHAIN=$TOOLCHAIN_FILE" + - mkdir -p build-${TARGET_PLATFORM}-${CMAKE_BUILD_TYPE}-${TOOLCHAIN_FILE} + - cd build-${TARGET_PLATFORM}-${CMAKE_BUILD_TYPE}-${TOOLCHAIN_FILE} + - export PATH="$EVAL_KIT_DIR/resources_downloaded/env/bin:$PATH" + - cmake -DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE} -DTARGET_PLATFORM=${TARGET_PLATFORM} -DCMAKE_TOOLCHAIN_FILE=scripts/cmake/toolchains/${TOOLCHAIN_FILE}.cmake -DLOG_LEVEL=$BUILD_LOG_LEVEL .. + - make VERBOSE=1 -j1 + # Run tests + - | + fail=0 + for TEST in ./bin/*tests; do + TEST_NAME=$(basename $TEST) + RESULT_XML=${TEST_NAME}-result.xml + $TEST -r junit -o $RESULT_XML || fail=1 + done + if [ $fail = 1 ]; then + echo "One or more tests have failed to run. Check console log" + exit 1 + fi + artifacts: + paths: + - build-${TARGET_PLATFORM}-${CMAKE_BUILD_TYPE}-${TOOLCHAIN_FILE}/*.xml + reports: + junit: + - build-${TARGET_PLATFORM}-${CMAKE_BUILD_TYPE}-${TOOLCHAIN_FILE}/*.xml + expire_in: 2h diff --git a/.ci/buildah_build_and_push.yml b/.ci/buildah_build_and_push.yml new file mode 100644 index 0000000000000000000000000000000000000000..20749587e65c3a58bd2bb998dbaf9875d0edd6ae --- /dev/null +++ b/.ci/buildah_build_and_push.yml @@ -0,0 +1,65 @@ +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited +# and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 +# +# 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. + +variables: + STORAGE_DRIVER: vfs + BUILDAH_FORMAT: docker + IMAGE_TAG: "$CI_COMMIT_SHORT_SHA" + PUSH_LATEST: false + +.default_buildah: + image: $CI_REGISTRY_IMAGE/buildah-stable:latest + stage: buildah_build_and_push + before_script: + - echo "$CI_REGISTRY_PASSWORD" | buildah login -u "$CI_REGISTRY_USER" --password-stdin $CI_REGISTRY + rules: + - changes: [CI.Dockerfile] + if: $CI_COMMIT_BRANCH == "main" || $CI_PIPELINE_SOURCE == "merge_request_event" || $CI_PIPELINE_SOURCE == "web" + when: always + +build-amd64: + extends: .default_buildah + tags: [amd64-shared] + script: + - buildah build --format=$BUILDAH_FORMAT --storage-driver=$STORAGE_DRIVER --platform=linux/amd64 -f .ci/CI.Dockerfile -t $CI_REGISTRY_IMAGE:amd64-$CI_COMMIT_SHORT_SHA . + - buildah push $CI_REGISTRY_IMAGE:amd64-$CI_COMMIT_SHORT_SHA + +build-arm64: + extends: .default_buildah + tags: [arm64-shared] + script: + - buildah build --format=$BUILDAH_FORMAT --storage-driver=$STORAGE_DRIVER --platform=linux/arm64/v8 -f .ci/CI.Dockerfile -t $CI_REGISTRY_IMAGE:arm64-$CI_COMMIT_SHORT_SHA . + - buildah push $CI_REGISTRY_IMAGE:arm64-$CI_COMMIT_SHORT_SHA + +create_manifest: + extends: .default_buildah + needs: ["build-amd64", "build-arm64"] + tags: [amd64-shared] + script: + - buildah manifest create $CI_REGISTRY_IMAGE:$IMAGE_TAG + - buildah manifest add $CI_REGISTRY_IMAGE:$IMAGE_TAG docker://$CI_REGISTRY_IMAGE:amd64-$CI_COMMIT_SHORT_SHA + - buildah manifest add $CI_REGISTRY_IMAGE:$IMAGE_TAG docker://$CI_REGISTRY_IMAGE:arm64-$CI_COMMIT_SHORT_SHA + - buildah manifest push --all $CI_REGISTRY_IMAGE:$IMAGE_TAG + # Only push 'latest' manifest on main branch, skip for merge requests + - | + if [[ "$CI_COMMIT_BRANCH" == "main" || "$PUSH_LATEST" == "true" ]]; then + buildah manifest create $CI_REGISTRY_IMAGE:latest + buildah manifest add $CI_REGISTRY_IMAGE:latest docker://$CI_REGISTRY_IMAGE:amd64-$CI_COMMIT_SHORT_SHA + buildah manifest add $CI_REGISTRY_IMAGE:latest docker://$CI_REGISTRY_IMAGE:arm64-$CI_COMMIT_SHORT_SHA + buildah manifest push --all $CI_REGISTRY_IMAGE:latest + else + echo "Not main branch, skipping 'latest' manifest push." + fi diff --git a/.gitlab-ci.yml b/.gitlab-ci.yml new file mode 100644 index 0000000000000000000000000000000000000000..aa4cf680dea27b9535f8935f02538ea4b6c652e8 --- /dev/null +++ b/.gitlab-ci.yml @@ -0,0 +1,23 @@ +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited +# and/or its affiliates +# SPDX-License-Identifier: Apache-2.0 +# +# 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. + +stages: + - buildah_build_and_push + - build_and_test + +include: + - local: '.ci/buildah_build_and_push.yml' + - local: '.ci/build_and_test.yml' diff --git a/.gitmodules b/.gitmodules index 259f1356ba520b5ef5d2a985f8a8743b631ccbff..07ed4314d1d2c05e31ba211af0cafbb65b753fdf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -22,3 +22,6 @@ [submodule "dependencies/avh"] path = dependencies/avh url = https://github.com/ARM-software/AVH/ +[submodule "dependencies/executorch"] + path = dependencies/executorch + url = https://github.com/pytorch/executorch.git diff --git a/CMakeLists.txt b/CMakeLists.txt index 2a12610fd6939ab432c4a27be5a3a2fc4cb5d9c8..28d5f327cd3185c8c338f37416e251ba95d29024 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -68,13 +68,9 @@ add_subdirectory(${SRC_PATH}/hal ${CMAKE_BINARY_DIR}/hal) if (NOT DEFINED PROFILER_DIR) set(PROFILER_DIR ${SRC_PATH}/profiler) endif () -add_subdirectory(${PROFILER_DIR} ${CMAKE_BINARY_DIR}/profiler) - -# Include the tensorflow build target -include(tensorflow_lite_micro) -# Add the common API library target (tensorflow-lite-micro target is needed) -add_subdirectory(${SRC_PATH}/application/api/common ${CMAKE_BINARY_DIR}/api/common) +add_subdirectory(${PROFILER_DIR} ${CMAKE_BINARY_DIR}/profiler) +add_subdirectory(${SRC_PATH}/application/api ${CMAKE_BINARY_DIR}/api) # Include directories for application module: set(APPLICATION_INCLUDE_DIRS @@ -115,7 +111,6 @@ message(STATUS "Use-cases excluded by platform configuration: ${EXCLUDED_USE_CAS message(STATUS "Building use-cases: ${USE_CASES}.") foreach(use_case ${USE_CASES}) - set(SRC_USE_CASE "") foreach(USE_CASES_SEARCH_DIR ${USE_CASES_SEARCH_DIR_LIST}) if (EXISTS ${USE_CASES_SEARCH_DIR}/${use_case}) @@ -148,6 +143,18 @@ foreach(use_case ${USE_CASES}) # Include the use case cmake file. include(${UC_CMAKE_FILE}) + # Link ExecuTorch library if required + if (${use_case}_supports_${ML_FRAMEWORK}) + if ("ExecuTorch" STREQUAL ${ML_FRAMEWORK}) + set(ML_FWK_LINK_LIBS ml_framework_et) + elseif("TensorFlowLiteMicro" STREQUAL ${ML_FRAMEWORK}) + set(ML_FWK_LINK_LIBS ml_framework_tflm) + endif() + else() + message(STATUS "${use_case} skipped") + continue() + endif () + file(GLOB_RECURSE UC_SRC "${SRC_USE_CASE}/${use_case}/src/*.cpp" "${SRC_USE_CASE}/${use_case}/src/*.cc" @@ -193,24 +200,15 @@ foreach(use_case ${USE_CASES}) "ACTIVATION_BUF_SZ=${${use_case}_ACTIVATION_BUF_SZ}") target_link_libraries(${UC_LIB_NAME} PUBLIC - log arm_math hal profiler - tensorflow-lite-micro - common_api) + common_api + ${ML_FWK_LINK_LIBS}) # If an API exists for this use case, include the projects here and add to # the library list. foreach(API_TO_USE ${${use_case}_API_LIST}) - - # If the required target doesn't yet exist, include the project here: - if (NOT TARGET ${API_TO_USE}_api) - add_subdirectory( - ${SRC_PATH}/application/api/use_case/${API_TO_USE} # Source path - ${CMAKE_BINARY_DIR}/api/use_case/${API_TO_USE}) # Binary path - endif() - # Check if the target now exists if (TARGET ${API_TO_USE}_api) message(STATUS "Using ${API_TO_USE}_api for ${use_case}") @@ -226,11 +224,15 @@ foreach(use_case ${USE_CASES}) "${SAMPLES_GEN_DIR}/*.c") add_executable(${TARGET_NAME} ${SRC_MAIN} ${SRC_SAMPLES}) - target_link_libraries(${TARGET_NAME} PUBLIC ${UC_LIB_NAME}) - platform_custom_post_build(TARGET_NAME ${TARGET_NAME}) + # ExecuTorch doesn't have tests enabled yet. + # TODO: Remove once they are. + if ("ExecuTorch" STREQUAL ${ML_FRAMEWORK} AND TARGET_PLATFORM STREQUAL native) + continue() + endif() + platform_custom_post_build(TARGET_NAME ${TARGET_NAME}) endforeach() print_useroptions() diff --git a/MlekModule.cmake b/MlekModule.cmake index 198b422b610eed2f0a807dc341d92d69a9a969b9..370a47e3f76fbb2d527d731a6ff0bd0fed868886 100644 --- a/MlekModule.cmake +++ b/MlekModule.cmake @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2024 Arm Limited and/or its +# SPDX-FileCopyrightText: Copyright 2024-2025 Arm Limited and/or its # affiliates # SPDX-License-Identifier: Apache-2.0 # @@ -68,13 +68,18 @@ set_platform_global_defaults() message(STATUS "Using CMAKE_TOOLCHAIN_FILE: ${CMAKE_TOOLCHAIN_FILE}") # Make sure the following options are defined before proceeding. -assert_defined(LOG_LEVEL) -assert_defined(TENSORFLOW_SRC_PATH) +assert_defined(MLEK_LOG_LEVEL) assert_defined(TARGET_PLATFORM) assert_defined(USE_CASE_BUILD) assert_defined(CPU_PROFILE_ENABLED) assert_defined(CMAKE_TOOLCHAIN_FILE) +if ("ExecuTorch" STREQUAL ${ML_FRAMEWORK}) + assert_defined(EXECUTORCH_SRC_PATH) +elseif("TensorFlowLiteMicro" STREQUAL ${ML_FRAMEWORK}) + assert_defined(TENSORFLOW_SRC_PATH) +endif() + # Explicit policy definitions. if(POLICY CMP0123) cmake_policy(SET CMP0123 NEW) diff --git a/Readme.md b/Readme.md index dc4b0a10c4a7275da79776d10b41a5cd1d4ac55d..5fb49a30cf506b90d270e2b31af9c0f2ba7e83ef 100644 --- a/Readme.md +++ b/Readme.md @@ -1,6 +1,9 @@ # Arm® ML embedded evaluation kit +> ⚠️ **NOTE**: This is an **experimental** branch to support ExecuTorch framework alongside +> TensorFlow Lite Micro. Review the limitations [here](#known-limitations-for-experimental-branch) before proceeding. + ## Overview The ML embedded evaluation kit provides a range of ready to use machine learning (ML) applications for users to develop ML workloads running on the Arm® Ethos-U NPU and @@ -95,6 +98,12 @@ To run ML applications on the Cortex-M and Ethos-U NPU: python3.10 ./build_default.py ``` + ###### Build with ExecuTorch framework + ML framework defaults to TensorFlow Lite Micro. To build with ExecuTorch + ```commandline + python3.10 ./build_default.py --ml-framework executorch + ``` + 5. Change directory to the generated cmake build folder which contains the `.axf` file output in the `bin` subdirectory. Launch the application by passing the `.axf` to the FVP you downloaded when installing the prerequisites. Alternatively, from the root directory add `` to the path to the axf and use one of the @@ -202,6 +211,21 @@ please create a new GitLab issue here: This product conforms to Arm's inclusive language policy and, to the best of our knowledge, does not contain any non-inclusive language. If you find something that concerns you, email terms@arm.com. +# Known Limitations for Experimental Branch + +This branch is experimental and not stable — breaking changes are expected in the near term. It serves as +a preview of refactoring that allows `ExecuTorch` and `TensorFlow Lite Micro` to be supported within the same +source tree. At this stage, only the **image classification example** is functional with the `ExecuTorch` path, +with the following limitations: + +* Arm® Compiler is not supported +* Runtime memory usage is higher than we would like +* `Dedicated_Sram` mode is not supported on any NPU +* Arm® Ethos™-U65 NPU is not supported + +The last two limitations are expected to be addressed with an upcoming update to a newer version of the +ExecuTorch source tree. + ## Licenses The ML Embedded applications samples are provided under the Apache 2.0 license, see [License Apache 2.0](LICENSE_APACHE_2.0.txt). diff --git a/build_default.py b/build_default.py index fda76a1c5d13a77f65693e6081ab9b4962a722f7..f1690e818ac5db0635c5679843d5e9ad9d86b32e 100755 --- a/build_default.py +++ b/build_default.py @@ -29,16 +29,16 @@ from argparse import ArgumentParser from dataclasses import dataclass from pathlib import Path -from set_up_default_resources import PathsConfig -from set_up_default_resources import SetupConfig +from scripts.py.setup.setup_config import SetupConfig, PathsConfig, OptimizationConfig from set_up_default_resources import default_downloads_path +from set_up_default_resources import default_executorch_path from set_up_default_resources import default_npu_configs from set_up_default_resources import default_requirements_path from set_up_default_resources import default_use_case_resources_path from set_up_default_resources import get_default_npu_config_from_name from set_up_default_resources import set_up_resources from set_up_default_resources import valid_npu_configs - +from set_up_default_resources import valid_ml_frameworks, MLFramework @dataclass(frozen=True) class BuildConfig: @@ -53,6 +53,7 @@ class BuildConfig: npu_config_name (str) : Ethos-U NPU configuration name. See "valid_npu_config_names" make_jobs (int) : The number of make jobs to use (`-j` flag). make_verbose (bool) : Runs make with VERBOSE=1. + ml_framework : Specifies ML framework to use for the build. """ toolchain: str download_resources: bool @@ -60,6 +61,7 @@ class BuildConfig: npu_config_name: str make_jobs: int make_verbose: bool + ml_framework: MLFramework class PipeLogging(threading.Thread): @@ -130,8 +132,7 @@ def prep_build_dir( current_file_dir: Path, target_platform: str, target_subsystem: str, - npu_config_name: str, - toolchain: str + build_config: BuildConfig ) -> Path: """ Create or clean the build directory for this project. @@ -140,17 +141,19 @@ def prep_build_dir( ---------- current_file_dir : The current directory of the running script target_platform : The name of the target platform, e.g. "mps3" - target_subsystem: : The name of the target subsystem, e.g. "sse-300" - npu_config_name : The NPU config name, e.g. "ethos-u55-32" - toolchain : The name of the specified toolchain, e.g."arm" + target_subsystem : The name of the target subsystem, e.g. "sse-300" + build_config : Build config object Returns ------- The path to the build directory """ + build_dir = ( current_file_dir / - f"cmake-build-{target_platform}-{target_subsystem}-{npu_config_name}-{toolchain}" + (f"cmake-build-{target_platform}-{target_subsystem}" + + f"-{build_config.npu_config_name}-{build_config.toolchain}"+ + f"-{build_config.ml_framework.value}") ) try: @@ -195,13 +198,42 @@ def run_command( sys.exit(err.returncode) +def download_resources(build_config: BuildConfig) -> Path: + """ + Download resources for MLEK use cases + + Parameters + ---------- + build_config (BuildArgs) : Config for the build + + Returns + ------- + The path to the downloaded resources + """ + setup_config = SetupConfig( + run_vela_on_models=build_config.run_vela_on_models, + set_up_tensorflow=(build_config.ml_framework == MLFramework.TENSORFLOW_LITE_MICRO), + set_up_executorch=(build_config.ml_framework == MLFramework.EXECUTORCH) + ) + optimization_config = OptimizationConfig( + additional_npu_config_names=[build_config.npu_config_name] + ) + paths_config = PathsConfig( + additional_requirements_file=default_requirements_path, + use_case_resources_file=default_use_case_resources_path, + downloads_dir=default_downloads_path, + executorch_path=default_executorch_path + ) + return set_up_resources(setup_config, optimization_config, paths_config) + + def run(build_config: BuildConfig): """ Run the helpers scripts. Parameters: ---------- - args (BuildArgs) : Arguments used to build the project + build_config (BuildArgs) : Config for the build """ current_file_dir = Path(__file__).parent.resolve() @@ -210,18 +242,8 @@ def run(build_config: BuildConfig): toolchain_file_name = get_toolchain_file_name(build_config.toolchain) # 2. Download models if specified - if build_config.download_resources is True: - logging.info("Downloading resources.") - setup_config = SetupConfig( - run_vela_on_models=build_config.run_vela_on_models, - additional_npu_config_names=[build_config.npu_config_name], - ) - paths_config = PathsConfig( - additional_requirements_file=default_requirements_path, - use_case_resources_file=default_use_case_resources_path, - downloads_dir=default_downloads_path - ) - env_path = set_up_resources(setup_config, paths_config) + if build_config.download_resources: + env_path = download_resources(build_config) else: env_path = default_downloads_path / "env" @@ -242,8 +264,7 @@ def run(build_config: BuildConfig): current_file_dir, target_platform, target_subsystem, - build_config.npu_config_name, - build_config.toolchain + build_config ) logpipe = PipeLogging(logging.INFO) @@ -257,13 +278,24 @@ def run(build_config: BuildConfig): ) ethos_u_cfg = get_default_npu_config_from_name(build_config.npu_config_name) cmake_path = env_path / "bin" / "cmake" + framework_arg = '' + if build_config.ml_framework == MLFramework.TENSORFLOW_LITE_MICRO: + # TensorFlow Lite Micro is already the default option. Just ensure + # a clean build for the framework. + framework_arg = '-DTENSORFLOW_LITE_MICRO_CLEAN_DOWNLOADS=ON' + elif build_config.ml_framework == MLFramework.EXECUTORCH: + # Current ExecuTorch rev doesn't support `Dedicated Sram` + framework_arg = '-DML_FRAMEWORK=ExecuTorch -DETHOS_U_NPU_MEMORY_MODE=Shared_Sram' + else: + raise NotImplementedError(f'Unsupported ML Framework {build_config.ml_framework}') + cmake_command = ( f"{cmake_path} -B {build_dir} -DTARGET_PLATFORM={target_platform}" f" -DTARGET_SUBSYSTEM={target_subsystem}" f" -DCMAKE_TOOLCHAIN_FILE={cmake_toolchain_file}" f" -DETHOS_U_NPU_ID={ethos_u_cfg.processor_id}" f" -DETHOS_U_NPU_CONFIG_ID={ethos_u_cfg.config_id}" - " -DTENSORFLOW_LITE_MICRO_CLEAN_DOWNLOADS=ON" + f" {framework_arg}" ) run_command(cmake_command, logpipe, fail_message="Failed to configure the project.") @@ -297,6 +329,13 @@ if __name__ == "__main__": help="Do not run Vela optimizer on downloaded models.", action="store_true", ) + parser.add_argument( + "--ml-framework", + help=f"""Specify the ML framework to use for building the examples. + Valid values are: {valid_ml_frameworks} + """, + default=f'{MLFramework.TENSORFLOW_LITE_MICRO.value}' + ) parser.add_argument( "--npu-config-name", help=f"""Arm Ethos-U configuration to build for. Choose from: @@ -324,7 +363,8 @@ if __name__ == "__main__": run_vela_on_models=not parsed_args.skip_vela, npu_config_name=parsed_args.npu_config_name, make_jobs=parsed_args.make_jobs, - make_verbose=parsed_args.make_verbose + make_verbose=parsed_args.make_verbose, + ml_framework=MLFramework(parsed_args.ml_framework) ) run(build) diff --git a/dependencies/executorch b/dependencies/executorch new file mode 160000 index 0000000000000000000000000000000000000000..ff9fcaa9863f476ef61b391bd219fb95c5756e0e --- /dev/null +++ b/dependencies/executorch @@ -0,0 +1 @@ +Subproject commit ff9fcaa9863f476ef61b391bd219fb95c5756e0e diff --git a/docs/quick_start.md b/docs/quick_start.md index 35a56af3a93a6e42f17e2570653aaef0d4382c92..d9d416b1c3fcb479ba047008462e9f7cfaafa1b1 100644 --- a/docs/quick_start.md +++ b/docs/quick_start.md @@ -44,6 +44,16 @@ To get started quickly, please follow these steps: python3 ./build_default.py --toolchain arm ``` + Similarly, for using LLVM based toolchain like the Arm Toolchain for Embedded: + ```commandline + python3 ./build_default.py --toolchain llvm + ``` + + > By default, the ML framework used is TensorFlow Lite Micro. To build with ExecuTorch instead, use: + > ```commandline + > python3 ./build_default.py --ml-framework executorch + > ``` + 5. Launch the project as explained in the following section: [Deployments](sections/deployment.md#deployment). In quick start guide, we use the keyword spotting application and the FVP. diff --git a/docs/sections/building.md b/docs/sections/building.md index e8bd14c0b367e491ee0a3df8067c8d1d84ca03ee..1313d626c53c7a1a6be39d85dba76be5bfe8efd5 100644 --- a/docs/sections/building.md +++ b/docs/sections/building.md @@ -95,10 +95,10 @@ For ATfE/LLVM: ``` > **Note:** Required version of CMake is also installed in the Python3 virtual environment created by -> `setup_default_resources.py` script. See [Fetching resource files](./building.md#fetching-resource-files) section. +> `set_up_default_resources.py` script. See [Fetching resource files](./building.md#fetching-resource-files) section. > > To add CMake to the PATH on Ubuntu for example, use: `export PATH=/absolute/path/for/cmake/:${PATH}` -> Once `setup_default_resources.py` has been executed, activating the virtual environment will enable you +> Once `set_up_default_resources.py` has been executed, activating the virtual environment will enable you > to use that CMake. Alternatively, from the root of the repository, you could use: > > `export PATH=$(readlink -e resources_downloaded/env/bin):${PATH}` @@ -172,11 +172,11 @@ Please refer to Tensorflow Lite Micro documentation for more info. ## Build options -The project build system allows you to specify custom neural network models (in the `.tflite` format) for each use-case -along with the network inputs. +The project build system allows you to specify custom neural network models for each use-case along with the network +inputs. -It also builds TensorFlow Lite for Microcontrollers library, Arm® *Ethos™-U* NPU driver library, and the CMSIS-DSP library -from sources. +It also builds the chosen ML framework (TensorFlow Lite for Microcontrollers or ExecuTorch) library, +Arm® *Ethos™-U* NPU driver library, and the CMSIS-DSP library from sources. The build script is parameterized to support different options (see [common_opts.cmake](../../scripts/cmake/configuration_options/common_opts.cmake)). Default values for these parameters configure the build for all use-cases to be executed on an MPS3 FPGA or the Fixed Virtual @@ -201,8 +201,21 @@ The build parameters are: build. All the valid toolchain files are located in the scripts directory. For example, see: [bare-metal-gcc.cmake](../../scripts/cmake/toolchains/bare-metal-gcc.cmake). -- `TENSORFLOW_SRC_PATH`: the path to the root of the TensorFlow directory. The default value points to the - `dependencies/tensorflow` git submodule. Repository is hosted here: [tensorflow](https://github.com/tensorflow/tensorflow) +- `ML_FRAMEWORK`: Optional parameter to set the ML framework to be used. Valid options are `TensorFlowLiteMicro` and + `ExecuTorch`. Default value is `TensorFlowLiteMicro`. This option will configure the framework build steps and + include them in the binary tree. All use case examples should advertise which framework they support and only + the examples that support the framework selected will be included in the binary tree. + + > **NOTE**: ExecuTorch support is experimental with known limitations documented + > [here](../../Readme.md#known-limitations-for-experimental-branch). + +- `TENSORFLOW_SRC_PATH`: Path for TensorFlow Lite Micro source tree. Default value points to the + `dependencies/tensorflow` git submodule. Repository is hosted + here: [TensorFlow Lite Micro](https://github.com/tensorflow/tflite-micro.git) + +- `EXECUTORCH_SRC_PATH`: Path for ExecuTorch source tree. Default value points to + `dependencies/executorch` git submodule. Repository is hosted + here: [ExecuTorch](https://github.com/pytorch/executorch) - `ETHOS_U_NPU_DRIVER_SRC_PATH`: The path to the *Ethos-U* NPU core driver sources. The default value points to the `dependencies/core-driver` git submodule. Repository is hosted here: @@ -242,7 +255,7 @@ The build parameters are: However, the user can override these defaults to a configuration ID from `H32`, `H64`, `H256`, `Y512`, `Z128`, `Z512`, `Z1024` and `Z2048`. - > **Note:** This ID is only used to choose which tflite file path is to be used by the CMake + > **Note:** This ID is only used to choose which tflite/pte file path is to be used by the CMake > configuration for all the use cases. If the user has overridden use-case specific model path > parameter `ETHOS_U_NPU_CONFIG_ID` parameter will become irrelevant for that use-case. Also, the > model files for the chosen `ETHOS_U_NPU_CONFIG_ID` are expected to exist in the default locations. @@ -257,11 +270,15 @@ The build parameters are: set to false, but can be turned on for FPGA targets. The FVP and the CPU core cycle counts are **not** meaningful and are not to be used. -- `LOG_LEVEL`: Sets the verbosity level for the output of the application over `UART`, or `stdout`. Valid values are: - `LOG_LEVEL_TRACE`, `LOG_LEVEL_DEBUG`, `LOG_LEVEL_INFO`, `LOG_LEVEL_WARN`, and `LOG_LEVEL_ERROR`. The default is set - to: `LOG_LEVEL_INFO`. +- `MLEK_LOG_ENABLE`: Enables/disables logging for the whole application. Default is set to `ON`, but if this + project is wrapped as a dependency, the definitions provided by logging interface could be overridden or disabled + altogether. + +- `MLEK_LOG_LEVEL`: Sets the verbosity level for the output of the application over `UART`, or `stdout`. Valid values + are: `MLEK_LOG_LEVEL_TRACE`, `MLEK_LOG_LEVEL_DEBUG`, `MLEK_LOG_LEVEL_INFO`, `MLEK_LOG_LEVEL_WARN`, and + `MLEK_LOG_LEVEL_ERROR`. The default is set to: `MLEK_LOG_LEVEL_INFO`. -- `_MODEL_TFLITE_PATH`: The path to the model file that is processed and is included into the application +- `_MODEL_PATH`: The path to the model file that is processed and is included into the application `axf` file. The default value points to one of the delivered set of models. Make sure that the model chosen is aligned with the `ETHOS_U_NPU_ENABLED` setting. @@ -346,7 +363,7 @@ The build process uses three major steps: 2. Configure the build for the platform chosen. This stage includes: - CMake options configuration - - When `_MODEL_TFLITE_PATH` build options are not provided, the default neural network models can be + - When `_MODEL_PATH` build options are not provided, the default neural network models can be downloaded from [Arm ML-Zoo](https://github.com/ARM-software/ML-zoo). For native builds, the network input and output data for tests are downloaded. - Some files such as neural network models, network inputs, and output labels are automatically converted into C/C++ @@ -761,7 +778,7 @@ see section 3.3 in the specific use-case documentation. ## Add custom model -The application performs inference using the model pointed to by the CMake parameter `MODEL_TFLITE_PATH`. +The application performs inference using the model pointed to by the CMake parameter `MODEL_PATH`. > **Note:** If you want to run the model using *Ethos-U* NPU, ensure that your custom model has been run through the > Vela compiler successfully before continuing. @@ -772,12 +789,12 @@ associated with the model. Each line of the file should correspond to one of the outputs in your model. See the provided `labels_mobilenet_v2_1.0_224.txt` file in the `img_class` use-case for an example. -Then, you must set `_MODEL_TFLITE_PATH` to the location of the Vela processed model file and +Then, you must set `_MODEL_PATH` to the location of the Vela processed model file and `_LABELS_TXT_FILE` to the location of the associated labels file (if necessary), like so: ```commandline cmake .. \ - -D_MODEL_TFLITE_PATH= \ + -D_MODEL_PATH= \ -D_LABELS_TXT_FILE= \ -DTARGET_PLATFORM=mps3 \ -DTARGET_SUBSYSTEM=sse-300 \ @@ -788,7 +805,7 @@ cmake .. \ > > **Note:** Clean the build directory before re-running the CMake command. -The TensorFlow Lite for Microcontrollers model pointed to by `_MODEL_TFLITE_PATH` and the labels text file +The TensorFlow Lite for Microcontrollers model pointed to by `_MODEL_PATH` and the labels text file pointed to by `_LABELS_TXT_FILE` are converted to C++ files during the CMake configuration stage. They are then compiled into the application for performing inference with. @@ -796,7 +813,7 @@ The log from the configuration stage tells you what model path and labels file h ```log -- User option TARGET_PLATFORM is set to mps3 --- User option _MODEL_TFLITE_PATH is set to +-- User option _MODEL_PATH is set to ... -- User option _LABELS_TXT_FILE is set to @@ -907,7 +924,7 @@ And the cmake command: ```commandline cmake .. \ -DETHOS_U_NPU_ID=U65 \ - -D_MODEL_TFLITE_PATH= + -D_MODEL_PATH= ``` ## Automatic file generation @@ -934,14 +951,14 @@ For example: -- Generating labels file from /tmp/labels/labels_mobilenet_v2_1.0_224.txt -- writing to /tmp/build/generated/img_class/include/Labels.hpp and /tmp/build/generated/img_class/src/Labels.cc -- User option img_class_ACTIVATION_BUF_SZ is set to 0x00200000 --- User option img_class_MODEL_TFLITE_PATH is set to /tmp/models/mobilenet_v2_1.0_224_INT8.tflite +-- User option img_class_MODEL_PATH is set to /tmp/models/mobilenet_v2_1.0_224_INT8.tflite -- Using /tmp/models/mobilenet_v2_1.0_224_INT8.tflite ++ Converting mobilenet_v2_1.0_224_INT8.tflite to mobilenet_v2_1.0_224_INT8.tflite.cc ... ``` In particular, the building options pointing to the input files `_FILE_PATH`, the model -`_MODEL_TFLITE_PATH`, and labels text file `_LABELS_TXT_FILE` are used by Python scripts in order to +`_MODEL_PATH`, and labels text file `_LABELS_TXT_FILE` are used by Python scripts in order to generate not only the converted array files, but also some headers with utility functions. > **Note**: The utility functions generated for `labels` and the `tflite` files are used directly at application level. @@ -1077,8 +1094,8 @@ generate_labels_code( ... # Generate model file -generate_tflite_code( - MODEL_PATH ${${use_case}_MODEL_TFLITE_PATH} +generate_model_code( + MODEL_PATH ${${use_case}_MODEL_PATH} DESTINATION ${SRC_GEN_DIR} NAMESPACE "arm" "app" "img_class") ``` @@ -1104,8 +1121,8 @@ generate_tflite_code( > "extern const int g_myvariable2 = value2" > ) > -> generate_tflite_code( -> MODEL_PATH ${${use_case}_MODEL_TFLITE_PATH} +> generate_model_code( +> MODEL_PATH ${${use_case}_MODEL_PATH} > DESTINATION ${SRC_GEN_DIR} > EXPRESSIONS ${EXTRA_MODEL_CODE} > NAMESPACE "namespace1" "namespace2" diff --git a/docs/sections/customizing.md b/docs/sections/customizing.md index dde9ee1f0f91ab1d143d0b9be30e2ba8a6e79bdb..2fa24641a4ee06bec5d9c234231bc7ca64bd9b4d 100644 --- a/docs/sections/customizing.md +++ b/docs/sections/customizing.md @@ -160,6 +160,9 @@ profiler.PrintProfilingResult(); ## NN Model API +> **WARNING**: Refers to stable API in main branch. Needs to be revised for ExecuTorch once support for it +> is no longer in experimental state. + The Model, which refers to neural network model, is an abstract class wrapping the underlying TensorFlow Lite Micro API. It provides methods to perform common operations such as TensorFlow Lite Micro framework initialization, inference execution, accessing input, and output tensor objects. @@ -260,6 +263,9 @@ You can now start filling this function with logic. ## Implementing custom NN model +> **WARNING**: Refers to stable API in main branch. Needs to be revised for ExecuTorch once support for it +> is no longer in experimental state. + Before inference could be run with a custom NN model, TensorFlow Lite Micro framework must learn about the operators, or layers, included in the model. You must register operators using the `MicroMutableOpResolver` API. @@ -346,19 +352,19 @@ it: ```cmake # Generate model file -USER_OPTION(${${use_case}_MODEL_TFLITE_PATH} +USER_OPTION(${${use_case}_MODEL_PATH} "NN model tflite path" "Path-to-your-model.tflite" FILEPATH) -generate_tflite_code( - MODEL_PATH ${${use_case}_MODEL_TFLITE_PATH} +generate_model_code( + MODEL_PATH ${${use_case}_MODEL_PATH} DESTINATION ${SRC_GEN_DIR} EXPRESSIONS ${EXTRA_MODEL_CODE} NAMESPACE "arm" "app" "hello_world") ``` -Use the `${use-case}_MODEL_TFLITE_PATH` CMake configuration parameter to include custom model in the generation or +Use the `${use-case}_MODEL_PATH` CMake configuration parameter to include custom model in the generation or compilation process. Please refer to: [Build options](./building.md#build-options) for further information. For more details on `usecase.cmake`, refer to: [Building options](./building.md#build-options). @@ -392,9 +398,12 @@ These functions can now be used in the `Model.Init` call. ## Executing inference +> **WARNING**: Refers to stable API in main branch. Needs to be revised for ExecuTorch once support for it +> is no longer in experimental state. + To run an inference successfully, you must use: -- A TensorFlow Lite model file, +- A TensorFlow Lite or the PTE (for ExecuTorch) model file, - An extended Model class, - A place to add the code to invoke inference, - A main loop function, @@ -411,7 +420,7 @@ images with `generate_images_code` CMake function. > generated C++ sources for images store image data as a `uint8` array. For models that were quantized to an `int8` data > type, convert the image data to `int8` correctly *before* inference execution. Converting asymmetric data to symmetric > data involves positioning the zero value. In other words, subtracting an offset for `uint8` values. Please check the -> image classification application source for the code example, such as the `ConvertImgToInt8` function. +> image classification application source for the code example, such as the `ConvertUint8ToInt8` function. The following code adds inference invocation to the main loop function: @@ -526,7 +535,7 @@ However, for clarity, here is the full list of available functions: - `warn` - printf wrapper for warning messages. - `printf_err` - printf wrapper for error messages. -`printf` wrappers can be switched off with `LOG_LEVEL` define: +`printf` wrappers can be switched off with `MLEK_LOG_LEVEL` define: `trace (0) < debug (1) < info (2) < warn (3) < error (4)`. @@ -588,7 +597,7 @@ in the root of your use-case. However, the name of the file is not important. > the variable name with `${use_case}`, the use-case name, to avoid names collisions with other CMake variables. Here > are some useful variable names visible in use-case CMake file: > -> - `DEFAULT_MODEL_PATH` – The default model path to use if use-case specific `${use_case}_MODEL_TFLITE_PATH` is not set +> - `DEFAULT_MODEL_PATH` – The default model path to use if use-case specific `${use_case}_MODEL_PATH` is not set > in the build arguments. >- `TARGET_NAME` – The name of the executable. > - `use_case` – The name of the current use-case. @@ -614,18 +623,18 @@ endif() This can be used in subsequent section, for example: ```cmake -USER_OPTION(${use_case}_MODEL_TFLITE_PATH "Neural network model in tflite format." +USER_OPTION(${use_case}_MODEL_PATH "Neural network model in tflite format." ${DEFAULT_MODEL_PATH} FILEPATH ) -generate_tflite_code( - MODEL_PATH ${${use_case}_MODEL_TFLITE_PATH} +generate_model_code( + MODEL_PATH ${${use_case}_MODEL_PATH} DESTINATION ${SRC_GEN_DIR} ) ``` -This ensures that the model path pointed to by `${use_case}_MODEL_TFLITE_PATH` is converted to a C++ array and is picked +This ensures that the model path pointed to by `${use_case}_MODEL_PATH` is converted to a C++ array and is picked up by the build system. More information on auto-generations is available under section: [Automatic file generation](./building.md#automatic-file-generation). diff --git a/docs/sections/faq.md b/docs/sections/faq.md index 9b18aa77c094a8fe82a0fd1c26805941d018d1b0..1f0f0a031d38db3c235bfb7822f7134c87677cd8 100644 --- a/docs/sections/faq.md +++ b/docs/sections/faq.md @@ -31,14 +31,14 @@ If the problem persist contact your Arm representative or raise an issue at ---- -**Q: When pointing to the TensorFlow Lite file explicitly in the cmake command, I get the following error message:** +**Q: When pointing to the TensorFlow Lite file explicitly in the CMake command, I get the following error message:** ```log -CMake Error at scripts/cmake/util_functions.cmake:73 (message): Invalid file path. -Description: NN models file to be used in the evaluation application. Model files must be in tflite format. +CMake Error at scripts/cmake/util_functions.cmake:76 (message): Invalid file path. +Description: Neural network model file to be used in the evaluation application. ``` -**A:** This issue is usually caused by an incorrect path to the model file, pointed by the `-D_MODEL_TFLITE_PATH` +**A:** This issue is usually caused by an incorrect path to the model file, pointed by the `-D_MODEL_PATH` parameter. Check that the path is correct, clean the build folder and re-run the `cmake` command. ---- diff --git a/docs/sections/memory_considerations.md b/docs/sections/memory_considerations.md index 5f479001f171f7eaa3612afa7c3887f14e738be5..14c10380c6b6f027d106080ad51253e9802daf6c 100644 --- a/docs/sections/memory_considerations.md +++ b/docs/sections/memory_considerations.md @@ -26,8 +26,8 @@ frequent read and write traffic. The second interface is expected to have a higher-latency, lower-bandwidth characteristic, and is typically wired to a flash device servicing read-only traffic. In this configuration, the Arm® *Cortex™-M55* CPU and Arm® *Ethos™-U* NPU -read the contents of the neural network model, or the `.tflite` file, from the flash memory region. With the Arm® -*Ethos™-U* NPU requesting these read transactions over its second AXI bus. +read the contents of the neural network model from the flash memory region. With the Arm® *Ethos™-U* NPU requesting +these read transactions over its second AXI bus. The input and output tensors, along with any intermediate computation buffers, are placed on SRAM. Therefore, both the Arm® *Cortex™-M55* CPU and Arm® *Ethos™-U* NPU would be reading, or writing, to this region when running an inference. diff --git a/docs/sections/troubleshooting.md b/docs/sections/troubleshooting.md index 1b09d4b03e95dca8062ccbe0c32bbd5987e369db..0f410d84ae4b1a16a0b5d87669549a55389288d0 100644 --- a/docs/sections/troubleshooting.md +++ b/docs/sections/troubleshooting.md @@ -25,10 +25,14 @@ set accordingly. More information on these cmake parameters is detailed in their ## The application does not work with my custom model -Ensure that your model is in a fully quantized `.tflite` file format, either `uint8` or `int8`, and that it has -successfully been run through the Vela compiler. +For TensorFlow Lite Micro, ensure that your model is in a fully quantized `.tflite` file format, either `uint8` or +`int8`, and that it has successfully been run through the Vela compiler. For ExecuTorch ensure the model has been +generated using the Arm AOT compiler (which calls Vela) with `--quantize` and `--delegate` options passed to it and the +right target selected with `--target `. See the AOT compiler script +[aot_arm_compiler.py](https://github.com/pytorch/executorch/blob/v0.6.0-rc2/examples/arm/aot_arm_compiler.py) +in ExecuTorch source tree for more details. -Also, please check that the cmake parameters used match the input requirements of your new model. +Also, please check that the CMake parameters used match the input requirements of your new model. > **Note:** The Vela tool is not available within this software project. It is a separate Python tool that is available > from: . The source code is hosted on @@ -45,10 +49,13 @@ ERROR - Invoke failed. ERROR - Inference failed. ``` -It shows that the configuration of the Vela compiled `.tflite` file doesn't match the number of MACs units on the FVP. +It shows that the configuration of model file doesn't match the number of MACs units on the FVP. + +For TensorFlow Lite Micro, the Vela configuration parameter `accelerator-config` used for producing the .`tflite` file +that is used while building the application should match the MACs configuration that the FVP is emulating. +For ExecuTorch, the Arm AOT compiler `--target` parameter determines the number of MACs the resulting model will run +with. -The Vela configuration parameter `accelerator-config` used for producing the .`tflite` file that is used -while building the application should match the MACs configuration that the FVP is emulating. For example, if the `accelerator-config` from the Vela command was `ethos-u55-128`, the FVP should be emulating the 128 MACs configuration of the Ethos™-U55 block(default FVP configuration). If the `accelerator-config` used was `ethos-u55-256`, the FVP must be executed with additional command line parameter to instruct it to emulate the diff --git a/docs/use_cases/ad.md b/docs/use_cases/ad.md index 6ab085e9b3119135f3f9a44c66cdc66b2ed851a1..b16f3434e31b1fb4ef9aeb6f91d6c0b5f1f2ac7b 100644 --- a/docs/use_cases/ad.md +++ b/docs/use_cases/ad.md @@ -21,6 +21,8 @@ This document describes the process of setting up and running the Arm® *Ethos Use-case code could be found in the following directory: [source/use_case/ad](../../source/use_case/ad). +> **NOTE**: This use case only supports `TensorFlow Lite Micro`. + ### Preprocessing and feature extraction The Anomaly Detection model that is used with the Code Samples and expects audio data to be preprocessed in a specific @@ -62,7 +64,7 @@ See [Prerequisites](../documentation.md#prerequisites) In addition to the already specified build option in the main documentation, the Anomaly Detection use-case adds: -- `ad_MODEL_TFLITE_PATH` - Path to the NN model file in the `TFLite` format. The model is then processed and included in +- `ad_MODEL_PATH` - Path to the NN model file in the `TFLite` format. The model is then processed and included in the application `axf` file. The default value points to one of the delivered set of models. Note that the parameters `ad_LABELS_TXT_FILE`, `TARGET_PLATFORM`, and `ETHOS_U_NPU_ENABLED` must be aligned with the @@ -220,7 +222,7 @@ After compiling, your custom inputs have now replaced the default ones in the ap ### Add custom model -The application performs inference using the model pointed to by the CMake parameter ``ad_MODEL_TFLITE_PATH``. +The application performs inference using the model pointed to by the CMake parameter ``ad_MODEL_PATH``. > **Note:** If you want to run the model using an *Ethos-U*, ensure that your custom model has been successfully run > through the Vela compiler *before* continuing. Please refer to this section for more help: @@ -230,20 +232,20 @@ For example: ```commandline cmake .. \ - -Dad_MODEL_TFLITE_PATH= \ + -Dad_MODEL_PATH= \ -DUSE_CASE_BUILD=ad ``` > **Note:** Clean the build directory before re-running the CMake command. -The `.tflite` model file pointed to by `ad_MODEL_TFLITE_PATH` is converted to C++ files during the CMake configuration +The `.tflite` model file pointed to by `ad_MODEL_PATH` is converted to C++ files during the CMake configuration stage and is then compiled into the application for performing inference with. The log from the configuration stage tells you what model path has been used. For example: ```log -- User option TARGET_PLATFORM is set to fastmodel --- User option ad_MODEL_TFLITE_PATH is set to +-- User option ad_MODEL_PATH is set to ... -- Using ++ Converting custom_ad_model_after_vela.tflite to custom_ad_model_after_vela.tflite.cc @@ -302,73 +304,10 @@ This also launches a telnet window with the standard output of the sample applic entries containing information about the pre-built application version, TensorFlow Lite Micro library version used, and data types. The log also includes the input and output tensor sizes of the model compiled into the executable binary. -After the application has started, if `ad_FILE_PATH` points to a single file, or even a folder that contains a single -input file, then the inference starts immediately. If there are multiple inputs, it outputs a menu and then waits for -input from the user. For example: - -```log -User input required -Enter option number from: - - 1. Classify next audio signal - 2. Classify audio signal at chosen index - 3. Run classification on all audio signals - 4. Show NN model info - 5. List audio signals - - Choice: - -``` - -What the preceding choices do: - -1. Classify next audio clip: Runs a single inference on the next in line. - -2. Classify audio clip at chosen index: Runs inference on the chosen audio clip. - - > **Note:** Please make sure to select audio clip index within the range of supplied audio clips during application - > build. By default, the pre-built application has one file with index `0`. - -3. Run ... on all: Triggers sequential inference executions on all built-in applications. - -4. Show NN model info: Prints information about the model data type, input, and output, tensor sizes: - - ```log - INFO - Model info: - INFO - Model INPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 1024 bytes with dimensions - INFO - 0: 1 - INFO - 1: 32 - INFO - 2: 32 - INFO - 3: 1 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.192437 - INFO - ZeroPoint[0] = 11 - INFO - Model OUTPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 8 bytes with dimensions - INFO - 0: 1 - INFO - 1: 8 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.048891 - INFO - ZeroPoint[0] = -30 - INFO - Activation buffer (a.k.a tensor arena) size used: 321252 - INFO - Number of operators: 1 - INFO - Operator 0: ethos-u - ``` - -5. List: Prints a list of pair ... indexes. The original filenames are embedded in the application, like so: - - ```log - INFO - List of Files: - INFO - 0 =>; random_id_00_000000.wav - ``` +After the application has started inferences are executed on inputs from `ad_FILE_PATH`. ### Running Anomaly Detection -Please select the first menu option to execute the Anomaly Detection. - The following example illustrates the output of an application: ```log diff --git a/docs/use_cases/asr.md b/docs/use_cases/asr.md index f85781272dc5e6418d60ebf72f5fabb94fa3ba8e..f0312fa467464931a964eb4955aba3b817ed4422 100644 --- a/docs/use_cases/asr.md +++ b/docs/use_cases/asr.md @@ -22,6 +22,8 @@ example. Use-case code could be found in the following directory: [source/use_case/asr](../../source/use_case/asr). +> **NOTE**: This use case only supports `TensorFlow Lite Micro`. + ### Preprocessing and feature extraction The *wav2letter* automatic speech recognition model that is used with the code samples, expects audio data to be @@ -108,7 +110,7 @@ See [Prerequisites](../documentation.md#prerequisites) In addition to the already specified build option in the main documentation, the Automatic Speech Recognition use-case adds: -- `asr_MODEL_TFLITE_PATH` - The path to the NN model file in `TFLite` format. The model is processed and then included +- `asr_MODEL_PATH` - The path to the NN model file in `TFLite` format. The model is processed and then included into the application `axf` file. The default value points to one of the delivered set of models. Note that the parameters `asr_LABELS_TXT_FILE`,`TARGET_PLATFORM`, and `ETHOS_U_NPU_ENABLED` must be aligned with the chosen model. In other words: @@ -267,7 +269,7 @@ After compiling, your custom inputs have now replaced the default ones in the ap ### Add custom model -The application performs inference using the model pointed to by the CMake parameter `MODEL_TFLITE_PATH`. +The application performs inference using the model pointed to by the CMake parameter `MODEL_PATH`. > **Note:** If you want to run the model using an *Ethos-U*, ensure that your custom model has been successfully run > through the Vela compiler *before* continuing. @@ -278,21 +280,21 @@ To run the application with a custom model, you must provide a `labels_ \ + -Dasr_MODEL_PATH= \ -Dasr_LABELS_TXT_FILE= \ -DUSE_CASE_BUILD=asr ``` > **Note:** Clean the build directory before re-running the CMake command. -The `.tflite` model file pointed to by `asr_MODEL_TFLITE_PATH`, and the labels text file pointed to by +The `.tflite` model file pointed to by `asr_MODEL_PATH`, and the labels text file pointed to by `asr_LABELS_TXT_FILE` are converted to C++ files during the CMake configuration stage. They are then compiled into the application for performing inference with. @@ -300,7 +302,7 @@ The log from the configuration stage tells you what model path and labels file h ```log -- User option TARGET_PLATFORM is set to mps3 --- User option asr_MODEL_TFLITE_PATH is set to +-- User option asr_MODEL_PATH is set to ... -- User option asr_LABELS_TXT_FILE is set to ... @@ -360,79 +362,10 @@ This also launches a telnet window with the standard output of the sample applic entries containing information about the pre-built application version, TensorFlow Lite Micro library version used, and data types. The log also includes the input and output tensor sizes of the model compiled into the executable binary. -After the application has started, if `asr_FILE_PATH` points to a single file, or even a folder that contains a single -input file, then the inference starts immediately. If there are multiple inputs, it outputs a menu and then waits for -input from the user. - -For example: - -```log -User input required -Enter option number from: - -1. Classify next audio clip -2. Classify audio clip at chosen index -3. Run classification on all audio clips -4. Show NN model info -5. List audio clips - -Choice: - -``` - -What the preceding choices do: - -1. Classify next audio clip: Runs a single inference on the next in line. - -2. Classify audio clip at chosen index: Runs inference on the chosen audio clip. - - > **Note:** Please make sure to select audio clip index within the range of supplied audio clips during application - > build. By default, a pre-built application has four files, with indexes from `0` to `3`. - -3. Run ... on all: Triggers sequential inference executions on all built-in applications. - -4. Show NN model info: Prints information about the model data type, input, and output, tensor sizes: - - ```log - INFO - Model info: - INFO - Model INPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 11544 bytes with dimensions - INFO - 0: 1 - INFO - 1: 296 - INFO - 2: 39 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.110316 - INFO - ZeroPoint[0] = -11 - INFO - Model OUTPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 4292 bytes with dimensions - INFO - 0: 1 - INFO - 1: 1 - INFO - 2: 148 - INFO - 3: 29 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.003906 - INFO - ZeroPoint[0] = -128 - INFO - Activation buffer (a.k.a tensor arena) size used: 783168 - INFO - Number of operators: 1 - INFO - Operator 0: ethos-u - ``` - -5. List audio clips: Prints a list of pair ... indexes. The original filenames are embedded in the application, like so: - - ```log - [INFO] List of Files: - [INFO] 0 => another_door.wav - [INFO] 1 => another_engineer.wav - [INFO] 2 => i_tell_you.wav - [INFO] 3 => testing_routine.wav - ``` +After the application has started inferences are executed on inputs from `asr_FILE_PATH`. ### Running Automatic Speech Recognition -Please select the first menu option to execute Automatic Speech Recognition. - The following example illustrates the output of an application: ```log diff --git a/docs/use_cases/img_class.md b/docs/use_cases/img_class.md index 7db6e395b08d3a183ac8c662479fded1b3ef9e1c..b472069ba53296ab1b1f84782bb8b99c8d97e373 100644 --- a/docs/use_cases/img_class.md +++ b/docs/use_cases/img_class.md @@ -20,7 +20,11 @@ This document describes the process of setting up and running the Arm® *Ethos This use-case example solves the classical computer vision problem of image classification. The ML sample was developed using the *MobileNet v2* model that was trained on the *ImageNet* dataset. -Use-case code could be found in the following directory:[source/use_case/img_class](../../source/use_case/img_class). +Use-case code could be found in the following directory: [source/use_case/img_class](../../source/use_case/img_class). + +> **NOTE**: This use case supports `TensorFlow Lite Micro` by default. It also has experimental support for +> `ExecuTorch` which can be enabled by providing `-DML_FRAMEWORK=ExecuTorch` to the CMake project configuration. +> See limitations of `ExecuTorch` support [here](../../Readme.md#known-limitations-for-experimental-branch). ### Prerequisites @@ -33,8 +37,9 @@ See [Prerequisites](../documentation.md#prerequisites) In addition to the already specified build option in the main documentation, the Image Classification use-case specifies: -- `img_class_MODEL_TFLITE_PATH` - The path to the NN model file in the `TFLite` format. The model is then processed and - included in the application `axf` file. The default value points to one of the delivered set of models. +- `img_class_MODEL_PATH` - The path to the NN model file in `TFLite` (for TensorFlow Lite Micro) or + `PTE` (for ExecuTorch) format. The model is then processed and included in the application `axf` file. + The default value points to one of the delivered set of models. Note that the parameters `img_class_LABELS_TXT_FILE`,`TARGET_PLATFORM`, and `ETHOS_U_NPU_ENABLED` must be aligned with the chosen model. In other words: @@ -191,7 +196,7 @@ of any image does not match `IMAGE_SIZE`, then it is rescaled and padded so that ### Add custom model -The application performs inference using the model pointed to by the CMake parameter `MODEL_TFLITE_PATH`. +The application performs inference using the model pointed to by the CMake parameter `MODEL_PATH`. > **Note:** If you want to run the model using an *Ethos-U*, ensure that your custom model has been successfully run > through the Vela compiler *before* continuing. @@ -203,28 +208,38 @@ associated with the model. Each line of the file must correspond to one of the o Refer to the provided `labels_mobilenet_v2_1.0_224.txt` file for an example. -Then, you must set `img_class_MODEL_TFLITE_PATH` to the location of the Vela processed model file and +Then, you must set `img_class_MODEL_PATH` to the location of the Vela processed model file and `img_class_LABELS_TXT_FILE` to the location of the associated labels file. -For example: +For example, a TensorFlow Lite Micro based build tree can be generated by: + +```commandline +cmake .. \ + -Dimg_class_MODEL_PATH= \ + -Dimg_class_LABELS_TXT_FILE= \ + -DUSE_CASE_BUILD=img_class +``` + +Or for ExecuTorch, it should be: ```commandline cmake .. \ - -Dimg_class_MODEL_TFLITE_PATH= \ + -DML_FRAMEWORK=ExecuTorch \ + -Dimg_class_MODEL_PATH= \ -Dimg_class_LABELS_TXT_FILE= \ -DUSE_CASE_BUILD=img_class ``` > **Note:** Clean the build directory before re-running the CMake command. -The `.tflite` model file pointed to by `img_class_MODEL_TFLITE_PATH`, and the labels text file pointed to by +The `.tflite` or `.pte` model file pointed to by `img_class_MODEL_PATH`, and the labels text file pointed to by `img_class_LABELS_TXT_FILE` are converted to C++ files during the CMake configuration stage. They are then compiled into the application for performing inference with. The log from the configuration stage tells you what model path and labels file have been used, for example: ```log --- User option img_class_MODEL_TFLITE_PATH is set to +-- User option img_class_MODEL_PATH is set to ... -- User option img_class_LABELS_TXT_FILE is set to ... @@ -285,73 +300,6 @@ This also launches a telnet window with the standard output of the sample applic entries containing information about the pre-built application version, TensorFlow Lite Micro library version used, and data types. The log also includes the input and output tensor sizes of the model compiled into the executable binary. -After the application has started, if `img_class_FILE_PATH` points to a single file, or even a folder that contains a -single image, then the inference starts immediately. If there are multiple inputs, it outputs a menu and then waits for -input from the user: - -```log -User input required -Enter option number from: - - 1. Classify next ifm - 2. Classify ifm at chosen index - 3. Run classification on all ifm - 4. Show NN model info - 5. List ifm - -Choice: - -``` - -What the preceding choices do: - -1. Classify next image: Runs a single inference on the next in line image from the collection of the compiled images. - -2. Classify image at chosen index: Runs inference on the chosen image. - - > **Note:** Please make sure to select image index from within the range of supplied audio clips during application - > build. By default, a pre-built application has four images, with indexes from `0` to `3`. - -3. Run classification on all images: Triggers sequential inference executions on all built-in images. - -4. Show NN model info: Prints information about the model data type, input, and output, tensor sizes: - - ```log - INFO - Model info: - INFO - Model INPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 150528 bytes with dimensions - INFO - 0: 1 - INFO - 1: 224 - INFO - 2: 224 - INFO - 3: 3 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.007843 - INFO - ZeroPoint[0] = -1 - INFO - Model OUTPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 1001 bytes with dimensions - INFO - 0: 1 - INFO - 1: 1001 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.03906 - INFO - ZeroPoint[0] = -128 - INFO - Activation buffer (a.k.a tensor arena) size used: 1510004 - INFO - Number of operators: 1 - INFO - Operator 0: ethos-u - - ``` - -5. List Images: Prints a list of pair image indexes. The original filenames are embedded in the application, like so: - - ```log - INFO - List of Files: - INFO - 0 => cat.bmp - INFO - 1 => dog.bmp - INFO - 2 => kimono.bmp - INFO - 3 => tiger.bmp - ``` - ### Running Image Classification Please select the first menu option to execute Image Classification. diff --git a/docs/use_cases/inference_runner.md b/docs/use_cases/inference_runner.md index 8511fe1e05b6c5db0812c6e89ba57a1d072649da..0b3fdc51b4888e118163a3cecd5beef6f87afd7b 100644 --- a/docs/use_cases/inference_runner.md +++ b/docs/use_cases/inference_runner.md @@ -32,6 +32,8 @@ then performed. Profiling results are then displayed in the console. The example use-case code can be found in the following directory: [source/use_case/inference_runner](../../source/use_case/inference_runner). +> **NOTE**: This use case only supports `TensorFlow Lite Micro`. + ### Prerequisites See [Prerequisites](../documentation.md#prerequisites) @@ -43,7 +45,7 @@ See [Prerequisites](../documentation.md#prerequisites) In addition to the already specified build option in the main documentation, the Inference Runner use-case adds the following: -- `inference_runner_MODEL_TFLITE_PATH` - The path to the NN model file in the `TFLite` format. The model is then +- `inference_runner_MODEL_PATH` - The path to the NN model file in the `TFLite` format. The model is then processed and included in the application `axf` file. The default value points to one of the delivered set of models. Note that the parameters `TARGET_PLATFORM` and `ETHOS_U_NPU_ENABLED` must be aligned with the chosen model. In other @@ -139,32 +141,32 @@ The `bin` folder contains the following files: ### Add custom model The application performs inference using the model pointed to by the CMake parameter -`inference_runner_MODEL_TFLITE_PATH`. +`inference_runner_MODEL_PATH`. > **Note:** If you want to run the model using an *Ethos-U*, ensure that your custom model has been successfully run > through the Vela compiler *before* continuing. For further information: [Optimize model with Vela compiler](../sections/building.md#Optimize-custom-model-with-Vela-compiler). -Then, you must set `inference_runner_MODEL_TFLITE_PATH` to the location of the Vela processed model file. +Then, you must set `inference_runner_MODEL_PATH` to the location of the Vela processed model file. An example: ```commandline cmake .. \ - -Dinference_runner_MODEL_TFLITE_PATH= \ + -Dinference_runner_MODEL_PATH= \ -DUSE_CASE_BUILD=inference_runner ``` > **Note:** Clean the build directory before re-running the CMake command. -The `.tflite` model file pointed to by `inference_runner_MODEL_TFLITE_PATH` is converted to C++ files during the CMake +The `.tflite` model file pointed to by `inference_runner_MODEL_PATH` is converted to C++ files during the CMake configuration stage. It is then compiled into the application for performing inference with. The log from the configuration stage tells you what model path and labels file have been used, for example: ```stdout --- User option inference_runner_MODEL_TFLITE_PATH is set to +-- User option inference_runner_MODEL_PATH is set to ... -- Using ++ Converting custom_model_after_vela.tflite to\ diff --git a/docs/use_cases/kws.md b/docs/use_cases/kws.md index bda22bf2a407ed6ff21ca8e32db3b70b670a3ed0..8dc99961e76affb023af4bc75d55881dbd17ace4 100644 --- a/docs/use_cases/kws.md +++ b/docs/use_cases/kws.md @@ -21,6 +21,8 @@ This document describes the process of setting up and running the Arm® *Ethos Use-case code could be found in the following directory: [source/use_case/kws](../../source/use_case/kws). +> **NOTE**: This use case only supports `TensorFlow Lite Micro`. + ### Preprocessing and feature extraction The `MicroNet` keyword spotting model that is used with the Code Samples expects audio data to be preprocessed in a @@ -76,7 +78,7 @@ See [Prerequisites](../documentation.md#prerequisites) In addition to the already specified build option in the main documentation, the Keyword Spotting use-case adds: -- `kws_MODEL_TFLITE_PATH` - The path to the NN model file in `TFLite` format. The model is processed and then included +- `kws_MODEL_PATH` - The path to the NN model file in `TFLite` format. The model is processed and then included into the application `axf` file. The default value points to one of the delivered set of models. Note that the parameters `kws_LABELS_TXT_FILE`,`TARGET_PLATFORM`, and `ETHOS_U_NPU_ENABLED` must be aligned with the chosen model. In other words: @@ -237,7 +239,7 @@ After compiling, your custom inputs have now replaced the default ones in the ap ### Add custom model -The application performs inference using the model pointed to by the CMake parameter `kws_MODEL_TFLITE_PATH`. +The application performs inference using the model pointed to by the CMake parameter `kws_MODEL_PATH`. > **Note:** If you want to run the model using an *Ethos-U*, ensure that your custom model has been successfully run > through the Vela compiler *before* continuing. @@ -248,28 +250,28 @@ To run the application with a custom model, you must provide a `labels_ \ + -Dkws_MODEL_PATH= \ -Dkws_LABELS_TXT_FILE= \ -DUSE_CASE_BUILD=kws ``` > **Note:** Clean the build directory before re-running the CMake command. -The `.tflite` model file pointed to by `kws_MODEL_TFLITE_PATH` and labels text file pointed to by `kws_LABELS_TXT_FILE` +The `.tflite` model file pointed to by `kws_MODEL_PATH` and labels text file pointed to by `kws_LABELS_TXT_FILE` are converted to C++ files during the CMake configuration stage. They are then compiled into the application for performing inference with. The log from the configuration stage tells you what model path and labels file have been used, for example: ```log --- User option kws_MODEL_TFLITE_PATH is set to +-- User option kws_MODEL_PATH is set to ... -- User option kws_LABELS_TXT_FILE is set to ... @@ -329,78 +331,10 @@ This also launches a telnet window with the standard output of the sample applic entries containing information about the pre-built application version, TensorFlow Lite Micro library version used, and data types. The log also includes the input and output tensor sizes of the model compiled into the executable binary. -After the application has started, if `kws_FILE_PATH` points to a single file, or even a folder that contains a single -input file, then the inference starts immediately. If there are multiple inputs, it outputs a menu and then waits for -input from the user. - -For example: - -```log -User input required -Enter option number from: - -1. Classify next audio clip -2. Classify audio clip at chosen index -3. Run classification on all audio clips -4. Show NN model info -5. List audio clips - -Choice: - -``` - -What the preceding choices do: - -1. Classify next audio clip: Runs a single inference on the next in line. - -2. Classify audio clip at chosen index: Runs inference on the chosen audio clip. - - > **Note:** Please make sure to select audio clip index within the range of supplied audio clips during application - > build. By default, a pre-built application has four files, with indexes from `0` to `3`. - -3. Run ... on all: Triggers sequential inference executions on all built-in applications. - -4. Show NN model info: Prints information about the model data type, input, and output, tensor sizes: - - ```log - INFO - Model info: - INFO - Model INPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 490 bytes with dimensions - INFO - 0: 1 - INFO - 1: 49 - INFO - 2: 10 - INFO - 3: 1 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.201095 - INFO - ZeroPoint[0] = -5 - INFO - Model OUTPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 12 bytes with dimensions - INFO - 0: 1 - INFO - 1: 12 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.056054 - INFO - ZeroPoint[0] = -54 - INFO - Activation buffer (a.k.a tensor arena) size used: 127068 - INFO - Number of operators: 0 - INFO - Operator 0: ethos-u - ``` - -5. List audio clips: Prints a list of pair ... indexes. The original filenames are embedded in the application, like so: - - ```log - [INFO] List of Files: - [INFO] 0 => down.wav - [INFO] 1 => right_left_up.wav - [INFO] 2 => yes.wav - [INFO] 3 => yes_no_go_stop.wav - ``` +After the application has started inferences are executed on inputs from `kws_FILE_PATH`. ### Running Keyword Spotting -Please select the first menu option to execute inference on the first file. - The following example illustrates the output for classification: ```log diff --git a/docs/use_cases/kws_asr.md b/docs/use_cases/kws_asr.md index d8b2fee1ad41e766a1e9e2a8d7d89221857e45ac..75198436fc373e29bea0fbd7d61c95597f6ab5c1 100644 --- a/docs/use_cases/kws_asr.md +++ b/docs/use_cases/kws_asr.md @@ -33,6 +33,8 @@ The `Yes` keyword is used to trigger full command recognition following the keyw Use-case code could be found in the following directory: [source/use_case/kws_asr](../../source/use_case/kws_asr). +> **NOTE**: This use case only supports `TensorFlow Lite Micro`. + ### Preprocessing and feature extraction In this use-case, there are two different models being used with different requirements for preprocessing. As such, each @@ -163,7 +165,7 @@ See [Prerequisites](../documentation.md#prerequisites) In addition to the already specified build option in the main documentation, the Keyword Spotting and Automatic Speech Recognition use-case adds: -- `kws_asr_MODEL_TFLITE_PATH_ASR` and `kws_asr_MODEL_TFLITE_PATH_KWS`: The path to the NN model file in `TFLite` format. +- `kws_asr_MODEL_PATH_ASR` and `kws_asr_MODEL_PATH_KWS`: The path to the NN model file in `TFLite` format. The model is processed and then included into the application `axf` file. The default value points to one of the delivered set of models. Note that the parameters `kws_asr_LABELS_TXT_FILE_KWS`, `kws_asr_LABELS_TXT_FILE_ASR`,`TARGET_PLATFORM`, and `ETHOS_U_NPU_ENABLED` must be aligned with the chosen model. In @@ -322,8 +324,8 @@ After compiling, your custom inputs have now replaced the default ones in the ap ### Add custom model The application performs KWS inference using the model pointed to by the CMake parameter -`kws_asr_MODEL_TFLITE_PATH_KWS`. ASR inference is performed using the model pointed to by the CMake parameter -`kws_asr_MODEL_TFLITE_PATH_ASR`. +`kws_asr_MODEL_PATH_KWS`. ASR inference is performed using the model pointed to by the CMake parameter +`kws_asr_MODEL_PATH_ASR`. This section assumes you want to change the existing ASR model to a custom one. If, instead, you want to change the KWS model, then the instructions are the same. Except ASR changes to KWS. @@ -338,21 +340,21 @@ To run the application with a custom model, you must provide a `labels_ \ + -Dkws_asr_MODEL_PATH_ASR= \ -Dkws_asr_LABELS_TXT_FILE_ASR= \ -DUSE_CASE_BUILD=kws_asr ``` > **Note:** Clean the build directory before re-running the CMake command. -The `.tflite` model files pointed to by `kws_asr_MODEL_TFLITE_PATH_KWS` and `kws_asr_MODEL_TFLITE_PATH_ASR`, and the +The `.tflite` model files pointed to by `kws_asr_MODEL_PATH_KWS` and `kws_asr_MODEL_PATH_ASR`, and the labels text files pointed to by `kws_asr_LABELS_TXT_FILE_KWS` and `kws_asr_LABELS_TXT_FILE_ASR` are converted to C++ files during the CMake configuration stage. They are then compiled into the application for performing inference with. @@ -360,7 +362,7 @@ The log from the configuration stage tells you what model path and labels file h ```log -- User option TARGET_PLATFORM is set to mps3 --- User option kws_asr_MODEL_TFLITE_PATH_ASR is set to +-- User option kws_asr_MODEL_PATH_ASR is set to ... -- User option kws_asr_LABELS_TXT_FILE_ASR is set to ... @@ -420,98 +422,10 @@ This also launches a telnet window with the standard output of the sample applic entries containing information about the pre-built application version, TensorFlow Lite Micro library version used, and data types. The log also includes the input and output tensor sizes of the model compiled into the executable binary. -After the application has started, if `kws_asr_FILE_PATH` points to a single file, or even a folder that contains a -single input file, then the inference starts immediately. If there are multiple inputs, it outputs a menu and then waits -for input from the user. - -For example: - -```log -User input required -Enter option number from: - -1. Classify next audio clip -2. Classify audio clip at chosen index -3. Run classification on all audio clips -4. Show NN model info -5. List audio clips - -Choice: - -``` - -What the preceding choices do: - -1. Classify next audio clip: Runs a single inference on the next in line. - -2. Classify audio clip at chosen index: Runs inference on the chosen audio clip. - - > **Note:** Please make sure to select audio clip index within the range of supplied audio clips during application - > build. By default, a pre-built application has four files, with indexes from `0` to `3`. - -3. Run ... on all: Triggers sequential inference executions on all built-in applications. - -4. Show NN model info: Prints information about the model data type, input, and output, tensor sizes: - - ```log - INFO - Model info: - INFO - Model INPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 490 bytes with dimensions - INFO - 0: 1 - INFO - 1: 49 - INFO - 2: 10 - INFO - 3: 1 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.201095 - INFO - ZeroPoint[0] = -5 - INFO - Model OUTPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 12 bytes with dimensions - INFO - 0: 1 - INFO - 1: 12 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.056054 - INFO - ZeroPoint[0] = -54 - INFO - Activation buffer (a.k.a tensor arena) size used: 127068 - INFO - Number of operators: 1 - INFO - Operator 0: ethos-u - - INFO - Model INPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 11544 bytes with dimensions - INFO - 0: 1 - INFO - 1: 296 - INFO - 2: 39 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.110316 - INFO - ZeroPoint[0] = -11 - INFO - Model OUTPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 4292 bytes with dimensions - INFO - 0: 1 - INFO - 1: 1 - INFO - 2: 148 - INFO - 3: 29 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.003906 - INFO - ZeroPoint[0] = -128 - INFO - Activation buffer (a.k.a tensor arena) size used: 4184332 - INFO - Number of operators: 1 - INFO - Operator 0: ethos-u - ``` - -5. List audio clips: Prints a list of pair ... indexes. The original filenames are embedded in the application, like so: - - ```log - [INFO] List of Files: - [INFO] 0 => yes_no_go_stop.wav - ``` +After the application has started inferences are executed on inputs from `kws_asr_FILE_PATH` . ### Running Keyword Spotting and Automatic Speech Recognition -Please select the first menu option to execute Keyword Spotting and Automatic Speech Recognition. - The following example illustrates the output of an application: ```log diff --git a/docs/use_cases/noise_reduction.md b/docs/use_cases/noise_reduction.md index b015492b5419b25b0ca6609ddbe0824dc25c8e80..f7090448582fbfbc7773a9d680a83428eaebc623 100644 --- a/docs/use_cases/noise_reduction.md +++ b/docs/use_cases/noise_reduction.md @@ -24,6 +24,8 @@ example. Use case code is stored in the following directory: [source/use_case/noise_reduction](../../source/use_case/noise_reduction). +> **NOTE**: This use case only supports `TensorFlow Lite Micro`. + ## How the default neural network model works Instead of replicating a "noisy audio in" and "clean audio out" problem, a simpler version is @@ -140,7 +142,7 @@ See [Prerequisites](../documentation.md#prerequisites) In addition to the already specified build option in the main documentation, keyword spotting use case adds: -- `noise_reduction_MODEL_TFLITE_PATH` - The path to the NN model file in *TFLite* format. The model +- `noise_reduction_MODEL_PATH` - The path to the NN model file in *TFLite* format. The model is processed and is included in the application axf file. The default value points to one of the delivered set of models. Note that the parameter `ETHOS_U_NPU_ENABLED` must be aligned with the chosen model. Therefore: @@ -266,7 +268,7 @@ cmake .. \ ### Add custom model The application performs inference using the model pointed to by the CMake parameter -`noise_reduction_MODEL_TFLITE_PATH`. +`noise_reduction_MODEL_PATH`. > **Note:** If you want to run the model using *Ethos-U* ensure that your custom model has been > run through the Vela compiler successfully before continuing. @@ -277,7 +279,7 @@ An example: ```commandline cmake .. \ - -Dnoise_reduction_MODEL_TFLITE_PATH= \ + -Dnoise_reduction_MODEL_PATH= \ -DUSE_CASE_BUILD=noise_reduction ``` @@ -287,14 +289,14 @@ cmake .. \ > > **Note:** Before re-running the CMake command, clean the build directory. -The `.tflite` model file, which is pointed to by `noise_reduction_MODEL_TFLITE_PATH`, is converted +The `.tflite` model file, which is pointed to by `noise_reduction_MODEL_PATH`, is converted to C++ files during the CMake configuration stage. It is then compiled into the application for performing inference with. To see which model path was used, inspect the configuration stage log: ```log --- User option noise_reduction_MODEL_TFLITE_PATH is set to +-- User option noise_reduction_MODEL_PATH is set to ... -- Using ++ Converting custom_model_after_vela.tflite to custom_model_after_vela.tflite.cc @@ -350,134 +352,10 @@ includes error log entries containing information about the pre-built applicatio TensorFlow Lite Micro library version used, and the data type. As well as the input and output tensor sizes of the model that was compiled into the executable binary. -After the application has started, if `noise_reduction_FILE_PATH` pointed to a single file (or a -folder containing a single input file), then the inference starts immediately. If multiple inputs -are chosen, then a menu is output and waits for the user input from telnet terminal. - -For example: - -```log -User input required -Enter option number from: - - 1. Run noise reduction on the next WAV - 2. Run noise reduction on a WAV at chosen index - 3. Run noise reduction on all WAVs - 4. Show NN model info - 5. List audio clips - -Choice: -``` - -1. “Run noise reduction on the next WAV”: Runs processing and inference on the next in line WAV file. - - > **Note:** Depending on the size of the input WAV file, multiple inferences can be invoked. - -2. “Run noise reduction on a WAV at chosen index”: Runs processing and inference on the WAV file - corresponding to the chosen index. - - > **Note:** Select the index in the range of supplied WAVs during application build. By default, - the pre-built application has three files and indexes from 0-2. - -3. “Run noise reduction on all WAVs”: Triggers sequential processing and inference executions on - all baked-in WAV files. - -4. “Show NN model info”: Prints information about the model data type, including the input and - output tensor sizes. For example: - - ```log - INFO - Model info: - INFO - Model INPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 42 bytes with dimensions - INFO - 0: 1 - INFO - 1: 1 - INFO - 2: 42 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.221501 - INFO - ZeroPoint[0] = 14 - INFO - tensor type is INT8 - INFO - tensor occupies 24 bytes with dimensions - INFO - 0: 1 - INFO - 1: 24 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.007843 - INFO - ZeroPoint[0] = -1 - INFO - tensor type is INT8 - INFO - tensor occupies 48 bytes with dimensions - INFO - 0: 1 - INFO - 1: 48 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.047942 - INFO - ZeroPoint[0] = -128 - INFO - tensor type is INT8 - INFO - tensor occupies 96 bytes with dimensions - INFO - 0: 1 - INFO - 1: 96 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.007843 - INFO - ZeroPoint[0] = -1 - INFO - Model OUTPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 96 bytes with dimensions - INFO - 0: 1 - INFO - 1: 1 - INFO - 2: 96 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.007843 - INFO - ZeroPoint[0] = -1 - INFO - tensor type is INT8 - INFO - tensor occupies 22 bytes with dimensions - INFO - 0: 1 - INFO - 1: 1 - INFO - 2: 22 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.003906 - INFO - ZeroPoint[0] = -128 - INFO - tensor type is INT8 - INFO - tensor occupies 48 bytes with dimensions - INFO - 0: 1 - INFO - 1: 1 - INFO - 2: 48 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.047942 - INFO - ZeroPoint[0] = -128 - INFO - tensor type is INT8 - INFO - tensor occupies 24 bytes with dimensions - INFO - 0: 1 - INFO - 1: 1 - INFO - 2: 24 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.007843 - INFO - ZeroPoint[0] = -1 - INFO - tensor type is INT8 - INFO - tensor occupies 1 bytes with dimensions - INFO - 0: 1 - INFO - 1: 1 - INFO - 2: 1 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.003906 - INFO - ZeroPoint[0] = -128 - INFO - Activation buffer (a.k.a tensor arena) size used: 1940 - INFO - Number of operators: 1 - INFO - Operator 0: ethos-u - INFO - Use of Arm uNPU is enabled - ``` - -5. “List audio clips”: Prints a list of pair audio indexes. The original filenames are embedded in - the application. For example: - - ```log - INFO - List of Files: - INFO - 0 => p232_113.wav - INFO - 1 => p232_208.wav - INFO - 2 => p257_031.wav - ``` +After the application has started inferences are executed on inputs from `noise_reduction_FILE_PATH` ### Running Noise Reduction -Selecting the first option runs inference on the first file. - The following example illustrates an application output: ```log diff --git a/docs/use_cases/object_detection.md b/docs/use_cases/object_detection.md index e946c1bb0caaba48c4692e13b4576953bec1b13b..49e5d523182c3b42519444f34d91f4b83a3f5243 100644 --- a/docs/use_cases/object_detection.md +++ b/docs/use_cases/object_detection.md @@ -25,6 +25,8 @@ The model makes detection faces in size of 20x20 pixels and above. Use-case code could be found in the following directory:[source/use_case/object_detection](../../source/use_case/object_detection). +> **NOTE**: This use case only supports `TensorFlow Lite Micro`. + ### Prerequisites See [Prerequisites](../documentation.md#prerequisites) @@ -36,7 +38,7 @@ See [Prerequisites](../documentation.md#prerequisites) In addition to the already specified build option in the main documentation, the Object Detection use-case specifies: -- `object_detection_MODEL_TFLITE_PATH` - The path to the NN model file in the *TFLite* format. The model is then processed and +- `object_detection_MODEL_PATH` - The path to the NN model file in the *TFLite* format. The model is then processed and included in the application `axf` file. The default value points to one of the delivered set of models. Note that the parameters `TARGET_PLATFORM`, and `ETHOS_U_NPU_ENABLED` must be aligned with the chosen model. In other words: @@ -192,7 +194,7 @@ of any image does not match `IMAGE_SIZE`, then it is rescaled and padded so that ### Add custom model -The application performs inference using the model pointed to by the CMake parameter `object_detection_MODEL_TFLITE_PATH`. +The application performs inference using the model pointed to by the CMake parameter `object_detection_MODEL_PATH`. > **Note:** If you want to run the model using an *Ethos-U*, ensure that your custom model has been successfully run > through the Vela compiler *before* continuing. @@ -203,20 +205,20 @@ For example: ```commandline cmake .. \ - -Dobject_detection_MODEL_TFLITE_PATH= \ + -Dobject_detection_MODEL_PATH= \ -DUSE_CASE_BUILD=object_detection ``` > **Note:** Clean the build directory before re-running the CMake command. -The `.tflite` model file pointed to by `object_detection_MODEL_TFLITE_PATH` is converted to +The `.tflite` model file pointed to by `object_detection_MODEL_PATH` is converted to C++ files during the CMake configuration stage. They are then compiled into the application for performing inference with. The log from the configuration stage tells you what model path and labels file have been used, for example: ```log --- User option object_detection_MODEL_TFLITE_PATH is set to +-- User option object_detection_MODEL_PATH is set to ... -- Using ++ Converting custom_model_after_vela.tflite to custom_model_after_vela.tflite.cc @@ -272,89 +274,10 @@ This also launches a telnet window with the standard output of the sample applic entries containing information about the pre-built application version, TensorFlow Lite Micro library version used, and data types. The log also includes the input and output tensor sizes of the model compiled into the executable binary. -After the application has started, if `object_detection_FILE_PATH` points to a single file, or even a folder that contains a -single image, then the inference starts immediately. If there are multiple inputs, it outputs a menu and then waits for -input from the user: - -```log -User input required -Enter option number from: - - 1. Run detection on next ifm - 2. Run detection ifm at chosen index - 3. Run detection on all ifm - 4. Show NN model info - 5. List ifm - -Choice: - -``` - -What the preceding choices do: - -1. Run detection on next ifm: Runs a single inference on the next in line image from the collection of the compiled images. - -2. Run detection ifm at chosen index: Runs inference on the chosen image. - - > **Note:** Please make sure to select image index from within the range of supplied audio clips during application - > build. By default, a pre-built application has four images, with indexes from `0` to `3`. - -3. Run detection on all ifm: Triggers sequential inference executions on all built-in images. - -4. Show NN model info: Prints information about the model data type, input, and output, tensor sizes. For example: - - ```log - INFO - Allocating tensors - INFO - Model INPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 36864 bytes with dimensions - INFO - 0: 1 - INFO - 1: 192 - INFO - 2: 192 - INFO - 3: 1 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.003921 - INFO - ZeroPoint[0] = -128 - INFO - Model OUTPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 648 bytes with dimensions - INFO - 0: 1 - INFO - 1: 6 - INFO - 2: 6 - INFO - 3: 18 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.134084 - INFO - ZeroPoint[0] = 47 - INFO - tensor type is INT8 - INFO - tensor occupies 2592 bytes with dimensions - INFO - 0: 1 - INFO - 1: 12 - INFO - 2: 12 - INFO - 3: 18 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.185359 - INFO - ZeroPoint[0] = 10 - INFO - Activation buffer (a.k.a tensor arena) size used: 443992 - INFO - Number of operators: 3 - INFO - Operator 0: ethos-u - INFO - Operator 1: RESIZE_NEAREST_NEIGHBOR - INFO - Operator 2: ethos-u - ``` - -5. List Images: Prints a list of pair image indexes. The original filenames are embedded in the application, like so: - - ```log - INFO - List of Files: - INFO - 0 => couple.bmp - INFO - 1 => glasses.bmp - INFO - 2 => man_and_baby.bmp - INFO - 3 => pitch_and_roll.bmp - ``` +After the application has started, inferences are executed on inputs from `object_detection_FILE_PATH`. ### Running Object Detection -Please select the first menu option to execute Object Detection. - The following example illustrates an application output for detection: ```log diff --git a/docs/use_cases/visual_wake_word.md b/docs/use_cases/visual_wake_word.md index 99aa3f2764386e7a66991c0bcd9f45aa550c2d6c..969581c4d53a79cf24a01f5ab502648391274829 100644 --- a/docs/use_cases/visual_wake_word.md +++ b/docs/use_cases/visual_wake_word.md @@ -16,10 +16,12 @@ ## Introduction This document describes the process of setting up and running the Arm® Ethos™-U NPU Visual Wake Word example. -Visual Wake Words is a common vision use-case to detect if a the provided image contains a person. +Visual Wake Words is a common vision use-case to detect if the provided image contains a person. Use case code could be found in [source/use_case/vww](../../source/use_case/vww) directory. +> **NOTE**: This use case only supports `TensorFlow Lite Micro`. + ### Prerequisites See [Prerequisites](../documentation.md#prerequisites) @@ -30,7 +32,7 @@ See [Prerequisites](../documentation.md#prerequisites) In addition to the already specified build option in the main reference manual, Visual Wake Word use case specifies: -- `vww_MODEL_TFLITE_PATH` - Path to the NN model file in the `TFLite` format. The model is then processed and included in +- `vww_MODEL_PATH` - Path to the NN model file in the `TFLite` format. The model is then processed and included in the application `axf` file. The default value points to one of the delivered set of models. Note that the parameters `vww_LABELS_TXT_FILE`, `TARGET_PLATFORM`, and `ETHOS_U_NPU_ENABLED` must be aligned with the chosen model. In other words: @@ -180,7 +182,7 @@ After compiling, your custom images will have now replaced the default ones in t ### Add custom model The application performs inference using the model pointed to by the CMake parameter -`vww_MODEL_TFLITE_PATH`. +`vww_MODEL_PATH`. > **Note:** If you want to run the model using Ethos-U, ensure your custom model has been run through the Vela compiler > successfully before continuing. @@ -189,28 +191,28 @@ To run the application with a custom model you will need to provide a labels_ \ + -Dvww_MODEL_PATH= \ -Dvww_LABELS_TXT_FILE= \ -DUSE_CASE_BUILD=vww .. ``` > **Note:** Clean the build directory before re-running the cmake command. -The TFLite model pointed to by `vww_MODEL_TFLITE_PATH` and labels text file pointed to by +The TFLite model pointed to by `vww_MODEL_PATH` and labels text file pointed to by `vww_LABELS_TXT_FILE` will be converted to C++ files during the CMake configuration stage and then compiled into the application for performing inference with. The log from the configuration stage should tell you what model path and labels file have been used: ```log --- User option vww_MODEL_TFLITE_PATH is set to +-- User option vww_MODEL_PATH is set to ... -- User option vww_LABELS_TXT_FILE is set to ... @@ -265,93 +267,10 @@ This will also launch a telnet window with the sample application's standard out information about the pre-built application version, TensorFlow Lite Micro library version used, data type as well as the input and output tensor sizes of the model compiled into the executable binary. -After the application has started if `vww_FILE_PATH` pointed to a single file (or a folder containing a -single image) the inference starts immediately. In case of multiple inputs choice, it outputs a menu and waits for the -user input from telnet terminal: - -```log -User input required -Enter option number from: - - 1. Classify next ifm - 2. Classify ifm at chosen index - 3. Run classification on all ifm - 4. Show NN model info - 5. List ifm - -Choice: - -``` - -1. “Classify next image” menu option will run single inference on the next in line image from the collection of the - compiled images. - -2. “Classify image at chosen index” menu option will run single inference on the chosen image. - - > **Note:** Please make sure to select image index in the range of supplied images during application build. By - default, pre-built application has 2 images, index should 0 or 1. - -3. “Run classification on all images” menu option triggers sequential inference executions on all built-in images. - -4. “Show NN model info” menu option prints information about model data type, input and output tensor sizes: - - ```log - INFO - Added ethos-u support to op resolver - INFO - Creating allocator using tensor arena in SRAM - INFO - Allocating tensors - INFO - Model INPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 16384 bytes with dimensions - INFO - 0: 1 - INFO - 1: 128 - INFO - 2: 128 - INFO - 3: 1 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.008138 - INFO - ZeroPoint[0] = -70 - INFO - Model OUTPUT tensors: - INFO - tensor type is INT8 - INFO - tensor occupies 2 bytes with dimensions - INFO - 0: 1 - INFO - 1: 2 - INFO - Quant dimension: 0 - INFO - Scale[0] = 0.022299 - INFO - ZeroPoint[0] = -17 - INFO - Activation buffer (a.k.a tensor arena) size used: 133716 - INFO - Number of operators: 19 - INFO - Operator 0: ethos-u - INFO - Operator 1: PAD - INFO - Operator 2: ethos-u - INFO - Operator 3: PAD - INFO - Operator 4: ethos-u - INFO - Operator 5: PAD - INFO - Operator 6: ethos-u - INFO - Operator 7: PAD - INFO - Operator 8: ethos-u - INFO - Operator 9: PAD - INFO - Operator 10: ethos-u - INFO - Operator 11: PAD - INFO - Operator 12: ethos-u - INFO - Operator 13: PAD - INFO - Operator 14: ethos-u - INFO - Operator 15: PAD - INFO - Operator 16: ethos-u - INFO - Operator 17: AVERAGE_POOL_2D - INFO - Operator 18: ethos-u - ``` - -5. “List Images” menu option prints a list of pair image indexes - the original filenames embedded in the application: - - ```log - INFO - List of Files: - INFO - 0 => man_in_red_jacket.png - INFO - 1 => st_paul_s_cathedral.png - ``` +After the application has started, inferences are executed on inputs from `vww_FILE_PATH`. ### Running Visual Wake Word -Please select the first menu option to execute Visual Wake Word. - The following example illustrates application output for classification: ```log diff --git a/resources/img_class/labels/labels_mobilenet_v2_1.IMAGENET1K_V2.txt b/resources/img_class/labels/labels_mobilenet_v2_1.IMAGENET1K_V2.txt new file mode 100644 index 0000000000000000000000000000000000000000..a509c007481d301e524e7b3c97561132dbfcc765 --- /dev/null +++ b/resources/img_class/labels/labels_mobilenet_v2_1.IMAGENET1K_V2.txt @@ -0,0 +1,1000 @@ +tench, Tinca tinca +goldfish, Carassius auratus +great white shark, white shark, man-eater, man-eating shark, Carcharodon carcharias +tiger shark, Galeocerdo cuvieri +hammerhead, hammerhead shark +electric ray, crampfish, numbfish, torpedo +stingray +cock +hen +ostrich, Struthio camelus +brambling, Fringilla montifringilla +goldfinch, Carduelis carduelis +house finch, linnet, Carpodacus mexicanus +junco, snowbird +indigo bunting, indigo finch, indigo bird, Passerina cyanea +robin, American robin, Turdus migratorius +bulbul +jay +magpie +chickadee +water ouzel, dipper +kite +bald eagle, American eagle, Haliaeetus leucocephalus +vulture +great grey owl, great gray owl, Strix nebulosa +European fire salamander, Salamandra salamandra +common newt, Triturus vulgaris +eft +spotted salamander, Ambystoma maculatum +axolotl, mud puppy, Ambystoma mexicanum +bullfrog, Rana catesbeiana +tree frog, tree-frog +tailed frog, bell toad, ribbed toad, tailed toad, Ascaphus trui +loggerhead, loggerhead turtle, Caretta caretta +leatherback turtle, leatherback, leathery turtle, Dermochelys coriacea +mud turtle +terrapin +box turtle, box tortoise +banded gecko +common iguana, iguana, Iguana iguana +American chameleon, anole, Anolis carolinensis +whiptail, whiptail lizard +agama +frilled lizard, Chlamydosaurus kingi +alligator lizard +Gila monster, Heloderma suspectum +green lizard, Lacerta viridis +African chameleon, Chamaeleo chamaeleon +Komodo dragon, Komodo lizard, dragon lizard, giant lizard, Varanus komodoensis +African crocodile, Nile crocodile, Crocodylus niloticus +American alligator, Alligator mississipiensis +triceratops +thunder snake, worm snake, Carphophis amoenus +ringneck snake, ring-necked snake, ring snake +hognose snake, puff adder, sand viper +green snake, grass snake +king snake, kingsnake +garter snake, grass snake +water snake +vine snake +night snake, Hypsiglena torquata +boa constrictor, Constrictor constrictor +rock python, rock snake, Python sebae +Indian cobra, Naja naja +green mamba +sea snake +horned viper, cerastes, sand viper, horned asp, Cerastes cornutus +diamondback, diamondback rattlesnake, Crotalus adamanteus +sidewinder, horned rattlesnake, Crotalus cerastes +trilobite +harvestman, daddy longlegs, Phalangium opilio +scorpion +black and gold garden spider, Argiope aurantia +barn spider, Araneus cavaticus +garden spider, Aranea diademata +black widow, Latrodectus mactans +tarantula +wolf spider, hunting spider +tick +centipede +black grouse +ptarmigan +ruffed grouse, partridge, Bonasa umbellus +prairie chicken, prairie grouse, prairie fowl +peacock +quail +partridge +African grey, African gray, Psittacus erithacus +macaw +sulphur-crested cockatoo, Kakatoe galerita, Cacatua galerita +lorikeet +coucal +bee eater +hornbill +hummingbird +jacamar +toucan +drake +red-breasted merganser, Mergus serrator +goose +black swan, Cygnus atratus +tusker +echidna, spiny anteater, anteater +platypus, duckbill, duckbilled platypus, duck-billed platypus, Ornithorhynchus anatinus +wallaby, brush kangaroo +koala, koala bear, kangaroo bear, native bear, Phascolarctos cinereus +wombat +jellyfish +sea anemone, anemone +brain coral +flatworm, platyhelminth +nematode, nematode worm, roundworm +conch +snail +slug +sea slug, nudibranch +chiton, coat-of-mail shell, sea cradle, polyplacophore +chambered nautilus, pearly nautilus, nautilus +Dungeness crab, Cancer magister +rock crab, Cancer irroratus +fiddler crab +king crab, Alaska crab, Alaskan king crab, Alaska king crab, Paralithodes camtschatica +American lobster, Northern lobster, Maine lobster, Homarus americanus +spiny lobster, langouste, rock lobster, crawfish, crayfish, sea crawfish +crayfish, crawfish, crawdad, crawdaddy +hermit crab +isopod +white stork, Ciconia ciconia +black stork, Ciconia nigra +spoonbill +flamingo +little blue heron, Egretta caerulea +American egret, great white heron, Egretta albus +bittern +crane +limpkin, Aramus pictus +European gallinule, Porphyrio porphyrio +American coot, marsh hen, mud hen, water hen, Fulica americana +bustard +ruddy turnstone, Arenaria interpres +red-backed sandpiper, dunlin, Erolia alpina +redshank, Tringa totanus +dowitcher +oystercatcher, oyster catcher +pelican +king penguin, Aptenodytes patagonica +albatross, mollymawk +grey whale, gray whale, devilfish, Eschrichtius gibbosus, Eschrichtius robustus +killer whale, killer, orca, grampus, sea wolf, Orcinus orca +dugong, Dugong dugon +sea lion +Chihuahua +Japanese spaniel +Maltese dog, Maltese terrier, Maltese +Pekinese, Pekingese, Peke +Shih-Tzu +Blenheim spaniel +papillon +toy terrier +Rhodesian ridgeback +Afghan hound, Afghan +basset, basset hound +beagle +bloodhound, sleuthhound +bluetick +black-and-tan coonhound +Walker hound, Walker foxhound +English foxhound +redbone +borzoi, Russian wolfhound +Irish wolfhound +Italian greyhound +whippet +Ibizan hound, Ibizan Podenco +Norwegian elkhound, elkhound +otterhound, otter hound +Saluki, gazelle hound +Scottish deerhound, deerhound +Weimaraner +Staffordshire bullterrier, Staffordshire bull terrier +American Staffordshire terrier, Staffordshire terrier, American pit bull terrier, pit bull terrier +Bedlington terrier +Border terrier +Kerry blue terrier +Irish terrier +Norfolk terrier +Norwich terrier +Yorkshire terrier +wire-haired fox terrier +Lakeland terrier +Sealyham terrier, Sealyham +Airedale, Airedale terrier +cairn, cairn terrier +Australian terrier +Dandie Dinmont, Dandie Dinmont terrier +Boston bull, Boston terrier +miniature schnauzer +giant schnauzer +standard schnauzer +Scotch terrier, Scottish terrier, Scottie +Tibetan terrier, chrysanthemum dog +silky terrier, Sydney silky +soft-coated wheaten terrier +West Highland white terrier +Lhasa, Lhasa apso +flat-coated retriever +curly-coated retriever +golden retriever +Labrador retriever +Chesapeake Bay retriever +German short-haired pointer +vizsla, Hungarian pointer +English setter +Irish setter, red setter +Gordon setter +Brittany spaniel +clumber, clumber spaniel +English springer, English springer spaniel +Welsh springer spaniel +cocker spaniel, English cocker spaniel, cocker +Sussex spaniel +Irish water spaniel +kuvasz +schipperke +groenendael +malinois +briard +kelpie +komondor +Old English sheepdog, bobtail +Shetland sheepdog, Shetland sheep dog, Shetland +collie +Border collie +Bouvier des Flandres, Bouviers des Flandres +Rottweiler +German shepherd, German shepherd dog, German police dog, alsatian +Doberman, Doberman pinscher +miniature pinscher +Greater Swiss Mountain dog +Bernese mountain dog +Appenzeller +EntleBucher +boxer +bull mastiff +Tibetan mastiff +French bulldog +Great Dane +Saint Bernard, St Bernard +Eskimo dog, husky +malamute, malemute, Alaskan malamute +Siberian husky +dalmatian, coach dog, carriage dog +affenpinscher, monkey pinscher, monkey dog +basenji +pug, pug-dog +Leonberg +Newfoundland, Newfoundland dog +Great Pyrenees +Samoyed, Samoyede +Pomeranian +chow, chow chow +keeshond +Brabancon griffon +Pembroke, Pembroke Welsh corgi +Cardigan, Cardigan Welsh corgi +toy poodle +miniature poodle +standard poodle +Mexican hairless +timber wolf, grey wolf, gray wolf, Canis lupus +white wolf, Arctic wolf, Canis lupus tundrarum +red wolf, maned wolf, Canis rufus, Canis niger +coyote, prairie wolf, brush wolf, Canis latrans +dingo, warrigal, warragal, Canis dingo +dhole, Cuon alpinus +African hunting dog, hyena dog, Cape hunting dog, Lycaon pictus +hyena, hyaena +red fox, Vulpes vulpes +kit fox, Vulpes macrotis +Arctic fox, white fox, Alopex lagopus +grey fox, gray fox, Urocyon cinereoargenteus +tabby, tabby cat +tiger cat +Persian cat +Siamese cat, Siamese +Egyptian cat +cougar, puma, catamount, mountain lion, painter, panther, Felis concolor +lynx, catamount +leopard, Panthera pardus +snow leopard, ounce, Panthera uncia +jaguar, panther, Panthera onca, Felis onca +lion, king of beasts, Panthera leo +tiger, Panthera tigris +cheetah, chetah, Acinonyx jubatus +brown bear, bruin, Ursus arctos +American black bear, black bear, Ursus americanus, Euarctos americanus +ice bear, polar bear, Ursus Maritimus, Thalarctos maritimus +sloth bear, Melursus ursinus, Ursus ursinus +mongoose +meerkat, mierkat +tiger beetle +ladybug, ladybeetle, lady beetle, ladybird, ladybird beetle +ground beetle, carabid beetle +long-horned beetle, longicorn, longicorn beetle +leaf beetle, chrysomelid +dung beetle +rhinoceros beetle +weevil +fly +bee +ant, emmet, pismire +grasshopper, hopper +cricket +walking stick, walkingstick, stick insect +cockroach, roach +mantis, mantid +cicada, cicala +leafhopper +lacewing, lacewing fly +dragonfly, darning needle, devil's darning needle, sewing needle, snake feeder, snake doctor, mosquito hawk, skeeter hawk +damselfly +admiral +ringlet, ringlet butterfly +monarch, monarch butterfly, milkweed butterfly, Danaus plexippus +cabbage butterfly +sulphur butterfly, sulfur butterfly +lycaenid, lycaenid butterfly +starfish, sea star +sea urchin +sea cucumber, holothurian +wood rabbit, cottontail, cottontail rabbit +hare +Angora, Angora rabbit +hamster +porcupine, hedgehog +fox squirrel, eastern fox squirrel, Sciurus niger +marmot +beaver +guinea pig, Cavia cobaya +sorrel +zebra +hog, pig, grunter, squealer, Sus scrofa +wild boar, boar, Sus scrofa +warthog +hippopotamus, hippo, river horse, Hippopotamus amphibius +ox +water buffalo, water ox, Asiatic buffalo, Bubalus bubalis +bison +ram, tup +bighorn, bighorn sheep, cimarron, Rocky Mountain bighorn, Rocky Mountain sheep, Ovis canadensis +ibex, Capra ibex +hartebeest +impala, Aepyceros melampus +gazelle +Arabian camel, dromedary, Camelus dromedarius +llama +weasel +mink +polecat, fitch, foulmart, foumart, Mustela putorius +black-footed ferret, ferret, Mustela nigripes +otter +skunk, polecat, wood pussy +badger +armadillo +three-toed sloth, ai, Bradypus tridactylus +orangutan, orang, orangutang, Pongo pygmaeus +gorilla, Gorilla gorilla +chimpanzee, chimp, Pan troglodytes +gibbon, Hylobates lar +siamang, Hylobates syndactylus, Symphalangus syndactylus +guenon, guenon monkey +patas, hussar monkey, Erythrocebus patas +baboon +macaque +langur +colobus, colobus monkey +proboscis monkey, Nasalis larvatus +marmoset +capuchin, ringtail, Cebus capucinus +howler monkey, howler +titi, titi monkey +spider monkey, Ateles geoffroyi +squirrel monkey, Saimiri sciureus +Madagascar cat, ring-tailed lemur, Lemur catta +indri, indris, Indri indri, Indri brevicaudatus +Indian elephant, Elephas maximus +African elephant, Loxodonta africana +lesser panda, red panda, panda, bear cat, cat bear, Ailurus fulgens +giant panda, panda, panda bear, coon bear, Ailuropoda melanoleuca +barracouta, snoek +eel +coho, cohoe, coho salmon, blue jack, silver salmon, Oncorhynchus kisutch +rock beauty, Holocanthus tricolor +anemone fish +sturgeon +gar, garfish, garpike, billfish, Lepisosteus osseus +lionfish +puffer, pufferfish, blowfish, globefish +abacus +abaya +academic gown, academic robe, judge's robe +accordion, piano accordion, squeeze box +acoustic guitar +aircraft carrier, carrier, flattop, attack aircraft carrier +airliner +airship, dirigible +altar +ambulance +amphibian, amphibious vehicle +analog clock +apiary, bee house +apron +ashcan, trash can, garbage can, wastebin, ash bin, ash-bin, ashbin, dustbin, trash barrel, trash bin +assault rifle, assault gun +backpack, back pack, knapsack, packsack, rucksack, haversack +bakery, bakeshop, bakehouse +balance beam, beam +balloon +ballpoint, ballpoint pen, ballpen, Biro +Band Aid +banjo +bannister, banister, balustrade, balusters, handrail +barbell +barber chair +barbershop +barn +barometer +barrel, cask +barrow, garden cart, lawn cart, wheelbarrow +baseball +basketball +bassinet +bassoon +bathing cap, swimming cap +bath towel +bathtub, bathing tub, bath, tub +beach wagon, station wagon, wagon, estate car, beach waggon, station waggon, waggon +beacon, lighthouse, beacon light, pharos +beaker +bearskin, busby, shako +beer bottle +beer glass +bell cote, bell cot +bib +bicycle-built-for-two, tandem bicycle, tandem +bikini, two-piece +binder, ring-binder +binoculars, field glasses, opera glasses +birdhouse +boathouse +bobsled, bobsleigh, bob +bolo tie, bolo, bola tie, bola +bonnet, poke bonnet +bookcase +bookshop, bookstore, bookstall +bottlecap +bow +bow tie, bow-tie, bowtie +brass, memorial tablet, plaque +brassiere, bra, bandeau +breakwater, groin, groyne, mole, bulwark, seawall, jetty +breastplate, aegis, egis +broom +bucket, pail +buckle +bulletproof vest +bullet train, bullet +butcher shop, meat market +cab, hack, taxi, taxicab +caldron, cauldron +candle, taper, wax light +cannon +canoe +can opener, tin opener +cardigan +car mirror +carousel, carrousel, merry-go-round, roundabout, whirligig +carpenter's kit, tool kit +carton +car wheel +cash machine, cash dispenser, automated teller machine, automatic teller machine, automated teller, automatic teller, ATM +cassette +cassette player +castle +catamaran +CD player +cello, violoncello +cellular telephone, cellular phone, cellphone, cell, mobile phone +chain +chainlink fence +chain mail, ring mail, mail, chain armor, chain armour, ring armor, ring armour +chain saw, chainsaw +chest +chiffonier, commode +chime, bell, gong +china cabinet, china closet +Christmas stocking +church, church building +cinema, movie theater, movie theatre, movie house, picture palace +cleaver, meat cleaver, chopper +cliff dwelling +cloak +clog, geta, patten, sabot +cocktail shaker +coffee mug +coffeepot +coil, spiral, volute, whorl, helix +combination lock +computer keyboard, keypad +confectionery, confectionary, candy store +container ship, containership, container vessel +convertible +corkscrew, bottle screw +cornet, horn, trumpet, trump +cowboy boot +cowboy hat, ten-gallon hat +cradle +crane +crash helmet +crate +crib, cot +Crock Pot +croquet ball +crutch +cuirass +dam, dike, dyke +desk +desktop computer +dial telephone, dial phone +diaper, nappy, napkin +digital clock +digital watch +dining table, board +dishrag, dishcloth +dishwasher, dish washer, dishwashing machine +disk brake, disc brake +dock, dockage, docking facility +dogsled, dog sled, dog sleigh +dome +doormat, welcome mat +drilling platform, offshore rig +drum, membranophone, tympan +drumstick +dumbbell +Dutch oven +electric fan, blower +electric guitar +electric locomotive +entertainment center +envelope +espresso maker +face powder +feather boa, boa +file, file cabinet, filing cabinet +fireboat +fire engine, fire truck +fire screen, fireguard +flagpole, flagstaff +flute, transverse flute +folding chair +football helmet +forklift +fountain +fountain pen +four-poster +freight car +French horn, horn +frying pan, frypan, skillet +fur coat +garbage truck, dustcart +gasmask, respirator, gas helmet +gas pump, gasoline pump, petrol pump, island dispenser +goblet +go-kart +golf ball +golfcart, golf cart +gondola +gong, tam-tam +gown +grand piano, grand +greenhouse, nursery, glasshouse +grille, radiator grille +grocery store, grocery, food market, market +guillotine +hair slide +hair spray +half track +hammer +hamper +hand blower, blow dryer, blow drier, hair dryer, hair drier +hand-held computer, hand-held microcomputer +handkerchief, hankie, hanky, hankey +hard disc, hard disk, fixed disk +harmonica, mouth organ, harp, mouth harp +harp +harvester, reaper +hatchet +holster +home theater, home theatre +honeycomb +hook, claw +hoopskirt, crinoline +horizontal bar, high bar +horse cart, horse-cart +hourglass +iPod +iron, smoothing iron +jack-o'-lantern +jean, blue jean, denim +jeep, landrover +jersey, T-shirt, tee shirt +jigsaw puzzle +jinrikisha, ricksha, rickshaw +joystick +kimono +knee pad +knot +lab coat, laboratory coat +ladle +lampshade, lamp shade +laptop, laptop computer +lawn mower, mower +lens cap, lens cover +letter opener, paper knife, paperknife +library +lifeboat +lighter, light, igniter, ignitor +limousine, limo +liner, ocean liner +lipstick, lip rouge +Loafer +lotion +loudspeaker, speaker, speaker unit, loudspeaker system, speaker system +loupe, jeweler's loupe +lumbermill, sawmill +magnetic compass +mailbag, postbag +mailbox, letter box +maillot +maillot, tank suit +manhole cover +maraca +marimba, xylophone +mask +matchstick +maypole +maze, labyrinth +measuring cup +medicine chest, medicine cabinet +megalith, megalithic structure +microphone, mike +microwave, microwave oven +military uniform +milk can +minibus +miniskirt, mini +minivan +missile +mitten +mixing bowl +mobile home, manufactured home +Model T +modem +monastery +monitor +moped +mortar +mortarboard +mosque +mosquito net +motor scooter, scooter +mountain bike, all-terrain bike, off-roader +mountain tent +mouse, computer mouse +mousetrap +moving van +muzzle +nail +neck brace +necklace +nipple +notebook, notebook computer +obelisk +oboe, hautboy, hautbois +ocarina, sweet potato +odometer, hodometer, mileometer, milometer +oil filter +organ, pipe organ +oscilloscope, scope, cathode-ray oscilloscope, CRO +overskirt +oxcart +oxygen mask +packet +paddle, boat paddle +paddlewheel, paddle wheel +padlock +paintbrush +pajama, pyjama, pj's, jammies +palace +panpipe, pandean pipe, syrinx +paper towel +parachute, chute +parallel bars, bars +park bench +parking meter +passenger car, coach, carriage +patio, terrace +pay-phone, pay-station +pedestal, plinth, footstall +pencil box, pencil case +pencil sharpener +perfume, essence +Petri dish +photocopier +pick, plectrum, plectron +pickelhaube +picket fence, paling +pickup, pickup truck +pier +piggy bank, penny bank +pill bottle +pillow +ping-pong ball +pinwheel +pirate, pirate ship +pitcher, ewer +plane, carpenter's plane, woodworking plane +planetarium +plastic bag +plate rack +plow, plough +plunger, plumber's helper +Polaroid camera, Polaroid Land camera +pole +police van, police wagon, paddy wagon, patrol wagon, wagon, black Maria +poncho +pool table, billiard table, snooker table +pop bottle, soda bottle +pot, flowerpot +potter's wheel +power drill +prayer rug, prayer mat +printer +prison, prison house +projectile, missile +projector +puck, hockey puck +punching bag, punch bag, punching ball, punchball +purse +quill, quill pen +quilt, comforter, comfort, puff +racer, race car, racing car +racket, racquet +radiator +radio, wireless +radio telescope, radio reflector +rain barrel +recreational vehicle, RV, R.V. +reel +reflex camera +refrigerator, icebox +remote control, remote +restaurant, eating house, eating place, eatery +revolver, six-gun, six-shooter +rifle +rocking chair, rocker +rotisserie +rubber eraser, rubber, pencil eraser +rugby ball +rule, ruler +running shoe +safe +safety pin +saltshaker, salt shaker +sandal +sarong +sax, saxophone +scabbard +scale, weighing machine +school bus +schooner +scoreboard +screen, CRT screen +screw +screwdriver +seat belt, seatbelt +sewing machine +shield, buckler +shoe shop, shoe-shop, shoe store +shoji +shopping basket +shopping cart +shovel +shower cap +shower curtain +ski +ski mask +sleeping bag +slide rule, slipstick +sliding door +slot, one-armed bandit +snorkel +snowmobile +snowplow, snowplough +soap dispenser +soccer ball +sock +solar dish, solar collector, solar furnace +sombrero +soup bowl +space bar +space heater +space shuttle +spatula +speedboat +spider web, spider's web +spindle +sports car, sport car +spotlight, spot +stage +steam locomotive +steel arch bridge +steel drum +stethoscope +stole +stone wall +stopwatch, stop watch +stove +strainer +streetcar, tram, tramcar, trolley, trolley car +stretcher +studio couch, day bed +stupa, tope +submarine, pigboat, sub, U-boat +suit, suit of clothes +sundial +sunglass +sunglasses, dark glasses, shades +sunscreen, sunblock, sun blocker +suspension bridge +swab, swob, mop +sweatshirt +swimming trunks, bathing trunks +swing +switch, electric switch, electrical switch +syringe +table lamp +tank, army tank, armored combat vehicle, armoured combat vehicle +tape player +teapot +teddy, teddy bear +television, television system +tennis ball +thatch, thatched roof +theater curtain, theatre curtain +thimble +thresher, thrasher, threshing machine +throne +tile roof +toaster +tobacco shop, tobacconist shop, tobacconist +toilet seat +torch +totem pole +tow truck, tow car, wrecker +toyshop +tractor +trailer truck, tractor trailer, trucking rig, rig, articulated lorry, semi +tray +trench coat +tricycle, trike, velocipede +trimaran +tripod +triumphal arch +trolleybus, trolley coach, trackless trolley +trombone +tub, vat +turnstile +typewriter keyboard +umbrella +unicycle, monocycle +upright, upright piano +vacuum, vacuum cleaner +vase +vault +velvet +vending machine +vestment +viaduct +violin, fiddle +volleyball +waffle iron +wall clock +wallet, billfold, notecase, pocketbook +wardrobe, closet, press +warplane, military plane +washbasin, handbasin, washbowl, lavabo, wash-hand basin +washer, automatic washer, washing machine +water bottle +water jug +water tower +whiskey jug +whistle +wig +window screen +window shade +Windsor tie +wine bottle +wing +wok +wooden spoon +wool, woolen, woollen +worm fence, snake fence, snake-rail fence, Virginia fence +wreck +yawl +yurt +web site, website, internet site, site +comic book +crossword puzzle, crossword +street sign +traffic light, traffic signal, stoplight +book jacket, dust cover, dust jacket, dust wrapper +menu +plate +guacamole +consomme +hot pot, hotpot +trifle +ice cream, icecream +ice lolly, lolly, lollipop, popsicle +French loaf +bagel, beigel +pretzel +cheeseburger +hotdog, hot dog, red hot +mashed potato +head cabbage +broccoli +cauliflower +zucchini, courgette +spaghetti squash +acorn squash +butternut squash +cucumber, cuke +artichoke, globe artichoke +bell pepper +cardoon +mushroom +Granny Smith +strawberry +orange +lemon +fig +pineapple, ananas +banana +jackfruit, jak, jack +custard apple +pomegranate +hay +carbonara +chocolate sauce, chocolate syrup +dough +meat loaf, meatloaf +pizza, pizza pie +potpie +burrito +red wine +espresso +cup +eggnog +alp +bubble +cliff, drop, drop-off +coral reef +geyser +lakeside, lakeshore +promontory, headland, head, foreland +sandbar, sand bar +seashore, coast, seacoast, sea-coast +valley, vale +volcano +ballplayer, baseball player +groom, bridegroom +scuba diver +rapeseed +daisy +yellow lady's slipper, yellow lady-slipper, Cypripedium calceolus, Cypripedium parviflorum +corn +acorn +hip, rose hip, rosehip +buckeye, horse chestnut, conker +coral fungus +agaric +gyromitra +stinkhorn, carrion fungus +earthstar +hen-of-the-woods, hen of the woods, Polyporus frondosus, Grifola frondosa +bolete +ear, spike, capitulum +toilet tissue, toilet paper, bathroom tissue diff --git a/scripts/cmake/cmsis-pack-gen/CMakeLists.txt b/scripts/cmake/cmsis-pack-gen/CMakeLists.txt index 174089fc0e7ab556906eb1ab2841499a9f28ca04..58d691667d55720a854a5350406c23e709d74111 100644 --- a/scripts/cmake/cmsis-pack-gen/CMakeLists.txt +++ b/scripts/cmake/cmsis-pack-gen/CMakeLists.txt @@ -1,5 +1,6 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates +# SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or +# its affiliates # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -39,7 +40,7 @@ if (NOT TARGET arm_math) add_subdirectory(${ARM_MATH_PROJECT_DIR} ${CMAKE_BINARY_DIR}/math) endif() -if (NOT TARGET log) +if (NOT TARGET mlek_log) add_subdirectory(${LOGGING_PROJECT_DIR} ${CMAKE_BINARY_DIR}/log) endif() diff --git a/scripts/cmake/configuration_options/common_opts.cmake b/scripts/cmake/configuration_options/common_opts.cmake index 509e520071a3accb13c3c68af04c93b9ae0b472c..149f171775d682afe2297e86c8663e02ef62d7d1 100644 --- a/scripts/cmake/configuration_options/common_opts.cmake +++ b/scripts/cmake/configuration_options/common_opts.cmake @@ -1,6 +1,6 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2021-2022, 2024 Arm Limited and/or its -# affiliates +# SPDX-FileCopyrightText: Copyright 2021-2022, 2024-2025 Arm Limited and/or +# its affiliates # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,26 +27,47 @@ message(STATUS "Assessing common user options...") include(util_functions) -USER_OPTION(LOG_LEVEL "Log level for the application" - LOG_LEVEL_INFO +USER_OPTION(MLEK_LOG_ENABLE "Enable MLEK logging functions." + ON + BOOL) + +USER_OPTION(MLEK_LOG_LEVEL "Log level for the application" + MLEK_LOG_LEVEL_INFO STRING) +USER_OPTION(ML_FRAMEWORK "Select the ML inference framework to be used." + "TensorFlowLiteMicro" + STRING +) + +set_property(CACHE ML_FRAMEWORK PROPERTY STRINGS + TensorFlowLiteMicro + ExecuTorch) + ## TensorFlow options -USER_OPTION(TENSORFLOW_SRC_PATH "Path to the root of the TensorFlow Lite Micro sources." - "${MLEK_DEPENDENCY_ROOT_DIR}/tensorflow" - PATH) +if (${ML_FRAMEWORK} STREQUAL TensorFlowLiteMicro) + USER_OPTION(TENSORFLOW_SRC_PATH "Path to the root of the TensorFlow Lite Micro sources." + "${MLEK_DEPENDENCY_ROOT_DIR}/tensorflow" + PATH) -USER_OPTION(TENSORFLOW_LITE_MICRO_BUILD_TYPE "TensorFlow Lite Mirco build type (release/debug etc.)" - $,release_with_logs,debug> - STRING) + USER_OPTION(TENSORFLOW_LITE_MICRO_BUILD_TYPE "TensorFlow Lite Mirco build type (release/debug etc.)" + $,release_with_logs,debug> + STRING) -USER_OPTION(TENSORFLOW_LITE_MICRO_CLEAN_DOWNLOADS "Select if TPIP downloads should be cleaned before each build." - OFF - BOOL) + USER_OPTION(TENSORFLOW_LITE_MICRO_CLEAN_DOWNLOADS "Select if TPIP downloads should be cleaned before each build." + OFF + BOOL) -USER_OPTION(TENSORFLOW_LITE_MICRO_CLEAN_BUILD "Select if clean target should be added to a list of targets." - ON - BOOL) + USER_OPTION(TENSORFLOW_LITE_MICRO_CLEAN_BUILD "Select if clean target should be added to a list of targets." + ON + BOOL) +elseif (${ML_FRAMEWORK} STREQUAL ExecuTorch) + USER_OPTION(EXECUTORCH_SRC_PATH "Root directory for ExecuTorch source tree." + "${MLEK_DEPENDENCY_ROOT_DIR}/executorch" + PATH) +else () + message(FATAL_ERROR "Invalid ML_FRAMEWORK: ${ML_FRAMEWORK}") +endif () USER_OPTION(TARGET_PLATFORM "Target platform to build for: mps3, mps4, simple_platform and native." mps3 diff --git a/scripts/cmake/configuration_options/options-preset.json b/scripts/cmake/configuration_options/options-preset.json index 57b7a7d78dccc0ca4342dc82eeba350947e96a71..e6401cdb53bf2880929334a7ac3d981e6969a387 100644 --- a/scripts/cmake/configuration_options/options-preset.json +++ b/scripts/cmake/configuration_options/options-preset.json @@ -10,9 +10,9 @@ "name": "log-trace", "hidden": true, "cacheVariables": { - "LOG_LEVEL": { + "MLEK_LOG_LEVEL": { "type": "STRING", - "value": "LOG_LEVEL_TRACE" + "value": "MLEK_LOG_LEVEL_TRACE" } } }, @@ -20,9 +20,9 @@ "name": "log-debug", "hidden": true, "cacheVariables": { - "LOG_LEVEL": { + "MLEK_LOG_LEVEL": { "type": "STRING", - "value": "LOG_LEVEL_DEBUG" + "value": "MLEK_LOG_LEVEL_DEBUG" } } }, @@ -30,9 +30,9 @@ "name": "log-info", "hidden": true, "cacheVariables": { - "LOG_LEVEL": { + "MLEK_LOG_LEVEL": { "type": "STRING", - "value": "LOG_LEVEL_INFO" + "value": "MLEK_LOG_LEVEL_INFO" } } }, @@ -40,9 +40,9 @@ "name": "log-warning", "hidden": true, "cacheVariables": { - "LOG_LEVEL": { + "MLEK_LOG_LEVEL": { "type": "STRING", - "value": "LOG_LEVEL_WARN" + "value": "MLEK_LOG_LEVEL_WARN" } } }, @@ -50,9 +50,9 @@ "name": "log-error", "hidden": true, "cacheVariables": { - "LOG_LEVEL": { + "MLEK_LOG_LEVEL": { "type": "STRING", - "value": "LOG_LEVEL_ERROR" + "value": "MLEK_LOG_LEVEL_ERROR" } } }, @@ -68,4 +68,4 @@ } } ] -} \ No newline at end of file +} diff --git a/scripts/cmake/executorch.cmake b/scripts/cmake/executorch.cmake new file mode 100644 index 0000000000000000000000000000000000000000..da8304e2a602e4bbdeb7d1923bddf3b8841a4646 --- /dev/null +++ b/scripts/cmake/executorch.cmake @@ -0,0 +1,146 @@ +#---------------------------------------------------------------------------- +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its +# affiliates +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +#---------------------------------------------------------------------------- + +# Limitations +# 1. Arm compiler is not supported. +if (CMAKE_CXX_COMPILER_ID STREQUAL "ARMClang") + message( + FATAL_ERROR + "ExecuTorch does not currently support Arm Compiler. " + "Use the ML_FRAMEWORK argument to specify a different framework." + ) +endif () + +# 2. Arm Ethos-U65 and Dedicated_Sram are not supported in current revision, but +# are known to be working in a more recent versions which we need to migrate to. +if (ETHOS_U_NPU_ENABLED) + if (ETHOS_U_NPU_ID STREQUAL U65) + message(FATAL_ERROR "Support for Arm Ethos-U65 is currently disabled in this " + "experimental branch. Use Arm Ethos-U55 or Arm Ethos-U85") + endif() + if (ETHOS_U_NPU_MEMORY_MODE STREQUAL Dedicated_Sram) + message(FATAL_ERROR "`Dedicated_Sram` memory mode is not supported " + "by current rev of ExecuTorch." + "Use `Sram_Only` or `Shared_Sram` modes") + endif() +endif() + +# Validate pre-requisites. +assert_defined(EXECUTORCH_SRC_PATH) +assert_defined(PYTHON_VENV) +assert_defined(PYTHON) + +# Prepare CMake configuration overrides. +set(EXECUTORCH_BUILD_EXECUTOR_RUNNER OFF) +set(EXECUTORCH_BUILD_KERNELS_QUANTIZED ON) +set(EXECUTORCH_BUILD_EXTENSION_RUNNER_UTIL ON) +set(EXECUTORCH_ENABLE_LOGGING ON) + +if(TARGET_PLATFORM STREQUAL native) + set(EXECUTORCH_BUILD_ARM_BAREMETAL OFF) + set(EXECUTORCH_BUILD_CPUINFO ON) +else() + set(EXECUTORCH_BUILD_ARM_BAREMETAL ON) + set(EXECUTORCH_BUILD_HOST_TARGETS OFF) +endif() + +set(EXECUTORCH_PAL_DEFAULT minimal) + +# Map ExecuTorch supported log levels +if (${LOG_LEVEL} STREQUAL MLEK_LOG_LEVEL_TRACE OR + ${LOG_LEVEL} STREQUAL MLEK_LOG_LEVEL_DEBUG) + set(EXECUTORCH_LOG_LEVEL "Debug") +elseif(${LOG_LEVEL} STREQUAL MLEK_LOG_LEVEL_INFO) + set(EXECUTORCH_LOG_LEVEL "Info") +else() + set(EXECUTORCH_LOG_LEVEL "Error") +endif() + +# Prevent littering callee/parent scope: Create a block for variables +# needed only by ExecuTorch related configuration. +block(SCOPE_FOR VARIABLES) + # Ensure Python virtual environment bin location is available. + set(ENV_PATH "${PYTHON_VENV}/bin:$ENV{PATH}") + + # Override the Python executable set by ExecuTorch's Utils.cmake + # It expects a conda environment and sets this. We set it here + # for our virtual environment python to be used instead. + set(PYTHON_EXECUTABLE ${PYTHON}) + + # Set FLATC location - this should always be available (built for host) + # or else a build will be attempted. + if (EXISTS ${PYTHON_VENV}/bin/flatc) + set(FLATC_EXECUTABLE "${PYTHON_VENV}/bin/flatc") + else() + message(FATAL_ERROR "flatc executable doesn't exist") + endif() + + # Add ET main subdirectory + add_subdirectory(${EXECUTORCH_SRC_PATH} ${CMAKE_BINARY_DIR}/executorch) + + # Generate C++ bindings to register kernels into both PyTorch (for AOT) and + # Executorch (for runtime). Here select all ops in functions.yaml + set(EXECUTORCH_ROOT ${EXECUTORCH_SRC_PATH}) + + # Set up build for portable ops library. + include(${EXECUTORCH_SRC_PATH}/tools/cmake/Utils.cmake) + include(${EXECUTORCH_SRC_PATH}/tools/cmake/Codegen.cmake) + + # @TODO Placeholder for experimentation - this needs to come from user. + set(EXECUTORCH_SELECT_OPS_LIST "aten::_softmax.out") + gen_selected_ops( + LIB_NAME "arm_portable_ops_lib" + OPS_SCHEMA_YAML "" + ROOT_OPS "${EXECUTORCH_SELECT_OPS_LIST}" + INCLUDE_ALL_OPS "" + ) + + generate_bindings_for_kernels( + LIB_NAME "arm_portable_ops_lib" + FUNCTIONS_YAML ${EXECUTORCH_SRC_PATH}/kernels/portable/functions.yaml + ) + + gen_operators_lib( + LIB_NAME "arm_portable_ops_lib" + KERNEL_LIBS portable_kernels + DEPS executorch + ) + + if(EXECUTORCH_ENABLE_EVENT_TRACER) + target_compile_options(executorch INTERFACE -DET_EVENT_TRACER_ENABLED) + target_compile_options(portable_ops_lib INTERFACE -DET_EVENT_TRACER_ENABLED) + endif() +endblock() + +set(MLEK_EXECUTORCH_LINK_STR) +list(APPEND MLEK_EXECUTORCH_LINK_STR + "-Wl,--whole-archive" + $<$:executorch_delegate_ethos_u> + "-Wl,--no-whole-archive" + quantized_ops_lib + quantized_kernels + portable_kernels + executorch + extension_runner_util +) + +if (TARGET_PLATFORM STREQUAL native) + list(APPEND MLEK_EXECUTORCH_LINK_STR portable_ops_lib) +else() + list(APPEND MLEK_EXECUTORCH_LINK_STR arm_portable_ops_lib) +endif() diff --git a/scripts/cmake/platforms/mps3/sse-300/mps3-sse-300-debug.gnu.ld b/scripts/cmake/platforms/mps3/sse-300/mps3-sse-300-debug.gnu.ld index 211111eb82aa9f235730e6c2baee53ca5b779166..455a34c2b75d85cb604b06f49c9c870b7a1546a5 100644 --- a/scripts/cmake/platforms/mps3/sse-300/mps3-sse-300-debug.gnu.ld +++ b/scripts/cmake/platforms/mps3/sse-300/mps3-sse-300-debug.gnu.ld @@ -206,6 +206,8 @@ SECTIONS /* activation buffers a.k.a tensor arena when memory mode dedicated sram */ *(activation_buf_dram) . = ALIGN (16); + /* explicit placement for global offset table */ + * (.got*) } > DDR AT > DDR .text.at_ddr : diff --git a/scripts/cmake/platforms/mps3/sse-300/mps3-sse-300-release.gnu.ld b/scripts/cmake/platforms/mps3/sse-300/mps3-sse-300-release.gnu.ld index 4e25271b9ad7553dedade5e1e2c2ef3ba06b1acc..c89b2ec24fb27de6db267f05a618827673b27e6d 100644 --- a/scripts/cmake/platforms/mps3/sse-300/mps3-sse-300-release.gnu.ld +++ b/scripts/cmake/platforms/mps3/sse-300/mps3-sse-300-release.gnu.ld @@ -201,6 +201,8 @@ SECTIONS /* activation buffers a.k.a tensor arena when memory mode dedicated sram */ *(activation_buf_dram) . = ALIGN (16); + /* explicit placement for global offset table */ + * (.got*) } > DDR AT > DDR /** diff --git a/scripts/cmake/source_gen_utils.cmake b/scripts/cmake/source_gen_utils.cmake index 238b139d8a84a55f914b9f6a84d99877dd74765e..1554b5803d70adca85e9ac544cc516cfa30e2309 100644 --- a/scripts/cmake/source_gen_utils.cmake +++ b/scripts/cmake/source_gen_utils.cmake @@ -83,7 +83,7 @@ endfunction() # @param[in] NAMESPACE model name space # NOTE: Uses python ############################################################################## -function(generate_tflite_code) +function(generate_model_code) set(multiValueArgs EXPRESSIONS NAMESPACE) set(oneValueArgs MODEL_PATH DESTINATION) diff --git a/scripts/cmake/tensorflow_lite_micro.cmake b/scripts/cmake/tensorflow_lite_micro.cmake index 4866fec0a71bc654c088853b5f76fe0c4ea5c14e..0aba0a95215aeec9ce06f56e6b6b87e64e6aa59f 100644 --- a/scripts/cmake/tensorflow_lite_micro.cmake +++ b/scripts/cmake/tensorflow_lite_micro.cmake @@ -94,7 +94,7 @@ function(build_tflite_micro_cmake) endif() # Create an alias to use in other parts of the project. - add_library(tensorflow-lite-micro ALIAS tflu) + add_library(google::tensorflow-lite-micro ALIAS tflu) endfunction() function(build_tflite_micro_makefile) @@ -229,12 +229,17 @@ function(build_tflite_micro_makefile) target_include_directories(tensorflow-lite-micro INTERFACE - ${TENSORFLOW_SRC_PATH}) + ${TENSORFLOW_SRC_PATH} + ${TENSORFLOW_SRC_PATH}/tensorflow/lite/micro/tools/make/downloads/flatbuffers/include + ${TENSORFLOW_SRC_PATH}/tensorflow/lite/micro/tools/make/downloads/gemmlowp) target_compile_definitions(tensorflow-lite-micro INTERFACE TF_LITE_STATIC_MEMORY) + # Create an alias to use in other parts of the project. + add_library(google::tensorflow-lite-micro ALIAS tensorflow-lite-micro) + endfunction() build_tflite_micro_cmake() diff --git a/scripts/cmake/toolchains/toolchain-preset.json b/scripts/cmake/toolchains/toolchain-preset.json index 34117c3258a99da9788c467abe3dae1e67ce65e9..ff8a7f4a74437abc7bf02d43066c2f7e639b97fe 100644 --- a/scripts/cmake/toolchains/toolchain-preset.json +++ b/scripts/cmake/toolchains/toolchain-preset.json @@ -47,6 +47,10 @@ "CMAKE_TOOLCHAIN_FILE": { "type": "STRING", "value": "$env{MLEK_BASE_DIR}scripts/cmake/toolchains/bare-metal-armclang.cmake" + }, + "ML_FRAMEWORK": { + "type": "STRING", + "value": "TensorFlowLiteMicro" } } }, diff --git a/scripts/py/requirements.txt b/scripts/py/requirements.txt index 39e4bf5a959491ad5e0703297c1cb394d6572fe4..7fe008c51df0c4b3d9db0198070dcb470528014c 100644 --- a/scripts/py/requirements.txt +++ b/scripts/py/requirements.txt @@ -3,11 +3,11 @@ cmake==3.30.5 flatbuffers==24.3.25 importlib_metadata==8.5.0 Jinja2==3.1.6 -llvmlite==0.43.0 +llvmlite==0.44.0 lxml==5.3.0 MarkupSafe==3.0.2 -numba==0.60.0 -numpy==1.26.4 +numba==0.61.2 +numpy==2.2.6 pillow==11.0.0 pycparser==2.22 resampy==0.4.3 diff --git a/scripts/py/setup/__init__.py b/scripts/py/setup/__init__.py new file mode 100644 index 0000000000000000000000000000000000000000..81f484155beebeed39f556391470ea51e19f7b69 --- /dev/null +++ b/scripts/py/setup/__init__.py @@ -0,0 +1,16 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its +# affiliates +# SPDX-License-Identifier: Apache-2.0 +# +# 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. diff --git a/scripts/py/vela_configs.py b/scripts/py/setup/npu_config.py similarity index 97% rename from scripts/py/vela_configs.py rename to scripts/py/setup/npu_config.py index b4af9fdddd7a45f5d2cd0f9843077357cbecf73a..cbf7f32cab3221dd08cdbdc238e9579ea43bf55b 100644 --- a/scripts/py/vela_configs.py +++ b/scripts/py/setup/npu_config.py @@ -1,5 +1,6 @@ #!/usr/bin/env python3 -# SPDX-FileCopyrightText: Copyright 2024 Arm Limited and/or its affiliates +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its +# affiliates # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); diff --git a/scripts/py/setup/python_venv.py b/scripts/py/setup/python_venv.py new file mode 100644 index 0000000000000000000000000000000000000000..fe25b6f481f87fe1d8fb15581d15062690614e9b --- /dev/null +++ b/scripts/py/setup/python_venv.py @@ -0,0 +1,110 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its +# affiliates +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +""" +Functions for creating and interacting with Python virtual environments +""" +import os +import sys +import typing +import venv +from pathlib import Path + +from .util import call_command + + +def set_up_python_venv( + download_dir: Path, + additional_requirements_file: Path = "" +) -> typing.Tuple[Path, str]: + """ + Set up the Python environment with which to set up the resources + + @param download_dir: Path to the resources_downloaded directory + @param additional_requirements_file: Optional additional requirements file + @return: Path to the venv Python binary + activate command + """ + env_path = download_dir / 'env' + + venv_builder = venv.EnvBuilder(with_pip=True, upgrade_deps=True) + venv_context = venv_builder.ensure_directories(env_dir=env_path) + + env_python = Path(venv_context.env_exe) + + if not env_python.is_file(): + # Create the virtual environment using current interpreter's venv + # (not necessarily the system's Python3) + venv_builder.create(env_dir=env_path) + + if sys.platform == "win32": + env_activate = Path(f"{venv_context.bin_path}/activate.bat") + env_activate_cmd = str(env_activate) + else: + env_activate = Path(f"{venv_context.bin_path}/activate") + env_activate_cmd = f". {env_activate}" + + if not env_activate.is_file(): + venv_builder.install_scripts(venv_context, venv_context.bin_path) + + # Install additional requirements first, if a valid file has been provided + if additional_requirements_file and os.path.isfile(additional_requirements_file): + call_command(f"{env_python} -m pip install -r {additional_requirements_file}") + + return env_path, env_activate_cmd + + +def is_pip_package_installed(package_name: str, env_activate_cmd: str) -> bool: + """ + Check if a named package is installed in the Python environment + :param package_name: The name of the pip package as it appears in `pip freeze` + :param env_activate_cmd: The command for activating the Python virtual environment + :return: True if the named package is installed, False otherwise + """ + packages = call_command(f"{env_activate_cmd} && pip freeze").split("\n") + return len([package for package in packages if package.startswith(package_name)]) > 0 + + +def install_pip_package_if_needed( + package_name: str, + env_activate_cmd: str, + installed_package_name: typing.Optional[str] = None, + environment=None, + no_deps: bool = False, +): + """ + Installs the specified pip package if it is not already installed in the + Python virtual environment + :param package_name: The name of the package as passed to `pip install` + (which could be a path, e.g. to a git repository) + :param env_activate_cmd: The command for activating the Python virtual environment + :param installed_package_name: The name of the pip package as it appears in `pip freeze`. + If this is not passed, it is assumed to be the same as + the `package_name` parameter. + :param environment: An optional dictionary of environment variables to set + when installing the specified package. + :param no_deps: Determine whether the `--no-deps` flag is passed to + `pip -install` + """ + if not is_pip_package_installed( + installed_package_name if installed_package_name else package_name, + env_activate_cmd + ): + env_token = " ".join([f"{k}={v}" for k, v in environment.items()]) if environment else "" + no_deps_token = "--no-deps" if no_deps else "" + call_command( + f"{env_activate_cmd} && {env_token} " + f"python -m pip install {package_name} {no_deps_token}" + ) diff --git a/scripts/py/setup/setup_config.py b/scripts/py/setup/setup_config.py new file mode 100644 index 0000000000000000000000000000000000000000..811b6eddc8e55c3be4e8358e7c119daceb5cfe26 --- /dev/null +++ b/scripts/py/setup/setup_config.py @@ -0,0 +1,127 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its +# affiliates +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +""" +Setup config definitions +""" +import typing +from dataclasses import dataclass +from pathlib import Path + + +@dataclass(frozen=True) +class SetupConfig: + """ + Configuration for setup behaviour. + + Attributes: + run_vela_on_models (bool) : Whether to run Vela on the downloaded models + use_case_names (list) : List of names of use cases to set up resources for + (default is all). + check_clean_folder (bool) : Indicates whether the resources folder needs to + be checked for updates and cleaned. + set_up_executorch (bool) : Indicates whether to set up ExecuTorch + set_up_tensorflow (bool) : Indicates whether to set up ExecuTorch + parallel (int) : Number of threads to use for downloads + and model optimisation + """ + run_vela_on_models: bool = False + use_case_names: typing.List[str] = () + check_clean_folder: bool = False + set_up_executorch: bool = True + set_up_tensorflow: bool = True + parallel: int = 1 + + +@dataclass(frozen=True) +class OptimizationConfig: + """ + Configuration for Vela optimization. + + Attributes: + additional_npu_config_names (list) : List of strings of Ethos-U NPU configs. + arena_cache_size (int) : Specifies arena cache size in bytes. If a value + greater than 0 is provided, this will be taken + as the cache size. If 0, the default values, as per + the NPU config requirements, are used. + """ + additional_npu_config_names: typing.List[str] = () + arena_cache_size: int = 0 + + +@dataclass(frozen=True) +class PathsConfig: + """ + Configuration of paths to resources used by the setup process. + + Attributes: + additional_requirements_file (str) : Path to a requirements.txt file if + additional packages need to be + installed. + use_case_resources_file (Path) : Path to a JSON file containing the use case + metadata resources. + + downloads_dir (Path) : Path to store model resources files. + executorch_path (Path) : Path to ExecuTorch repository + """ + additional_requirements_file: Path = "" + use_case_resources_file: Path = "" + downloads_dir: Path = "" + executorch_path: Path = "" + + +class SetupContext: + """ + Used to manage state during the setup progress + """ + + def __init__(self, + setup_config: SetupConfig, + optimization_config: OptimizationConfig, + paths_config: PathsConfig, + ): + self._setup_config = setup_config + self._optimization_config = optimization_config + self._paths_config = paths_config + + self.env_path: Path = Path("/") + self.env_activate_cmd: str = "" + + self.quantized_ops_lib_path: Path = Path("/") + + @property + def setup_config(self): + """ + Get the setup config + :return: The setup config + """ + return self._setup_config + + @property + def optimization_config(self): + """ + Get the optimization config + :return: The optimization config + """ + return self._optimization_config + + @property + def paths_config(self): + """ + Get the paths config + :return: The paths config + """ + return self._paths_config diff --git a/scripts/py/setup/use_case.py b/scripts/py/setup/use_case.py new file mode 100644 index 0000000000000000000000000000000000000000..49b8d4d7cf8ab6c8344a1d5b65e028166961e9da --- /dev/null +++ b/scripts/py/setup/use_case.py @@ -0,0 +1,80 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its +# affiliates +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +""" +Use case domain object definitions +""" +import json +import typing +from dataclasses import dataclass, field +from pathlib import Path + + +@dataclass(frozen=True) +class UseCaseResource: + """ + Represent a use case's resource + """ + name: str + url: str + sub_folder: typing.Optional[str] = None + + +@dataclass(frozen=True) +class UseCase: + """ + Represent a use case + """ + name: str + url_prefix: str + resources: typing.List[UseCaseResource] + executorch_models: typing.Optional[typing.List[str]] = field(default_factory=lambda: []) + + +def load_use_case_resources( + use_case_resources_file: Path, + use_case_names: typing.List[str] = () +) -> typing.List[UseCase]: + """ + Load use case metadata resources + + Parameters + ---------- + use_case_resources_file : Path to a JSON file containing the use case + metadata resources. + use_case_names : List of named use cases to restrict + resource loading to. + Returns + ------- + The use cases resources object parsed to a dict + """ + + with open(use_case_resources_file, encoding="utf8") as f: + parsed_use_cases = json.load(f) + use_cases = ( + UseCase( + name=u["name"], + url_prefix=u["url_prefix"], + resources=[UseCaseResource(**r) for r in u["resources"]], + executorch_models=u.get("executorch_models", []), + ) + for u in parsed_use_cases + ) + + if len(use_case_names) == 0: + return list(use_cases) + + return [uc for uc in use_cases if uc.name in use_case_names] diff --git a/scripts/py/setup/util.py b/scripts/py/setup/util.py new file mode 100644 index 0000000000000000000000000000000000000000..ce0b900c0c35312dfbd36146760c256f4096fa6c --- /dev/null +++ b/scripts/py/setup/util.py @@ -0,0 +1,97 @@ +#!/usr/bin/env python3 +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its +# affiliates +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +""" +Utility functions for setup +""" +import logging +import os +import shutil +import subprocess +import typing +import urllib +import urllib.request +from pathlib import Path +from urllib.error import URLError + + +def download_file(url: str, dest: Path) -> Path: + """ + Download a file + + @param url: The URL of the file to download + @param dest: The destination of downloaded file + """ + try: + with urllib.request.urlopen(url) as g: + with open(dest, "b+w") as f: + f.write(g.read()) + logging.info("- Downloaded %s to %s.", url, dest) + except URLError: + logging.error("URLError while downloading %s.", url) + raise + return dest + + +def call_command( + command: str, + verbose: bool = True, + cwd: typing.Optional[Path] = None +) -> str: + """ + Helpers function that call subprocess and return the output. + + Parameters: + ---------- + command (string): Specifies the command to run. + verbose (bool): When True, log the command before running. + cwd (Path): Set the working directory in which to run the command. + """ + if verbose: + logging.info(command) + try: + proc = subprocess.run( + command, + check=True, + stdout=subprocess.PIPE, + stderr=subprocess.STDOUT, + shell=True, + cwd=cwd + ) + log = proc.stdout.decode("utf-8") + logging.info(log) + return log + except subprocess.CalledProcessError as err: + log = err.stdout.decode("utf-8") + logging.error(log) + raise err + + +def remove_tree_dir(dir_path: Path): + """ + Delete and re-create a directory + + Parameters + ---------- + dir_path : The directory path + """ + try: + # Remove the full directory. + shutil.rmtree(dir_path) + # Re-create an empty one. + os.mkdir(dir_path) + except OSError: + logging.error("Failed to delete %s.", dir_path) diff --git a/scripts/py/templates/tflite.cc.template b/scripts/py/templates/tflite.cc.template index 955f01d96e92aa17acbb19d5b2c463079f7151ba..f028ceaa1afebf3052d64b7a483519083ddc283e 100644 --- a/scripts/py/templates/tflite.cc.template +++ b/scripts/py/templates/tflite.cc.template @@ -33,7 +33,7 @@ namespace {{namespace}} { {{expression}}; {% endfor %} -static const uint8_t nn_model[] MODEL_TFLITE_ATTRIBUTE = +static const uint8_t nn_model[] MODEL_SECTION_ATTRIBUTE = {% for model_hex_line in model_data %} {{model_hex_line}} {% endfor %} diff --git a/scripts/py/use_case_resources.json b/scripts/py/use_case_resources.json index 80fa28dcc79e1d83c6bd1450b8db8a2fa20bb79e..239725ef073764aa13e886f13df73e3eb4ae53ed 100644 --- a/scripts/py/use_case_resources.json +++ b/scripts/py/use_case_resources.json @@ -9,8 +9,14 @@ "name": "ad_medium_int8.tflite", "url": "{url_prefix:0}ad_medium_int8.tflite" }, - {"name": "ifm0.npy", "url": "{url_prefix:0}testing_input/input/0.npy"}, - {"name": "ofm0.npy", "url": "{url_prefix:0}testing_output/Identity/0.npy"} + { + "name": "ifm0.npy", + "url": "{url_prefix:0}testing_input/input/0.npy" + }, + { + "name": "ofm0.npy", + "url": "{url_prefix:0}testing_output/Identity/0.npy" + } ] }, { @@ -31,6 +37,9 @@ "name": "ofm0.npy", "url": "{url_prefix:0}testing_output/Identity_int8/0.npy" } + ], + "executorch_models": [ + "w2l" ] }, { @@ -51,6 +60,9 @@ "name": "ofm0.npy", "url": "{url_prefix:0}testing_output/MobilenetV2/Predictions/Reshape_11/0.npy" } + ], + "executorch_models": [ + "mv2" ] }, { @@ -71,8 +83,14 @@ "https://github.com/ARM-software/ML-zoo/raw/9f506fe52b39df545f0e6c5ff9223f671bc5ae00/models/keyword_spotting/micronet_medium/tflite_int8/" ], "resources": [ - {"name": "ifm0.npy", "url": "{url_prefix:0}testing_input/input/0.npy"}, - {"name": "ofm0.npy", "url": "{url_prefix:0}testing_output/Identity/0.npy"}, + { + "name": "ifm0.npy", + "url": "{url_prefix:0}testing_input/input/0.npy" + }, + { + "name": "ofm0.npy", + "url": "{url_prefix:0}testing_output/Identity/0.npy" + }, { "name": "kws_micronet_m.tflite", "url": "{url_prefix:0}kws_micronet_m.tflite" @@ -89,8 +107,14 @@ "name": "vww4_128_128_INT8.tflite", "url": "{url_prefix:0}vww4_128_128_INT8.tflite" }, - {"name": "ifm0.npy", "url": "{url_prefix:0}testing_input/input/0.npy"}, - {"name": "ofm0.npy", "url": "{url_prefix:0}testing_output/Identity/0.npy"} + { + "name": "ifm0.npy", + "url": "{url_prefix:0}testing_input/input/0.npy" + }, + { + "name": "ofm0.npy", + "url": "{url_prefix:0}testing_output/Identity/0.npy" + } ] }, { @@ -136,7 +160,10 @@ "https://github.com/ARM-software/ML-zoo/raw/a061600058097a2785d6f1f7785e5a2d2a142955/models/noise_suppression/RNNoise/tflite_int8/" ], "resources": [ - {"name": "rnnoise_INT8.tflite", "url": "{url_prefix:0}rnnoise_INT8.tflite"}, + { + "name": "rnnoise_INT8.tflite", + "url": "{url_prefix:0}rnnoise_INT8.tflite" + }, { "name": "ifm0.npy", "url": "{url_prefix:0}testing_input/main_input_int8/0.npy" diff --git a/set_up_default_resources.py b/set_up_default_resources.py index 468a11990686b2d64a799b490e1810b5f64553bd..a172809bf12f9d5bb234fce147010595b25cdac5 100755 --- a/set_up_default_resources.py +++ b/set_up_default_resources.py @@ -17,28 +17,31 @@ """ Script to set up default resources for ML Embedded Evaluation Kit """ +import concurrent.futures import dataclasses import errno import fnmatch +import itertools import json import logging import os import re import shutil -import subprocess import sys import textwrap import typing -import urllib.request -import venv from argparse import ArgumentParser from argparse import ArgumentTypeError -from dataclasses import dataclass from pathlib import Path -from urllib.error import URLError +from enum import Enum from scripts.py.check_update_resources_downloaded import get_md5sum_for_file -from scripts.py.vela_configs import NpuConfigs, NpuConfig +from scripts.py.setup.npu_config import NpuConfigs, NpuConfig +from scripts.py.setup.python_venv import install_pip_package_if_needed +from scripts.py.setup.python_venv import set_up_python_venv, is_pip_package_installed +from scripts.py.setup.setup_config import SetupConfig, PathsConfig, OptimizationConfig, SetupContext +from scripts.py.setup.use_case import UseCase, load_use_case_resources +from scripts.py.setup.util import download_file, call_command, remove_tree_dir # Supported version of Python and Vela VELA_VERSION = "4.3.0" @@ -97,131 +100,26 @@ default_npu_configs = NpuConfigs.create( valid_npu_configs.get("ethos-u85", 256), ) -current_file_dir = Path(__file__).parent.resolve() -default_use_case_resources_path = current_file_dir / 'scripts' / 'py' / 'use_case_resources.json' -default_requirements_path = current_file_dir / 'scripts' / 'py' / 'requirements.txt' -default_downloads_path = current_file_dir / 'resources_downloaded' - - -@dataclass(frozen=True) -class UseCaseResource: - """ - Represent a use case's resource - """ - name: str - url: str - sub_folder: typing.Optional[str] = None - - -@dataclass(frozen=True) -class UseCase: - """ - Represent a use case - """ - name: str - url_prefix: str - resources: typing.List[UseCaseResource] - - -@dataclass(frozen=True) -class SetupConfig: - """ - Configuration for setup behaviour. - - Attributes: - run_vela_on_models (bool) : Whether to run Vela on the downloaded models - additional_npu_config_names (list) : List of strings of Ethos-U NPU configs. - use_case_names (list) : List of names of use cases to set up resources for - (default is all). - arena_cache_size (int) : Specifies arena cache size in bytes. If a value - greater than 0 is provided, this will be taken - as the cache size. If 0, the default values, as per - the NPU config requirements, are used. - check_clean_folder (bool) : Indicates whether the resources folder needs to - be checked for updates and cleaned. - """ - run_vela_on_models: bool = False - additional_npu_config_names: typing.List[str] = () - use_case_names: typing.List[str] = () - arena_cache_size: int = 0 - check_clean_folder: bool = False - - -@dataclass(frozen=True) -class PathsConfig: - """ - Configuration of paths to resources used by the setup process. - - Attributes: - additional_requirements_file (str) : Path to a requirements.txt file if - additional packages need to be - installed. - use_case_resources_file (Path) : Path to a JSON file containing the use case - metadata resources. - - downloads_dir (Path) : Path to store model resources files. - """ - additional_requirements_file: Path = "" - use_case_resources_file: Path = "" - downloads_dir: Path = "" - - -def load_use_case_resources( - use_case_resources_file: Path, - use_case_names: typing.List[str] = () -) -> typing.List[UseCase]: +class MLFramework(Enum): """ - Load use case metadata resources - - Parameters - ---------- - use_case_resources_file : Path to a JSON file containing the use case - metadata resources. - use_case_names : List of named use cases to restrict - resource loading to. - Returns - ------- - The use cases resources object parsed to a dict + Enum to pick ML framework to use for build. """ + TENSORFLOW_LITE_MICRO = "tflm" + EXECUTORCH = "executorch" - with open(use_case_resources_file, encoding="utf8") as f: - parsed_use_cases = json.load(f) - use_cases = ( - UseCase( - name=u["name"], - url_prefix=u["url_prefix"], - resources=[UseCaseResource(**r) for r in u["resources"]], - ) - for u in parsed_use_cases - ) - - if len(use_case_names) == 0: - return list(use_cases) - - return [uc for uc in use_cases if uc.name in use_case_names] +valid_ml_frameworks: typing.Set[str] = {f.value for f in MLFramework} -def call_command(command: str, verbose: bool = True) -> str: - """ - Helpers function that call subprocess and return the output. +current_file_dir = Path(__file__).parent.resolve() +default_use_case_resources_path = current_file_dir / 'scripts' / 'py' / 'use_case_resources.json' +default_requirements_path = current_file_dir / 'scripts' / 'py' / 'requirements.txt' +default_downloads_path = current_file_dir / 'resources_downloaded' +default_executorch_path = current_file_dir / 'dependencies' / 'executorch' +vela_config_file = current_file_dir / "scripts" / "vela" / "default_vela.ini" - Parameters: - ---------- - command (string): Specifies the command to run. - """ - if verbose: - logging.info(command) - try: - proc = subprocess.run( - command, check=True, stdout=subprocess.PIPE, stderr=subprocess.STDOUT, shell=True - ) - log = proc.stdout.decode("utf-8") - logging.info(log) - return log - except subprocess.CalledProcessError as err: - log = err.stdout.decode("utf-8") - logging.error(log) - raise err +VELA_URL = "https://git.gitlab.arm.com/artificial-intelligence/ethos-u/ethos-u-vela.git" +TOSA_URL = "https://git.gitlab.arm.com/tosa/tosa-reference-model.git" +TOSA_VER = "70ed0b40fa831387e36abdb4f7fb9670a3464f5a" def get_default_npu_config_from_name( @@ -257,23 +155,6 @@ def get_default_npu_config_from_name( return npu_config.overwrite_arena_cache_size(arena_cache_size) -def remove_tree_dir(dir_path: Path): - """ - Delete and re-create a directory - - Parameters - ---------- - dir_path : The directory path - """ - try: - # Remove the full directory. - shutil.rmtree(dir_path) - # Re-create an empty one. - os.mkdir(dir_path) - except OSError: - logging.error("Failed to delete %s.", dir_path) - - def initialize_use_case_resources_directory( use_case: UseCase, metadata: typing.Dict, @@ -289,10 +170,12 @@ def initialize_use_case_resources_directory( @param download_dir: The parent directory @param check_clean_folder: Whether to clean the folder @param setup_script_hash_verified: Whether the hash of this script is verified + @return The path to this use case's downloaded resources """ + use_case_resources_dir = get_downloaded_resources_directory(use_case, download_dir) try: # Does the usecase_name download dir exist? - (download_dir / use_case.name).mkdir() + use_case_resources_dir.mkdir() except OSError as err: if err.errno == errno.EEXIST: # The usecase_name download dir exist. @@ -306,84 +189,55 @@ def initialize_use_case_resources_directory( ): if metadata_uc_url_prefix != use_case.url_prefix[idx]: logging.info("Removing %s resources.", use_case.name) - remove_tree_dir(download_dir / use_case.name) + remove_tree_dir(use_case_resources_dir) break elif err.errno != errno.EEXIST: logging.error("Error creating %s directory.", use_case.name) raise -def download_file(url: str, dest: Path): +def get_downloaded_resources_directory(use_case: UseCase, downloads_dir: Path) -> Path: """ - Download a file - - @param url: The URL of the file to download - @param dest: The destination of downloaded file + Get the directory for a use case's downloaded resources + :param use_case: The use case + :param downloads_dir: The parent directory for all downloaded resources + :return: The directory for the specified use case's resources """ - try: - with urllib.request.urlopen(url) as g: - with open(dest, "b+w") as f: - f.write(g.read()) - logging.info("- Downloaded %s to %s.", url, dest) - except URLError: - logging.error("URLError while downloading %s.", url) - raise + return downloads_dir / use_case.name -def download_resources( +def get_resources_to_download( use_case: UseCase, - metadata: typing.Dict, - download_dir: Path, - check_clean_folder: bool, - setup_script_hash_verified: bool, -): + download_dir: Path +) -> typing.List[typing.Tuple[str, Path]]: """ Download the resources associated with a use case @param use_case: The use case - @param metadata: The metadata @param download_dir: The parent directory - @param check_clean_folder: Whether to clean the folder - @param setup_script_hash_verified: Whether the hash is already verified - """ - initialize_use_case_resources_directory( - use_case, - metadata, - download_dir, - check_clean_folder, - setup_script_hash_verified - ) - + @param parallel: Number of download threads to use + """ reg_expr_str = r"{url_prefix:(.*\d)}" reg_expr_pattern = re.compile(reg_expr_str) - for res in use_case.resources: - res_name = res.name - url_prefix_idx = int(reg_expr_pattern.search(res.url).group(1)) - res_url = use_case.url_prefix[url_prefix_idx] + re.sub( - reg_expr_str, "", res.url + to_download = [] + for resource in use_case.resources: + url_prefix_idx = int(reg_expr_pattern.search(resource.url).group(1)) + url = use_case.url_prefix[url_prefix_idx] + re.sub( + reg_expr_str, "", resource.url ) - sub_folder = "" - if res.sub_folder is not None: - try: - # Does the usecase_name/sub_folder download dir exist? - (download_dir / use_case.name / res.sub_folder).mkdir() - except OSError as err: - if err.errno != errno.EEXIST: - logging.error( - "Error creating %s/%s directory.", - use_case.name, - res.sub_folder - ) - raise - sub_folder = res.sub_folder + dest_dir = get_downloaded_resources_directory(use_case, download_dir) + if resource.sub_folder is not None: + dest_dir = dest_dir / resource.sub_folder - res_dst = download_dir / use_case.name / sub_folder / res_name + os.makedirs(dest_dir, exist_ok=True) + dest = dest_dir / resource.name - if res_dst.is_file(): - logging.info("File %s exists, skipping download.", res_dst) + if dest.is_file(): + logging.info("File %s exists, skipping download.", dest) else: - download_file(res_url, res_dst) + to_download.append((url, dest)) + return to_download def run_vela( @@ -402,11 +256,21 @@ def run_vela( @param output_dir: The output directory @return: True if the optimisation was skipped, false otherwise """ - # model name after compiling with vela is an initial model name + _vela suffix - vela_optimised_model_path = model.parent / (model.stem + "_vela.tflite") + # We want the name to include the configuration suffix. For example: vela_H128, + # vela_Y512 etc. + new_suffix = f"_vela_{config.config_id}.tflite" + new_vela_optimised_model_path = output_dir / (model.stem + new_suffix) - vela_command_arena_cache_size = "" + if new_vela_optimised_model_path.is_file(): + logging.info( + "File %s exists, skipping optimisation.", + new_vela_optimised_model_path + ) + return True + + work_dir = output_dir / config.config_name / model.stem + vela_command_arena_cache_size = "" if config.arena_cache_size: vela_command_arena_cache_size = ( f"--arena-cache-size={config.arena_cache_size}" @@ -419,88 +283,35 @@ def run_vela( + f"--config {config_file} " + f"--memory-mode={config.memory_mode} " + f"--system-config={config.system_config} " - + f"--output-dir={output_dir} " + + f"--output-dir={work_dir} " + f"{vela_command_arena_cache_size}" ) - # We want the name to include the configuration suffix. For example: vela_H128, - # vela_Y512 etc. - new_suffix = "_vela_" + config.config_id + ".tflite" - new_vela_optimised_model_path = model.parent / (model.stem + new_suffix) - - skip_optimisation = new_vela_optimised_model_path.is_file() - - if skip_optimisation: - logging.info( - "File %s exists, skipping optimisation.", - new_vela_optimised_model_path - ) - else: - call_command(vela_command) + call_command(vela_command) - # Rename default vela model. - vela_optimised_model_path.rename(new_vela_optimised_model_path) - logging.info( - "Renaming %s to %s.", - vela_optimised_model_path, - new_vela_optimised_model_path - ) + # Relocate any other files output by Vela, e.g. csv output data + for vela_output in work_dir.glob("*"): + new_file_name = f"{vela_output.stem}_{config.config_id}{vela_output.suffix}" + logging.info("Renaming %s to %s.", vela_output.name, new_file_name) + vela_output.rename(output_dir / new_file_name) + work_dir.rmdir() - return skip_optimisation + return False -def run_vela_on_all_models( - download_dir: Path, - env_activate_cmd: str, - arena_cache_size: int, - npu_config_names: typing.List[str] -): +def find_unoptimized_tflite_files(download_dir: Path) -> typing.List[Path]: """ - Run vela on downloaded models for the specified NPU configurations - - @param download_dir: Path to the downloaded resources directory - @param env_activate_cmd: Command used to activate Python venv - @param npu_config_names: Names of NPU configurations for which to run Vela - @param arena_cache_size: The arena cache size + Find paths for .tflite files that have not yet been optimised with Vela + :param download_dir: The parent directory in which to search for .tflite files + :return: A list of paths for unoptimized .tflite files """ - config_file = current_file_dir / "scripts" / "vela" / "default_vela.ini" - models = [ + return [ Path(dirpath) / f for dirpath, dirnames, files in os.walk(download_dir) for f in fnmatch.filter(files, "*.tflite") if "vela" not in f ] - # Get npu config tuple for each config name in a list: - npu_configs = [ - get_default_npu_config_from_name(name, arena_cache_size) - for name in npu_config_names - ] - - logging.info("All models will be optimised for these configs:") - for config in npu_configs: - logging.info(config) - - optimisation_skipped = False - - for model in models: - for config in npu_configs: - optimisation_skipped = run_vela( - config, - env_activate_cmd, - model, - config_file, - output_dir=model.parent - ) or optimisation_skipped - - # If any optimisation was skipped, show how to regenerate: - if optimisation_skipped: - logging.warning("One or more optimisations were skipped.") - logging.warning( - "To optimise all the models, please remove the directory %s.", - download_dir - ) - def initialize_resources_directory( download_dir: Path, @@ -554,60 +365,166 @@ def initialize_resources_directory( return metadata_dict, setup_script_hash_verified -def set_up_python_venv( - download_dir: Path, - additional_requirements_file: Path = "" -): +def install_executorch(executorch_path: Path, env_activate_cmd: str) -> None: + """ + Installs ExecuTorch Python bindings within Python virtual environment. + + :param executorch_path: Root of Executorch source tree. + :param env_activate_cmd: Command to activate the Python virtual + environment where we need to install. """ - Set up the Python environment with which to set up the resources + if not executorch_path.is_dir(): + raise NotADirectoryError(f'Invalid dir {executorch_path}') + + if len(env_activate_cmd.strip()) == 0: + raise ValueError('venv activation command cannot be empty.') + + install_script = executorch_path / 'install_executorch.sh' - @param download_dir: Path to the resources_downloaded directory - @param additional_requirements_file: Optional additional requirements file - @return: Path to the venv Python binary + activate command + call_command( + command=f'{env_activate_cmd} && {install_script} --clean && {install_script}', + verbose=True + ) + + +def install_executorch_quantized_ops_lib( + executorch_path: Path, + env_activate_cmd: str +) -> typing.List[Path]: """ - env_dirname = "env" - env_path = download_dir / env_dirname + Builds the quantized ops shared library for host. The path to this lib + is needed for generating PTE files. + :param executorch_path: Root of Executorch source tree + :param env_activate_cmd: Command to activate the Python virtual + environment where we need to install. + returns List of paths to generated shared_lib files. + """ + if not executorch_path.is_dir(): + raise NotADirectoryError(f'Invalid dir {executorch_path}') + + if len(env_activate_cmd.strip()) == 0: + raise ValueError('venv activation command cannot be empty.') + + script_dir = executorch_path / 'backends' / 'arm' / 'scripts' + install_script = script_dir / 'build_quantized_ops_aot_lib.sh' + expected_lib_path = executorch_path / 'cmake-out-aot-lib' / 'kernels' / 'quantized' + expected_ext = '.so' - venv_builder = venv.EnvBuilder(with_pip=True, upgrade_deps=True) - venv_context = venv_builder.ensure_directories(env_dir=env_path) + call_command( + command=f'{env_activate_cmd} && {install_script}', + verbose=True + ) - env_python = Path(venv_context.env_exe) + return list(expected_lib_path.glob('*' + expected_ext)) - if not env_python.is_file(): - # Create the virtual environment using current interpreter's venv - # (not necessarily the system's Python3) - venv_builder.create(env_dir=env_path) - if sys.platform == "win32": - env_activate = Path(f"{venv_context.bin_path}/activate.bat") - env_activate_cmd = str(env_activate) +def optimize_executorch_model( + model_name: str, + npu_config: NpuConfig, + setup_context: SetupContext, + output_dir: Path, +): + """ + Generate an optimized .pte file for ExecuTorch + :param model_name: The named of the ExecuTorch model to optimize + :param npu_config: The NPU config for which to optimize. If this is + None, a TOSA PTE file is generated for native host. + :param setup_context: The setup context + :param output_dir: The output directory + :return: True if optimization was skipped, False otherwise + """ + if npu_config is not None: + # Should be --memory_mode {npu_config.memory_mode} but Dedicated_Sram mode + # doesn't work with current rev of ExecuTorch. + cfg = (f" --target {npu_config.config_name}" + f" --system_config {npu_config.system_config}" + " --delegate --quantize" + " --memory_mode Shared_Sram") + optimized_model_name = f"{model_name}_arm_delegate_{npu_config.config_name}.pte" else: - env_activate = Path(f"{venv_context.bin_path}/activate") - env_activate_cmd = f". {env_activate}" + cfg = " --quantize --target TOSA" + optimized_model_name = output_dir / f"{model_name}_arm_TOSA.pte" - if not env_activate.is_file(): - venv_builder.install_scripts(venv_context, venv_context.bin_path) + optimized_model_path = output_dir / optimized_model_name + if optimized_model_path.is_file(): + logging.info( + "File %s exists, skipping optimisation.", + optimized_model_path + ) + return True + + call_command( + command=(f"{setup_context.env_activate_cmd} && python3 -m examples.arm.aot_arm_compiler" + f" --model_name={model_name}" + f"{cfg}" + f" --so_library={setup_context.quantized_ops_lib_path}" + f" --output {output_dir}"), + cwd=setup_context.paths_config.executorch_path) + + return False + + +def setup_executorch(setup_context: SetupContext) -> typing.List[Path]: + """ + Installs Tosa tools and ExecuTorch in the Python virtual environment and builds the + quantized ops shared libraries. + :param setup_context: SetupContext + :return: list of shared library files needed for generating PTE files. + """ + # Install TOSA tools: + install_pip_package_if_needed( + f"git+{TOSA_URL}@{TOSA_VER}", + setup_context.env_activate_cmd, + installed_package_name="tosa-tools", + environment={ + "CMAKE_POLICY_VERSION_MINIMUM": 3.5 + }, + no_deps=True + ) - # Install additional requirements first, if a valid file has been provided - if additional_requirements_file and os.path.isfile(additional_requirements_file): - command = f"{env_python} -m pip install -r {additional_requirements_file}" - call_command(command) + # Install ExecuTorch package + if not is_pip_package_installed("executorch", setup_context.env_activate_cmd): + install_executorch(default_executorch_path, setup_context.env_activate_cmd) - # Make sure to have all the main requirements - if INSTALL_VELA_FROM_SOURCE: - vela_url = "https://git.gitlab.arm.com/artificial-intelligence/ethos-u/ethos-u-vela.git" - requirements = [f"git+{vela_url}@{VELA_VERSION}"] + # Build and install quantized ops libraries: + lib_aot_quantized_ops_path = setup_context.env_path / "libquantized_ops_aot_lib.so" + if lib_aot_quantized_ops_path.exists(): + et_ops_lib_list = [lib_aot_quantized_ops_path] else: - requirements = [f"ethos-u-vela=={VELA_VERSION}"] + et_ops_lib_list = install_executorch_quantized_ops_lib( + default_executorch_path, + setup_context.env_activate_cmd + ) + if not et_ops_lib_list: + raise FileNotFoundError('No shared libraries found for ExecuTorch quantized ops') + + logging.info('Libs required for pte file generation: %s,', et_ops_lib_list) + for file in et_ops_lib_list: + if file.is_file(): + shutil.copy(file, setup_context.env_path / file.name) + + et_ops_lib_list = [setup_context.env_path / file.name for file in et_ops_lib_list] + logging.info('Libs copied here: %s', et_ops_lib_list) + + return et_ops_lib_list - command = f"{env_python} -m pip freeze" - packages = call_command(command) - for req in requirements: - if req not in packages: - command = f"{env_python} -m pip install {req}" - call_command(command) - return env_path, env_activate_cmd +def setup_vela(env_activate_cmd: str): + """ + Install Vela into the Python virtual environment + :param env_activate_cmd: The command for activating the Python virtual environment + """ + if INSTALL_VELA_FROM_SOURCE: + install_pip_package_if_needed( + f"git+{VELA_URL}@{VELA_VERSION}", + env_activate_cmd, + installed_package_name="ethos-u-vela" + ) + else: + install_pip_package_if_needed( + f"ethos-u-vela=={VELA_VERSION}", + env_activate_cmd + ) def update_metadata( @@ -660,7 +577,198 @@ def check_paths_config(paths_config: PathsConfig): logging.warning(textwrap.dedent(message)) -def set_up_resources(setup_config: SetupConfig, paths_config: PathsConfig) -> Path: +def optimize_tflite_models_async( + executor: concurrent.futures.ThreadPoolExecutor, + unoptimized_models: typing.List[Path], + npu_configs: typing.List[NpuConfig], + env_activate_cmd: str +) -> typing.List[concurrent.futures.Future[bool]]: + """ + Run Vela on a list of models for the given NPU configs using a ThreadPoolExecutor + :param executor: The ThreadPoolExecutor + :param unoptimized_models: The list of tflite models to be optimized + :param npu_configs: The NPU configs for which to optimize the models + :param env_activate_cmd: The Python environment activation command + :return: A list of futures for the optimizations + """ + return [ + executor.submit( + run_vela, + npu_config, + env_activate_cmd, + model_path, + vela_config_file, + model_path.parent + ) + for model_path, npu_config + in itertools.product(unoptimized_models, npu_configs) + ] + + +def optimize_executorch_models_async( + executor: concurrent.futures.ThreadPoolExecutor, + use_cases: typing.List[UseCase], + npu_configs: typing.List[NpuConfig], + setup_context: SetupContext +) -> typing.List[concurrent.futures.Future[bool]]: + """ + Download and optimize ExecuTorch models for the specified use cases and NPU Configs + using a ThreadPoolExecutor + :param executor: The ThreadPoolExecutor + :param use_cases: The use cases for which to download and optimize ExecuTorch models + :param npu_configs: The NPU configs for which to optimize the models + :param setup_context: The setup context + :return: A list of futures for the optimizations + """ + return list(itertools.chain(*( + [ + executor.submit( + optimize_executorch_model, + model_name, + npu_config, + setup_context, + setup_context.paths_config.downloads_dir / use_case.name + ) + for model_name + in use_case.executorch_models + ] + for use_case, npu_config + in itertools.product(use_cases, npu_configs) + ))) + + +def parallel_setup( + setup_context: SetupContext, + use_cases: typing.List[UseCase], + resources_to_download: typing.List[typing.Tuple[str, Path]], + tflm_npu_configs: typing.List[NpuConfig], + executorch_npu_configs: typing.List[NpuConfig], +): + """ + Download and optimize models using a thread pool + :param setup_context: The setup context + :param use_cases: The list of use cases to generate resources for + :param resources_to_download: The list of resources yet to be downloaded + :param tflm_npu_configs: The list of NPU configs for which to generate + optimized TensorFlow Lite Micro models + :param executorch_npu_configs: The list of NPU configs for which to generate + optimized ExecuTorch models + """ + optimize_tflite = (setup_context.setup_config.set_up_tensorflow + and setup_context.setup_config.run_vela_on_models) + optimize_executorch = (setup_context.setup_config.set_up_executorch + and setup_context.setup_config.run_vela_on_models) + with concurrent.futures.ThreadPoolExecutor( + max_workers=setup_context.setup_config.parallel + ) as executor: + # Find previously-downloaded unoptimized models + unoptimized_models = [] + if optimize_tflite: + unoptimized_models = find_unoptimized_tflite_files( + setup_context.paths_config.downloads_dir + ) + # Start parallel downloads + download_futures = [ + executor.submit(download_file, url, dest) + for url, dest in resources_to_download + ] + # Start optimizing previously-downloaded tflite models and ExecuTorch models + model_optimize_futures = [] + if optimize_tflite: + model_optimize_futures += optimize_tflite_models_async( + executor, unoptimized_models, tflm_npu_configs, setup_context.env_activate_cmd + ) + if optimize_executorch: + model_optimize_futures += optimize_executorch_models_async( + executor, use_cases, executorch_npu_configs, setup_context + ) + # When tflite models finish downloading, start optimizing them + if optimize_tflite: + for download_future in concurrent.futures.as_completed(download_futures): + downloaded_path = download_future.result() + if downloaded_path.suffix.endswith("tflite"): + model_optimize_futures += optimize_tflite_models_async( + executor, + [downloaded_path], + tflm_npu_configs, + setup_context.env_activate_cmd + ) + # Wait for all models to finish optimizing + concurrent.futures.wait( + model_optimize_futures, + return_when=concurrent.futures.ALL_COMPLETED + ) + + optimisation_skipped = any(future.result() for future in model_optimize_futures) + + # If any optimisation was skipped, show how to regenerate: + if optimisation_skipped: + logging.warning("One or more optimisations were skipped.") + logging.warning( + "To optimise all the models, please remove the directory %s.", + setup_context.paths_config.downloads_dir + ) + + +def serial_setup( + setup_context: SetupContext, + use_cases: typing.List[UseCase], + resources_to_download: typing.List[typing.Tuple[str, Path]], + tflm_npu_configs: typing.List[NpuConfig], + executorch_npu_configs: typing.List[NpuConfig] +): + """ + Download and optimize models without parallelism + :param setup_context: The setup context + :param use_cases: The list of use cases to generate resources for + :param resources_to_download: The list of resources yet to be downloaded + :param tflm_npu_configs: The list of NPU configs for which to generate + optimized TensorFlow Lite Micro models + :param executorch_npu_configs: The list of NPU configs for which to generate + optimized ExecuTorch models + """ + for url, dest in resources_to_download: + download_file(url, dest) + optimisation_skipped = False + if (setup_context.setup_config.set_up_tensorflow + and setup_context.setup_config.run_vela_on_models): + model_paths = find_unoptimized_tflite_files(setup_context.paths_config.downloads_dir) + for model_path in model_paths: + for config in tflm_npu_configs: + optimisation_skipped = run_vela( + config, + setup_context.env_activate_cmd, + model_path, + vela_config_file, + output_dir=model_path.parent + ) or optimisation_skipped + + if (setup_context.setup_config.set_up_executorch + and setup_context.setup_config.run_vela_on_models): + to_optimize = itertools.product(use_cases, executorch_npu_configs) + for use_case, npu_config in to_optimize: + for model_name in use_case.executorch_models: + optimisation_skipped = optimize_executorch_model( + model_name=model_name, + npu_config=npu_config, + setup_context=setup_context, + output_dir=setup_context.paths_config.downloads_dir / use_case.name + ) or optimisation_skipped + + # If any optimisation was skipped, show how to regenerate: + if optimisation_skipped: + logging.warning("One or more optimisations were skipped.") + logging.warning( + "To optimise all the models, please remove the directory %s.", + setup_context.paths_config.downloads_dir + ) + + +def set_up_resources( + setup_config: SetupConfig, + optimization_config: OptimizationConfig, + paths_config: PathsConfig +) -> Path: """ Helpers function that retrieve the output from a command. @@ -676,6 +784,8 @@ def set_up_resources(setup_config: SetupConfig, paths_config: PathsConfig) -> Pa download_directory_path : Root of the directory where the resources have been downloaded to. virtual_env_path : Path to the root of virtual environment. """ + context = SetupContext(setup_config, optimization_config, paths_config) + # Paths. check_paths_config(paths_config) metadata_file_path = paths_config.downloads_dir / "resources_downloaded_metadata.json" @@ -701,21 +811,50 @@ def set_up_resources(setup_config: SetupConfig, paths_config: PathsConfig) -> Pa setup_script_hash ) - env_path, env_activate = set_up_python_venv( + context.env_path, context.env_activate_cmd = set_up_python_venv( paths_config.downloads_dir, paths_config.additional_requirements_file ) + setup_vela(context.env_activate_cmd) + + if setup_config.set_up_executorch: + et_ops_lib_list = setup_executorch(context) + context.quantized_ops_lib_path = [ + lib for lib in et_ops_lib_list + if lib.name == "libquantized_ops_aot_lib.so" + ][0] + # Download models + + npu_configs = [ + get_default_npu_config_from_name(npu_config_name, optimization_config.arena_cache_size) + for npu_config_name in set( + default_npu_configs.names + + list(optimization_config.additional_npu_config_names) + ) + ] + + executorch_npu_configs = [config for config in npu_configs if config.processor_id != "U65"] + + # For ExecuTorch, we need to generate TOSA PTE files for native host as well. + # Appending None here to the list as a temporary workaround. + executorch_npu_configs.append(None) + logging.info("Downloading resources.") + to_download = [] for use_case in use_case_resources: - download_resources( + initialize_use_case_resources_directory( use_case, metadata_dict, paths_config.downloads_dir, setup_config.check_clean_folder, setup_script_hash_verified ) + to_download += get_resources_to_download( + use_case, + download_dir=paths_config.downloads_dir, + ) # Run vela on models in resources_downloaded # New models will have same name with '_vela' appended. @@ -725,15 +864,22 @@ def set_up_resources(setup_config: SetupConfig, paths_config: PathsConfig) -> Pa # # Note: To avoid to run vela twice on the same model, it's supposed that # downloaded model names don't contain the 'vela' word. - if setup_config.run_vela_on_models is True: - # Consolidate all config names while discarding duplicates: - run_vela_on_all_models( - paths_config.downloads_dir, - env_activate, - setup_config.arena_cache_size, - npu_config_names=list( - set(default_npu_configs.names + list(setup_config.additional_npu_config_names)) - ) + + if setup_config.parallel > 1: + parallel_setup( + context, + use_case_resources, + to_download, + npu_configs, + executorch_npu_configs + ) + else: + serial_setup( + context, + use_case_resources, + to_download, + npu_configs, + executorch_npu_configs ) # Collect and write metadata @@ -745,7 +891,7 @@ def set_up_resources(setup_config: SetupConfig, paths_config: PathsConfig) -> Pa metadata_file_path ) - return env_path + return context.env_path if __name__ == "__main__": @@ -755,6 +901,15 @@ if __name__ == "__main__": help="Do not run Vela optimizer on downloaded models.", action="store_true", ) + parser.add_argument( + "--ml-frameworks", + help=f"""Specify the ML frameworks for which to set up resources. + Valid values are: {valid_ml_frameworks} + """, + nargs="+", + default=[f'{MLFramework.TENSORFLOW_LITE_MICRO.value}'], + action="store", + ) parser.add_argument( "--additional-ethos-u-config-name", help=f"""Additional (non-default) configurations for Vela: @@ -781,6 +936,12 @@ if __name__ == "__main__": help="Clean the directory and optimize the downloaded resources", action="store_true", ) + parser.add_argument( + "--parallel", + help="Number of threads to use for downloads and model optimisation", + type=int, + default=1 + ) parser.add_argument( "--requirements-file", help="Path to requirements.txt file to install additional packages", @@ -811,18 +972,29 @@ if __name__ == "__main__": logging.basicConfig(filename="log_build_default.log", level=logging.DEBUG) logging.getLogger().addHandler(logging.StreamHandler(sys.stdout)) + ml_frameworks = valid_ml_frameworks \ + if len(parsed_args.ml_frameworks) == 0 \ + else valid_ml_frameworks.intersection(parsed_args.ml_frameworks) + setup = SetupConfig( run_vela_on_models=not parsed_args.skip_vela, - additional_npu_config_names=parsed_args.additional_ethos_u_config_name, use_case_names=parsed_args.use_case, - arena_cache_size=parsed_args.arena_cache_size, check_clean_folder=parsed_args.clean, + set_up_executorch=MLFramework.EXECUTORCH.value in ml_frameworks, + set_up_tensorflow=MLFramework.TENSORFLOW_LITE_MICRO.value in ml_frameworks, + parallel=parsed_args.parallel + ) + + optimization = OptimizationConfig( + additional_npu_config_names=parsed_args.additional_ethos_u_config_name, + arena_cache_size=parsed_args.arena_cache_size, ) paths = PathsConfig( use_case_resources_file=parsed_args.use_case_resources_file, downloads_dir=parsed_args.downloads_dir, additional_requirements_file=parsed_args.requirements_file, + executorch_path=default_executorch_path ) - set_up_resources(setup, paths) + set_up_resources(setup, optimization, paths) diff --git a/source/application/api/CMakeLists.txt b/source/application/api/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..559931301c41443c48b602c1686affadeb34fec2 --- /dev/null +++ b/source/application/api/CMakeLists.txt @@ -0,0 +1,21 @@ +#---------------------------------------------------------------------------- +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its +# affiliates +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +#---------------------------------------------------------------------------- + +add_subdirectory(fwk) +add_subdirectory(common) +add_subdirectory(use_case) diff --git a/source/application/api/common/CMakeLists.txt b/source/application/api/common/CMakeLists.txt index c4064ba4529f54abd17c1de3801a4a6dd7e0ae7d..2183822f865cd1a65aa979041b7cfc39633cde5f 100644 --- a/source/application/api/common/CMakeLists.txt +++ b/source/application/api/common/CMakeLists.txt @@ -1,5 +1,6 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2022-2023 Arm Limited and/or its affiliates +# SPDX-FileCopyrightText: Copyright 2022-2023, 2025 Arm Limited and/or +# its affiliates # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,7 +18,11 @@ ######################################################### # Common utility library used by use case libraries. # -# NOTE: this library should not depend on HAL. # +# NOTE: # +# 1. This library should not depend on HAL. # +# 2. This library should depend on ML framework # +# interface and not on the framework wrapper # +# itself. # ######################################################### cmake_minimum_required(VERSION 3.21.0) @@ -31,30 +36,22 @@ project(${COMMON_UC_UTILS_TARGET} add_library(${COMMON_UC_UTILS_TARGET} STATIC) ## Include directories - public -target_include_directories(${COMMON_UC_UTILS_TARGET} - PUBLIC - include - ${TENSORFLOW_SRC_PATH}/tensorflow/lite/micro/tools/make/downloads/flatbuffers/include - ${TENSORFLOW_SRC_PATH}/tensorflow/lite/micro/tools/make/downloads/gemmlowp) +target_include_directories(${COMMON_UC_UTILS_TARGET} PUBLIC include) ## Sources -target_sources(${COMMON_UC_UTILS_TARGET} - PRIVATE +target_sources(${COMMON_UC_UTILS_TARGET} PRIVATE source/Classifier.cc source/ImageUtils.cc - source/Mfcc.cc - source/Model.cc - source/TensorFlowLiteMicro.cc) + source/Mfcc.cc) # Link time library targets: -target_link_libraries(${COMMON_UC_UTILS_TARGET} - PUBLIC - log # Logging functions - arm_math # Math functions - tensorflow-lite-micro) # TensorFlow Lite Micro library +target_link_libraries(${COMMON_UC_UTILS_TARGET} PUBLIC + mlek_log # Logging interface + arm_math # Math functions + ml-framework-iface) # ML framework interface # Display status: message(STATUS "*******************************************************") -message(STATUS "Library : " ${COMMON_UC_UTILS_TARGET}) -message(STATUS "CMAKE_SYSTEM_PROCESSOR : " ${CMAKE_SYSTEM_PROCESSOR}) +message(STATUS "Library: " ${COMMON_UC_UTILS_TARGET}) +message(STATUS "CMAKE_SYSTEM_PROCESSOR: " ${CMAKE_SYSTEM_PROCESSOR}) message(STATUS "*******************************************************") diff --git a/source/application/api/common/include/BaseProcessing.hpp b/source/application/api/common/include/BaseProcessing.hpp index a557520b76aa33422ddcb44355db4bfc967de82f..693037b8967cd0d4b1688d6c47a1f452c7237674 100644 --- a/source/application/api/common/include/BaseProcessing.hpp +++ b/source/application/api/common/include/BaseProcessing.hpp @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or + * its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,7 +36,7 @@ namespace app { /** * @brief Should perform pre-processing of 'raw' input data and load it into - * TFLite Micro input tensors ready for inference + * input tensors ready for inference * @param[in] input Pointer to the data that pre-processing will work on. * @param[in] inputSize Size of the input data. * @return true if successful, false otherwise. @@ -55,7 +56,7 @@ namespace app { /** * @brief Should perform post-processing of the result of inference then populate - * populate result data for any later use. + * result data for any later use. * @return true if successful, false otherwise. **/ virtual bool DoPostProcess() = 0; @@ -64,4 +65,4 @@ namespace app { } /* namespace app */ } /* namespace arm */ -#endif /* BASE_PROCESSING_HPP */ \ No newline at end of file +#endif /* BASE_PROCESSING_HPP */ diff --git a/source/application/api/common/include/Classifier.hpp b/source/application/api/common/include/Classifier.hpp index 8e2cf47a4a1a2501a314257810c8bf4d236e2326..e96aa2c152130a4fbe4ab829af49061883ff78e2 100644 --- a/source/application/api/common/include/Classifier.hpp +++ b/source/application/api/common/include/Classifier.hpp @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021-2022, 2025 Arm Limited and/or its + * affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,8 +19,10 @@ #define CLASSIFIER_HPP #include "ClassificationResult.hpp" -#include "TensorFlowLiteMicro.hpp" +#include "Tensor.hpp" +#include +#include #include namespace arm { @@ -39,7 +42,8 @@ namespace app { /** * @brief Gets the top N classification results from the * output vector. - * @param[in] outputTensor Inference output tensor from an NN model. + * @param[in] outputTensor Output tensor retrieved from the model interface, expressed + * as a shared pointer. * @param[out] vecResults A vector of classification results. * populated by this function. * @param[in] labels Labels vector to match classified classes. @@ -48,11 +52,12 @@ namespace app { * @return true if successful, false otherwise. **/ - virtual bool GetClassificationResults( - TfLiteTensor* outputTensor, - std::vector& vecResults, - const std::vector & labels, uint32_t topNCount, - bool use_softmax); + virtual bool + GetClassificationResults(const std::shared_ptr outputTensor, + std::vector& vecResults, + const std::vector& labels, + uint32_t topNCount, + bool use_softmax); /** * @brief Populate the elements of the Classification Result object. diff --git a/source/application/api/common/include/ImageUtils.hpp b/source/application/api/common/include/ImageUtils.hpp index ae8677892c5d7da8acf9e9d4c7f9b05bdfe6d038..fb9b9eaabc3c95550407839b79fcdb15190e5c33 100644 --- a/source/application/api/common/include/ImageUtils.hpp +++ b/source/application/api/common/include/ImageUtils.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2022, 2024 Arm Limited and/or + * SPDX-FileCopyrightText: Copyright 2022, 2024-2025 Arm Limited and/or * its affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -18,6 +18,8 @@ #ifndef IMAGE_UTILS_HPP #define IMAGE_UTILS_HPP +#include "Tensor.hpp" + #include #include #include @@ -102,10 +104,37 @@ namespace image { /** * @brief Helper function to convert a UINT8 image to INT8 format. - * @param[in,out] data Pointer to the data start. - * @param[in] kMaxImageSize Total number of pixels in the image. + * @param[in,out] data Pointer to the data start. + * @param[in] nElem Total number of pixels in the image. + * @note Performance in-place transformation. + * + **/ + void ConvertUint8ToInt8(void* data, size_t nElem); + + /** + * @brief Helper function to convert a UINT8 image to INT8 format. + * @param[out] dst Destination buffer. + * @param[in] src Source pointer. + * @param[in] nElem Number of elements to be copied over. + * @param[in] layout Data layout for destination. **/ - void ConvertImgToInt8(void* data, size_t kMaxImageSize); + void ConvertUint8ToInt8(int8_t* dst, + const uint8_t* const src, + size_t nElem, + fwk::iface::TensorLayout layout); + + /** + * @brief Function to convert unsigned 8-bit src image to + * floating point. + * @param[out] dst Destination buffer. + * @param[in] src Source pointer. + * @param[in] nElem Number of elements to be copied over. + * @param[in] layout Data layout for destination. + */ + void ConvertUint8ToFp32(float* dst, + const uint8_t* src, + const size_t nElem, + fwk::iface::TensorLayout layout); /** * @brief Converts RGB image to grayscale. @@ -119,4 +148,4 @@ namespace image { } /* namespace app */ } /* namespace arm */ -#endif /* IMAGE_UTILS_HPP */ \ No newline at end of file +#endif /* IMAGE_UTILS_HPP */ diff --git a/source/application/api/common/include/Model.hpp b/source/application/api/common/include/Model.hpp deleted file mode 100644 index 6eefd02a48304ec30d6cb014e80280fd5ee11411..0000000000000000000000000000000000000000 --- a/source/application/api/common/include/Model.hpp +++ /dev/null @@ -1,151 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright 2021-2023 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 - * - * 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. - */ -#ifndef MODEL_HPP -#define MODEL_HPP - -#include "TensorFlowLiteMicro.hpp" - -#include - -namespace arm { -namespace app { - - /** - * @brief NN model class wrapping the underlying TensorFlow-Lite-Micro API. - */ - class Model { - public: - /** @brief Constructor. */ - Model(); - - /** @brief Destructor. */ - virtual ~Model() = default; - - /** @brief Gets the pointer to the model's input tensor at given input index. */ - TfLiteTensor* GetInputTensor(size_t index) const; - - /** @brief Gets the pointer to the model's output tensor at given output index. */ - TfLiteTensor* GetOutputTensor(size_t index) const; - - /** @brief Gets the model's data type. */ - TfLiteType GetType() const; - - /** @brief Gets the pointer to the model's input shape. */ - TfLiteIntArray* GetInputShape(size_t index) const; - - /** @brief Gets the pointer to the model's output shape at given output index. */ - TfLiteIntArray* GetOutputShape(size_t index) const; - - /** @brief Gets the number of input tensors the model has. */ - size_t GetNumInputs() const; - - /** @brief Gets the number of output tensors the model has. */ - size_t GetNumOutputs() const; - - /** @brief Logs the tensor information to stdout. */ - void LogTensorInfo(TfLiteTensor* tensor); - - /** @brief Logs the interpreter information to stdout. */ - void LogInterpreterInfo(); - - /** @brief Initialise the model class object. - * @param[in] tensorArenaAddress Pointer to the tensor arena buffer. - * @param[in] tensorArenaAddress Size of the tensor arena buffer in bytes. - * @param[in] nnModelAddr Pointer to the model. - * @param[in] nnModelSize Size of the model in bytes, if known. - * @param[in] allocator Optional: a pre-initialised micro allocator pointer, - * if available. If supplied, this allocator will be used - * to create the interpreter instance. - * @return true if initialisation succeeds, false otherwise. - **/ - bool Init(uint8_t* tensorArenaAddr, - uint32_t tensorArenaSize, - const uint8_t* nnModelAddr, - uint32_t nnModelSize, - tflite::MicroAllocator* allocator = nullptr); - - /** - * @brief Gets the allocator pointer for this instance. - * @return Pointer to a tflite::MicroAllocator object, if - * available; nullptr otherwise. - **/ - tflite::MicroAllocator* GetAllocator(); - - /** @brief Checks if this object has been initialised. */ - bool IsInited() const; - - /** @brief Checks if the model uses signed data. */ - bool IsDataSigned() const; - - /** @brief Checks if the model uses Ethos-U operator */ - bool ContainsEthosUOperator() const; - - /** @brief Runs the inference (invokes the interpreter). */ - virtual bool RunInference(); - - /** @brief Model information handler common to all models. - * @return true or false based on execution success. - **/ - bool ShowModelInfoHandler(); - - /** @brief Gets a pointer to the tensor arena. */ - uint8_t* GetTensorArena(); - - protected: - /** @brief Gets the pointer to the NN model data array. - * @return Pointer of uint8_t type. - **/ - const uint8_t* ModelPointer(); - - /** @brief Gets the model size. - * @return size_t, size in bytes. - **/ - uint32_t ModelSize(); - - /** - * @brief Gets the op resolver for the model instance. - * @return const reference to a tflite::MicroOpResolver object. - **/ - virtual const tflite::MicroOpResolver& GetOpResolver() = 0; - - /** - * @brief Add all the operators required for the given model. - * Implementation of this should come from the use case. - * @return true is ops are successfully added, false otherwise. - **/ - virtual bool EnlistOperations() = 0; - - /** @brief Gets the total size of tensor arena available for use. */ - size_t GetActivationBufferSize(); - - private: - const tflite::Model* m_pModel{nullptr}; /* Tflite model pointer. */ - std::unique_ptr m_pInterpreter{nullptr}; /* Tflite interpreter. */ - tflite::MicroAllocator* m_pAllocator{nullptr}; /* Tflite micro allocator. */ - bool m_inited{false}; /* Indicates whether this object has been initialised. */ - const uint8_t* m_modelAddr{nullptr}; /* Model address */ - uint32_t m_modelSize{0}; /* Model size */ - - std::vector m_input{}; /* Model's input tensor pointers. */ - std::vector m_output{}; /* Model's output tensor pointers. */ - TfLiteType m_type{kTfLiteNoType}; /* Model's data type. */ - }; - -} /* namespace app */ -} /* namespace arm */ - -#endif /* MODEL_HPP */ diff --git a/source/application/api/common/source/Classifier.cc b/source/application/api/common/source/Classifier.cc index 9b14ffd97b5cf9df0829380984ef18e055e997ca..aec43e7f7009a924808356b6a379401531883de8 100644 --- a/source/application/api/common/source/Classifier.cc +++ b/source/application/api/common/source/Classifier.cc @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2023 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021-2023, 2025 Arm Limited and/or + * its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,7 +17,6 @@ */ #include "Classifier.hpp" -#include "TensorFlowLiteMicro.hpp" #include "PlatformMath.hpp" #include "log_macros.h" @@ -62,7 +62,7 @@ namespace app { auto setFwdIter = sortedSet.begin(); /* Scan through the rest of elements with compare operations. */ - for (uint32_t i = topNCount; i < labels.size(); ++i) { + for (uint32_t i = topNCount; i < tensor.size(); ++i) { if (setFwdIter->first < tensor[i]) { sortedSet.erase(*setFwdIter); sortedSet.insert({tensor[i], i}); @@ -77,25 +77,25 @@ namespace app { return true; } - bool Classifier::GetClassificationResults(TfLiteTensor* outputTensor, - std::vector& vecResults, const std::vector & labels, - uint32_t topNCount, bool useSoftmax) + bool Classifier::GetClassificationResults( + const std::shared_ptr outputTensor, + std::vector& vecResults, + const std::vector& labels, + uint32_t topNCount, + bool useSoftmax) { if (outputTensor == nullptr) { printf_err("Output vector is null pointer.\n"); return false; } - uint32_t totalOutputSize = 1; - for (int inputDim = 0; inputDim < outputTensor->dims->size; inputDim++) { - totalOutputSize *= outputTensor->dims->data[inputDim]; - } + const uint32_t nOutputElements = outputTensor->GetNumElements(); /* Health check */ - if (totalOutputSize < topNCount) { + if (nOutputElements < topNCount) { printf_err("Output vector is smaller than %" PRIu32 "\n", topNCount); return false; - } else if (totalOutputSize != labels.size()) { + } else if (nOutputElements != labels.size()) { printf_err("Output size doesn't match the labels' size\n"); return false; } else if (topNCount == 0) { @@ -103,61 +103,59 @@ namespace app { return false; } - bool resultState; vecResults.clear(); /* De-Quantize Output Tensor */ - QuantParams quantParams = GetTensorQuantParams(outputTensor); + auto quantParams = outputTensor->GetQuantParams(); /* Floating point tensor data to be populated * NOTE: The assumption here is that the output tensor size isn't too * big and therefore, there's neglibible impact on heap usage. */ - std::vector tensorData(totalOutputSize); + std::vector tensorData(nOutputElements); /* Populate the floating point buffer */ - switch (outputTensor->type) { - case kTfLiteUInt8: { - uint8_t *tensor_buffer = tflite::GetTensorData(outputTensor); - for (size_t i = 0; i < totalOutputSize; ++i) { - tensorData[i] = quantParams.scale * - (static_cast(tensor_buffer[i]) - quantParams.offset); - } - break; + switch (outputTensor->Type()) { + case fwk::iface::TensorType::UINT8: { + uint8_t* tensor_buffer = outputTensor->GetData(); + for (size_t i = 0; i < nOutputElements; ++i) { + tensorData[i] = + quantParams.scale * (static_cast(tensor_buffer[i]) - quantParams.offset); } - case kTfLiteInt8: { - int8_t *tensor_buffer = tflite::GetTensorData(outputTensor); - for (size_t i = 0; i < totalOutputSize; ++i) { - tensorData[i] = quantParams.scale * - (static_cast(tensor_buffer[i]) - quantParams.offset); - } - break; + break; + } + case fwk::iface::TensorType::INT8: { + int8_t* tensor_buffer = outputTensor->GetData(); + for (size_t i = 0; i < nOutputElements; ++i) { + tensorData[i] = + quantParams.scale * (static_cast(tensor_buffer[i]) - quantParams.offset); } - case kTfLiteFloat32: { - float *tensor_buffer = tflite::GetTensorData(outputTensor); - for (size_t i = 0; i < totalOutputSize; ++i) { - tensorData[i] = tensor_buffer[i]; - } - break; + break; + } + case fwk::iface::TensorType::FP32: { + float* tensor_buffer = outputTensor->GetData(); + for (size_t i = 0; i < nOutputElements; ++i) { + tensorData[i] = tensor_buffer[i]; } + break; + } default: printf_err("Tensor type %s not supported by classifier\n", - TfLiteTypeGetName(outputTensor->type)); + fwk::iface::GetTensorDataTypeName(outputTensor->Type())); return false; - } + } if (useSoftmax) { math::MathUtils::SoftmaxF32(tensorData); } /* Get the top N results. */ - resultState = GetTopNResults(tensorData, vecResults, topNCount, labels); + bool resultState = GetTopNResults(tensorData, vecResults, topNCount, labels); if (!resultState) { printf_err("Failed to get top N results set\n"); - return false; } - return true; + return resultState; } } /* namespace app */ -} /* namespace arm */ \ No newline at end of file +} /* namespace arm */ diff --git a/source/application/api/common/source/ImageUtils.cc b/source/application/api/common/source/ImageUtils.cc index 072dcdf11ce164b4c9165e0e23f2696609c6c9bb..8a2e5d611192c3b71a7116235dfc801a93e82adf 100644 --- a/source/application/api/common/source/ImageUtils.cc +++ b/source/application/api/common/source/ImageUtils.cc @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or its + * affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,6 +17,7 @@ */ #include "ImageUtils.hpp" +#include #include namespace arm { @@ -96,7 +98,7 @@ namespace image { } } - void ConvertImgToInt8(void* data, const size_t kMaxImageSize) + void ConvertUint8ToInt8(void* data, const size_t kMaxImageSize) { auto* tmp_req_data = static_cast(data); auto* tmp_signed_req_data = static_cast(data); @@ -107,12 +109,72 @@ namespace image { } } + void ConvertUint8ToInt8(int8_t* dst, + const uint8_t* src, + const size_t nElem, + fwk::iface::TensorLayout layout) + { + constexpr size_t nChannels = 3; + const size_t imgArraySz = nElem / nChannels; + + if (layout == fwk::iface::TensorLayout::NCHW) { + for (size_t i = 0; i < imgArraySz; i++) { + for (size_t j = 0; j < nChannels; ++j) { + dst[(j * imgArraySz) + i] = + static_cast(static_cast(src[i * nChannels + j]) - 128); + } + } + } else { + for (size_t i = 0; i < nElem; ++i) { + dst[i] = static_cast(static_cast(src[i]) - 128); + } + } + } + + static inline float Normalize(const uint8_t val, float mean, float std) + { + return ((static_cast(val) / 255.f) - mean) / std; + } + + void ConvertUint8ToFp32(float* dst, + const uint8_t* src, + const size_t nElem, + fwk::iface::TensorLayout layout) + { + constexpr size_t nChannels = 3; + /** + * The normalisation process happens per channel, and these are the + * default values for the Red, Green and Blue channels. If needed, + * this function could accept these are arguments later. + * + * Mean and standard deviation values: {R, G, B } */ + const std::array mean{0.485, 0.456, 0.406}; + const std::array stddev{0.229, 0.224, 0.225}; + const size_t imgArraySz = nElem / nChannels; + + if (layout == fwk::iface::TensorLayout::NCHW) { + for (size_t i = 0; i < imgArraySz; i++) { + for (size_t j = 0; j < nChannels; ++j) { + dst[(j * imgArraySz) + i] = + Normalize(src[i * nChannels + j], mean[j], stddev[j]); + } + } + } else { + for (size_t i = 0; i < nElem; i += nChannels) { + dst[i] = Normalize(src[i], mean[0], stddev[0]); + dst[i + 1] = Normalize(src[i], mean[1], stddev[1]); + dst[i + 2] = Normalize(src[i], mean[2], stddev[2]); + } + } + } + void RgbToGrayscale(const uint8_t* srcPtr, uint8_t* dstPtr, const size_t dstImgSz) { const float R = 0.299; const float G = 0.587; const float B = 0.114; - for (size_t i = 0; i < dstImgSz; ++i, srcPtr += 3) { + constexpr size_t nChannels = 3; + for (size_t i = 0; i < dstImgSz; ++i, srcPtr += nChannels) { uint32_t int_gray = R * (*srcPtr) + G * (*(srcPtr + 1)) + B * (*(srcPtr + 2)); @@ -123,4 +185,4 @@ namespace image { } /* namespace image */ } /* namespace app */ -} /* namespace arm */ \ No newline at end of file +} /* namespace arm */ diff --git a/source/application/api/fwk/CMakeLists.txt b/source/application/api/fwk/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..edcc71f605b1df3016643cc2d5e944f669b4fb51 --- /dev/null +++ b/source/application/api/fwk/CMakeLists.txt @@ -0,0 +1,47 @@ +#---------------------------------------------------------------------------- +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its +# affiliates +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +#---------------------------------------------------------------------------- + +######################################################### +# ML framework abstraction # +######################################################### +cmake_minimum_required(VERSION 3.21.0) + +set(ML_FWK_IFACE_TARGET ml-framework-iface) +project(${ML_FWK_IFACE_TARGET} + DESCRIPTION "ML framework interface library" + LANGUAGES CXX) + +# Add interface library +add_library(${ML_FWK_IFACE_TARGET} INTERFACE) +target_include_directories(${ML_FWK_IFACE_TARGET} INTERFACE iface) + +if ("ExecuTorch" STREQUAL ${ML_FRAMEWORK}) + message(STATUS "Adding ExecuTorch framework to the build...") + add_subdirectory(executorch) +endif () + +if ("TensorFlowLiteMicro" STREQUAL ${ML_FRAMEWORK}) + message(STATUS "Adding TensorFlow Lite Micro framework to the build...") + add_subdirectory(tflm) +endif () + +# Display status: +message(STATUS "*******************************************************") +message(STATUS "Library: " ${ML_FWK_IFACE_TARGET}) +message(STATUS "CMAKE_SYSTEM_PROCESSOR: " ${CMAKE_SYSTEM_PROCESSOR}) +message(STATUS "*******************************************************") diff --git a/source/application/api/fwk/executorch/CMakeLists.txt b/source/application/api/fwk/executorch/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..eb8b626ecd79faa26c24a8632fdf8b08bbf62480 --- /dev/null +++ b/source/application/api/fwk/executorch/CMakeLists.txt @@ -0,0 +1,60 @@ +#---------------------------------------------------------------------------- +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its +# affiliates +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +#---------------------------------------------------------------------------- + +######################################################### +# ExecuTorch wrapper # +######################################################### +cmake_minimum_required(VERSION 3.21.0) + +set(ML_FWK_ET_TARGET ml_framework_et) +project(${ML_FWK_ET_TARGET} + DESCRIPTION "ExecuTorch framework module" + LANGUAGES CXX) + +if (NOT "ExecuTorch" STREQUAL ${ML_FRAMEWORK}) + message(FATAL_ERROR "${CMAKE_CURRENT_SOURCE_DIR} should not be included for ${ML_FRAMEWORK}") +endif() + +# Include the ExecuTorch build target +set(CMAKE_POLICY_VERSION_MINIMUM 3.5) +include(executorch) + +# Create static library +add_library(${ML_FWK_ET_TARGET} STATIC + source/EtModelAllocator.cc + source/EtTensor.cc + source/EtModel.cc +) + +# Include directories - public +target_include_directories(${ML_FWK_ET_TARGET} PUBLIC + include) + +# Link time library targets: +target_link_libraries(${ML_FWK_ET_TARGET} PUBLIC + mlek_log # Logging functions + ml-framework-iface # ML framework interface lib + ${MLEK_EXECUTORCH_LINK_STR}) # ExecuTorch libraries + +target_compile_definitions(${ML_FWK_ET_TARGET} INTERFACE MLEK_FWK_EXECUTORCH=1) + +# Display status: +message(STATUS "*******************************************************") +message(STATUS "Library: " ${ML_FWK_ET_TARGET}) +message(STATUS "CMAKE_SYSTEM_PROCESSOR: " ${CMAKE_SYSTEM_PROCESSOR}) +message(STATUS "*******************************************************") diff --git a/source/application/api/fwk/executorch/include/EtMemoryAllocator.hpp b/source/application/api/fwk/executorch/include/EtMemoryAllocator.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9a730027c347d0cdf42e5e30bf1a5ce38e597416 --- /dev/null +++ b/source/application/api/fwk/executorch/include/EtMemoryAllocator.hpp @@ -0,0 +1,63 @@ +/** + * SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ + +#ifndef MLEK_EXECUTORCH_MEMORY_ALLOCATOR_HPP +#define MLEK_EXECUTORCH_MEMORY_ALLOCATOR_HPP + +#include "ExecuTorch.hpp" + +namespace arm::app::fwk::et { + +/** + * Arm allocator to help show associated metrics easily. + **/ +class EtMemoryAllocator : public executorch::runtime::MemoryAllocator { +public: + /** + * @brief Constructor + * @param[in] size Size of memory pool in bytes. + * @param[in] baseAddress Base address of the memory pool to be used by this allocator. + */ + EtMemoryAllocator(uint32_t size, uint8_t* baseAddress); + + /** + * @brief Gets the total numbers of bytes allocated by this allocator. + * @return Number of bytes as `size_t` + */ + size_t UsedSize() const; + + /** + * @brief Gets the total numbers of free space in this allocator's buffer. + * @return Number of bytes as `size_t` + */ + size_t FreeSize() const; + + /** + * @brief Allocate a chunk of memory. This is just a wrapper override around the base + * class implementation. + * @param[in] size Size (in bytes) of memory to be allocated. + * @param[in] alignment Alignment (in bytes) of the allocated region. + * @return Pointer to the allocated memory or `nullptr` on failure. + */ + virtual void* allocate(size_t size, size_t alignment = kDefaultAlignment) override; + +private: + size_t m_used; /**< Number of bytes used (allocated). */ +}; +} /* namespace arm::app::fwk::et */ + +#endif /* MLEK_EXECUTORCH_MEMORY_ALLOCATOR_HPP */ diff --git a/source/application/api/fwk/executorch/include/EtModel.hpp b/source/application/api/fwk/executorch/include/EtModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..99490f1104f7e388bdb63cc3da37de57a630f416 --- /dev/null +++ b/source/application/api/fwk/executorch/include/EtModel.hpp @@ -0,0 +1,150 @@ +/* + * SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#ifndef MLEK_ET_MODEL_HPP +#define MLEK_ET_MODEL_HPP + +#include "EtMemoryAllocator.hpp" +#include "ExecuTorch.hpp" +#include "Model.hpp" +#include + +namespace arm::app::fwk::et { + +using executorch::runtime::HierarchicalAllocator; +using executorch::runtime::MemoryManager; +using executorch::runtime::Program; +using executorch::runtime::Result; + +/** + * Backend data for ExecuTorch framework. + */ +struct EtBackendData { + std::shared_ptr m_methodAllocPtr{nullptr}; + std::shared_ptr m_tmpAllocPtr{nullptr}; + std::shared_ptr m_plannedMemAllocPtr{nullptr}; + std::shared_ptr m_memMgrPtr{nullptr}; + std::shared_ptr> m_program{nullptr}; + std::string m_methodName{}; +}; + +/** + * @brief NN model class wrapping the underlying TensorFlow-Lite-Micro API. + */ +class EtModel : public iface::Model { +public: + /** @brief Constructor. */ + EtModel(); + + /** @brief Destructor. */ + virtual ~EtModel(); + + /** @brief Gets the pointer to the model's input tensor at given input index. */ + std::shared_ptr GetInputTensor(size_t index) const override; + + /** @brief Gets the pointer to the model's output tensor at given output index. */ + std::shared_ptr GetOutputTensor(size_t index) const override; + + /** @brief Gets the model's data type. */ + iface::TensorType GetType() const override; + + /** @brief Gets the pointer to the model's input shape. */ + std::vector GetInputShape(size_t index) const override; + + /** @brief Gets the pointer to the model's output shape at given output index. */ + std::vector GetOutputShape(size_t index) const override; + + /** @brief Gets the number of input tensors the model has. */ + size_t GetNumInputs() const override; + + /** @brief Gets the number of output tensors the model has. */ + size_t GetNumOutputs() const override; + + /** @brief Logs the tensor information to stdout. */ + void LogTensorInfo(std::shared_ptr tensor) override; + + /** @brief Logs the interpreter information to stdout. */ + void LogInterpreterInfo() override; + + /** @brief Logs overall runtime memory usage to stdout. */ + void LogMemoryUsage() const; + + /** @brief Initialise the model class object. + * @return true if initialisation succeeds, false otherwise. + **/ + bool Init(iface::MemoryRegion& computeBuffer, + iface::MemoryRegion& nnModel, + const void* backendData = nullptr) override; + + /** @brief Checks if this object has been initialised. */ + bool IsInited() const override; + + /** @brief Checks if the model uses signed data. */ + bool IsDataSigned() const override; + + /** @brief Checks if the model uses Ethos-U operator */ + bool ContainsEthosUOperator() const override; + + /** @brief Runs the inference (invokes the interpreter). */ + bool RunInference() override; + + /** + * @brief Gets memory region information for the backend's compute buffer. + * @return Const reference to MemoryRegion object. + */ + const iface::MemoryRegion& GetComputeBuffer() const override; + + /** + * @brief Gets memory region information for neural network model. + * @return Const reference to MemoryRegion object. + */ + const iface::MemoryRegion& GetModelBuffer() const override; + + /** + * @brief Gets the allocator pointer for this instance. + * @return Pointer to a tflite::MicroAllocator object, if + * available; nullptr otherwise. + **/ + const EtBackendData& GetBackendData() const; + +protected: + /** @brief Prepares input tensors. */ + virtual bool PrepareInputTensors(); + +private: + std::function m_fnGetMethod; /**< Gets the forward inference + method. */ + EtBackendData m_backendData{}; /**< Backend data object */ + bool m_inited{false}; /**< Indicates whether this object has been initialised. */ + iface::MemoryRegion m_computeBuffer{}; /**< Compute buffer region */ + iface::MemoryRegion m_modelBuffer{}; /**< Buffer where model is hosted */ + + std::vector> + m_input{}; /**< Model's input tensor pointers. */ + std::vector> + m_output{}; /**< Model's output tensor pointers. */ + + std::vector> + m_inputTensorImplMap{}; /**< ExecuTorch's tensor implementation vector mapping to index of + inputs */ + std::vector + m_outputTensor{}; /**< ExecuTorch output tensor (results are read from this space) */ + + iface::TensorType m_type{iface::TensorType::INVALID}; /**< Model's data type. */ +}; +} /* namespace arm::app::fwk::et */ + +#endif /* MLEK_ET_MODEL_HPP */ diff --git a/source/application/api/fwk/executorch/include/EtTensor.hpp b/source/application/api/fwk/executorch/include/EtTensor.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3290122f64c72123314cf215ef63d4ba59a9af51 --- /dev/null +++ b/source/application/api/fwk/executorch/include/EtTensor.hpp @@ -0,0 +1,48 @@ +/* + * SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or + * its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ + +#ifndef MLEK_ET_TENSOR_HPP +#define MLEK_ET_TENSOR_HPP + +#include "ExecuTorch.hpp" +#include "Tensor.hpp" +#include +#include + +namespace arm::app::fwk::et { + +class EtTensor : public iface::TensorIface { +public: + explicit EtTensor(executorch::aten::TensorImpl* tensorImpl); /**< Constructor */ + explicit EtTensor(executorch::aten::Tensor& tensor); /** Constructor */ + ~EtTensor() override = default; /** Destructor */ + void* GetData() override; + size_t Bytes() override; + size_t GetNumElements() override; + iface::TensorType Type() override; + iface::TensorLayout Layout() override; + std::vector Shape() override; + iface::QuantParams GetQuantParams() override; + +private: + executorch::aten::Tensor m_tensor{nullptr}; +}; + +} // namespace arm::app::fwk::et + +#endif /* MLEK_ET_TENSOR_HPP */ diff --git a/source/application/api/fwk/executorch/include/ExecuTorch.hpp b/source/application/api/fwk/executorch/include/ExecuTorch.hpp new file mode 100644 index 0000000000000000000000000000000000000000..c7f430f9ded6e5bd70a63d181a239c7bb54595fd --- /dev/null +++ b/source/application/api/fwk/executorch/include/ExecuTorch.hpp @@ -0,0 +1,30 @@ +/* + * SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ + +#ifndef MLEK_EXECUTORCH_HPP +#define MLEK_EXECUTORCH_HPP + +#include +#include +#include +#include +#include +#include +#include +#include + +#endif /* MLEK_EXECUTORCH_HPP */ diff --git a/source/application/api/fwk/executorch/include/MobileNetModel.hpp b/source/application/api/fwk/executorch/include/MobileNetModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..9c52d0a4123baf9edb40026dfab01a246498a7d4 --- /dev/null +++ b/source/application/api/fwk/executorch/include/MobileNetModel.hpp @@ -0,0 +1,35 @@ +/* + * SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#ifndef IMG_CLASS_ET_MOBILENETMODEL_HPP +#define IMG_CLASS_ET_MOBILENETMODEL_HPP + +#include "EtModel.hpp" + +namespace arm::app::fwk::et { + +class MobileNetModel : public EtModel { + +public: + /* Indices for the expected model - based on input tensor shape */ + static constexpr uint32_t ms_inputChannelsIdx = 1; + static constexpr uint32_t ms_inputRowsIdx = 2; + static constexpr uint32_t ms_inputColsIdx = 3; +}; + +} /* namespace arm::app::fwk::et */ + +#endif /* IMG_CLASS_ET_MOBILENETMODEL_HPP */ diff --git a/source/application/api/fwk/executorch/source/EtModel.cc b/source/application/api/fwk/executorch/source/EtModel.cc new file mode 100644 index 0000000000000000000000000000000000000000..6b76adf758e05e8230986853feb3975af9ae44e3 --- /dev/null +++ b/source/application/api/fwk/executorch/source/EtModel.cc @@ -0,0 +1,382 @@ +/* + * SPDX-FileCopyrightText: Copyright 2021-2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#include "EtModel.hpp" + +#include "EtTensor.hpp" +#include "Model.hpp" +#include "log_macros.h" + +#include + +/** + * @TODO: Remove this bss mem from here. It should be passed into the init function + * like other memory regions. + */ +constexpr size_t sTmpAllocationPoolSz = 0x100000; /**< 1 MiB of temp allocation pool size. */ +static uint8_t __attribute__((aligned(16), section("ifm"))) + sTmpAllocationPool[sTmpAllocationPoolSz]; /**< temp allocation buffer */ + +namespace arm::app::fwk::et { + +EtModel::EtModel() +{ + info("Initialising ExecuTorch runtime\n"); + executorch::runtime::runtime_init(); +} + +EtModel::~EtModel() +{ + this->LogMemoryUsage(); +} + +/* Initialise the model */ +bool EtModel::Init(iface::MemoryRegion& computeBuffer, + iface::MemoryRegion& nnModel, + const void* backendData) +{ + debug("loading model from @ 0x%p\n", nnModel.data); + debug("model size: %" PRIu32 " bytes.\n", nnModel.size); + + if (backendData) { + this->m_backendData = *static_cast(backendData); + } + + info("Model PTE file loaded. Size: %zu bytes.\n", nnModel.size); + auto loader = executorch::extension::BufferDataLoader(nnModel.data, nnModel.size); + + this->m_backendData.m_program = std::make_shared>(Program::load(&loader)); + + if (!this->m_backendData.m_program->ok()) { + printf_err("Program loading failed @ 0x%p: 0x%" PRIx32 "\n", + nnModel.data, + static_cast(this->m_backendData.m_program->error())); + return false; + } + info("Model buffer loaded, has %zu methods\n", + this->m_backendData.m_program->get().num_methods()); + + if (this->m_backendData.m_methodName.empty()) { + const auto methodNameResult = this->m_backendData.m_program->get().get_method_name(0); + ET_CHECK_MSG(methodNameResult.ok(), "Program has no methods"); + this->m_backendData.m_methodName = std::string(*methodNameResult); + } + + info("Method found \"%s\"\n", this->m_backendData.m_methodName.c_str()); + + auto methodMeta = + this->m_backendData.m_program->get().method_meta(this->m_backendData.m_methodName.c_str()); + if (!methodMeta.ok()) { + printf_err("Failed to get method_meta for %s: 0x%" PRIx32 "\n", + this->m_backendData.m_methodName.c_str(), + static_cast(methodMeta.error())); + return false; + } + + info("Setting up method allocator pool. Size: %zu bytes.\n", computeBuffer.size); + + if (!this->m_backendData.m_methodAllocPtr) { + this->m_backendData.m_methodAllocPtr = + std::make_shared(computeBuffer.size, computeBuffer.data); + } + + std::vector> plannedSpans; // Passed to the allocator + const size_t numMemoryPlannedBuffers = methodMeta->num_memory_planned_buffers(); + size_t plannedMemBeforeMark = this->m_backendData.m_methodAllocPtr->UsedSize(); + + for (size_t id = 0; id < numMemoryPlannedBuffers; ++id) { + size_t bufferSize = static_cast(methodMeta->memory_planned_buffer_size(id).get()); + info("Setting up planned buffer %zu, size %zu.\n", id, bufferSize); + + /* Move to its own allocator when MemoryPlanner is in place. */ + uint8_t* buffer = + reinterpret_cast(this->m_backendData.m_methodAllocPtr->allocate(bufferSize)); + plannedSpans.push_back(executorch::runtime::Span{buffer, bufferSize}); + } + + info("Total planned memory allocated: %zu bytes\n", + this->m_backendData.m_methodAllocPtr->UsedSize() - plannedMemBeforeMark); + + if (!this->m_backendData.m_plannedMemAllocPtr) { + this->m_backendData.m_plannedMemAllocPtr = std::make_shared( + executorch::runtime::Span{plannedSpans.data(), plannedSpans.size()}); + } + + if (!this->m_backendData.m_tmpAllocPtr) { + this->m_backendData.m_tmpAllocPtr = + std::make_shared(sTmpAllocationPoolSz, sTmpAllocationPool); + } + + if (!this->m_backendData.m_memMgrPtr) { + this->m_backendData.m_memMgrPtr = + std::make_shared(this->m_backendData.m_methodAllocPtr.get(), + this->m_backendData.m_plannedMemAllocPtr.get(), + this->m_backendData.m_tmpAllocPtr.get()); + } + + size_t methodMemBeforeMark = this->m_backendData.m_methodAllocPtr->UsedSize(); + + executorch::runtime::EventTracer* eventTracerPtr = nullptr; + static auto method = + this->m_backendData.m_program->get().load_method(this->m_backendData.m_methodName.c_str(), + this->m_backendData.m_memMgrPtr.get(), + eventTracerPtr); + + if (!method.ok()) { + info("Loading of method \"%s\" failed with status 0x%" PRIx32 "\n", + this->m_backendData.m_methodName.c_str(), + static_cast(method.error())); + return false; + } + + /** + * Bypass the method class restrictions by creating a lambda + * function to get reference. This function can then be used + * by other member functions to get a reference to the method + * (static/bss) we created above + **/ + this->m_fnGetMethod = [&]() { return std::ref(method.get()); }; + + info("Method memory allocated: %zu bytes\n", + this->m_backendData.m_methodAllocPtr->UsedSize() - methodMemBeforeMark); + info("Method %s loaded.\n", this->m_backendData.m_methodName.c_str()); + + info("Preparing inputs...\n"); + size_t input_membase = this->m_backendData.m_methodAllocPtr->UsedSize(); + + std::vector> inputBuffers; + if (!this->PrepareInputTensors()) { + printf_err("Failed to prepare inputs\n"); + return false; + } + + info("Inputs allocated: %zu bytes.\n", + this->m_backendData.m_methodAllocPtr->UsedSize() - input_membase); + + std::vector outputs(method->outputs_size()); + info("Preparing outputs; count: %zu\n", outputs.size()); + auto status = method->get_outputs(outputs.data(), outputs.size()); + if (status != executorch::runtime::Error::Ok) { + printf_err("Failed to get output tensors\n"); + return false; + } + + for (size_t i = 0; i < outputs.size(); ++i) { + this->m_outputTensor.emplace_back(outputs[i].toTensor()); + this->m_output.emplace_back( + std::make_shared(this->m_outputTensor[this->m_outputTensor.size() - 1])); + } + info("Outputs prepared\n"); + this->m_inited = true; + this->LogInterpreterInfo(); + + info("Model initialisation complete\n"); + return true; +} + +std::shared_ptr EtModel::GetInputTensor(size_t index) const +{ + if (index < this->GetNumInputs()) { + return this->m_input.at(index); + } + return nullptr; +} + +std::shared_ptr EtModel::GetOutputTensor(size_t index) const +{ + if (index < this->GetNumOutputs()) { + return this->m_output.at(index); + } + return nullptr; +} + +void EtModel::LogTensorInfo(std::shared_ptr tensor) +{ + if (!tensor) { + printf_err("Invalid tensor\n"); + return; + } + + info("Tensor: %zu bytes; %zu elements\n", tensor->Bytes(), tensor->GetNumElements()); + auto shape = tensor->Shape(); + std::string shapeStr("["); + for (auto s : shape) { + shapeStr += std::to_string(s) + ", "; + } + shapeStr.erase(shapeStr.size() - 2, shapeStr.size()); + shapeStr += "]"; + info("\tShape: %s\n", shapeStr.c_str()); +} + +void EtModel::LogInterpreterInfo() +{ + info("Number of input tensors: %zu\n", this->GetNumInputs()); + for (const auto& i : this->m_input) { + this->LogTensorInfo(i); + } + info("Number of output tensors: %zu\n", this->GetNumOutputs()); + for (const auto& o : this->m_output) { + this->LogTensorInfo(o); + } + + this->LogMemoryUsage(); +} + +void EtModel::LogMemoryUsage() const +{ + info("Total memory usage: \n"); + info("\tMethod memory: %zu/%zu\n", this->m_backendData.m_methodAllocPtr->UsedSize(), + this->m_backendData.m_methodAllocPtr->size()); + info("\tTemp memory: %zu/%zu\n", this->m_backendData.m_tmpAllocPtr->UsedSize(), + this->m_backendData.m_tmpAllocPtr->size()); +} + +bool EtModel::IsInited() const +{ + return this->m_inited; +} + +bool EtModel::IsDataSigned() const +{ + switch (this->GetType()) { + case iface::TensorType::INT8: + [[fallthrough]]; + case iface::TensorType::INT16: + return true; + default: + return false; + } +} + +bool EtModel::ContainsEthosUOperator() const +{ + return false; +} + +bool EtModel::RunInference() +{ + assert(this->m_inited); + trace("Running inference\n"); + auto& method = this->m_fnGetMethod(); + executorch::runtime::Error err = executorch::runtime::Error::Ok; + + /** Populate input tensors */ + for (size_t i = 0; i < this->m_inputTensorImplMap.size(); ++i) { + executorch::aten::Tensor t(&this->m_inputTensorImplMap[i].first); + err = method.set_input(t, this->m_inputTensorImplMap[i].second); + if (executorch::runtime::Error::Ok != err) { + printf_err("Failed to set input tensor. Error: %d\n", static_cast(err)); + return false; + } + trace("Input tensor %zu registered with the model; address: %p\n", + i, t.const_data_ptr()); + } + err = method.execute(); + trace("Inference done; status = %d\n", static_cast(err)); + return err == executorch::runtime::Error::Ok; +} + +size_t EtModel::GetNumInputs() const +{ + assert(this->m_inited); + return this->m_input.size(); +} + +size_t EtModel::GetNumOutputs() const +{ + assert(this->m_inited); + return this->m_output.size(); +} + +iface::TensorType EtModel::GetType() const +{ + return this->m_type; +} + +std::vector EtModel::GetInputShape(size_t index) const +{ + assert(index < this->GetNumInputs()); + return this->m_input[index]->Shape(); +} + +std::vector EtModel::GetOutputShape(size_t index) const +{ + assert(index < this->GetNumOutputs()); + return this->m_output[index]->Shape(); +} + +const iface::MemoryRegion& EtModel::GetComputeBuffer() const +{ + return std::ref(this->m_computeBuffer); +} + +const iface::MemoryRegion& EtModel::GetModelBuffer() const +{ + return std::ref(this->m_modelBuffer); +} + +const EtBackendData& EtModel::GetBackendData() const +{ + return std::ref(this->m_backendData); +} + +bool EtModel::PrepareInputTensors() +{ + auto& method = this->m_fnGetMethod(); + auto methodMeta = method.method_meta(); + size_t numInputs = methodMeta.num_inputs(); + size_t numAllocated = 0; + + void** inputs = static_cast( + this->m_backendData.m_methodAllocPtr->allocate(numInputs * sizeof(void*))); + + ET_CHECK_MSG(inputs != nullptr, "Could not allocate memory for pointers to input buffers."); + + for (size_t i = 0; i < numInputs; i++) { + auto tag = methodMeta.input_tag(i); + if (!tag.ok()) { + printf_err("Failed to retrieve tag\n"); + return false; + } + if (tag.get() != executorch::runtime::Tag::Tensor) { + debug("Skipping non-tensor input %zu", i); + continue; + } + auto tensorMeta = methodMeta.input_tensor_meta(i); + if (!tensorMeta.ok()) { + printf_err("Failed to retrieve input tensor meta\n"); + return false; + } + + // Input is a tensor. Allocate a buffer for it. + void* dataPtr = this->m_backendData.m_methodAllocPtr->allocate(tensorMeta->nbytes()); + ET_CHECK_MSG(dataPtr != nullptr, "Could not allocate memory for input buffers."); + inputs[numAllocated++] = dataPtr; + + this->m_inputTensorImplMap.push_back({executorch::runtime::etensor::TensorImpl( + tensorMeta.get().scalar_type(), + tensorMeta.get().sizes().size(), + const_cast(tensorMeta.get().sizes().data()), + dataPtr, + const_cast( + tensorMeta.get().dim_order().data())), i}); + + this->m_input.emplace_back(std::make_shared( + &this->m_inputTensorImplMap[this->m_inputTensorImplMap.size() - 1].first)); + } + return true; +} +} /* namespace arm::app::fwk::et */ diff --git a/source/application/api/fwk/executorch/source/EtModelAllocator.cc b/source/application/api/fwk/executorch/source/EtModelAllocator.cc new file mode 100644 index 0000000000000000000000000000000000000000..220da311b8742027eb6b24f6a0e926b77a1446d8 --- /dev/null +++ b/source/application/api/fwk/executorch/source/EtModelAllocator.cc @@ -0,0 +1,51 @@ +/** + * SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ + +#include "EtMemoryAllocator.hpp" + +namespace arm::app::fwk::et { + +EtMemoryAllocator::EtMemoryAllocator(uint32_t size, uint8_t* baseAddress) : + MemoryAllocator(size, baseAddress), m_used(0) +{} + +void* EtMemoryAllocator::allocate(size_t size, size_t alignment) +{ + void* ret = MemoryAllocator::allocate(size, alignment); + if (ret != nullptr) { + /* Align with the same code as in MemoryAllocator::allocate() to keep + * m_used "in sync". */ + if (!(size & (alignment - 1))) { + this->m_used += size; + } else { + this->m_used = (this->m_used | (alignment - 1)) + 1 + size; + } + } + return ret; +} + +size_t EtMemoryAllocator::UsedSize() const +{ + return this->m_used; +} + +size_t EtMemoryAllocator::FreeSize() const +{ + return (this->size() - this->m_used); +} + +} /* namespace arm::app::fwk::et */ diff --git a/source/application/api/fwk/executorch/source/EtTensor.cc b/source/application/api/fwk/executorch/source/EtTensor.cc new file mode 100644 index 0000000000000000000000000000000000000000..e0d4d22b169e17f40e787a717bdcdfb378aa04fb --- /dev/null +++ b/source/application/api/fwk/executorch/source/EtTensor.cc @@ -0,0 +1,76 @@ +/* + * SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or + * its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ + +#include "EtTensor.hpp" + +namespace arm::app::fwk::et { + +EtTensor::EtTensor(executorch::aten::TensorImpl* tensorImpl) : m_tensor(tensorImpl) {} + +EtTensor::EtTensor(executorch::aten::Tensor& tensor) : m_tensor(tensor) {} + +void* EtTensor::GetData() +{ + return this->m_tensor.mutable_data_ptr(); +} + +size_t EtTensor::Bytes() +{ + return this->m_tensor.nbytes(); +} + +size_t EtTensor::GetNumElements() +{ + return this->m_tensor.numel(); +} + +std::vector EtTensor::Shape() +{ + auto sz = this->m_tensor.sizes(); + return std::vector(sz.begin(), sz.end()); +} + +iface::TensorType EtTensor::Type() +{ + switch (this->m_tensor.dtype()) { + case executorch::aten::ScalarType::QInt8: + return iface::TensorType::INT8; + case executorch::aten::ScalarType::QUInt8: + return iface::TensorType::UINT8; + case executorch::aten::ScalarType::Float: + return iface::TensorType::FP32; + default: + return iface::TensorType::INVALID; + } +} + +iface::TensorLayout EtTensor::Layout() +{ + return iface::TensorLayout::NCHW; +} + +iface::QuantParams EtTensor::GetQuantParams() +{ + iface::QuantParams params{0.f, 0}; + /** + * TODO: work out how to get quant params from ExecuTorch Tensor. + */ + return params; +} + +} // namespace arm::app::fwk::et diff --git a/source/application/api/fwk/iface/Model.hpp b/source/application/api/fwk/iface/Model.hpp new file mode 100644 index 0000000000000000000000000000000000000000..81d915c86787595dc193f0d9999ade9773070e1d --- /dev/null +++ b/source/application/api/fwk/iface/Model.hpp @@ -0,0 +1,106 @@ +/* + * SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#ifndef MLEK_MODEL_HPP +#define MLEK_MODEL_HPP + +#include "Tensor.hpp" +#include +#include + +namespace arm::app::fwk::iface { + +struct MemoryRegion { + uint8_t* data{nullptr}; + size_t size{0}; + explicit MemoryRegion(uint8_t* addr, size_t bytes) : data(addr), size(bytes) {} + MemoryRegion() = default; +}; + +/** + * @brief NN model class wrapping the underlying TensorFlow-Lite-Micro API. + */ +class Model { +public: + /** @brief Constructor. */ + Model() = default; + + /** @brief Destructor. */ + virtual ~Model() = 0; + + /** @brief Gets the pointer to the model's input tensor at given input index. */ + virtual std::shared_ptr GetInputTensor(size_t index) const = 0; + + /** @brief Gets the pointer to the model's output tensor at given output index. */ + virtual std::shared_ptr GetOutputTensor(size_t index) const = 0; + + /** @brief Gets the model's data type. */ + virtual TensorType GetType() const = 0; + + /** @brief Gets the pointer to the model's input shape. */ + virtual std::vector GetInputShape(size_t index) const = 0; + + /** @brief Gets the pointer to the model's output shape at given output index. */ + virtual std::vector GetOutputShape(size_t index) const = 0; + + /** @brief Gets the number of input tensors the model has. */ + virtual size_t GetNumInputs() const = 0; + + /** @brief Gets the number of output tensors the model has. */ + virtual size_t GetNumOutputs() const = 0; + + /** @brief Logs the tensor information to stdout. */ + virtual void LogTensorInfo(std::shared_ptr tensor) = 0; + + /** @brief Logs the interpreter information to stdout. */ + virtual void LogInterpreterInfo() = 0; + + /** @brief Initialise the model class object. + * @return true if initialisation succeeds, false otherwise. + **/ + virtual bool + Init(MemoryRegion& computeBuffer, MemoryRegion& nnModel, const void* backendData) = 0; + + /** @brief Checks if this object has been initialised. */ + virtual bool IsInited() const = 0; + + /** @brief Checks if the model uses signed data. */ + virtual bool IsDataSigned() const = 0; + + /** @brief Checks if the model uses Ethos-U operator */ + virtual bool ContainsEthosUOperator() const = 0; + + /** @brief Runs the inference (invokes the interpreter). */ + virtual bool RunInference() = 0; + + /** + * @brief Gets memory region information for the backend's compute buffer. + * @return Const reference to MemoryRegion object. + */ + virtual const MemoryRegion& GetComputeBuffer() const = 0; + + /** + * @brief Gets memory region information for neural network model. + * @return Const reference to MemoryRegion object. + */ + virtual const MemoryRegion& GetModelBuffer() const = 0; +}; + +inline Model::~Model() {} + +} /* namespace arm::app::fwk::iface */ + +#endif /* MLEK_MODEL_HPP */ diff --git a/source/application/api/fwk/iface/Tensor.hpp b/source/application/api/fwk/iface/Tensor.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e9cee80bf2a1858915adc20a9670ca1e4d0a4488 --- /dev/null +++ b/source/application/api/fwk/iface/Tensor.hpp @@ -0,0 +1,140 @@ +/* + * SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or + * its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ + +#ifndef MLEK_TENSOR_HPP +#define MLEK_TENSOR_HPP + +#include +#include + +namespace arm::app::fwk::iface { + +/** Struct for quantization parameters. */ +struct QuantParams { + float scale{0.0}; + int offset{0}; +}; + +/** Enum representing tensor data types supported. */ +enum class TensorType { INT8 = 0, UINT8 = 1, INT16 = 2, FP16 = 3, FP32 = 4, INVALID = 5 }; + +/** Enum representing tensor layout. */ +enum class TensorLayout { NHWC = 0, NCHW = 1, INVALID = 2 }; + +/** + * @brief Gets the tensor data type name as character array. + * @param[in] type Tensor data type object. + * @return String represented as const char pointer. + */ +const inline char* GetTensorDataTypeName(const TensorType type) +{ + switch (type) { + case TensorType::INT8: + return "int8"; + case TensorType::UINT8: + return "uint8"; + case TensorType::INT16: + return "int16"; + case TensorType::FP16: + return "fp16"; + case TensorType::FP32: + return "fp32"; + default: + return "unknown"; + } +} + +/** + * @brief Gets the tensor data type name as character array. + * @param[in] type Tensor data type object. + * @return String represented as const char pointer. + */ +const inline char* GetTensorLayoutName(const TensorLayout layout) +{ + switch (layout) { + case TensorLayout::NHWC: + return "NHWC"; + case TensorLayout::NCHW: + return "NCHW"; + default: + return "unknown"; + } +} + +/** + * @brief Gets the tensor element size in bytes. + * @param[in] type Tensor data type object. + * @return Number of bytes as a signed integer. + */ +inline int GetTensorDataTypeSize(const TensorType type) +{ + switch (type) { + case TensorType::INT8: + [[fallthrough]]; + case TensorType::UINT8: + return 1; + case TensorType::INT16: + [[fallthrough]]; + case TensorType::FP16: + return 2; + case TensorType::FP32: + return 4; + default: + return 0; + } +} + +/** + * @brief Tensor interface class. + */ +class TensorIface { +public: + TensorIface() = default; /**< Constructor */ + virtual ~TensorIface() = default; /** Destructor */ + + /** Gets pointer to actual tensor data. */ + virtual void* GetData() = 0; + + /** Gets number of bytes occupied by tensor data. */ + virtual size_t Bytes() = 0; + + /** Gets number of elements in the tensor (all dimensions combined). */ + virtual size_t GetNumElements() = 0; + + /** Gets the tensor data type. */ + virtual TensorType Type() = 0; + + /** Gets the tensor layout. */ + virtual TensorLayout Layout() = 0; + + /** Gets the tensor shape, expressed as a std::vector */ + virtual std::vector Shape() = 0; + + /** Gets the quantization parameters for this tensor. */ + virtual QuantParams GetQuantParams() = 0; + + /** Utility function to get the tensor data pointer in required pointer format. */ + template + T* GetData() + { + return reinterpret_cast(this->GetData()); + } +}; +} // namespace arm::app::fwk::iface + +#endif /* MLEK_TENSOR_HPP */ diff --git a/source/application/api/fwk/readme.md b/source/application/api/fwk/readme.md new file mode 100644 index 0000000000000000000000000000000000000000..353e3b601c8cca1db9cb0c19442720f5d1103b03 --- /dev/null +++ b/source/application/api/fwk/readme.md @@ -0,0 +1,33 @@ + + +# Framework Abstraction Layer + +## Overview + +This module provides a **framework-agnostic interface layer** for inference backends. Its purpose is to isolate +higher-level logic (like common APIs and use-case implementations) from the specifics of any single ML framework, +such as TensorFlow Lite Micro or ExecuTorch. + +## Design Principles + +- **Modularity:** Framework-specific logic is isolated into backend modules (e.g., `tflm` and `executorch`), while + `fwk` defines generic interfaces. +- **Minimal Intrusion:** Existing use-case and model logic can progressively migrate to use `fwk` interfaces. + +## Namespaces + +### `fwk::iface` + +This namespace contains **pure virtual interfaces** for components like `Tensors` and `Model`. These interfaces +are used by [`common_api`](../common) and [`use_case` APIs](../use_case). The application logic should also rely +on this interface wherever possible. + +#### `fwk::tflm` + +This namespace contains TensorFlow Lite Micro specific implementation of `Tensors` as `TflmTensor` class and +`Model` as `TflmModel` class. These, specifically `TflmModel`, are used to instantiate TensorFlow Lite Micro +specific models in the examples. However, once instantiated, the other orchestration logic relies on interface +exposed by `fwk::iface` namespace. diff --git a/source/application/api/fwk/tflm/CMakeLists.txt b/source/application/api/fwk/tflm/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..6db9b1fb175af70bed53b3a9329c606e74d9568e --- /dev/null +++ b/source/application/api/fwk/tflm/CMakeLists.txt @@ -0,0 +1,73 @@ +#---------------------------------------------------------------------------- +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its +# affiliates +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +#---------------------------------------------------------------------------- + +######################################################### +# TensorFlow Lite Micro wrapper # +######################################################### +cmake_minimum_required(VERSION 3.21.0) + +set(ML_FWK_TFLM_TARGET ml_framework_tflm) +project(${ML_FWK_TFLM_TARGET} + DESCRIPTION "TensorFlow Lite Micro framework module" + LANGUAGES CXX) + +if (NOT "TensorFlowLiteMicro" STREQUAL ${ML_FRAMEWORK}) + message(FATAL_ERROR "${CMAKE_CURRENT_SOURCE_DIR} should not be included for ${ML_FRAMEWORK}") +endif() + +# Include the TensorFlow Lite Micro build target +# if the target is not available. An external project +# including this project can provide its own version +# of the target. +if (NOT TARGET google::tensorflow-lite-micro) + include(tensorflow_lite_micro) +endif() + +# Create static library +add_library(${ML_FWK_TFLM_TARGET} STATIC + source/YoloFastestModel.cc + source/AdModel.cc + source/MicroNetKwsModel.cc + source/Wav2LetterModel.cc + source/VisualWakeWordModel.cc + source/TflmTensor.cc + source/TensorFlowLiteMicro.cc + source/RNNoiseModel.cc + source/MobileNetModel.cc + source/TestModel.cc + source/TflmModel.cc + source/MicroMutableAllOpsResolver.cc +) + +## Include directories - public +target_include_directories(${ML_FWK_TFLM_TARGET} PUBLIC + include) + +# Link time library targets: +target_link_libraries(${ML_FWK_TFLM_TARGET} PUBLIC + mlek_log # Logging functions + ml-framework-iface # ML framework interface + google::tensorflow-lite-micro) # TensorFlow Lite Micro library + +target_compile_definitions(${ML_FWK_TFLM_TARGET} INTERFACE MLEK_FWK_TFLM=1) + +# Display status: +message(STATUS "*******************************************************") +message(STATUS "Library: " ${ML_FWK_TFLM_TARGET}) +message(STATUS "CMAKE_SYSTEM_PROCESSOR: " ${CMAKE_SYSTEM_PROCESSOR}) +message(STATUS "*******************************************************") diff --git a/source/application/api/fwk/tflm/include/AdModel.hpp b/source/application/api/fwk/tflm/include/AdModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..951ee2dd7d4aa1830532d01a40ce5a19564639ee --- /dev/null +++ b/source/application/api/fwk/tflm/include/AdModel.hpp @@ -0,0 +1,54 @@ +/* + * SPDX-FileCopyrightText: Copyright 2021-2022, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#ifndef AD_MODEL_HPP +#define AD_MODEL_HPP + +#include "TflmModel.hpp" + +namespace arm::app::ad { +extern const int g_FrameLength; +extern const int g_FrameStride; +extern const float g_ScoreThreshold; +extern const float g_TrainingMean; +} // namespace arm::app::ad + +namespace arm::app::fwk::tflm { + +class AdModel : public TflmModel { + +public: + /* Indices for the expected model - based on input tensor shape */ + static constexpr uint32_t ms_inputRowsIdx = 1; + static constexpr uint32_t ms_inputColsIdx = 2; + +protected: + /** @brief Gets the reference to op resolver interface class */ + const tflite::MicroOpResolver& GetOpResolver() override; + + /** @brief Adds operations to the op resolver instance */ + bool EnlistOperations() override; + +private: + /* Maximum number of individual operations that can be enlisted */ + static constexpr int ms_maxOpCnt = 6; + + /* A mutable op resolver instance */ + tflite::MicroMutableOpResolver m_opResolver; +}; +} /* namespace arm::app::fwk::tflm */ + +#endif /* AD_MODEL_HPP */ diff --git a/source/application/api/use_case/inference_runner/include/MicroMutableAllOpsResolver.hpp b/source/application/api/fwk/tflm/include/MicroMutableAllOpsResolver.hpp similarity index 56% rename from source/application/api/use_case/inference_runner/include/MicroMutableAllOpsResolver.hpp rename to source/application/api/fwk/tflm/include/MicroMutableAllOpsResolver.hpp index 4f22c2a17e14bfae8fb5a8d54451d0c666b65d80..b193420d537d7ac0053ce3836431166e3c0647cf 100644 --- a/source/application/api/use_case/inference_runner/include/MicroMutableAllOpsResolver.hpp +++ b/source/application/api/fwk/tflm/include/MicroMutableAllOpsResolver.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2023, 2024 Arm Limited and/or + * SPDX-FileCopyrightText: Copyright 2023, 2024-2025 Arm Limited and/or * its affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -18,22 +18,20 @@ #ifndef INF_RUNNER_MICRO_MUTABLE_ALL_OPS_RESOLVER_HPP #define INF_RUNNER_MICRO_MUTABLE_ALL_OPS_RESOLVER_HPP -#include +#include "TensorFlowLiteMicro.hpp" -namespace arm { -namespace app { +namespace arm::app::fwk::tflm { - /* Maximum number of individual operations that can be enlisted. */ - constexpr int kNumberOperators = 98; +/* Maximum number of individual operations that can be enlisted. */ +constexpr int kNumberOperators = 98; - /** An Op resolver containing all ops is no longer supplied with TFLite Micro - * so we create our own instead for the generic inference runner. - * - * @return MicroMutableOpResolver containing all TFLite Micro Ops registered. - */ - tflite::MicroMutableOpResolver CreateAllOpsResolver(); +/** An Op resolver containing all ops is no longer supplied with TFLite Micro + * so we create our own instead for the generic inference runner. + * + * @return MicroMutableOpResolver containing all TFLite Micro Ops registered. + */ +tflite::MicroMutableOpResolver CreateAllOpsResolver(); -} /* namespace app */ -} /* namespace arm */ +} /* namespace arm::app::fwk::tflm */ #endif /* INF_RUNNER_MICRO_MUTABLE_ALL_OPS_RESOLVER_HPP */ diff --git a/source/application/api/fwk/tflm/include/MicroNetKwsModel.hpp b/source/application/api/fwk/tflm/include/MicroNetKwsModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..924ea319027636e763c15b112844f665688f504a --- /dev/null +++ b/source/application/api/fwk/tflm/include/MicroNetKwsModel.hpp @@ -0,0 +1,57 @@ +/* + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#ifndef KWS_MICRONETMODEL_HPP +#define KWS_MICRONETMODEL_HPP + +#include "TflmModel.hpp" + +namespace arm::app::kws { +extern const int g_FrameLength; +extern const int g_FrameStride; +extern const float g_ScoreThreshold; +extern const uint32_t g_NumMfcc; +extern const uint32_t g_NumAudioWins; +} // namespace arm::app::kws + +namespace arm::app::fwk::tflm { + +class MicroNetKwsModel : public TflmModel { +public: + /* Indices for the expected model - based on input and output tensor shapes */ + static constexpr uint32_t ms_inputRowsIdx = 1; + static constexpr uint32_t ms_inputColsIdx = 2; + static constexpr uint32_t ms_outputRowsIdx = 2; + static constexpr uint32_t ms_outputColsIdx = 3; + +protected: + /** @brief Gets the reference to op resolver interface class. */ + const tflite::MicroOpResolver& GetOpResolver() override; + + /** @brief Adds operations to the op resolver instance. */ + bool EnlistOperations() override; + +private: + /* Maximum number of individual operations that can be enlisted. */ + static constexpr int ms_maxOpCnt = 7; + + /* A mutable op resolver instance. */ + tflite::MicroMutableOpResolver m_opResolver; +}; + +} /* namespace arm::app::fwk::tflm */ + +#endif /* KWS_MICRONETMODEL_HPP */ diff --git a/source/application/api/fwk/tflm/include/MobileNetModel.hpp b/source/application/api/fwk/tflm/include/MobileNetModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..afc2b4305bdfdc04f65b5b47950bc1e49650e2a9 --- /dev/null +++ b/source/application/api/fwk/tflm/include/MobileNetModel.hpp @@ -0,0 +1,49 @@ +/* + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#ifndef IMG_CLASS_MOBILENETMODEL_HPP +#define IMG_CLASS_MOBILENETMODEL_HPP + +#include "TflmModel.hpp" + +namespace arm::app::fwk::tflm { + +class MobileNetModel : public TflmModel { + +public: + /* Indices for the expected model - based on input tensor shape */ + static constexpr uint32_t ms_inputRowsIdx = 1; + static constexpr uint32_t ms_inputColsIdx = 2; + static constexpr uint32_t ms_inputChannelsIdx = 3; + +protected: + /** @brief Gets the reference to op resolver interface class. */ + const tflite::MicroOpResolver& GetOpResolver() override; + + /** @brief Adds operations to the op resolver instance. */ + bool EnlistOperations() override; + +private: + /* Maximum number of individual operations that can be enlisted. */ + static constexpr int ms_maxOpCnt = 7; + + /* A mutable op resolver instance. */ + tflite::MicroMutableOpResolver m_opResolver; +}; + +} /* namespace arm::app::fwk::tflm */ + +#endif /* IMG_CLASS_MOBILENETMODEL_HPP */ diff --git a/source/application/api/fwk/tflm/include/RNNoiseModel.hpp b/source/application/api/fwk/tflm/include/RNNoiseModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..cc88360151875785ca189bc20c4f724957a40943 --- /dev/null +++ b/source/application/api/fwk/tflm/include/RNNoiseModel.hpp @@ -0,0 +1,70 @@ +/* + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#ifndef RNNOISE_MODEL_HPP +#define RNNOISE_MODEL_HPP + +#include "TflmModel.hpp" + +namespace arm::app::rnn { +extern const uint32_t g_NumInputFeatures; +extern const uint32_t g_FrameLength; +extern const uint32_t g_FrameStride; +} /* namespace arm::app::rnn */ + +namespace arm::app::fwk::tflm { + +class RNNoiseModel : public TflmModel { +public: + /** + * @brief Sets GRU input states to zeros. + * Call this method before starting processing the new sequence of logically related data. + */ + void ResetGruState(); + + /** + * @brief Copy current GRU output states to input states. + * Call this method before starting processing the next sequence of logically related data. + */ + bool CopyGruStates() const; + + /* Which index of model outputs does the main output (gains) come from. */ + const size_t m_indexForModelOutput = 1; + +protected: + /** @brief Gets the reference to op resolver interface class. */ + const tflite::MicroOpResolver& GetOpResolver() override; + + /** @brief Adds operations to the op resolver instance. */ + bool EnlistOperations() override; + + /* + Each inference after the first needs to copy 3 GRU states from a output index to input index + (model dependent): 0 -> 3, 2 -> 2, 3 -> 1 + */ + const std::vector> m_gruStateMap = {{0, 3}, {2, 2}, {3, 1}}; + +private: + /* Maximum number of individual operations that can be enlisted. */ + static constexpr int ms_maxOpCnt = 15; + + /* A mutable op resolver instance. */ + tflite::MicroMutableOpResolver m_opResolver; +}; + +} /* namespace arm::app::fwk::tflm */ + +#endif /* RNNOISE_MODEL_HPP */ diff --git a/source/application/api/common/include/TensorFlowLiteMicro.hpp b/source/application/api/fwk/tflm/include/TensorFlowLiteMicro.hpp similarity index 56% rename from source/application/api/common/include/TensorFlowLiteMicro.hpp rename to source/application/api/fwk/tflm/include/TensorFlowLiteMicro.hpp index 30d21ecc1ee13e2356037171a9879cf019a67e2b..8dc82a902f9ae55e916a0b8f678a64017c48642e 100644 --- a/source/application/api/common/include/TensorFlowLiteMicro.hpp +++ b/source/application/api/fwk/tflm/include/TensorFlowLiteMicro.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -27,20 +27,20 @@ * application sources. */ #if defined(__ARMCC_VERSION) && (__ARMCC_VERSION >= 6010050) - #pragma clang diagnostic push - #pragma clang diagnostic ignored "-Wunused-parameter" - #include "tensorflow/lite/micro/micro_mutable_op_resolver.h" - #include "tensorflow/lite/micro/micro_interpreter.h" - #pragma clang diagnostic pop +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-parameter" +#include "tensorflow/lite/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/micro_mutable_op_resolver.h" +#pragma clang diagnostic pop #elif defined(__GNUC__) - #pragma GCC diagnostic push - #pragma GCC diagnostic ignored "-Wunused-parameter" - #include "tensorflow/lite/micro/micro_mutable_op_resolver.h" - #include "tensorflow/lite/micro/micro_interpreter.h" - #pragma GCC diagnostic pop +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wunused-parameter" +#include "tensorflow/lite/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/micro_mutable_op_resolver.h" +#pragma GCC diagnostic pop #else - #include "tensorflow/lite/micro/micro_mutable_op_resolver.h" - #include "tensorflow/lite/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/micro_interpreter.h" +#include "tensorflow/lite/micro/micro_mutable_op_resolver.h" #endif #include "tensorflow/lite/c/common.h" @@ -48,33 +48,17 @@ #include "tensorflow/lite/schema/schema_generated.h" #include "tensorflow/lite/schema/schema_utils.h" -#if defined (TESTS) - #include "tensorflow/lite/micro/test_helpers.h" +#if defined(TESTS) +#include "tensorflow/lite/micro/test_helpers.h" #endif /* defined (TESTS) */ -namespace arm { -namespace app { - - /** Struct for quantization parameters. */ - struct QuantParams { - float scale = 1.0; - int offset = 0; - }; - - /** - * @brief Gets the quantization parameters from a tensor - * @param[in] tensor pointer to the tensor. - * @return QuantParams object. - */ - QuantParams GetTensorQuantParams(TfLiteTensor* tensor); - -} /* namespace app */ -} /* namespace arm */ - +namespace arm::app::fwk::tflm { /** * @brief Enables TensorFlow Lite Micro's error reporter to log to standard * output stream. */ void EnableTFLMLog(); +} /* namespace arm::app::fwk::tflm */ + #endif /* TENSORFLOW_LITE_MICRO_LOCAL_HPP */ diff --git a/source/application/api/fwk/tflm/include/TestModel.hpp b/source/application/api/fwk/tflm/include/TestModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..edc67470d1631594b05bdd0dd79649948f0198e9 --- /dev/null +++ b/source/application/api/fwk/tflm/include/TestModel.hpp @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: Copyright 2021, 2023, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#ifndef INF_RUNNER_TESTMODEL_HPP +#define INF_RUNNER_TESTMODEL_HPP + +#include "MicroMutableAllOpsResolver.hpp" +#include "TflmModel.hpp" + +namespace arm::app::fwk::tflm { +class TestModel : public TflmModel { + +protected: + /** @brief Gets the reference to op resolver interface class. */ + const tflite::MicroOpResolver& GetOpResolver() override; + + /** @brief Adds operations to the op resolver instance. */ + bool EnlistOperations() override; + +private: + /* A mutable op resolver instance including every operation for Inference runner. */ + tflite::MicroMutableOpResolver m_opResolver; +}; + +} /* namespace arm::app::fwk::tflm */ + +#endif /* INF_RUNNER_TESTMODEL_HPP */ diff --git a/source/application/api/fwk/tflm/include/TflmModel.hpp b/source/application/api/fwk/tflm/include/TflmModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..00e9460528febf4c08f0b54c7220b881c7dbf999 --- /dev/null +++ b/source/application/api/fwk/tflm/include/TflmModel.hpp @@ -0,0 +1,137 @@ +/* + * SPDX-FileCopyrightText: Copyright 2021-2023, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#ifndef MLEK_TFLM_MODEL_HPP +#define MLEK_TFLM_MODEL_HPP + +#include "Model.hpp" +#include "TensorFlowLiteMicro.hpp" +#include + +namespace arm::app::fwk::tflm { + +struct TflmBackendData { + const tflite::Model* m_pModel{nullptr}; /**< Tflite model pointer. */ + tflite::MicroAllocator* m_pAllocator{nullptr}; /**< Tflite micro allocator. */ + + /** Tflite interpreter. */ + std::unique_ptr m_pInterpreter{nullptr}; +}; + +/** + * @brief NN model class wrapping the underlying TensorFlow-Lite-Micro API. + */ +class TflmModel : public iface::Model { +public: + /** @brief Constructor. */ + TflmModel(); + + /** @brief Destructor. */ + virtual ~TflmModel() = default; + + /** @brief Gets the pointer to the model's input tensor at given input index. */ + std::shared_ptr GetInputTensor(size_t index) const override; + + /** @brief Gets the pointer to the model's output tensor at given output index. */ + std::shared_ptr GetOutputTensor(size_t index) const override; + + /** @brief Gets the model's data type. */ + iface::TensorType GetType() const override; + + /** @brief Gets the pointer to the model's input shape. */ + std::vector GetInputShape(size_t index) const override; + + /** @brief Gets the pointer to the model's output shape at given output index. */ + std::vector GetOutputShape(size_t index) const override; + + /** @brief Gets the number of input tensors the model has. */ + size_t GetNumInputs() const override; + + /** @brief Gets the number of output tensors the model has. */ + size_t GetNumOutputs() const override; + + /** @brief Logs the tensor information to stdout. */ + void LogTensorInfo(std::shared_ptr tensor) override; + + /** @brief Logs the interpreter information to stdout. */ + void LogInterpreterInfo() override; + + /** @brief Initialise the model class object. + * @return true if initialisation succeeds, false otherwise. + **/ + bool Init(iface::MemoryRegion& computeBuffer, + iface::MemoryRegion& nnModel, + const void* backendData = nullptr) override; + + /** @brief Checks if this object has been initialised. */ + bool IsInited() const override; + + /** @brief Checks if the model uses signed data. */ + bool IsDataSigned() const override; + + /** @brief Checks if the model uses Ethos-U operator */ + bool ContainsEthosUOperator() const override; + + /** @brief Runs the inference (invokes the interpreter). */ + bool RunInference() override; + + /** + * @brief Gets memory region information for the backend's compute buffer. + * @return Const reference to MemoryRegion object. + */ + const iface::MemoryRegion& GetComputeBuffer() const override; + + /** + * @brief Gets memory region information for neural network model. + * @return Const reference to MemoryRegion object. + */ + const iface::MemoryRegion& GetModelBuffer() const override; + + /** + * @brief Gets the allocator pointer for this instance. + * @return Pointer to a tflite::MicroAllocator object, if + * available; nullptr otherwise. + **/ + const TflmBackendData& GetBackendData() const; + +protected: + /** + * @brief Gets the op resolver for the model instance. + * @return const reference to a tflite::MicroOpResolver object. + **/ + virtual const tflite::MicroOpResolver& GetOpResolver() = 0; + + /** + * @brief Add all the operators required for the given model. + * Implementation of this should come from the use case. + * @return true is ops are successfully added, false otherwise. + **/ + virtual bool EnlistOperations() = 0; + +private: + TflmBackendData m_backendData{}; + bool m_inited{false}; /* Indicates whether this object has been initialised. */ + iface::MemoryRegion m_computeBuffer{}; + iface::MemoryRegion m_modelBuffer{}; + + std::vector> m_input{}; /* Model's input tensor pointers. */ + std::vector> + m_output{}; /* Model's output tensor pointers. */ + iface::TensorType m_type{iface::TensorType::INVALID}; /* Model's data type. */ +}; +} /* namespace arm::app::fwk::tflm */ + +#endif /* MLEK_TFLM_MODEL_HPP */ diff --git a/source/application/api/fwk/tflm/include/TflmTensor.hpp b/source/application/api/fwk/tflm/include/TflmTensor.hpp new file mode 100644 index 0000000000000000000000000000000000000000..a6d47ef6a587b68abd828db7d9fabdb851207b89 --- /dev/null +++ b/source/application/api/fwk/tflm/include/TflmTensor.hpp @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or + * its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ + +#ifndef MLEK_TFLM_TENSOR_HPP +#define MLEK_TFLM_TENSOR_HPP + +#include "Tensor.hpp" +#include "TensorFlowLiteMicro.hpp" +#include + +namespace arm::app::fwk::tflm { + +class TflmTensor : public iface::TensorIface { +public: + explicit TflmTensor(TfLiteTensor* tensor); /**< Constructor */ + ~TflmTensor() override = default; /** Destructor */ + void* GetData() override; + size_t Bytes() override; + size_t GetNumElements() override; + iface::TensorType Type() override; + iface::TensorLayout Layout() override; + std::vector Shape() override; + iface::QuantParams GetQuantParams() override; + +private: + TfLiteTensor* m_tensor{nullptr}; +}; + +} // namespace arm::app::fwk::tflm + +#endif /* MLEK_TFLM_TENSOR_HPP */ diff --git a/source/application/api/fwk/tflm/include/VisualWakeWordModel.hpp b/source/application/api/fwk/tflm/include/VisualWakeWordModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..85c68a2dc703b759aa3d2f75754d75193cf1faf2 --- /dev/null +++ b/source/application/api/fwk/tflm/include/VisualWakeWordModel.hpp @@ -0,0 +1,50 @@ +/* + * SPDX-FileCopyrightText: Copyright 2021 - 2022, 2025 Arm Limited and/or its + * affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#ifndef VISUAL_WAKE_WORD_MODEL_HPP +#define VISUAL_WAKE_WORD_MODEL_HPP + +#include "TflmModel.hpp" + +namespace arm::app::fwk::tflm { + +class VisualWakeWordModel : public TflmModel { + +public: + /* Indices for the expected model - based on input tensor shape */ + static constexpr uint32_t ms_inputRowsIdx = 1; + static constexpr uint32_t ms_inputColsIdx = 2; + static constexpr uint32_t ms_inputChannelsIdx = 3; + +protected: + /** @brief Gets the reference to op resolver interface class. */ + const tflite::MicroOpResolver& GetOpResolver() override; + + /** @brief Adds operations to the op resolver instance. */ + bool EnlistOperations() override; + +private: + /* Maximum number of individual operations that can be enlisted. */ + static constexpr int ms_maxOpCnt = 7; + + /* A mutable op resolver instance. */ + tflite::MicroMutableOpResolver m_opResolver; +}; + +} /* namespace arm::app::fwk::tflm */ + +#endif /* VISUAL_WAKE_WORD_MODEL_HPP */ diff --git a/source/application/api/fwk/tflm/include/Wav2LetterModel.hpp b/source/application/api/fwk/tflm/include/Wav2LetterModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..e6fadb9485e37d165715d394abf3ec4e4236291f --- /dev/null +++ b/source/application/api/fwk/tflm/include/Wav2LetterModel.hpp @@ -0,0 +1,61 @@ +/* + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#ifndef ASR_WAV2LETTER_MODEL_HPP +#define ASR_WAV2LETTER_MODEL_HPP + +#include "TflmModel.hpp" + +namespace arm::app::asr { +extern const int g_FrameLength; +extern const int g_FrameStride; +extern const float g_ScoreThreshold; +extern const int g_ctxLen; +} // namespace arm::app::asr + +namespace arm::app::fwk::tflm { + +class Wav2LetterModel : public TflmModel { + +public: + /* Indices for the expected model - based on input and output tensor shapes */ + static constexpr uint32_t ms_inputRowsIdx = 1; + static constexpr uint32_t ms_inputColsIdx = 2; + static constexpr uint32_t ms_outputRowsIdx = 2; + static constexpr uint32_t ms_outputColsIdx = 3; + + /* Model specific constants. */ + static constexpr uint32_t ms_blankTokenIdx = 28; + static constexpr uint32_t ms_numMfccFeatures = 13; + +protected: + /** @brief Gets the reference to op resolver interface class. */ + const tflite::MicroOpResolver& GetOpResolver() override; + + /** @brief Adds operations to the op resolver instance. */ + bool EnlistOperations() override; + +private: + /* Maximum number of individual operations that can be enlisted. */ + static constexpr int ms_maxOpCnt = 5; + + /* A mutable op resolver instance. */ + tflite::MicroMutableOpResolver m_opResolver; +}; + +} /* namespace arm::app::fwk::tflm */ + +#endif /* ASR_WAV2LETTER_MODEL_HPP */ diff --git a/source/application/api/fwk/tflm/include/YoloFastestModel.hpp b/source/application/api/fwk/tflm/include/YoloFastestModel.hpp new file mode 100644 index 0000000000000000000000000000000000000000..3ce78c28237b8d165b57013fdb26ee333a5e3cce --- /dev/null +++ b/source/application/api/fwk/tflm/include/YoloFastestModel.hpp @@ -0,0 +1,59 @@ +/* + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#ifndef YOLO_FASTEST_MODEL_HPP +#define YOLO_FASTEST_MODEL_HPP + +#include "TflmModel.hpp" + +/* @TODO: this should possibly sit outside tflm if the namespace is generic? */ +namespace arm::app::object_detection { +extern const int originalImageSize; +extern const int channelsImageDisplayed; +/* NOTE: anchors are different for any given input model size, estimated during training + * phase */ +extern const float anchor1[]; +extern const float anchor2[]; +} /* namespace arm::app::object_detection */ + +namespace arm::app::fwk::tflm { + +class YoloFastestModel : public TflmModel { + +public: + /* Indices for the expected model - based on input tensor shape */ + static constexpr uint32_t ms_inputRowsIdx = 1; + static constexpr uint32_t ms_inputColsIdx = 2; + static constexpr uint32_t ms_inputChannelsIdx = 3; + +protected: + /** @brief Gets the reference to op resolver interface class. */ + const tflite::MicroOpResolver& GetOpResolver() override; + + /** @brief Adds operations to the op resolver instance. */ + bool EnlistOperations() override; + +private: + /* Maximum number of individual operations that can be enlisted. */ + static constexpr int ms_maxOpCnt = 8; + + /* A mutable op resolver instance. */ + tflite::MicroMutableOpResolver m_opResolver; +}; + +} /* namespace arm::app::fwk::tflm */ + +#endif /* YOLO_FASTEST_MODEL_HPP */ diff --git a/source/application/api/use_case/ad/src/AdModel.cc b/source/application/api/fwk/tflm/source/AdModel.cc similarity index 74% rename from source/application/api/use_case/ad/src/AdModel.cc rename to source/application/api/fwk/tflm/source/AdModel.cc index 7630db55ca49734f96063da4e56a39a5a6499bdb..39c9d8b2503c17a29d71dba27b80e8103dec58ad 100644 --- a/source/application/api/use_case/ad/src/AdModel.cc +++ b/source/application/api/fwk/tflm/source/AdModel.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,12 @@ #include "AdModel.hpp" #include "log_macros.h" -const tflite::MicroOpResolver& arm::app::AdModel::GetOpResolver() +const tflite::MicroOpResolver& arm::app::fwk::tflm::AdModel::GetOpResolver() { return this->m_opResolver; } -bool arm::app::AdModel::EnlistOperations() +bool arm::app::fwk::tflm::AdModel::EnlistOperations() { this->m_opResolver.AddAveragePool2D(); this->m_opResolver.AddConv2D(); @@ -31,8 +31,7 @@ bool arm::app::AdModel::EnlistOperations() this->m_opResolver.AddReshape(); if (kTfLiteOk == this->m_opResolver.AddEthosU()) { - info("Added %s support to op resolver\n", - tflite::GetString_ETHOSU()); + info("Added %s support to op resolver\n", tflite::GetString_ETHOSU()); } else { printf_err("Failed to add Arm NPU support to op resolver."); return false; diff --git a/source/application/api/fwk/tflm/source/MicroMutableAllOpsResolver.cc b/source/application/api/fwk/tflm/source/MicroMutableAllOpsResolver.cc new file mode 100644 index 0000000000000000000000000000000000000000..5101cfb389a584f9687c3461383e2e568807a557 --- /dev/null +++ b/source/application/api/fwk/tflm/source/MicroMutableAllOpsResolver.cc @@ -0,0 +1,129 @@ +/* + * SPDX-FileCopyrightText: Copyright 2023, 2024-2025 Arm Limited and/or + * its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ + +#include "MicroMutableAllOpsResolver.hpp" + +namespace arm::app::fwk::tflm { + +/* Create our own AllOpsResolver by adding all Ops to MicroMutableOpResolver. */ +tflite::MicroMutableOpResolver CreateAllOpsResolver() +{ + tflite::MicroMutableOpResolver mutableAllOpResolver; + + mutableAllOpResolver.AddAbs(); + mutableAllOpResolver.AddAdd(); + mutableAllOpResolver.AddAddN(); + mutableAllOpResolver.AddArgMax(); + mutableAllOpResolver.AddArgMin(); + mutableAllOpResolver.AddAssignVariable(); + mutableAllOpResolver.AddAveragePool2D(); + mutableAllOpResolver.AddBatchMatMul(); + mutableAllOpResolver.AddBatchToSpaceNd(); + mutableAllOpResolver.AddBroadcastArgs(); + mutableAllOpResolver.AddBroadcastTo(); + mutableAllOpResolver.AddCallOnce(); + mutableAllOpResolver.AddCast(); + mutableAllOpResolver.AddCeil(); + mutableAllOpResolver.AddCircularBuffer(); + mutableAllOpResolver.AddConcatenation(); + mutableAllOpResolver.AddConv2D(); + mutableAllOpResolver.AddCos(); + mutableAllOpResolver.AddCumSum(); + mutableAllOpResolver.AddDepthToSpace(); + mutableAllOpResolver.AddDepthwiseConv2D(); + mutableAllOpResolver.AddDequantize(); + mutableAllOpResolver.AddDetectionPostprocess(); + mutableAllOpResolver.AddDiv(); + mutableAllOpResolver.AddElu(); + mutableAllOpResolver.AddEqual(); + mutableAllOpResolver.AddEthosU(); + mutableAllOpResolver.AddExp(); + mutableAllOpResolver.AddExpandDims(); + mutableAllOpResolver.AddFill(); + mutableAllOpResolver.AddFloor(); + mutableAllOpResolver.AddFloorDiv(); + mutableAllOpResolver.AddFloorMod(); + mutableAllOpResolver.AddFullyConnected(); + mutableAllOpResolver.AddGather(); + mutableAllOpResolver.AddGatherNd(); + mutableAllOpResolver.AddGreater(); + mutableAllOpResolver.AddGreaterEqual(); + mutableAllOpResolver.AddHardSwish(); + mutableAllOpResolver.AddIf(); + mutableAllOpResolver.AddL2Normalization(); + mutableAllOpResolver.AddL2Pool2D(); + mutableAllOpResolver.AddLeakyRelu(); + mutableAllOpResolver.AddLess(); + mutableAllOpResolver.AddLessEqual(); + mutableAllOpResolver.AddLog(); + mutableAllOpResolver.AddLogicalAnd(); + mutableAllOpResolver.AddLogicalNot(); + mutableAllOpResolver.AddLogicalOr(); + mutableAllOpResolver.AddLogistic(); + mutableAllOpResolver.AddLogSoftmax(); + mutableAllOpResolver.AddMaxPool2D(); + mutableAllOpResolver.AddMaximum(); + mutableAllOpResolver.AddMean(); + mutableAllOpResolver.AddMinimum(); + mutableAllOpResolver.AddMirrorPad(); + mutableAllOpResolver.AddMul(); + mutableAllOpResolver.AddNeg(); + mutableAllOpResolver.AddNotEqual(); + mutableAllOpResolver.AddPack(); + mutableAllOpResolver.AddPad(); + mutableAllOpResolver.AddPadV2(); + mutableAllOpResolver.AddPrelu(); + mutableAllOpResolver.AddQuantize(); + mutableAllOpResolver.AddReadVariable(); + mutableAllOpResolver.AddReduceMax(); + mutableAllOpResolver.AddRelu(); + mutableAllOpResolver.AddRelu6(); + mutableAllOpResolver.AddReshape(); + mutableAllOpResolver.AddResizeBilinear(); + mutableAllOpResolver.AddResizeNearestNeighbor(); + mutableAllOpResolver.AddRound(); + mutableAllOpResolver.AddRsqrt(); + mutableAllOpResolver.AddSelectV2(); + mutableAllOpResolver.AddShape(); + mutableAllOpResolver.AddSin(); + mutableAllOpResolver.AddSlice(); + mutableAllOpResolver.AddSoftmax(); + mutableAllOpResolver.AddSpaceToBatchNd(); + mutableAllOpResolver.AddSpaceToDepth(); + mutableAllOpResolver.AddSplit(); + mutableAllOpResolver.AddSplitV(); + mutableAllOpResolver.AddSqrt(); + mutableAllOpResolver.AddSquare(); + mutableAllOpResolver.AddSquaredDifference(); + mutableAllOpResolver.AddSqueeze(); + mutableAllOpResolver.AddStridedSlice(); + mutableAllOpResolver.AddSub(); + mutableAllOpResolver.AddSum(); + mutableAllOpResolver.AddSvdf(); + mutableAllOpResolver.AddTanh(); + mutableAllOpResolver.AddTranspose(); + mutableAllOpResolver.AddTransposeConv(); + mutableAllOpResolver.AddUnidirectionalSequenceLSTM(); + mutableAllOpResolver.AddUnpack(); + mutableAllOpResolver.AddVarHandle(); + mutableAllOpResolver.AddWhile(); + mutableAllOpResolver.AddZerosLike(); + return mutableAllOpResolver; +} + +} /* namespace arm::app::fwk::tflm */ diff --git a/source/application/api/use_case/kws/src/MicroNetKwsModel.cc b/source/application/api/fwk/tflm/source/MicroNetKwsModel.cc similarity index 74% rename from source/application/api/use_case/kws/src/MicroNetKwsModel.cc rename to source/application/api/fwk/tflm/source/MicroNetKwsModel.cc index 6cb094c831071cf1a207b0ba54148e09b79e4d37..dab60206d09f15ba3e178ea31c319977176beeb7 100644 --- a/source/application/api/use_case/kws/src/MicroNetKwsModel.cc +++ b/source/application/api/fwk/tflm/source/MicroNetKwsModel.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,12 @@ #include "MicroNetKwsModel.hpp" #include "log_macros.h" -const tflite::MicroOpResolver& arm::app::MicroNetKwsModel::GetOpResolver() +const tflite::MicroOpResolver& arm::app::fwk::tflm::MicroNetKwsModel::GetOpResolver() { return this->m_opResolver; } -bool arm::app::MicroNetKwsModel::EnlistOperations() +bool arm::app::fwk::tflm::MicroNetKwsModel::EnlistOperations() { this->m_opResolver.AddReshape(); this->m_opResolver.AddAveragePool2D(); @@ -32,8 +32,7 @@ bool arm::app::MicroNetKwsModel::EnlistOperations() this->m_opResolver.AddRelu(); if (kTfLiteOk == this->m_opResolver.AddEthosU()) { - info("Added %s support to op resolver\n", - tflite::GetString_ETHOSU()); + info("Added %s support to op resolver\n", tflite::GetString_ETHOSU()); } else { printf_err("Failed to add Arm NPU support to op resolver."); return false; diff --git a/source/application/api/use_case/img_class/src/MobileNetModel.cc b/source/application/api/fwk/tflm/source/MobileNetModel.cc similarity index 74% rename from source/application/api/use_case/img_class/src/MobileNetModel.cc rename to source/application/api/fwk/tflm/source/MobileNetModel.cc index a2808b100fd601a547bb9c392ab5db9575f1ae1e..edd8eed73ffd075e5538774ec0e1932ab98d7afe 100644 --- a/source/application/api/use_case/img_class/src/MobileNetModel.cc +++ b/source/application/api/fwk/tflm/source/MobileNetModel.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,12 @@ #include "MobileNetModel.hpp" #include "log_macros.h" -const tflite::MicroOpResolver& arm::app::MobileNetModel::GetOpResolver() +const tflite::MicroOpResolver& arm::app::fwk::tflm::MobileNetModel::GetOpResolver() { return this->m_opResolver; } -bool arm::app::MobileNetModel::EnlistOperations() +bool arm::app::fwk::tflm::MobileNetModel::EnlistOperations() { this->m_opResolver.AddDepthwiseConv2D(); this->m_opResolver.AddConv2D(); @@ -32,8 +32,7 @@ bool arm::app::MobileNetModel::EnlistOperations() this->m_opResolver.AddSoftmax(); if (kTfLiteOk == this->m_opResolver.AddEthosU()) { - info("Added %s support to op resolver\n", - tflite::GetString_ETHOSU()); + info("Added %s support to op resolver\n", tflite::GetString_ETHOSU()); } else { printf_err("Failed to add Arm NPU support to op resolver."); return false; diff --git a/source/application/api/use_case/noise_reduction/src/RNNoiseModel.cc b/source/application/api/fwk/tflm/source/RNNoiseModel.cc similarity index 56% rename from source/application/api/use_case/noise_reduction/src/RNNoiseModel.cc rename to source/application/api/fwk/tflm/source/RNNoiseModel.cc index e8febeed45db76653c37ccaf14df2377024239e5..d7eca8c6c9e17027680f9e097d9e7e874b83f37a 100644 --- a/source/application/api/use_case/noise_reduction/src/RNNoiseModel.cc +++ b/source/application/api/fwk/tflm/source/RNNoiseModel.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,12 @@ #include "RNNoiseModel.hpp" #include "log_macros.h" -const tflite::MicroOpResolver& arm::app::RNNoiseModel::GetOpResolver() +const tflite::MicroOpResolver& arm::app::fwk::tflm::RNNoiseModel::GetOpResolver() { return this->m_opResolver; } -bool arm::app::RNNoiseModel::EnlistOperations() +bool arm::app::fwk::tflm::RNNoiseModel::EnlistOperations() { this->m_opResolver.AddUnpack(); this->m_opResolver.AddFullyConnected(); @@ -40,8 +40,7 @@ bool arm::app::RNNoiseModel::EnlistOperations() this->m_opResolver.AddRelu(); if (kTfLiteOk == this->m_opResolver.AddEthosU()) { - info("Added %s support to op resolver\n", - tflite::GetString_ETHOSU()); + info("Added %s support to op resolver\n", tflite::GetString_ETHOSU()); } else { printf_err("Failed to add Arm NPU support to op resolver."); return false; @@ -49,48 +48,45 @@ bool arm::app::RNNoiseModel::EnlistOperations() return true; } -bool arm::app::RNNoiseModel::RunInference() +void arm::app::fwk::tflm::RNNoiseModel::ResetGruState() { - return Model::RunInference(); -} - -void arm::app::RNNoiseModel::ResetGruState() -{ - for (auto& stateMapping: this->m_gruStateMap) { - TfLiteTensor* inputGruStateTensor = this->GetInputTensor(stateMapping.second); - auto* inputGruState = tflite::GetTensorData(inputGruStateTensor); + for (auto& stateMapping : this->m_gruStateMap) { + auto inputGruStateTensor = this->GetInputTensor(stateMapping.second); + auto* inputGruState = inputGruStateTensor->GetData(); /* Initial value of states is 0, but this is affected by quantization zero point. */ - auto quantParams = arm::app::GetTensorQuantParams(inputGruStateTensor); - memset(inputGruState, quantParams.offset, inputGruStateTensor->bytes); + auto quantParams = inputGruStateTensor->GetQuantParams(); + memset(inputGruState, quantParams.offset, inputGruStateTensor->Bytes()); } } -bool arm::app::RNNoiseModel::CopyGruStates() +bool arm::app::fwk::tflm::RNNoiseModel::CopyGruStates() const { std::vector>> tempOutGruStates; - /* Saving output states before copying them to input states to avoid output states modification in the tensor. - * tflu shares input and output tensors memory, thus writing to input tensor can change output tensor values. */ - for (auto& stateMapping: this->m_gruStateMap) { - TfLiteTensor* outputGruStateTensor = this->GetOutputTensor(stateMapping.first); - std::vector tempOutGruState(outputGruStateTensor->bytes); - auto* outGruState = tflite::GetTensorData(outputGruStateTensor); - memcpy(tempOutGruState.data(), outGruState, outputGruStateTensor->bytes); + /* Saving output states before copying them to input states to avoid output states modification + * in the tensor. tflu shares input and output tensors memory, thus writing to input tensor can + * change output tensor values. */ + for (auto& stateMapping : this->m_gruStateMap) { + auto outputGruStateTensor = this->GetOutputTensor(stateMapping.first); + std::vector tempOutGruState(outputGruStateTensor->Bytes()); + auto* outGruState = outputGruStateTensor->GetData(); + memcpy(tempOutGruState.data(), outGruState, outputGruStateTensor->Bytes()); /* Index of the input tensor and the data to copy. */ tempOutGruStates.emplace_back(stateMapping.second, std::move(tempOutGruState)); } /* Updating input GRU states with saved GRU output states. */ - for (auto& stateMapping: tempOutGruStates) { + for (auto& stateMapping : tempOutGruStates) { auto outputGruStateTensorData = stateMapping.second; - TfLiteTensor* inputGruStateTensor = this->GetInputTensor(stateMapping.first); - if (outputGruStateTensorData.size() != inputGruStateTensor->bytes) { - printf_err("Unexpected number of bytes for GRU state mapping. Input = %zuz, output = %zuz.\n", - inputGruStateTensor->bytes, - outputGruStateTensorData.size()); + auto inputGruStateTensor = this->GetInputTensor(stateMapping.first); + if (outputGruStateTensorData.size() != inputGruStateTensor->Bytes()) { + printf_err( + "Unexpected number of bytes for GRU state mapping. Input = %zuz, output = %zuz.\n", + inputGruStateTensor->Bytes(), + outputGruStateTensorData.size()); return false; } - auto* inputGruState = tflite::GetTensorData(inputGruStateTensor); - auto* outGruState = outputGruStateTensorData.data(); - memcpy(inputGruState, outGruState, inputGruStateTensor->bytes); + auto* inputGruState = inputGruStateTensor->GetData(); + auto* outGruState = outputGruStateTensorData.data(); + memcpy(inputGruState, outGruState, inputGruStateTensor->Bytes()); } return true; } diff --git a/source/application/api/common/source/TensorFlowLiteMicro.cc b/source/application/api/fwk/tflm/source/TensorFlowLiteMicro.cc similarity index 53% rename from source/application/api/common/source/TensorFlowLiteMicro.cc rename to source/application/api/fwk/tflm/source/TensorFlowLiteMicro.cc index 3e2c999b4d8708581017ced368bb911f462ad5cd..1ef0a2fc9c8d8e0dad0879e8450fdf2a6e4fbcf9 100644 --- a/source/application/api/common/source/TensorFlowLiteMicro.cc +++ b/source/application/api/fwk/tflm/source/TensorFlowLiteMicro.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -16,6 +16,7 @@ * limitations under the License. */ #include "TensorFlowLiteMicro.hpp" +#include /* If target is arm-none-eabi and arch profile is 'M', wire the logging for * TensorFlow Lite Micro */ @@ -24,39 +25,22 @@ #ifdef __cplusplus extern "C" { -#endif // __cplusplus -static void TFLMLog(const char *s) { +#endif // __cplusplus +static void TFLMLog(const char* s) +{ printf("TFLM - %s\n", s); } #ifdef __cplusplus } -#endif // __cplusplus +#endif // __cplusplus -void EnableTFLMLog() +void arm::app::fwk::tflm::EnableTFLMLog() { RegisterDebugLogCallback(TFLMLog); } + #else /* defined(__arm__) && (__ARM_ARCH_PROFILE == 77) */ -void EnableTFLMLog() {} -#endif /* defined(__arm__) && (__ARM_ARCH_PROFILE == 77) */ -arm::app::QuantParams arm::app::GetTensorQuantParams(TfLiteTensor* tensor) -{ - arm::app::QuantParams params; - if (kTfLiteAffineQuantization == tensor->quantization.type) { - auto* quantParams = (TfLiteAffineQuantization*) (tensor->quantization.params); - if (quantParams && 0 == quantParams->quantized_dimension) { - if (quantParams->scale->size) { - params.scale = quantParams->scale->data[0]; - } - if (quantParams->zero_point->size) { - params.offset = quantParams->zero_point->data[0]; - } - } else if (tensor->params.scale != 0.0) { - /* Legacy tensorflow quantisation parameters */ - params.scale = tensor->params.scale; - params.offset = tensor->params.zero_point; - } - } - return params; -} +void arm::app::fwk::tflm::EnableTFLMLog() {} + +#endif /* defined(__arm__) && (__ARM_ARCH_PROFILE == 77) */ diff --git a/source/application/api/use_case/inference_runner/src/TestModel.cc b/source/application/api/fwk/tflm/source/TestModel.cc similarity index 63% rename from source/application/api/use_case/inference_runner/src/TestModel.cc rename to source/application/api/fwk/tflm/source/TestModel.cc index 94f17eff488f3ccc23b68047c9a5bf661988ea46..92a2b157b5bd26e8d1c9df1a2e3baaba4c347457 100644 --- a/source/application/api/use_case/inference_runner/src/TestModel.cc +++ b/source/application/api/fwk/tflm/source/TestModel.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021, 2023 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021, 2023, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,16 +15,16 @@ * limitations under the License. */ #include "TestModel.hpp" -#include "log_macros.h" #include "MicroMutableAllOpsResolver.hpp" +#include "log_macros.h" -const tflite::MicroOpResolver& arm::app::TestModel::GetOpResolver() +const tflite::MicroOpResolver& arm::app::fwk::tflm::TestModel::GetOpResolver() { - return this->m_opResolver; + return this->m_opResolver; } -bool arm::app::TestModel::EnlistOperations() +bool arm::app::fwk::tflm::TestModel::EnlistOperations() { - this->m_opResolver = CreateAllOpsResolver(); - return true; + this->m_opResolver = CreateAllOpsResolver(); + return true; } diff --git a/source/application/api/common/source/Model.cc b/source/application/api/fwk/tflm/source/TflmModel.cc similarity index 52% rename from source/application/api/common/source/Model.cc rename to source/application/api/fwk/tflm/source/TflmModel.cc index ab3c409418a1f99fe861b7f448cd5e4ae7a5cb6b..73b50437b6f6868258fef093fb351de6d3397951 100644 --- a/source/application/api/common/source/Model.cc +++ b/source/application/api/fwk/tflm/source/TflmModel.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2024 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021-2025 Arm Limited and/or its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -14,39 +14,44 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "TflmModel.hpp" #include "Model.hpp" +#include "TflmTensor.hpp" #include "log_macros.h" #include #include -arm::app::Model::Model() : m_inited(false), m_type(kTfLiteNoType) {} +namespace arm::app::fwk::tflm { + +TflmModel::TflmModel() {} /* Initialise the model */ -bool arm::app::Model::Init(uint8_t* tensorArenaAddr, - uint32_t tensorArenaSize, - const uint8_t* nnModelAddr, - uint32_t nnModelSize, - tflite::MicroAllocator* allocator) +bool TflmModel::Init(iface::MemoryRegion& computeBuffer, + iface::MemoryRegion& nnModel, + const void* backendData) { /* Following tf lite micro example: * Map the model into a usable data structure. This doesn't involve any * copying or parsing, it's a very lightweight operation. */ - debug("loading model from @ 0x%p\n", nnModelAddr); - debug("model size: %" PRIu32 " bytes.\n", nnModelSize); + debug("loading model from @ 0x%p\n", nnModel.data); + debug("model size: %" PRIu32 " bytes.\n", nnModel.size); + + /* Enable logging before we start interacting with the framework. */ + EnableTFLMLog(); - this->m_pModel = ::tflite::GetModel(nnModelAddr); + this->m_backendData.m_pModel = ::tflite::GetModel(nnModel.data); - if (this->m_pModel->version() != TFLITE_SCHEMA_VERSION) { + if (this->m_backendData.m_pModel->version() != TFLITE_SCHEMA_VERSION) { printf_err("Model's schema version %" PRIu32 " is not equal " "to supported version %d.", - this->m_pModel->version(), + this->m_backendData.m_pModel->version(), TFLITE_SCHEMA_VERSION); return false; } - this->m_modelAddr = nnModelAddr; - this->m_modelSize = nnModelSize; + this->m_computeBuffer = computeBuffer; + this->m_modelBuffer = nnModel; /* Pull in only the operation implementations we need. * This relies on a complete list of all the ops needed by this graph. @@ -59,34 +64,40 @@ bool arm::app::Model::Init(uint8_t* tensorArenaAddr, this->EnlistOperations(); + /** If backend data has been provided, re-use the allocator */ + if (backendData) { + this->m_backendData.m_pAllocator = + reinterpret_cast(backendData)->m_pAllocator; + } + /* Create allocator instance, if it doesn't exist */ - this->m_pAllocator = allocator; - if (!this->m_pAllocator) { + if (!this->m_backendData.m_pAllocator) { /* Create an allocator instance */ - info("Creating allocator using tensor arena at 0x%p\n", tensorArenaAddr); + info("Creating allocator using tensor arena at 0x%p\n", computeBuffer.data); - this->m_pAllocator = tflite::MicroAllocator::Create(tensorArenaAddr, tensorArenaSize); + this->m_backendData.m_pAllocator = + tflite::MicroAllocator::Create(computeBuffer.data, computeBuffer.size); - if (!this->m_pAllocator) { + if (!this->m_backendData.m_pAllocator) { printf_err("Failed to create allocator\n"); return false; } - debug("Created new allocator @ 0x%p\n", this->m_pAllocator); + debug("Created new allocator @ 0x%p\n", this->m_backendData.m_pAllocator); } else { - debug("Using existing allocator @ 0x%p\n", this->m_pAllocator); + debug("Using existing allocator @ 0x%p\n", this->m_backendData.m_pAllocator); } - this->m_pInterpreter = std::make_unique( - this->m_pModel, this->GetOpResolver(), this->m_pAllocator); + this->m_backendData.m_pInterpreter = std::make_unique( + this->m_backendData.m_pModel, this->GetOpResolver(), this->m_backendData.m_pAllocator); - if (!this->m_pInterpreter) { + if (!this->m_backendData.m_pInterpreter) { printf_err("Failed to allocate interpreter\n"); return false; } /* Allocate memory from the tensor_arena for the model's tensors. */ info("Allocating tensors\n"); - TfLiteStatus allocate_status = this->m_pInterpreter->AllocateTensors(); + TfLiteStatus allocate_status = this->m_backendData.m_pInterpreter->AllocateTensors(); if (allocate_status != kTfLiteOk) { printf_err("tensor allocation failed!\n"); @@ -95,25 +106,29 @@ bool arm::app::Model::Init(uint8_t* tensorArenaAddr, /* Get information about the memory area to use for the model's input. */ this->m_input.resize(this->GetNumInputs()); - for (size_t inIndex = 0; inIndex < this->GetNumInputs(); inIndex++) - this->m_input[inIndex] = this->m_pInterpreter->input(inIndex); + for (size_t inIndex = 0; inIndex < this->GetNumInputs(); inIndex++) { + this->m_input[inIndex] = + std::make_shared(this->m_backendData.m_pInterpreter->input(inIndex)); + } this->m_output.resize(this->GetNumOutputs()); - for (size_t outIndex = 0; outIndex < this->GetNumOutputs(); outIndex++) - this->m_output[outIndex] = this->m_pInterpreter->output(outIndex); + for (size_t outIndex = 0; outIndex < this->GetNumOutputs(); outIndex++) { + this->m_output[outIndex] = + std::make_shared(this->m_backendData.m_pInterpreter->output(outIndex)); + } if (this->m_input.empty() || this->m_output.empty()) { printf_err("failed to get tensors\n"); return false; } else { - this->m_type = this->m_input[0]->type; /* Input 0 should be the main input */ + this->m_type = this->m_input[0]->Type(); /* Input 0 should be the main input */ /* Clear the input & output tensors */ for (size_t inIndex = 0; inIndex < this->GetNumInputs(); inIndex++) { - std::memset(this->m_input[inIndex]->data.data, 0, this->m_input[inIndex]->bytes); + std::memset(this->m_input[inIndex]->GetData(), 0, this->m_input[inIndex]->Bytes()); } for (size_t outIndex = 0; outIndex < this->GetNumOutputs(); outIndex++) { - std::memset(this->m_output[outIndex]->data.data, 0, this->m_output[outIndex]->bytes); + std::memset(this->m_output[outIndex]->GetData(), 0, this->m_output[outIndex]->Bytes()); } this->LogInterpreterInfo(); @@ -123,15 +138,23 @@ bool arm::app::Model::Init(uint8_t* tensorArenaAddr, return true; } -tflite::MicroAllocator* arm::app::Model::GetAllocator() +std::shared_ptr TflmModel::GetInputTensor(size_t index) const +{ + if (index < this->GetNumInputs()) { + return this->m_input.at(index); + } + return nullptr; +} + +std::shared_ptr TflmModel::GetOutputTensor(size_t index) const { - if (this->IsInited()) { - return this->m_pAllocator; + if (index < this->GetNumOutputs()) { + return this->m_output.at(index); } return nullptr; } -void arm::app::Model::LogTensorInfo(TfLiteTensor* tensor) +void TflmModel::LogTensorInfo(std::shared_ptr tensor) { if (!tensor) { printf_err("Invalid tensor\n"); @@ -139,53 +162,46 @@ void arm::app::Model::LogTensorInfo(TfLiteTensor* tensor) return; } - debug("\ttensor is assigned to 0x%p\n", tensor); - info("\ttensor type is %s\n", TfLiteTypeGetName(tensor->type)); - info("\ttensor occupies %zu bytes with dimensions\n", tensor->bytes); - for (int i = 0; i < tensor->dims->size; ++i) { - info("\t\t%d: %3d\n", i, tensor->dims->data[i]); + debug("\ttensor is assigned to 0x%p\n", tensor.get()); + info("\ttensor type is %s\n", GetTensorDataTypeName(tensor->Type())); + info("\ttensor occupies %zu bytes with dimensions\n", tensor->Bytes()); + const auto shape = tensor->Shape(); + for (size_t i = 0; i < shape.size(); ++i) { + info("\t\t%zu: %3d\n", i, static_cast(shape[i])); } - TfLiteQuantization quant = tensor->quantization; - if (kTfLiteAffineQuantization == quant.type) { - auto* quantParams = (TfLiteAffineQuantization*)quant.params; - info("Quant dimension: %" PRIi32 "\n", quantParams->quantized_dimension); - for (int i = 0; i < quantParams->scale->size; ++i) { - info("Scale[%d] = %f\n", i, quantParams->scale->data[i]); - } - for (int i = 0; i < quantParams->zero_point->size; ++i) { - info("ZeroPoint[%d] = %d\n", i, quantParams->zero_point->data[i]); - } - } + const auto quant = tensor->GetQuantParams(); + info("Scale = %f\n", quant.scale); + info("ZeroPoint = %d\n", quant.offset); } -void arm::app::Model::LogInterpreterInfo() +void TflmModel::LogInterpreterInfo() { - if (!this->m_pInterpreter) { + if (!this->m_backendData.m_pInterpreter) { printf_err("Invalid interpreter\n"); return; } info("Model INPUT tensors: \n"); - for (auto input : this->m_input) { + for (auto& input : this->m_input) { this->LogTensorInfo(input); } info("Model OUTPUT tensors: \n"); - for (auto output : this->m_output) { + for (auto& output : this->m_output) { this->LogTensorInfo(output); } info("Activation buffer (a.k.a tensor arena) size used: %zu\n", - this->m_pInterpreter->arena_used_bytes()); + this->m_backendData.m_pInterpreter->arena_used_bytes()); /* We expect there to be only one subgraph. */ - const uint32_t nOperators = tflite::NumSubgraphOperators(this->m_pModel, 0); + const uint32_t nOperators = tflite::NumSubgraphOperators(this->m_backendData.m_pModel, 0); info("Number of operators: %" PRIu32 "\n", nOperators); - const tflite::SubGraph* subgraph = this->m_pModel->subgraphs()->Get(0); + const tflite::SubGraph* subgraph = this->m_backendData.m_pModel->subgraphs()->Get(0); - auto* opcodes = this->m_pModel->operator_codes(); + auto* opcodes = this->m_backendData.m_pModel->operator_codes(); /* For each operator, display registration information. */ for (size_t i = 0; i < nOperators; ++i) { @@ -208,22 +224,29 @@ void arm::app::Model::LogInterpreterInfo() } } -bool arm::app::Model::IsInited() const +bool TflmModel::IsInited() const { return this->m_inited; } -bool arm::app::Model::IsDataSigned() const +bool TflmModel::IsDataSigned() const { - return this->GetType() == kTfLiteInt8; + switch (this->GetType()) { + case iface::TensorType::INT8: + [[fallthrough]]; + case iface::TensorType::INT16: + return true; + default: + return false; + } } -bool arm::app::Model::ContainsEthosUOperator() const +bool TflmModel::ContainsEthosUOperator() const { /* We expect there to be only one subgraph. */ - const uint32_t nOperators = tflite::NumSubgraphOperators(this->m_pModel, 0); - const tflite::SubGraph* subgraph = this->m_pModel->subgraphs()->Get(0); - const auto* opcodes = this->m_pModel->operator_codes(); + const uint32_t nOperators = tflite::NumSubgraphOperators(this->m_backendData.m_pModel, 0); + const tflite::SubGraph* subgraph = this->m_backendData.m_pModel->subgraphs()->Get(0); + const auto* opcodes = this->m_backendData.m_pModel->operator_codes(); /* check for custom operators */ for (size_t i = 0; (i < nOperators); ++i) { @@ -240,11 +263,11 @@ bool arm::app::Model::ContainsEthosUOperator() const return false; } -bool arm::app::Model::RunInference() +bool TflmModel::RunInference() { bool inference_state = false; - if (this->m_pModel && this->m_pInterpreter) { - if (kTfLiteOk != this->m_pInterpreter->Invoke()) { + if (this->m_backendData.m_pModel && this->m_backendData.m_pInterpreter) { + if (kTfLiteOk != this->m_backendData.m_pInterpreter->Invoke()) { printf_err("Invoke failed.\n"); } else { inference_state = true; @@ -255,83 +278,58 @@ bool arm::app::Model::RunInference() return inference_state; } -TfLiteTensor* arm::app::Model::GetInputTensor(size_t index) const +size_t TflmModel::GetNumInputs() const { - if (index < this->GetNumInputs()) { - return this->m_input.at(index); - } - return nullptr; -} - -TfLiteTensor* arm::app::Model::GetOutputTensor(size_t index) const -{ - if (index < this->GetNumOutputs()) { - return this->m_output.at(index); - } - return nullptr; -} - -size_t arm::app::Model::GetNumInputs() const -{ - if (this->m_pModel && this->m_pInterpreter) { - return this->m_pInterpreter->inputs_size(); + if (this->m_backendData.m_pModel && this->m_backendData.m_pInterpreter) { + return this->m_backendData.m_pInterpreter->inputs_size(); } return 0; } -size_t arm::app::Model::GetNumOutputs() const +size_t TflmModel::GetNumOutputs() const { - if (this->m_pModel && this->m_pInterpreter) { - return this->m_pInterpreter->outputs_size(); + if (this->m_backendData.m_pModel && this->m_backendData.m_pInterpreter) { + return this->m_backendData.m_pInterpreter->outputs_size(); } return 0; } -TfLiteType arm::app::Model::GetType() const +iface::TensorType TflmModel::GetType() const { return this->m_type; } -TfLiteIntArray* arm::app::Model::GetInputShape(size_t index) const +std::vector TflmModel::GetInputShape(size_t index) const { if (index < this->GetNumInputs()) { - return this->m_input.at(index)->dims; + return this->m_input.at(index)->Shape(); } - return nullptr; + return std::vector(); } -TfLiteIntArray* arm::app::Model::GetOutputShape(size_t index) const +std::vector TflmModel::GetOutputShape(size_t index) const { if (index < this->GetNumOutputs()) { - return this->m_output.at(index)->dims; + return this->m_output.at(index)->Shape(); } - return nullptr; + return std::vector(); } -bool arm::app::Model::ShowModelInfoHandler() +/** @brief Gets a pointer to the backend's compute buffer. */ +const iface::MemoryRegion& TflmModel::GetComputeBuffer() const { - if (!this->IsInited()) { - printf_err("Model is not initialised! Terminating processing.\n"); - return false; - } - - info("Model address: 0x%p", this->ModelPointer()); - info("Model size: %" PRIu32 " bytes.", this->ModelSize()); - info("Model info:\n"); - this->LogInterpreterInfo(); - - info("The model is optimised for Ethos-U NPU: %s.\n", - this->ContainsEthosUOperator() ? "yes" : "no"); - - return true; + return std::ref(this->m_computeBuffer); } -const uint8_t* arm::app::Model::ModelPointer() +/** @brief Gets the pointer to the NN model data array. */ +const iface::MemoryRegion& TflmModel::GetModelBuffer() const { - return this->m_modelAddr; + return std::ref(this->m_modelBuffer); } -uint32_t arm::app::Model::ModelSize() +const TflmBackendData& TflmModel::GetBackendData() const { - return this->m_modelSize; + return std::ref(this->m_backendData); } + +} /* namespace arm::app::fwk::tflm */ diff --git a/source/application/api/fwk/tflm/source/TflmTensor.cc b/source/application/api/fwk/tflm/source/TflmTensor.cc new file mode 100644 index 0000000000000000000000000000000000000000..6625f35e722a090263b7d58da53ee191e6cfef0d --- /dev/null +++ b/source/application/api/fwk/tflm/source/TflmTensor.cc @@ -0,0 +1,105 @@ +/* + * SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or + * its affiliates + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ + +#include "TflmTensor.hpp" +#include + +namespace arm::app::fwk::tflm { + +TflmTensor::TflmTensor(TfLiteTensor* tensor) : m_tensor(tensor) {} + +void* TflmTensor::GetData() +{ + assert(this->m_tensor); + return this->m_tensor->data.data; +} + +size_t TflmTensor::Bytes() +{ + assert(this->m_tensor); + return this->m_tensor->bytes; +} + +size_t TflmTensor::GetNumElements() +{ + assert(this->m_tensor); + const auto shape = this->Shape(); + size_t nElements = 1; + for (auto& dim : shape) { + nElements *= dim; + } + return nElements; +} + +std::vector TflmTensor::Shape() +{ + assert(this->m_tensor); + assert(this->m_tensor->dims); + return std::vector(this->m_tensor->dims->data, + this->m_tensor->dims->data + this->m_tensor->dims->size); +} + +iface::TensorType TflmTensor::Type() +{ + assert(this->m_tensor); + + switch (this->m_tensor->type) { + case kTfLiteUInt8: + return iface::TensorType::UINT8; + case kTfLiteInt8: + return iface::TensorType::INT8; + case kTfLiteInt16: + return iface::TensorType::INT16; + case kTfLiteFloat16: + return iface::TensorType::FP16; + case kTfLiteFloat32: + return iface::TensorType::FP32; + default: + return iface::TensorType::INVALID; + } +} + +iface::TensorLayout TflmTensor::Layout() +{ + return iface::TensorLayout::NHWC; +} + +iface::QuantParams TflmTensor::GetQuantParams() +{ + iface::QuantParams params{0.f, 0}; + assert(this->m_tensor); + if (kTfLiteAffineQuantization == this->m_tensor->quantization.type) { + auto* quantParams = + reinterpret_cast(this->m_tensor->quantization.params); + if (quantParams && 0 == quantParams->quantized_dimension) { + if (quantParams->scale->size) { + params.scale = quantParams->scale->data[0]; + } + if (quantParams->zero_point->size) { + params.offset = quantParams->zero_point->data[0]; + } + } else if (this->m_tensor->params.scale != 0.0) { + /* Legacy tensorflow quantisation parameters */ + params.scale = this->m_tensor->params.scale; + params.offset = this->m_tensor->params.zero_point; + } + } + return params; +} + +} // namespace arm::app::fwk::tflm diff --git a/source/application/api/use_case/vww/src/VisualWakeWordModel.cc b/source/application/api/fwk/tflm/source/VisualWakeWordModel.cc similarity index 73% rename from source/application/api/use_case/vww/src/VisualWakeWordModel.cc rename to source/application/api/fwk/tflm/source/VisualWakeWordModel.cc index aab122e4ba521d02f4cadf77e95b19619b9b6d85..10c2a5c1e5819b97e97060f07e9fc8f4c6a0c7a8 100644 --- a/source/application/api/use_case/vww/src/VisualWakeWordModel.cc +++ b/source/application/api/fwk/tflm/source/VisualWakeWordModel.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,12 @@ #include "VisualWakeWordModel.hpp" #include "log_macros.h" -const tflite::MicroOpResolver& arm::app::VisualWakeWordModel::GetOpResolver() +const tflite::MicroOpResolver& arm::app::fwk::tflm::VisualWakeWordModel::GetOpResolver() { return this->m_opResolver; } -bool arm::app::VisualWakeWordModel::EnlistOperations() +bool arm::app::fwk::tflm::VisualWakeWordModel::EnlistOperations() { this->m_opResolver.AddDepthwiseConv2D(); this->m_opResolver.AddConv2D(); @@ -32,8 +32,7 @@ bool arm::app::VisualWakeWordModel::EnlistOperations() this->m_opResolver.AddAdd(); if (kTfLiteOk == this->m_opResolver.AddEthosU()) { - info("Added %s support to op resolver\n", - tflite::GetString_ETHOSU()); + info("Added %s support to op resolver\n", tflite::GetString_ETHOSU()); } else { printf_err("Failed to add Arm NPU support to op resolver."); return false; diff --git a/source/application/api/use_case/asr/src/Wav2LetterModel.cc b/source/application/api/fwk/tflm/source/Wav2LetterModel.cc similarity index 72% rename from source/application/api/use_case/asr/src/Wav2LetterModel.cc rename to source/application/api/fwk/tflm/source/Wav2LetterModel.cc index 93fb60e112ef6a547b2087534150bb098d164550..f66dbd1470a15577128c14d476a93f11a2f4bd3a 100644 --- a/source/application/api/use_case/asr/src/Wav2LetterModel.cc +++ b/source/application/api/fwk/tflm/source/Wav2LetterModel.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,13 +18,12 @@ #include "log_macros.h" - -const tflite::MicroOpResolver& arm::app::Wav2LetterModel::GetOpResolver() +const tflite::MicroOpResolver& arm::app::fwk::tflm::Wav2LetterModel::GetOpResolver() { return this->m_opResolver; } -bool arm::app::Wav2LetterModel::EnlistOperations() +bool arm::app::fwk::tflm::Wav2LetterModel::EnlistOperations() { this->m_opResolver.AddConv2D(); this->m_opResolver.AddReshape(); @@ -32,8 +31,7 @@ bool arm::app::Wav2LetterModel::EnlistOperations() this->m_opResolver.AddSoftmax(); if (kTfLiteOk == this->m_opResolver.AddEthosU()) { - info("Added %s support to op resolver\n", - tflite::GetString_ETHOSU()); + info("Added %s support to op resolver\n", tflite::GetString_ETHOSU()); } else { printf_err("Failed to add Arm NPU support to op resolver."); return false; diff --git a/source/application/api/use_case/object_detection/src/YoloFastestModel.cc b/source/application/api/fwk/tflm/source/YoloFastestModel.cc similarity index 75% rename from source/application/api/use_case/object_detection/src/YoloFastestModel.cc rename to source/application/api/fwk/tflm/source/YoloFastestModel.cc index 9a155528bc1b5caeffb2437e8fa59c0c1475335b..308ada9cff2cb047dc2c07efdfc6f0549cd0d9fd 100644 --- a/source/application/api/use_case/object_detection/src/YoloFastestModel.cc +++ b/source/application/api/fwk/tflm/source/YoloFastestModel.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,12 +18,12 @@ #include "log_macros.h" -const tflite::MicroOpResolver& arm::app::YoloFastestModel::GetOpResolver() +const tflite::MicroOpResolver& arm::app::fwk::tflm::YoloFastestModel::GetOpResolver() { return this->m_opResolver; } -bool arm::app::YoloFastestModel::EnlistOperations() +bool arm::app::fwk::tflm::YoloFastestModel::EnlistOperations() { this->m_opResolver.AddDepthwiseConv2D(); this->m_opResolver.AddConv2D(); @@ -35,8 +35,7 @@ bool arm::app::YoloFastestModel::EnlistOperations() this->m_opResolver.AddConcatenation(); if (kTfLiteOk == this->m_opResolver.AddEthosU()) { - info("Added %s support to op resolver\n", - tflite::GetString_ETHOSU()); + info("Added %s support to op resolver\n", tflite::GetString_ETHOSU()); } else { printf_err("Failed to add Arm NPU support to op resolver."); return false; diff --git a/source/application/api/use_case/CMakeLists.txt b/source/application/api/use_case/CMakeLists.txt new file mode 100644 index 0000000000000000000000000000000000000000..4e1eee8d7ffe02ee2286a30f72a940e7a13a486a --- /dev/null +++ b/source/application/api/use_case/CMakeLists.txt @@ -0,0 +1,27 @@ +#---------------------------------------------------------------------------- +# SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its +# affiliates +# SPDX-License-Identifier: Apache-2.0 +# +# 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. +#---------------------------------------------------------------------------- + +# Add individual use case APIs but don't include them in default build +add_subdirectory(ad EXCLUDE_FROM_ALL) +add_subdirectory(asr EXCLUDE_FROM_ALL) +add_subdirectory(img_class EXCLUDE_FROM_ALL) +add_subdirectory(inference_runner EXCLUDE_FROM_ALL) +add_subdirectory(kws EXCLUDE_FROM_ALL) +add_subdirectory(noise_reduction EXCLUDE_FROM_ALL) +add_subdirectory(object_detection EXCLUDE_FROM_ALL) +add_subdirectory(vww EXCLUDE_FROM_ALL) diff --git a/source/application/api/use_case/ad/CMakeLists.txt b/source/application/api/use_case/ad/CMakeLists.txt index b1d920209d26d943f2b94d8e1459f31ac06b237b..1a02f285b8055b4ccbfa3b62f83a95ab0f2adfd6 100644 --- a/source/application/api/use_case/ad/CMakeLists.txt +++ b/source/application/api/use_case/ad/CMakeLists.txt @@ -1,5 +1,6 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates +# SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or its +# affiliates # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,7 +27,6 @@ project(${AD_API_TARGET} # Create static library add_library(${AD_API_TARGET} STATIC - src/AdModel.cc src/AdProcessing.cc src/AdMelSpectrogram.cc src/MelSpectrogram.cc) diff --git a/source/application/api/use_case/ad/include/AdModel.hpp b/source/application/api/use_case/ad/include/AdModel.hpp deleted file mode 100644 index 25eeb1fe6bf75e25dcf1d059dcbfb6deae8e24a7..0000000000000000000000000000000000000000 --- a/source/application/api/use_case/ad/include/AdModel.hpp +++ /dev/null @@ -1,56 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 - * - * 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. - */ -#ifndef AD_MODEL_HPP -#define AD_MODEL_HPP - -#include "Model.hpp" - -namespace arm { -namespace app { - namespace ad { - extern const int g_FrameLength; - extern const int g_FrameStride; - extern const float g_ScoreThreshold; - extern const float g_TrainingMean; - } /* namespace ad */ - - class AdModel : public Model { - - public: - /* Indices for the expected model - based on input tensor shape */ - static constexpr uint32_t ms_inputRowsIdx = 1; - static constexpr uint32_t ms_inputColsIdx = 2; - - protected: - /** @brief Gets the reference to op resolver interface class */ - const tflite::MicroOpResolver& GetOpResolver() override; - - /** @brief Adds operations to the op resolver instance */ - bool EnlistOperations() override; - - private: - /* Maximum number of individual operations that can be enlisted */ - static constexpr int ms_maxOpCnt = 6; - - /* A mutable op resolver instance */ - tflite::MicroMutableOpResolver m_opResolver; - }; - -} /* namespace app */ -} /* namespace arm */ - -#endif /* AD_MODEL_HPP */ diff --git a/source/application/api/use_case/ad/include/AdProcessing.hpp b/source/application/api/use_case/ad/include/AdProcessing.hpp index 04c9381da2d42cc5f36860c9e28d81799905f886..00a2284e285194eecf54306458ec2ede15b7742e 100644 --- a/source/application/api/use_case/ad/include/AdProcessing.hpp +++ b/source/application/api/use_case/ad/include/AdProcessing.hpp @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,12 +17,16 @@ #ifndef AD_PROCESSING_HPP #define AD_PROCESSING_HPP -#include "BaseProcessing.hpp" -#include "TensorFlowLiteMicro.hpp" -#include "AudioUtils.hpp" #include "AdMelSpectrogram.hpp" +#include "AudioUtils.hpp" +#include "BaseProcessing.hpp" +#include "Tensor.hpp" #include "log_macros.h" +#include +#include +#include + namespace arm { namespace app { @@ -37,11 +41,15 @@ namespace app { /** * @brief Constructor for AdPreProcess class objects * @param[in] inputTensor input tensor pointer from the tensor arena. + * @param[in] numRowsIdx index in tensor shape to get number of rows. + * @param[in] numColsIdx index in tensor shape to get number of columns. * @param[in] melSpectrogramFrameLen MEL spectrogram's frame length * @param[in] melSpectrogramFrameStride MEL spectrogram's frame stride * @param[in] adModelTrainingMean Training mean for the Anomaly detection model being used. */ - explicit AdPreProcess(TfLiteTensor* inputTensor, + explicit AdPreProcess(const std::shared_ptr inputTensor, + uint32_t numRowsIdx, + uint32_t numColsIdx, uint32_t melSpectrogramFrameLen, uint32_t melSpectrogramFrameStride, float adModelTrainingMean); @@ -100,7 +108,7 @@ namespace app { * @brief Constructor for AdPostProcess object. * @param[in] outputTensor Output tensor pointer. */ - explicit AdPostProcess(TfLiteTensor* outputTensor); + explicit AdPostProcess(const std::shared_ptr outputTensor); ~AdPostProcess() = default; @@ -118,7 +126,7 @@ namespace app { float GetOutputValue(uint32_t index); private: - TfLiteTensor* m_outputTensor{}; /**< Output tensor pointer */ + std::shared_ptr m_outputTensor{}; /**< Output tensor pointer */ std::vector m_dequantizedOutputVec{}; /**< Internal output vector */ /** @@ -129,20 +137,17 @@ namespace app { template bool Dequantize() { - TfLiteTensor* tensor = this->m_outputTensor; + const std::shared_ptr tensor = this->m_outputTensor; if (tensor == nullptr) { printf_err("Invalid output tensor.\n"); return false; } - T* tensorData = tflite::GetTensorData(tensor); + T* tensorData = tensor->GetData(); - uint32_t totalOutputSize = 1; - for (int inputDim = 0; inputDim < tensor->dims->size; inputDim++){ - totalOutputSize *= tensor->dims->data[inputDim]; - } + const uint32_t totalOutputSize = tensor->GetNumElements(); /* For getting the floating point values, we need quantization parameters */ - QuantParams quantParams = GetTensorQuantParams(tensor); + auto quantParams = tensor->GetQuantParams(); this->m_dequantizedOutputVec = std::vector(totalOutputSize, 0); @@ -170,10 +175,11 @@ namespace app { * @param compute features calculator function. * @return lambda function to compute features. */ - template - std::function&, size_t, bool, size_t, size_t)> - FeatureCalc(TfLiteTensor* inputTensor, size_t cacheSize, - std::function (std::vector& )> compute) + template + std::function&, size_t, bool, size_t, size_t)> + FeatureCalc(const std::shared_ptr inputTensor, + size_t cacheSize, + std::function(std::vector&)> compute) { /* Feature cache to be captured by lambda function*/ static std::vector> featureCache = std::vector>(cacheSize); @@ -182,9 +188,8 @@ namespace app { size_t index, bool useCache, size_t featuresOverlapIndex, - size_t resizeScale) - { - T* tensorData = tflite::GetTensorData(inputTensor); + size_t resizeScale) { + T* tensorData = inputTensor->GetData(); std::vector features; /* Reuse features from cache if cache is ready and sliding windows overlap. @@ -209,19 +214,19 @@ namespace app { }; } - template std::function&, size_t , bool, size_t, size_t)> - FeatureCalc(TfLiteTensor* inputTensor, + template std::function&, size_t, bool, size_t, size_t)> + FeatureCalc(const std::shared_ptr inputTensor, size_t cacheSize, - std::function (std::vector&)> compute); + std::function(std::vector&)> compute); template std::function&, size_t, bool, size_t, size_t)> - FeatureCalc(TfLiteTensor *inputTensor, + FeatureCalc(const std::shared_ptr inputTensor, size_t cacheSize, std::function(std::vector&)> compute); - std::function&, int, bool, size_t, size_t)> + std::function&, int, bool, size_t, size_t)> GetFeatureCalculator(audio::AdMelSpectrogram& melSpec, - TfLiteTensor* inputTensor, + const std::shared_ptr inputTensor, size_t cacheSize, float trainingMean); diff --git a/source/application/api/use_case/ad/src/AdProcessing.cc b/source/application/api/use_case/ad/src/AdProcessing.cc index e3e2aed50fcc6ea01ca1d9b5a134fa850bf552ff..7d1aa4d13f4eb08cba43473ba8ef0e5e2e435f64 100644 --- a/source/application/api/use_case/ad/src/AdProcessing.cc +++ b/source/application/api/use_case/ad/src/AdProcessing.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,65 +16,64 @@ */ #include "AdProcessing.hpp" -#include "AdModel.hpp" +#include namespace arm { namespace app { -AdPreProcess::AdPreProcess(TfLiteTensor* inputTensor, - uint32_t melSpectrogramFrameLen, - uint32_t melSpectrogramFrameStride, - float adModelTrainingMean): - m_validInstance{false}, - m_melSpectrogramFrameLen{melSpectrogramFrameLen}, - m_melSpectrogramFrameStride{melSpectrogramFrameStride}, + AdPreProcess::AdPreProcess(const std::shared_ptr inputTensor, + uint32_t numRowsIdx, + uint32_t numColsIdx, + uint32_t melSpectrogramFrameLen, + uint32_t melSpectrogramFrameStride, + float adModelTrainingMean) : + m_validInstance{false}, m_melSpectrogramFrameLen{melSpectrogramFrameLen}, + m_melSpectrogramFrameStride{melSpectrogramFrameStride}, /**< Model is trained on features downsampled 2x */ - m_inputResizeScale{2}, + m_inputResizeScale{2}, /**< We are choosing to move by 20 frames across the audio for each inference. */ - m_numMelSpecVectorsInAudioStride{20}, - m_audioDataStride{m_numMelSpecVectorsInAudioStride * melSpectrogramFrameStride}, - m_melSpec{melSpectrogramFrameLen} -{ - UNUSED(this->m_melSpectrogramFrameStride); + m_numMelSpecVectorsInAudioStride{20}, + m_audioDataStride{m_numMelSpecVectorsInAudioStride * melSpectrogramFrameStride}, + m_melSpec{melSpectrogramFrameLen} + { + UNUSED(this->m_melSpectrogramFrameStride); + + if (!inputTensor) { + printf_err("Invalid input tensor provided to pre-process\n"); + return; + } - if (!inputTensor) { - printf_err("Invalid input tensor provided to pre-process\n"); - return; - } + auto inputShape = inputTensor->Shape(); - TfLiteIntArray* inputShape = inputTensor->dims; + if (inputShape.size() < std::max(numRowsIdx, numColsIdx)) { + printf_err("Invalid input tensor dims\n"); + return; + } - if (!inputShape) { - printf_err("Invalid input tensor dims\n"); - return; - } + const uint32_t kNumRows = inputShape[numRowsIdx]; + const uint32_t kNumCols = inputShape[numColsIdx]; - const uint32_t kNumRows = inputShape->data[AdModel::ms_inputRowsIdx]; - const uint32_t kNumCols = inputShape->data[AdModel::ms_inputColsIdx]; - - /* Deduce the data length required for 1 inference from the network parameters. */ - this->m_audioDataWindowSize = (((this->m_inputResizeScale * kNumCols) - 1) * - melSpectrogramFrameStride) + - melSpectrogramFrameLen; - this->m_numReusedFeatureVectors = kNumRows - - (this->m_numMelSpecVectorsInAudioStride / - this->m_inputResizeScale); - this->m_melSpec.Init(); - - /* Creating a Mel Spectrogram sliding window for the data required for 1 inference. - * "resizing" done here by multiplying stride by resize scale. */ - this->m_melWindowSlider = audio::SlidingWindow( + /* Deduce the data length required for 1 inference from the network parameters. */ + this->m_audioDataWindowSize = + (((this->m_inputResizeScale * kNumCols) - 1) * melSpectrogramFrameStride) + + melSpectrogramFrameLen; + this->m_numReusedFeatureVectors = + kNumRows - (this->m_numMelSpecVectorsInAudioStride / this->m_inputResizeScale); + this->m_melSpec.Init(); + + /* Creating a Mel Spectrogram sliding window for the data required for 1 inference. + * "resizing" done here by multiplying stride by resize scale. */ + this->m_melWindowSlider = audio::SlidingWindow( nullptr, /* to be populated later. */ this->m_audioDataWindowSize, melSpectrogramFrameLen, melSpectrogramFrameStride * this->m_inputResizeScale); - /* Construct feature calculation function. */ - this->m_featureCalc = GetFeatureCalculator(this->m_melSpec, inputTensor, - this->m_numReusedFeatureVectors, - adModelTrainingMean); - this->m_validInstance = true; -} + /* Construct feature calculation function. */ + this->m_featureCalc = GetFeatureCalculator( + this->m_melSpec, inputTensor, this->m_numReusedFeatureVectors, adModelTrainingMean); + this->m_validInstance = true; + } bool AdPreProcess::DoPreProcess(const void* input, size_t inputSize) { @@ -130,19 +129,19 @@ void AdPreProcess::SetAudioWindowIndex(uint32_t idx) this->m_audioWindowIndex = idx; } -AdPostProcess::AdPostProcess(TfLiteTensor* outputTensor) : - m_outputTensor {outputTensor} +AdPostProcess::AdPostProcess(const std::shared_ptr outputTensor) : + m_outputTensor{outputTensor} {} bool AdPostProcess::DoPostProcess() { - switch (this->m_outputTensor->type) { - case kTfLiteInt8: - this->Dequantize(); - break; - default: - printf_err("Unsupported tensor type"); - return false; + switch (this->m_outputTensor->Type()) { + case fwk::iface::TensorType::INT8: + this->Dequantize(); + break; + default: + printf_err("Unsupported tensor type"); + return false; } math::MathUtils::SoftmaxF32(this->m_dequantizedOutputVec); @@ -158,50 +157,35 @@ float AdPostProcess::GetOutputValue(uint32_t index) return 0.0; } -std::function&, int, bool, size_t, size_t)> +std::function&, int, bool, size_t, size_t)> GetFeatureCalculator(audio::AdMelSpectrogram& melSpec, - TfLiteTensor* inputTensor, + const std::shared_ptr inputTensor, size_t cacheSize, float trainingMean) { std::function&, size_t, bool, size_t, size_t)> melSpecFeatureCalc = nullptr; - TfLiteQuantization quant = inputTensor->quantization; - - if (kTfLiteAffineQuantization == quant.type) { - - auto* quantParams = static_cast(quant.params); - const float quantScale = quantParams->scale->data[0]; - const int quantOffset = quantParams->zero_point->data[0]; - - switch (inputTensor->type) { - case kTfLiteInt8: { - melSpecFeatureCalc = FeatureCalc( - inputTensor, - cacheSize, - [=, &melSpec](std::vector& audioDataWindow) { - return melSpec.MelSpecComputeQuant( - audioDataWindow, - quantScale, - quantOffset, - trainingMean); - } - ); - break; - } - default: - printf_err("Tensor type %s not supported\n", TfLiteTypeGetName(inputTensor->type)); + auto quant = inputTensor->GetQuantParams(); + + if (quant.scale != 0) { + switch (inputTensor->Type()) { + case fwk::iface::TensorType::INT8: { + melSpecFeatureCalc = FeatureCalc( + inputTensor, cacheSize, [=, &melSpec](const std::vector& audioDataWindow) { + return melSpec.MelSpecComputeQuant( + audioDataWindow, quant.scale, quant.offset, trainingMean); + }); + break; + } + default: + printf_err("Tensor type %s not supported\n", + fwk::iface::GetTensorDataTypeName(inputTensor->Type())); } } else { melSpecFeatureCalc = FeatureCalc( - inputTensor, - cacheSize, - [=, &melSpec]( - std::vector& audioDataWindow) { - return melSpec.ComputeMelSpec( - audioDataWindow, - trainingMean); - }); + inputTensor, cacheSize, [=, &melSpec](const std::vector& audioDataWindow) { + return melSpec.ComputeMelSpec(audioDataWindow, trainingMean); + }); } return melSpecFeatureCalc; } diff --git a/source/application/api/use_case/asr/CMakeLists.txt b/source/application/api/use_case/asr/CMakeLists.txt index 112b4240285ca8ad733274c4841e0b8483d0c571..bc586139b15523f39f17ede8a1145363751b9f16 100644 --- a/source/application/api/use_case/asr/CMakeLists.txt +++ b/source/application/api/use_case/asr/CMakeLists.txt @@ -1,5 +1,6 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates +# SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or +# its affiliates # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -30,8 +31,7 @@ add_library(${ASR_API_TARGET} STATIC src/Wav2LetterPostprocess.cc src/Wav2LetterMfcc.cc src/AsrClassifier.cc - src/OutputDecode.cc - src/Wav2LetterModel.cc) + src/OutputDecode.cc) target_include_directories(${ASR_API_TARGET} PUBLIC include) diff --git a/source/application/api/use_case/asr/include/AsrClassifier.hpp b/source/application/api/use_case/asr/include/AsrClassifier.hpp index 805f4c5fed7deb3d682ce2f627ab5b18123aef17..04e869f5f08a688b254c1aeb6cc0c2f43051ed4e 100644 --- a/source/application/api/use_case/asr/include/AsrClassifier.hpp +++ b/source/application/api/use_case/asr/include/AsrClassifier.hpp @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or + * its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,6 +25,10 @@ namespace app { class AsrClassifier : public Classifier { public: + explicit AsrClassifier(uint32_t inputRowsIdx, + uint32_t inputColsIdx, + uint32_t outputRowsIdx, + uint32_t outputColsIdx); /** * @brief Gets the top N classification results from the * output vector. @@ -35,10 +40,16 @@ namespace app { * @param[in] use_softmax Whether softmax scaling should be applied to model output. * @return true if successful, false otherwise. **/ - bool GetClassificationResults(TfLiteTensor* outputTensor, + bool GetClassificationResults(const std::shared_ptr outputTensor, std::vector& vecResults, const std::vector& labels, - uint32_t topNCount, bool use_softmax = false) override; + uint32_t topNCount, + bool use_softmax = false) override; + + const uint32_t m_inputTensorRowsIdx; + const uint32_t m_inputTensorColsIdx; + const uint32_t m_outputTensorRowsIdx; + const uint32_t m_outputTensorColsIdx; private: /** @@ -51,13 +62,15 @@ namespace app { * @param[in] zeroPoint Quantization zero point. * @return true if successful, false otherwise. **/ - template - bool GetTopResults(TfLiteTensor* tensor, + template + bool GetTopResults(const std::shared_ptr tensor, std::vector& vecResults, - const std::vector& labels, double scale, double zeroPoint); + const std::vector& labels, + double scale, + double zeroPoint); }; } /* namespace app */ } /* namespace arm */ -#endif /* ASR_CLASSIFIER_HPP */ \ No newline at end of file +#endif /* ASR_CLASSIFIER_HPP */ diff --git a/source/application/api/use_case/asr/include/Wav2LetterModel.hpp b/source/application/api/use_case/asr/include/Wav2LetterModel.hpp deleted file mode 100644 index d45b1a09e9c925fb2b0ff90e80f81f04652dbbfd..0000000000000000000000000000000000000000 --- a/source/application/api/use_case/asr/include/Wav2LetterModel.hpp +++ /dev/null @@ -1,67 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 - * - * 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. - */ -#ifndef ASR_WAV2LETTER_MODEL_HPP -#define ASR_WAV2LETTER_MODEL_HPP - -#include "Model.hpp" - -namespace arm { -namespace app { -namespace asr { - extern const int g_FrameLength; - extern const int g_FrameStride; - extern const float g_ScoreThreshold; - extern const int g_ctxLen; -} /* namespace asr */ -} /* namespace app */ -} /* namespace arm */ - -namespace arm { -namespace app { - - class Wav2LetterModel : public Model { - - public: - /* Indices for the expected model - based on input and output tensor shapes */ - static constexpr uint32_t ms_inputRowsIdx = 1; - static constexpr uint32_t ms_inputColsIdx = 2; - static constexpr uint32_t ms_outputRowsIdx = 2; - static constexpr uint32_t ms_outputColsIdx = 3; - - /* Model specific constants. */ - static constexpr uint32_t ms_blankTokenIdx = 28; - static constexpr uint32_t ms_numMfccFeatures = 13; - - protected: - /** @brief Gets the reference to op resolver interface class. */ - const tflite::MicroOpResolver& GetOpResolver() override; - - /** @brief Adds operations to the op resolver instance. */ - bool EnlistOperations() override; - - private: - /* Maximum number of individual operations that can be enlisted. */ - static constexpr int ms_maxOpCnt = 5; - - /* A mutable op resolver instance. */ - tflite::MicroMutableOpResolver m_opResolver; - }; - -} /* namespace app */ -} /* namespace arm */ - -#endif /* ASR_WAV2LETTER_MODEL_HPP */ diff --git a/source/application/api/use_case/asr/include/Wav2LetterPostprocess.hpp b/source/application/api/use_case/asr/include/Wav2LetterPostprocess.hpp index 547a81fd74d44be8a829f34460e52c1e6c50488f..208cb2eb6ac56d2e87bb02fcb6e52e8a2d3360cd 100644 --- a/source/application/api/use_case/asr/include/Wav2LetterPostprocess.hpp +++ b/source/application/api/use_case/asr/include/Wav2LetterPostprocess.hpp @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021-2022, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -17,11 +17,11 @@ #ifndef ASR_WAV2LETTER_POSTPROCESS_HPP #define ASR_WAV2LETTER_POSTPROCESS_HPP -#include "TensorFlowLiteMicro.hpp" /* TensorFlow headers. */ -#include "BaseProcessing.hpp" -#include "Model.hpp" #include "AsrClassifier.hpp" #include "AsrResult.hpp" +#include "BaseProcessing.hpp" +#include "Model.hpp" +#include "Tensor.hpp" #include "log_macros.h" namespace arm { @@ -37,18 +37,23 @@ namespace app { /** * @brief Constructor - * @param[in] outputTensor Pointer to the TFLite Micro output Tensor. + * @param[in] model Model object reference. * @param[in] classifier Object used to get top N results from classification. - * @param[in] labels Vector of string labels to identify each output of the model. - * @param[in/out] result Vector of classification results to store decoded outputs. - * @param[in] outputContextLen Left/right context length for output tensor. + * @param[in] labels Vector of string labels to identify each output of + *the model. + * @param[in/out] results Vector of classification results to store decoded + *outputs. + * @param[in] inputContextLen Left/right context length for input tensor. * @param[in] blankTokenIdx Index in the labels that the "Blank token" takes. - * @param[in] reductionAxis The axis that the logits of each time step is on. + * @param[in] reductionAxisIdx The axis that the logits of each time step is on. **/ - AsrPostProcess(TfLiteTensor* outputTensor, AsrClassifier& classifier, - const std::vector& labels, asr::ResultVec& result, - uint32_t outputContextLen, - uint32_t blankTokenIdx, uint32_t reductionAxis); + AsrPostProcess(const fwk::iface::Model& model, + AsrClassifier& classifier, + const std::vector& labels, + asr::ResultVec& results, + uint32_t inputContextLen, + uint32_t blankTokenIdx, + uint32_t reductionAxisIdx); /** * @brief Should perform post-processing of the result of inference then @@ -57,20 +62,19 @@ namespace app { **/ bool DoPostProcess() override; - /** @brief Gets the output inner length for post-processing. */ - static uint32_t GetOutputInnerLen(const TfLiteTensor*, uint32_t outputCtxLen); - /** @brief Gets the output context length (left/right) for post-processing. */ - static uint32_t GetOutputContextLen(const Model& model, uint32_t inputCtxLen); + uint32_t GetOutputContextLen() const; /** @brief Gets the number of feature vectors to be computed. */ - static uint32_t GetNumFeatureVectors(const Model& model); + uint32_t GetNumFeatureVectors() const; private: AsrClassifier& m_classifier; /* ASR Classifier object. */ - TfLiteTensor* m_outputTensor; /* Model output tensor. */ + const fwk::iface::Model& m_model; /* Model reference */ + std::shared_ptr m_outputTensor; /* Model output tensor. */ const std::vector& m_labels; /* ASR Labels. */ asr::ResultVec & m_results; /* Results vector for a single inference. */ + uint32_t m_inputContextLen; /* length of left/right contexts for input */ uint32_t m_outputContextLen; /* lengths of left/right contexts for output. */ uint32_t m_outputInnerLen; /* Length of output inner context. */ uint32_t m_totalLen; /* Total length of the required axis. */ @@ -78,21 +82,17 @@ namespace app { uint32_t m_blankTokenIdx; /* Index of the labels blank token. */ uint32_t m_reductionAxisIdx; /* Axis containing output logits for a single step. */ + /** @brief Gets the output inner length for post-processing. */ + uint32_t GetOutputInnerLen() const; + /** * @brief Checks if the tensor and axis index are valid * inputs to the object - based on how it has been initialised. * @return true if valid, false otherwise. */ - bool IsInputValid(TfLiteTensor* tensor, + bool IsInputValid(const std::shared_ptr tensor, uint32_t axisIdx) const; - /** - * @brief Gets the tensor data element size in bytes based - * on the tensor type. - * @return Size in bytes, 0 if not supported. - */ - static uint32_t GetTensorElementSize(TfLiteTensor* tensor); - /** * @brief Erases sections from the data assuming row-wise * arrangement along the context axis. @@ -106,4 +106,4 @@ namespace app { } /* namespace app */ } /* namespace arm */ -#endif /* ASR_WAV2LETTER_POSTPROCESS_HPP */ \ No newline at end of file +#endif /* ASR_WAV2LETTER_POSTPROCESS_HPP */ diff --git a/source/application/api/use_case/asr/include/Wav2LetterPreprocess.hpp b/source/application/api/use_case/asr/include/Wav2LetterPreprocess.hpp index 2f29fb074d629ad4caa2ce2030c3620dd9ed50fd..3e966d1fcdb8847f2ea3430c2ae487d4429b61df 100644 --- a/source/application/api/use_case/asr/include/Wav2LetterPreprocess.hpp +++ b/source/application/api/use_case/asr/include/Wav2LetterPreprocess.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2023 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021-2023, 2025 Arm Limited and/or its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,12 +17,13 @@ #ifndef ASR_WAV2LETTER_PREPROCESS_HPP #define ASR_WAV2LETTER_PREPROCESS_HPP -#include "TensorFlowLiteMicro.hpp" -#include "Wav2LetterMfcc.hpp" #include "AudioUtils.hpp" -#include "DataStructures.hpp" #include "BaseProcessing.hpp" +#include "DataStructures.hpp" +#include "Tensor.hpp" +#include "Wav2LetterMfcc.hpp" #include "log_macros.h" +#include namespace arm { namespace app { @@ -35,18 +36,18 @@ namespace app { public: /** * @brief Constructor. - * @param[in] inputTensor Pointer to the TFLite Micro input Tensor. + * @param[in] inputTensor Shared pointer representing a tensor interface object. * @param[in] numMfccFeatures Number of MFCC features per window. * @param[in] numFeatureFrames Number of MFCC vectors that need to be calculated * for an inference. * @param[in] mfccWindowLen Number of audio elements to calculate MFCC features per window. * @param[in] mfccWindowStride Stride (in number of elements) for moving the MFCC window. */ - AsrPreProcess(TfLiteTensor* inputTensor, - uint32_t numMfccFeatures, - uint32_t numFeatureFrames, - uint32_t mfccWindowLen, - uint32_t mfccWindowStride); + AsrPreProcess(const std::shared_ptr inputTensor, + uint32_t numMfccFeatures, + uint32_t numFeatureFrames, + uint32_t mfccWindowLen, + uint32_t mfccWindowStride); /** * @brief Calculates the features required from audio data. This @@ -161,7 +162,7 @@ namespace app { private: audio::Wav2LetterMFCC m_mfcc; /* MFCC instance. */ - TfLiteTensor* m_inputTensor; /* Model input tensor. */ + std::shared_ptr m_inputTensor; /* Model input tensor. */ /* Actual buffers to be populated. */ Array2d m_mfccBuf; /* Contiguous buffer 1D: MFCC */ @@ -179,4 +180,4 @@ namespace app { } /* namespace app */ } /* namespace arm */ -#endif /* ASR_WAV2LETTER_PREPROCESS_HPP */ \ No newline at end of file +#endif /* ASR_WAV2LETTER_PREPROCESS_HPP */ diff --git a/source/application/api/use_case/asr/src/AsrClassifier.cc b/source/application/api/use_case/asr/src/AsrClassifier.cc index 3c9c32ea816d01042df461a976568e6d4063355e..bba14053f34382c7ca84b433a9113217366bc55b 100644 --- a/source/application/api/use_case/asr/src/AsrClassifier.cc +++ b/source/application/api/use_case/asr/src/AsrClassifier.cc @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021,2023 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021,2023, 2025 Arm Limited and/or + * its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,20 +17,29 @@ */ #include "AsrClassifier.hpp" +#include "Tensor.hpp" #include "log_macros.h" -#include "TensorFlowLiteMicro.hpp" -#include "Wav2LetterModel.hpp" namespace arm { namespace app { - template - bool AsrClassifier::GetTopResults(TfLiteTensor* tensor, + AsrClassifier::AsrClassifier(uint32_t inputRowsIdx, + uint32_t inputColsIdx, + uint32_t outputRowsIdx, + uint32_t outputColsIdx) : + m_inputTensorRowsIdx(inputRowsIdx), m_inputTensorColsIdx(inputColsIdx), + m_outputTensorRowsIdx(outputRowsIdx), m_outputTensorColsIdx(outputColsIdx) + {} + + template + bool AsrClassifier::GetTopResults(const std::shared_ptr tensor, std::vector& vecResults, - const std::vector & labels, double scale, double zeroPoint) + const std::vector& labels, + double scale, + double zeroPoint) { - const uint32_t nElems = tensor->dims->data[Wav2LetterModel::ms_outputRowsIdx]; - const uint32_t nLetters = tensor->dims->data[Wav2LetterModel::ms_outputColsIdx]; + const uint32_t nElems = tensor->Shape()[this->m_outputTensorRowsIdx]; + const uint32_t nLetters = tensor->Shape()[this->m_outputTensorColsIdx]; if (nLetters != labels.size()) { printf("Output size doesn't match the labels' size\n"); @@ -45,7 +55,7 @@ namespace app { /* Final results' container. */ vecResults = std::vector(nElems); - T* tensorData = tflite::GetTensorData(tensor); + T* tensorData = tensor->GetData(); /* Get the top 1 results. */ for (uint32_t i = 0, row = 0; i < nElems; ++i, row+=nLetters) { @@ -66,40 +76,47 @@ namespace app { return true; } - template bool AsrClassifier::GetTopResults(TfLiteTensor* tensor, - std::vector& vecResults, - const std::vector & labels, - double scale, double zeroPoint); - template bool AsrClassifier::GetTopResults(TfLiteTensor* tensor, - std::vector& vecResults, - const std::vector & labels, - double scale, double zeroPoint); + template bool + AsrClassifier::GetTopResults(const std::shared_ptr tensor, + std::vector& vecResults, + const std::vector& labels, + double scale, + double zeroPoint); + template bool + AsrClassifier::GetTopResults(const std::shared_ptr tensor, + std::vector& vecResults, + const std::vector& labels, + double scale, + double zeroPoint); bool AsrClassifier::GetClassificationResults( - TfLiteTensor* outputTensor, - std::vector& vecResults, - const std::vector & labels, uint32_t topNCount, bool use_softmax) + const std::shared_ptr outputTensor, + std::vector& vecResults, + const std::vector& labels, + uint32_t topNCount, + bool use_softmax) { UNUSED(use_softmax); vecResults.clear(); - constexpr int minTensorDims = static_cast( - (Wav2LetterModel::ms_outputRowsIdx > Wav2LetterModel::ms_outputColsIdx)? - Wav2LetterModel::ms_outputRowsIdx : Wav2LetterModel::ms_outputColsIdx); + const auto minTensorDims = (this->m_outputTensorRowsIdx > this->m_outputTensorColsIdx) + ? this->m_outputTensorRowsIdx + : this->m_outputTensorColsIdx; - constexpr uint32_t outColsIdx = Wav2LetterModel::ms_outputColsIdx; + const uint32_t outColsIdx = this->m_outputTensorColsIdx; + auto outputShape = outputTensor->Shape(); /* Health checks. */ if (outputTensor == nullptr) { printf_err("Output vector is null pointer.\n"); return false; - } else if (outputTensor->dims->size < minTensorDims) { + } else if (outputShape.size() < minTensorDims) { printf_err("Output tensor expected to be %dD\n", minTensorDims); return false; - } else if (static_cast(outputTensor->dims->data[outColsIdx]) < topNCount) { + } else if (outputShape[outColsIdx] < topNCount) { printf_err("Output vectors are smaller than %" PRIu32 "\n", topNCount); return false; - } else if (static_cast(outputTensor->dims->data[outColsIdx]) != labels.size()) { + } else if (outputShape[outColsIdx] != labels.size()) { printf("Output size doesn't match the labels' size\n"); return false; } @@ -109,27 +126,23 @@ namespace app { } /* To return the floating point values, we need quantization parameters. */ - QuantParams quantParams = GetTensorQuantParams(outputTensor); + auto quantParams = outputTensor->GetQuantParams(); bool resultState; - switch (outputTensor->type) { - case kTfLiteUInt8: - resultState = this->GetTopResults( - outputTensor, vecResults, - labels, quantParams.scale, - quantParams.offset); - break; - case kTfLiteInt8: - resultState = this->GetTopResults( - outputTensor, vecResults, - labels, quantParams.scale, - quantParams.offset); - break; - default: - printf_err("Tensor type %s not supported by classifier\n", - TfLiteTypeGetName(outputTensor->type)); - return false; + switch (outputTensor->Type()) { + case fwk::iface::TensorType::UINT8: + resultState = this->GetTopResults( + outputTensor, vecResults, labels, quantParams.scale, quantParams.offset); + break; + case fwk::iface::TensorType::INT8: + resultState = this->GetTopResults( + outputTensor, vecResults, labels, quantParams.scale, quantParams.offset); + break; + default: + printf_err("Tensor type %s not supported by classifier\n", + fwk::iface::GetTensorDataTypeName(outputTensor->Type())); + return false; } if (!resultState) { @@ -141,4 +154,4 @@ namespace app { } } /* namespace app */ -} /* namespace arm */ \ No newline at end of file +} /* namespace arm */ diff --git a/source/application/api/use_case/asr/src/Wav2LetterPostprocess.cc b/source/application/api/use_case/asr/src/Wav2LetterPostprocess.cc index c2cc9fd1f393fc42cb612b24f5996d47767348c1..bd217447572c8d01307af6adb2cd24288df3857a 100644 --- a/source/application/api/use_case/asr/src/Wav2LetterPostprocess.cc +++ b/source/application/api/use_case/asr/src/Wav2LetterPostprocess.cc @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2023 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021-2023, 2025 Arm Limited and/or + * its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -15,30 +16,28 @@ * limitations under the License. */ #include "Wav2LetterPostprocess.hpp" - -#include "Wav2LetterModel.hpp" #include "log_macros.h" #include +#include namespace arm { namespace app { - AsrPostProcess::AsrPostProcess(TfLiteTensor* outputTensor, AsrClassifier& classifier, - const std::vector& labels, std::vector& results, - const uint32_t outputContextLen, - const uint32_t blankTokenIdx, const uint32_t reductionAxisIdx - ): - m_classifier(classifier), - m_outputTensor(outputTensor), - m_labels{labels}, - m_results(results), - m_outputContextLen(outputContextLen), - m_countIterations(0), - m_blankTokenIdx(blankTokenIdx), - m_reductionAxisIdx(reductionAxisIdx) + AsrPostProcess::AsrPostProcess(const fwk::iface::Model& model, + AsrClassifier& classifier, + const std::vector& labels, + std::vector& results, + const uint32_t inputContextLen, + const uint32_t blankTokenIdx, + const uint32_t reductionAxisIdx) : + m_classifier(classifier), m_model(model), m_labels{labels}, m_results(results), + m_inputContextLen(inputContextLen), m_countIterations(0), m_blankTokenIdx(blankTokenIdx), + m_reductionAxisIdx(reductionAxisIdx) { - this->m_outputInnerLen = AsrPostProcess::GetOutputInnerLen(this->m_outputTensor, this->m_outputContextLen); + this->m_outputTensor = model.GetOutputTensor(0); + this->m_outputContextLen = this->GetOutputContextLen(); + this->m_outputInnerLen = this->GetOutputInnerLen(); this->m_totalLen = (2 * this->m_outputContextLen + this->m_outputInnerLen); } @@ -50,76 +49,57 @@ namespace app { } /* Irrespective of tensor type, we use unsigned "byte" */ - auto* ptrData = tflite::GetTensorData(this->m_outputTensor); - const uint32_t elemSz = AsrPostProcess::GetTensorElementSize(this->m_outputTensor); + const auto ptrData = this->m_outputTensor->GetData(); + const uint32_t elemSz = fwk::iface::GetTensorDataTypeSize(this->m_outputTensor->Type()); /* Other health checks. */ if (0 == elemSz) { printf_err("Tensor type not supported for post processing\n"); return false; - } else if (elemSz * this->m_totalLen > this->m_outputTensor->bytes) { + } else if (elemSz * this->m_totalLen > this->m_outputTensor->Bytes()) { printf_err("Insufficient number of tensor bytes\n"); return false; } /* Which axis do we need to process? */ - switch (this->m_reductionAxisIdx) { - case Wav2LetterModel::ms_outputRowsIdx: - this->EraseSectionsRowWise( - ptrData, elemSz * this->m_outputTensor->dims->data[Wav2LetterModel::ms_outputColsIdx], - this->m_lastIteration); - break; - default: - printf_err("Unsupported axis index: %" PRIu32 "\n", this->m_reductionAxisIdx); - return false; + if (this->m_reductionAxisIdx == this->m_classifier.m_outputTensorRowsIdx) { + this->EraseSectionsRowWise( + ptrData, + elemSz * this->m_outputTensor->Shape()[this->m_classifier.m_outputTensorColsIdx], + this->m_lastIteration); + } else { + printf_err("Unsupported axis index: %" PRIu32 "\n", this->m_reductionAxisIdx); + return false; } - this->m_classifier.GetClassificationResults(this->m_outputTensor, - this->m_results, this->m_labels, 1); - + this->m_classifier.GetClassificationResults( + this->m_outputTensor, this->m_results, this->m_labels, 1); return true; } - bool AsrPostProcess::IsInputValid(TfLiteTensor* tensor, const uint32_t axisIdx) const + bool AsrPostProcess::IsInputValid(const std::shared_ptr tensor, + const uint32_t axisIdx) const { if (nullptr == tensor) { return false; } - if (static_cast(axisIdx) >= tensor->dims->size) { - printf_err("Invalid axis index: %" PRIu32 "; Max: %d\n", - axisIdx, tensor->dims->size); + const auto shape = tensor->Shape(); + + if (axisIdx >= shape.size()) { + printf_err("Invalid axis index: %" PRIu32 "; Max: %zu\n", axisIdx, shape.size()); return false; } - if (static_cast(this->m_totalLen) != - tensor->dims->data[axisIdx]) { - printf_err("Unexpected tensor dimension for axis %" PRIu32", got %d.\n", - axisIdx, tensor->dims->data[axisIdx]); + if (this->m_totalLen != shape[axisIdx]) { + printf_err("Unexpected tensor dimension for axis %" PRIu32 ", got %zu.\n", + axisIdx, + shape[axisIdx]); return false; } return true; } - uint32_t AsrPostProcess::GetTensorElementSize(TfLiteTensor* tensor) - { - switch(tensor->type) { - case kTfLiteUInt8: - case kTfLiteInt8: - return 1; - case kTfLiteInt16: - return 2; - case kTfLiteInt32: - case kTfLiteFloat32: - return 4; - default: - printf_err("Unsupported tensor type %s\n", - TfLiteTypeGetName(tensor->type)); - } - - return 0; - } - bool AsrPostProcess::EraseSectionsRowWise( uint8_t* ptrData, const uint32_t strideSzBytes, @@ -157,57 +137,61 @@ namespace app { return true; } - uint32_t AsrPostProcess::GetNumFeatureVectors(const Model& model) + uint32_t AsrPostProcess::GetNumFeatureVectors() const { - TfLiteTensor* inputTensor = model.GetInputTensor(0); - const int inputRows = std::max(inputTensor->dims->data[Wav2LetterModel::ms_inputRowsIdx], 0); + const auto inputTensor = this->m_model.GetInputTensor(0); + const size_t inputRows = inputTensor->Shape()[this->m_classifier.m_inputTensorRowsIdx]; if (inputRows == 0) { printf_err("Error getting number of input rows for axis: %" PRIu32 "\n", - Wav2LetterModel::ms_inputRowsIdx); + this->m_classifier.m_inputTensorRowsIdx); } return inputRows; } - uint32_t AsrPostProcess::GetOutputInnerLen(const TfLiteTensor* outputTensor, const uint32_t outputCtxLen) + uint32_t AsrPostProcess::GetOutputInnerLen() const { - const uint32_t outputRows = std::max(outputTensor->dims->data[Wav2LetterModel::ms_outputRowsIdx], 0); + const uint32_t outputRows = + this->m_outputTensor->Shape()[this->m_classifier.m_outputTensorRowsIdx]; if (outputRows == 0) { printf_err("Error getting number of output rows for axis: %" PRIu32 "\n", - Wav2LetterModel::ms_outputRowsIdx); + this->m_classifier.m_outputTensorRowsIdx); } /* Watching for underflow. */ - int innerLen = (outputRows - (2 * outputCtxLen)); + if (this->m_outputContextLen * 2 > outputRows) { + printf_err("Invalid context length.\n"); + return 0; + } - return std::max(innerLen, 0); + return (outputRows - (2 * this->m_outputContextLen)); } - uint32_t AsrPostProcess::GetOutputContextLen(const Model& model, const uint32_t inputCtxLen) + uint32_t AsrPostProcess::GetOutputContextLen() const { - const uint32_t inputRows = AsrPostProcess::GetNumFeatureVectors(model); - const uint32_t inputInnerLen = inputRows - (2 * inputCtxLen); - constexpr uint32_t ms_outputRowsIdx = Wav2LetterModel::ms_outputRowsIdx; + const uint32_t inputRows = this->GetNumFeatureVectors(); + const uint32_t inputInnerLen = inputRows - (2 * this->m_inputContextLen); + const uint32_t outputRowsIdx = this->m_classifier.m_outputTensorRowsIdx; /* Check to make sure that the input tensor supports the above * context and inner lengths. */ - if (inputRows <= 2 * inputCtxLen || inputRows <= inputInnerLen) { + if (inputRows <= 2 * this->m_inputContextLen || inputRows <= inputInnerLen) { printf_err("Input rows not compatible with ctx of %" PRIu32 "\n", - inputCtxLen); + this->m_inputContextLen); return 0; } - TfLiteTensor* outputTensor = model.GetOutputTensor(0); - const uint32_t outputRows = std::max(outputTensor->dims->data[ms_outputRowsIdx], 0); + const auto outputTensor = this->m_model.GetOutputTensor(0); + const uint32_t outputRows = outputTensor->Shape()[outputRowsIdx]; if (outputRows == 0) { printf_err("Error getting number of output rows for axis: %" PRIu32 "\n", - Wav2LetterModel::ms_outputRowsIdx); + this->m_classifier.m_outputTensorRowsIdx); return 0; } const float inOutRowRatio = static_cast(inputRows) / static_cast(outputRows); - return std::round(static_cast(inputCtxLen) / inOutRowRatio); + return std::round(static_cast(this->m_inputContextLen) / inOutRowRatio); } } /* namespace app */ diff --git a/source/application/api/use_case/asr/src/Wav2LetterPreprocess.cc b/source/application/api/use_case/asr/src/Wav2LetterPreprocess.cc index 17948945f54b9feadc938beaf33215f40f6f4b46..e29804bec05b1e8875450aa5e2d000b4b944ee6f 100644 --- a/source/application/api/use_case/asr/src/Wav2LetterPreprocess.cc +++ b/source/application/api/use_case/asr/src/Wav2LetterPreprocess.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2023 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021-2023, 2025 Arm Limited and/or its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,27 +17,26 @@ #include "Wav2LetterPreprocess.hpp" #include "PlatformMath.hpp" -#include "TensorFlowLiteMicro.hpp" +#include "Tensor.hpp" #include #include +#include namespace arm { namespace app { - AsrPreProcess::AsrPreProcess(TfLiteTensor* inputTensor, const uint32_t numMfccFeatures, - const uint32_t numFeatureFrames, const uint32_t mfccWindowLen, - const uint32_t mfccWindowStride - ): - m_mfcc(numMfccFeatures, mfccWindowLen), - m_inputTensor(inputTensor), - m_mfccBuf(numMfccFeatures, numFeatureFrames), - m_delta1Buf(numMfccFeatures, numFeatureFrames), - m_delta2Buf(numMfccFeatures, numFeatureFrames), - m_mfccWindowLen(mfccWindowLen), - m_mfccWindowStride(mfccWindowStride), - m_numMfccFeats(numMfccFeatures), - m_numFeatureFrames(numFeatureFrames) + AsrPreProcess::AsrPreProcess(const std::shared_ptr inputTensor, + const uint32_t numMfccFeatures, + const uint32_t numFeatureFrames, + const uint32_t mfccWindowLen, + const uint32_t mfccWindowStride) : + m_mfcc(numMfccFeatures, mfccWindowLen), m_inputTensor(inputTensor), + m_mfccBuf(numMfccFeatures, numFeatureFrames), + m_delta1Buf(numMfccFeatures, numFeatureFrames), + m_delta2Buf(numMfccFeatures, numFeatureFrames), m_mfccWindowLen(mfccWindowLen), + m_mfccWindowStride(mfccWindowStride), m_numMfccFeats(numMfccFeatures), + m_numFeatureFrames(numFeatureFrames) { if (numMfccFeatures > 0 && mfccWindowLen > 0) { this->m_mfcc.Init(); @@ -88,25 +87,27 @@ namespace app { this->Standarize(); /* Quantise. */ - QuantParams quantParams = GetTensorQuantParams(this->m_inputTensor); + auto quantParams = this->m_inputTensor->GetQuantParams(); if (0 == quantParams.scale) { printf_err("Quantisation scale can't be 0\n"); return false; } - switch(this->m_inputTensor->type) { - case kTfLiteUInt8: - return this->Quantise( - tflite::GetTensorData(this->m_inputTensor), this->m_inputTensor->bytes, - quantParams.scale, quantParams.offset); - case kTfLiteInt8: - return this->Quantise( - tflite::GetTensorData(this->m_inputTensor), this->m_inputTensor->bytes, - quantParams.scale, quantParams.offset); - default: - printf_err("Unsupported tensor type %s\n", - TfLiteTypeGetName(this->m_inputTensor->type)); + switch (this->m_inputTensor->Type()) { + case fwk::iface::TensorType::UINT8: + return this->Quantise(this->m_inputTensor->GetData(), + this->m_inputTensor->Bytes(), + quantParams.scale, + quantParams.offset); + case fwk::iface::TensorType::INT8: + return this->Quantise(this->m_inputTensor->GetData(), + this->m_inputTensor->Bytes(), + quantParams.scale, + quantParams.offset); + default: + printf_err("Unsupported tensor type %s\n", + fwk::iface::GetTensorDataTypeName(this->m_inputTensor->Type())); } return false; @@ -205,4 +206,4 @@ namespace app { } } /* namespace app */ -} /* namespace arm */ \ No newline at end of file +} /* namespace arm */ diff --git a/source/application/api/use_case/img_class/CMakeLists.txt b/source/application/api/use_case/img_class/CMakeLists.txt index 7fd1092cb081f99879b6ea77fe954840672a9071..c76b774717ff136cf65adf74ba2e5a94a06de26f 100644 --- a/source/application/api/use_case/img_class/CMakeLists.txt +++ b/source/application/api/use_case/img_class/CMakeLists.txt @@ -1,5 +1,6 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates +# SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or +# its affiliates # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,8 +27,7 @@ project(${IMG_CLASS_API_TARGET} # Create static library add_library(${IMG_CLASS_API_TARGET} STATIC - src/ImgClassProcessing.cc - src/MobileNetModel.cc) + src/ImgClassProcessing.cc) target_include_directories(${IMG_CLASS_API_TARGET} PUBLIC include) diff --git a/source/application/api/use_case/img_class/include/ImgClassProcessing.hpp b/source/application/api/use_case/img_class/include/ImgClassProcessing.hpp index 8c6dbb59cece3820cc9d2ad65047fdb233499621..28d855acaa4be8772152be911217a1ba4010f98d 100644 --- a/source/application/api/use_case/img_class/include/ImgClassProcessing.hpp +++ b/source/application/api/use_case/img_class/include/ImgClassProcessing.hpp @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -19,6 +19,7 @@ #include "BaseProcessing.hpp" #include "Classifier.hpp" +#include namespace arm { namespace app { @@ -33,14 +34,13 @@ namespace app { public: /** * @brief Constructor - * @param[in] inputTensor Pointer to the TFLite Micro input Tensor. - * @param[in] convertToInt8 Should the image be converted to Int8 range. + * @param[in] inputTensor Shared pointer representing a tensor interface object. **/ - explicit ImgClassPreProcess(TfLiteTensor* inputTensor, bool convertToInt8); + explicit ImgClassPreProcess(const std::shared_ptr inputTensor); /** * @brief Should perform pre-processing of 'raw' input image data and load it into - * TFLite Micro input tensors ready for inference + * input tensors ready for inference * @param[in] input Pointer to the data that pre-processing will work on. * @param[in] inputSize Size of the input data. * @return true if successful, false otherwise. @@ -48,7 +48,7 @@ namespace app { bool DoPreProcess(const void* input, size_t inputSize) override; private: - TfLiteTensor* m_inputTensor; + std::shared_ptr m_inputTensor; bool m_convertToInt8; }; @@ -62,12 +62,13 @@ namespace app { public: /** * @brief Constructor - * @param[in] outputTensor Pointer to the TFLite Micro output Tensor. + * @param[in] outputTensor Shared pointer representing a tensor interface object * @param[in] classifier Classifier object used to get top N results from classification. * @param[in] labels Vector of string labels to identify each output of the model. * @param[in] results Vector of classification results to store decoded outputs. **/ - ImgClassPostProcess(TfLiteTensor* outputTensor, Classifier& classifier, + ImgClassPostProcess(const std::shared_ptr outputTensor, + Classifier& classifier, const std::vector& labels, std::vector& results); @@ -79,7 +80,7 @@ namespace app { bool DoPostProcess() override; private: - TfLiteTensor* m_outputTensor; + std::shared_ptr m_outputTensor{nullptr}; Classifier& m_imgClassifier; const std::vector& m_labels; std::vector& m_results; @@ -88,4 +89,4 @@ namespace app { } /* namespace app */ } /* namespace arm */ -#endif /* IMG_CLASS_PROCESSING_HPP */ \ No newline at end of file +#endif /* IMG_CLASS_PROCESSING_HPP */ diff --git a/source/application/api/use_case/img_class/include/MobileNetModel.hpp b/source/application/api/use_case/img_class/include/MobileNetModel.hpp deleted file mode 100644 index 525151ab2936259cca798f125245abaf7b5a3f0e..0000000000000000000000000000000000000000 --- a/source/application/api/use_case/img_class/include/MobileNetModel.hpp +++ /dev/null @@ -1,51 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 - * - * 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. - */ -#ifndef IMG_CLASS_MOBILENETMODEL_HPP -#define IMG_CLASS_MOBILENETMODEL_HPP - -#include "Model.hpp" - -namespace arm { -namespace app { - - class MobileNetModel : public Model { - - public: - /* Indices for the expected model - based on input tensor shape */ - static constexpr uint32_t ms_inputRowsIdx = 1; - static constexpr uint32_t ms_inputColsIdx = 2; - static constexpr uint32_t ms_inputChannelsIdx = 3; - - protected: - /** @brief Gets the reference to op resolver interface class. */ - const tflite::MicroOpResolver& GetOpResolver() override; - - /** @brief Adds operations to the op resolver instance. */ - bool EnlistOperations() override; - - private: - /* Maximum number of individual operations that can be enlisted. */ - static constexpr int ms_maxOpCnt = 7; - - /* A mutable op resolver instance. */ - tflite::MicroMutableOpResolver m_opResolver; - }; - -} /* namespace app */ -} /* namespace arm */ - -#endif /* IMG_CLASS_MOBILENETMODEL_HPP */ diff --git a/source/application/api/use_case/img_class/src/ImgClassProcessing.cc b/source/application/api/use_case/img_class/src/ImgClassProcessing.cc index 0724320bfffe86411a9e24d9f877dc38b6f8e66c..9fc334cc7f406c78dba247458dd6e8b511fdeed9 100644 --- a/source/application/api/use_case/img_class/src/ImgClassProcessing.cc +++ b/source/application/api/use_case/img_class/src/ImgClassProcessing.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,13 +18,14 @@ #include "ImageUtils.hpp" #include "log_macros.h" +#include +#include namespace arm { namespace app { - ImgClassPreProcess::ImgClassPreProcess(TfLiteTensor* inputTensor, bool convertToInt8) - :m_inputTensor{inputTensor}, - m_convertToInt8{convertToInt8} + ImgClassPreProcess::ImgClassPreProcess( + const std::shared_ptr inputTensor) : m_inputTensor{inputTensor} {} bool ImgClassPreProcess::DoPreProcess(const void* data, size_t inputSize) @@ -34,25 +35,42 @@ namespace app { return false; } - auto input = static_cast(data); + auto src = static_cast(data); - std::memcpy(this->m_inputTensor->data.data, input, inputSize); - debug("Input tensor populated \n"); - - if (this->m_convertToInt8) { - image::ConvertImgToInt8(this->m_inputTensor->data.data, this->m_inputTensor->bytes); + switch (this->m_inputTensor->Type()) { + case fwk::iface::TensorType::INT8: + assert(inputSize == this->m_inputTensor->Bytes()); + image::ConvertUint8ToInt8(this->m_inputTensor->GetData(), + src, + this->m_inputTensor->GetNumElements(), + this->m_inputTensor->Layout()); + break; + case fwk::iface::TensorType::UINT8: + assert(inputSize == this->m_inputTensor->Bytes()); + std::memcpy(this->m_inputTensor->GetData(), src, inputSize); + break; + case fwk::iface::TensorType::FP32: + assert(inputSize * sizeof(float) == this->m_inputTensor->Bytes()); + image::ConvertUint8ToFp32(this->m_inputTensor->GetData(), + src, + this->m_inputTensor->GetNumElements(), + this->m_inputTensor->Layout()); + break; + default: + return false; } + debug("Input tensor populated \n"); return true; } - ImgClassPostProcess::ImgClassPostProcess(TfLiteTensor* outputTensor, Classifier& classifier, - const std::vector& labels, - std::vector& results) - :m_outputTensor{outputTensor}, - m_imgClassifier{classifier}, - m_labels{labels}, - m_results{results} + ImgClassPostProcess::ImgClassPostProcess( + const std::shared_ptr outputTensor, + Classifier& classifier, + const std::vector& labels, + std::vector& results) : + m_outputTensor{outputTensor}, m_imgClassifier{classifier}, m_labels{labels}, + m_results{results} {} bool ImgClassPostProcess::DoPostProcess() @@ -63,4 +81,4 @@ namespace app { } } /* namespace app */ -} /* namespace arm */ \ No newline at end of file +} /* namespace arm */ diff --git a/source/application/api/use_case/inference_runner/CMakeLists.txt b/source/application/api/use_case/inference_runner/CMakeLists.txt index e4754e672beb5cb74ac8ef31a02b5515a112088a..dcb876dc8f1953f0dad91636fc5e7e3bce8bd42d 100644 --- a/source/application/api/use_case/inference_runner/CMakeLists.txt +++ b/source/application/api/use_case/inference_runner/CMakeLists.txt @@ -1,5 +1,6 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2022-2023 Arm Limited and/or its affiliates +# SPDX-FileCopyrightText: Copyright 2022-2023, 2025 Arm Limited and/or +# its affiliates # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -24,14 +25,10 @@ project(${INFERENCE_RUNNER_API_TARGET} DESCRIPTION "Inference runner use case API library" LANGUAGES C CXX) -# Create static library -add_library(${INFERENCE_RUNNER_API_TARGET} STATIC src/TestModel.cc src/MicroMutableAllOpsResolver.cc) - -target_include_directories(${INFERENCE_RUNNER_API_TARGET} PUBLIC include) - -target_link_libraries(${INFERENCE_RUNNER_API_TARGET} PUBLIC common_api) +# Create an aliased library, inference runner doesn't need any API other than this. +add_library(${INFERENCE_RUNNER_API_TARGET} ALIAS ml-framework-iface) message(STATUS "*******************************************************") -message(STATUS "Library : " ${INFERENCE_RUNNER_API_TARGET}) -message(STATUS "CMAKE_SYSTEM_PROCESSOR : " ${CMAKE_SYSTEM_PROCESSOR}) +message(STATUS "Library: " ${INFERENCE_RUNNER_API_TARGET}) +message(STATUS "CMAKE_SYSTEM_PROCESSOR: " ${CMAKE_SYSTEM_PROCESSOR}) message(STATUS "*******************************************************") diff --git a/source/application/api/use_case/inference_runner/include/TestModel.hpp b/source/application/api/use_case/inference_runner/include/TestModel.hpp deleted file mode 100644 index 4fbbfc0110f2bd12ca9fb30848c88ceb59cf8840..0000000000000000000000000000000000000000 --- a/source/application/api/use_case/inference_runner/include/TestModel.hpp +++ /dev/null @@ -1,43 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright 2021, 2023 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 - * - * 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. - */ -#ifndef INF_RUNNER_TESTMODEL_HPP -#define INF_RUNNER_TESTMODEL_HPP - -#include "Model.hpp" -#include "MicroMutableAllOpsResolver.hpp" - -namespace arm { -namespace app { - - class TestModel : public Model { - - protected: - /** @brief Gets the reference to op resolver interface class. */ - const tflite::MicroOpResolver& GetOpResolver() override; - - /** @brief Adds operations to the op resolver instance. */ - bool EnlistOperations() override; - - private: - /* A mutable op resolver instance including every operation for Inference runner. */ - tflite::MicroMutableOpResolver m_opResolver; - }; - -} /* namespace app */ -} /* namespace arm */ - -#endif /* INF_RUNNER_TESTMODEL_HPP */ diff --git a/source/application/api/use_case/inference_runner/src/MicroMutableAllOpsResolver.cc b/source/application/api/use_case/inference_runner/src/MicroMutableAllOpsResolver.cc deleted file mode 100644 index 72e5faf4f5bef5b540ab20f57f617b1d9caa6466..0000000000000000000000000000000000000000 --- a/source/application/api/use_case/inference_runner/src/MicroMutableAllOpsResolver.cc +++ /dev/null @@ -1,130 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright 2023, 2024 Arm Limited and/or - * its affiliates - * SPDX-License-Identifier: Apache-2.0 - * - * 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. - */ - -#include "MicroMutableAllOpsResolver.hpp" - -namespace arm { -namespace app { - /* Create our own AllOpsResolver by adding all Ops to MicroMutableOpResolver. */ - tflite::MicroMutableOpResolver CreateAllOpsResolver() - { - tflite::MicroMutableOpResolver mutableAllOpResolver; - - mutableAllOpResolver.AddAbs(); - mutableAllOpResolver.AddAdd(); - mutableAllOpResolver.AddAddN(); - mutableAllOpResolver.AddArgMax(); - mutableAllOpResolver.AddArgMin(); - mutableAllOpResolver.AddAssignVariable(); - mutableAllOpResolver.AddAveragePool2D(); - mutableAllOpResolver.AddBatchMatMul(); - mutableAllOpResolver.AddBatchToSpaceNd(); - mutableAllOpResolver.AddBroadcastArgs(); - mutableAllOpResolver.AddBroadcastTo(); - mutableAllOpResolver.AddCallOnce(); - mutableAllOpResolver.AddCast(); - mutableAllOpResolver.AddCeil(); - mutableAllOpResolver.AddCircularBuffer(); - mutableAllOpResolver.AddConcatenation(); - mutableAllOpResolver.AddConv2D(); - mutableAllOpResolver.AddCos(); - mutableAllOpResolver.AddCumSum(); - mutableAllOpResolver.AddDepthToSpace(); - mutableAllOpResolver.AddDepthwiseConv2D(); - mutableAllOpResolver.AddDequantize(); - mutableAllOpResolver.AddDetectionPostprocess(); - mutableAllOpResolver.AddDiv(); - mutableAllOpResolver.AddElu(); - mutableAllOpResolver.AddEqual(); - mutableAllOpResolver.AddEthosU(); - mutableAllOpResolver.AddExp(); - mutableAllOpResolver.AddExpandDims(); - mutableAllOpResolver.AddFill(); - mutableAllOpResolver.AddFloor(); - mutableAllOpResolver.AddFloorDiv(); - mutableAllOpResolver.AddFloorMod(); - mutableAllOpResolver.AddFullyConnected(); - mutableAllOpResolver.AddGather(); - mutableAllOpResolver.AddGatherNd(); - mutableAllOpResolver.AddGreater(); - mutableAllOpResolver.AddGreaterEqual(); - mutableAllOpResolver.AddHardSwish(); - mutableAllOpResolver.AddIf(); - mutableAllOpResolver.AddL2Normalization(); - mutableAllOpResolver.AddL2Pool2D(); - mutableAllOpResolver.AddLeakyRelu(); - mutableAllOpResolver.AddLess(); - mutableAllOpResolver.AddLessEqual(); - mutableAllOpResolver.AddLog(); - mutableAllOpResolver.AddLogicalAnd(); - mutableAllOpResolver.AddLogicalNot(); - mutableAllOpResolver.AddLogicalOr(); - mutableAllOpResolver.AddLogistic(); - mutableAllOpResolver.AddLogSoftmax(); - mutableAllOpResolver.AddMaxPool2D(); - mutableAllOpResolver.AddMaximum(); - mutableAllOpResolver.AddMean(); - mutableAllOpResolver.AddMinimum(); - mutableAllOpResolver.AddMirrorPad(); - mutableAllOpResolver.AddMul(); - mutableAllOpResolver.AddNeg(); - mutableAllOpResolver.AddNotEqual(); - mutableAllOpResolver.AddPack(); - mutableAllOpResolver.AddPad(); - mutableAllOpResolver.AddPadV2(); - mutableAllOpResolver.AddPrelu(); - mutableAllOpResolver.AddQuantize(); - mutableAllOpResolver.AddReadVariable(); - mutableAllOpResolver.AddReduceMax(); - mutableAllOpResolver.AddRelu(); - mutableAllOpResolver.AddRelu6(); - mutableAllOpResolver.AddReshape(); - mutableAllOpResolver.AddResizeBilinear(); - mutableAllOpResolver.AddResizeNearestNeighbor(); - mutableAllOpResolver.AddRound(); - mutableAllOpResolver.AddRsqrt(); - mutableAllOpResolver.AddSelectV2(); - mutableAllOpResolver.AddShape(); - mutableAllOpResolver.AddSin(); - mutableAllOpResolver.AddSlice(); - mutableAllOpResolver.AddSoftmax(); - mutableAllOpResolver.AddSpaceToBatchNd(); - mutableAllOpResolver.AddSpaceToDepth(); - mutableAllOpResolver.AddSplit(); - mutableAllOpResolver.AddSplitV(); - mutableAllOpResolver.AddSqrt(); - mutableAllOpResolver.AddSquare(); - mutableAllOpResolver.AddSquaredDifference(); - mutableAllOpResolver.AddSqueeze(); - mutableAllOpResolver.AddStridedSlice(); - mutableAllOpResolver.AddSub(); - mutableAllOpResolver.AddSum(); - mutableAllOpResolver.AddSvdf(); - mutableAllOpResolver.AddTanh(); - mutableAllOpResolver.AddTranspose(); - mutableAllOpResolver.AddTransposeConv(); - mutableAllOpResolver.AddUnidirectionalSequenceLSTM(); - mutableAllOpResolver.AddUnpack(); - mutableAllOpResolver.AddVarHandle(); - mutableAllOpResolver.AddWhile(); - mutableAllOpResolver.AddZerosLike(); - return mutableAllOpResolver; - } - -} /* namespace app */ -} /* namespace arm */ diff --git a/source/application/api/use_case/kws/CMakeLists.txt b/source/application/api/use_case/kws/CMakeLists.txt index dd627950d688c1451ae2788dbf3cb42ddb513213..1f2450d536bedf187d4fa2d42f56e6b281c3f9cb 100644 --- a/source/application/api/use_case/kws/CMakeLists.txt +++ b/source/application/api/use_case/kws/CMakeLists.txt @@ -1,5 +1,6 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates +# SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or +# its affiliates # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,7 +28,6 @@ project(${KWS_API_TARGET} # Create static library add_library(${KWS_API_TARGET} STATIC src/KwsProcessing.cc - src/MicroNetKwsModel.cc src/KwsClassifier.cc) target_include_directories(${KWS_API_TARGET} PUBLIC include) diff --git a/source/application/api/use_case/kws/include/KwsClassifier.hpp b/source/application/api/use_case/kws/include/KwsClassifier.hpp index 7e21d7c9fe3e040ade50741cd7b08b3633f04e6f..86db8b9c8d2dd00d96c9f7289c52e41e33ede09d 100644 --- a/source/application/api/use_case/kws/include/KwsClassifier.hpp +++ b/source/application/api/use_case/kws/include/KwsClassifier.hpp @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -18,8 +18,8 @@ #define KWS_CLASSIFIER_HPP #include "ClassificationResult.hpp" -#include "TensorFlowLiteMicro.hpp" #include "Classifier.hpp" +#include "Tensor.hpp" #include @@ -47,15 +47,18 @@ namespace app { * @return true if successful, false otherwise. **/ using Classifier::GetClassificationResults; /* We are overloading not overriding. */ - bool GetClassificationResults(TfLiteTensor* outputTensor, std::vector& vecResults, - const std::vector & labels, uint32_t topNCount, - bool use_softmax, std::vector>& resultHistory); + bool GetClassificationResults(const std::shared_ptr outputTensor, + std::vector& vecResults, + const std::vector& labels, + uint32_t topNCount, + bool use_softmax, + std::vector>& resultHistory); - /** - * @brief Average the given history of results. - * @param[in] resultHistory The history of results to take on average of. - * @param[out] averageResult The calculated average. - **/ + /** + * @brief Average the given history of results. + * @param[in] resultHistory The history of results to take on average of. + * @param[out] averageResult The calculated average. + **/ static void AveragResults(const std::vector>& resultHistory, std::vector& averageResult); }; diff --git a/source/application/api/use_case/kws/include/KwsProcessing.hpp b/source/application/api/use_case/kws/include/KwsProcessing.hpp index 829bf2baa7d1bfddca5927a91f13faaa3224ea67..611755b17537c3914ced3e79dbecb8296707eeb9 100644 --- a/source/application/api/use_case/kws/include/KwsProcessing.hpp +++ b/source/application/api/use_case/kws/include/KwsProcessing.hpp @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -37,7 +37,7 @@ namespace app { public: /** * @brief Constructor - * @param[in] inputTensor Pointer to the TFLite Micro input Tensor. + * @param[in] inputTensor Shared pointer representing a tensor interface object. * @param[in] numFeatures How many MFCC features to use. * @param[in] numFeatureFrames Number of MFCC vectors that need to be calculated * for an inference. @@ -45,12 +45,15 @@ namespace app { * sliding a window through the audio sample. * @param[in] mfccFrameStride Number of audio samples between consecutive windows. **/ - explicit KwsPreProcess(TfLiteTensor* inputTensor, size_t numFeatures, size_t numFeatureFrames, - int mfccFrameLength, int mfccFrameStride); + explicit KwsPreProcess(const std::shared_ptr inputTensor, + size_t numFeatures, + size_t numFeatureFrames, + int mfccFrameLength, + int mfccFrameStride); /** * @brief Should perform pre-processing of 'raw' input audio data and load it into - * TFLite Micro input tensors ready for inference. + * input tensors ready for inference. * @param[in] input Pointer to the data that pre-processing will work on. * @param[in] inputSize Size of the input data. * @return true if successful, false otherwise. @@ -61,7 +64,7 @@ namespace app { size_t m_audioDataStride; /* Amount of audio to stride across if doing >1 inference in longer clips. */ private: - TfLiteTensor* m_inputTensor; /* Model input tensor. */ + const std::shared_ptr m_inputTensor; /* Model input tensor. */ const int m_mfccFrameLength; const int m_mfccFrameStride; const size_t m_numMfccFrames; /* How many sets of m_numMfccFeats. */ @@ -86,15 +89,16 @@ namespace app { * @param[in] cacheSize Size of the feature vectors cache (number of feature vectors). * @return Function to be called providing audio sample and sliding window index. */ - std::function&, int, bool, size_t)> - GetFeatureCalculator(audio::MicroNetKwsMFCC& mfcc, - TfLiteTensor* inputTensor, - size_t cacheSize); - - template - std::function&, size_t, bool, size_t)> - FeatureCalc(TfLiteTensor* inputTensor, size_t cacheSize, - std::function (std::vector& )> compute); + std::function&, int, bool, size_t)> + GetFeatureCalculator(audio::MicroNetKwsMFCC& mfcc, + std::shared_ptr inputTensor, + size_t cacheSize); + + template + std::function&, size_t, bool, size_t)> + FeatureCalc(const std::shared_ptr inputTensor, + size_t cacheSize, + std::function(std::vector&)> compute); }; /** @@ -105,7 +109,7 @@ namespace app { class KwsPostProcess : public BasePostProcess { private: - TfLiteTensor* m_outputTensor; /* Model output tensor. */ + std::shared_ptr m_outputTensor; /* Model output tensor. */ KwsClassifier& m_kwsClassifier; /* KWS Classifier object. */ const std::vector& m_labels; /* KWS Labels. */ std::vector& m_results; /* Results vector for a single inference. */ @@ -113,14 +117,16 @@ namespace app { public: /** * @brief Constructor - * @param[in] outputTensor Pointer to the TFLite Micro output Tensor. + * @param[in] outputTensor Shared pointer representing a tensor interface object * @param[in] classifier Classifier object used to get top N results from classification. * @param[in] labels Vector of string labels to identify each output of the model. * @param[in/out] results Vector of classification results to store decoded outputs. **/ - KwsPostProcess(TfLiteTensor* outputTensor, KwsClassifier& classifier, + KwsPostProcess(const std::shared_ptr outputTensor, + KwsClassifier& classifier, const std::vector& labels, - std::vector& results, size_t averagingWindowLen = 1); + std::vector& results, + size_t averagingWindowLen = 1); /** * @brief Should perform post-processing of the result of inference then @@ -133,4 +139,4 @@ namespace app { } /* namespace app */ } /* namespace arm */ -#endif /* KWS_PROCESSING_HPP */ \ No newline at end of file +#endif /* KWS_PROCESSING_HPP */ diff --git a/source/application/api/use_case/kws/include/KwsResult.hpp b/source/application/api/use_case/kws/include/KwsResult.hpp index e3f895bea6420d8d6c73a4a848b19d4fa2981ce5..472626f5714750751dd71dcbb89d962565d8795c 100644 --- a/source/application/api/use_case/kws/include/KwsResult.hpp +++ b/source/application/api/use_case/kws/include/KwsResult.hpp @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2022 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021-2022, 2025 Arm Limited and/or + * its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -60,4 +61,4 @@ namespace kws { } /* namespace app */ } /* namespace arm */ -#endif /* KWS_RESULT_HPP */ \ No newline at end of file +#endif /* KWS_RESULT_HPP */ diff --git a/source/application/api/use_case/kws/include/MicroNetKwsModel.hpp b/source/application/api/use_case/kws/include/MicroNetKwsModel.hpp deleted file mode 100644 index a8f3bd92a3b8f338623466bc63caeb97e4ff65b0..0000000000000000000000000000000000000000 --- a/source/application/api/use_case/kws/include/MicroNetKwsModel.hpp +++ /dev/null @@ -1,58 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 - * - * 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. - */ -#ifndef KWS_MICRONETMODEL_HPP -#define KWS_MICRONETMODEL_HPP - -#include "Model.hpp" - -namespace arm { -namespace app { -namespace kws { - extern const int g_FrameLength; - extern const int g_FrameStride; - extern const float g_ScoreThreshold; - extern const uint32_t g_NumMfcc; - extern const uint32_t g_NumAudioWins; -} /* namespace kws */ - - class MicroNetKwsModel : public Model { - public: - /* Indices for the expected model - based on input and output tensor shapes */ - static constexpr uint32_t ms_inputRowsIdx = 1; - static constexpr uint32_t ms_inputColsIdx = 2; - static constexpr uint32_t ms_outputRowsIdx = 2; - static constexpr uint32_t ms_outputColsIdx = 3; - - protected: - /** @brief Gets the reference to op resolver interface class. */ - const tflite::MicroOpResolver& GetOpResolver() override; - - /** @brief Adds operations to the op resolver instance. */ - bool EnlistOperations() override; - - private: - /* Maximum number of individual operations that can be enlisted. */ - static constexpr int ms_maxOpCnt = 7; - - /* A mutable op resolver instance. */ - tflite::MicroMutableOpResolver m_opResolver; - }; - -} /* namespace app */ -} /* namespace arm */ - -#endif /* KWS_MICRONETMODEL_HPP */ diff --git a/source/application/api/use_case/kws/src/KwsClassifier.cc b/source/application/api/use_case/kws/src/KwsClassifier.cc index e498f06674e60712d647d19a07069ba9ea9e6d97..3124bd12531f9111030e0ea344c39b413c7711be 100644 --- a/source/application/api/use_case/kws/src/KwsClassifier.cc +++ b/source/application/api/use_case/kws/src/KwsClassifier.cc @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022-2023 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2022-2023, 2025 Arm Limited and/or + * its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -16,36 +17,32 @@ */ #include "KwsClassifier.hpp" -#include "TensorFlowLiteMicro.hpp" #include "PlatformMath.hpp" +#include "Tensor.hpp" #include "log_macros.h" -#include "../include/KwsClassifier.hpp" - -#include #include -#include -#include -#include #include - +#include +#include namespace arm { namespace app { - bool KwsClassifier::GetClassificationResults(TfLiteTensor* outputTensor, - std::vector& vecResults, const std::vector & labels, - uint32_t topNCount, bool useSoftmax, std::vector>& resultHistory) + bool KwsClassifier::GetClassificationResults( + const std::shared_ptr outputTensor, + std::vector& vecResults, + const std::vector& labels, + uint32_t topNCount, + bool useSoftmax, + std::vector>& resultHistory) { if (outputTensor == nullptr) { printf_err("Output vector is null pointer.\n"); return false; } - uint32_t totalOutputSize = 1; - for (int inputDim = 0; inputDim < outputTensor->dims->size; inputDim++) { - totalOutputSize *= outputTensor->dims->data[inputDim]; - } + const uint32_t totalOutputSize = outputTensor->GetNumElements(); /* Health check */ if (totalOutputSize < topNCount) { @@ -63,7 +60,7 @@ namespace app { vecResults.clear(); /* De-Quantize Output Tensor */ - QuantParams quantParams = GetTensorQuantParams(outputTensor); + auto quantParams = outputTensor->GetQuantParams(); /* Floating point tensor data to be populated * NOTE: The assumption here is that the output tensor size isn't too @@ -72,34 +69,34 @@ namespace app { resultData.resize(totalOutputSize); /* Populate the floating point buffer */ - switch (outputTensor->type) { - case kTfLiteUInt8: { - uint8_t* tensor_buffer = tflite::GetTensorData(outputTensor); - for (size_t i = 0; i < totalOutputSize; ++i) { - resultData[i] = quantParams.scale * - (static_cast(tensor_buffer[i]) - quantParams.offset); - } - break; + switch (outputTensor->Type()) { + case fwk::iface::TensorType::UINT8: { + uint8_t* tensor_buffer = outputTensor->GetData(); + for (size_t i = 0; i < totalOutputSize; ++i) { + resultData[i] = + quantParams.scale * (static_cast(tensor_buffer[i]) - quantParams.offset); } - case kTfLiteInt8: { - int8_t* tensor_buffer = tflite::GetTensorData(outputTensor); - for (size_t i = 0; i < totalOutputSize; ++i) { - resultData[i] = quantParams.scale * - (static_cast(tensor_buffer[i]) - quantParams.offset); - } - break; + break; + } + case fwk::iface::TensorType::INT8: { + int8_t* tensor_buffer = outputTensor->GetData(); + for (size_t i = 0; i < totalOutputSize; ++i) { + resultData[i] = + quantParams.scale * (static_cast(tensor_buffer[i]) - quantParams.offset); } - case kTfLiteFloat32: { - float* tensor_buffer = tflite::GetTensorData(outputTensor); - for (size_t i = 0; i < totalOutputSize; ++i) { - resultData[i] = tensor_buffer[i]; - } - break; + break; + } + case fwk::iface::TensorType::FP32: { + float* tensor_buffer = outputTensor->GetData(); + for (size_t i = 0; i < totalOutputSize; ++i) { + resultData[i] = tensor_buffer[i]; } - default: - printf_err("Tensor type %s not supported by classifier\n", - TfLiteTypeGetName(outputTensor->type)); - return false; + break; + } + default: + printf_err("Tensor type %s not supported by classifier\n", + fwk::iface::GetTensorDataTypeName(outputTensor->Type())); + return false; } if (useSoftmax) { @@ -139,4 +136,4 @@ namespace app { } } /* namespace app */ -} /* namespace arm */ \ No newline at end of file +} /* namespace arm */ diff --git a/source/application/api/use_case/kws/src/KwsProcessing.cc b/source/application/api/use_case/kws/src/KwsProcessing.cc index db0ece86efe47366c215acd6d8b2e7ff88502429..4d90fd5ab0879d6030f670dc9dd72a38e7fe085a 100644 --- a/source/application/api/use_case/kws/src/KwsProcessing.cc +++ b/source/application/api/use_case/kws/src/KwsProcessing.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -16,18 +16,19 @@ */ #include "KwsProcessing.hpp" #include "log_macros.h" -#include "MicroNetKwsModel.hpp" + +#include namespace arm { namespace app { - KwsPreProcess::KwsPreProcess(TfLiteTensor* inputTensor, size_t numFeatures, size_t numMfccFrames, - int mfccFrameLength, int mfccFrameStride - ): - m_inputTensor{inputTensor}, - m_mfccFrameLength{mfccFrameLength}, - m_mfccFrameStride{mfccFrameStride}, - m_numMfccFrames{numMfccFrames}, + KwsPreProcess::KwsPreProcess(const std::shared_ptr inputTensor, + size_t numFeatures, + size_t numMfccFrames, + int mfccFrameLength, + int mfccFrameStride) : + m_inputTensor{inputTensor}, m_mfccFrameLength{mfccFrameLength}, + m_mfccFrameStride{mfccFrameStride}, m_numMfccFrames{numMfccFrames}, m_mfcc{audio::MicroNetKwsMFCC(numFeatures, mfccFrameLength)} { this->m_mfcc.Init(); @@ -109,10 +110,11 @@ namespace app { * @param[in] compute Features calculator function. * @return Lambda function to compute features. */ - template - std::function&, size_t, bool, size_t)> - KwsPreProcess::FeatureCalc(TfLiteTensor* inputTensor, size_t cacheSize, - std::function (std::vector& )> compute) + template + std::function&, size_t, bool, size_t)> + KwsPreProcess::FeatureCalc(const std::shared_ptr inputTensor, + size_t cacheSize, + std::function(std::vector&)> compute) { /* Feature cache to be captured by lambda function. */ static std::vector> featureCache = std::vector>(cacheSize); @@ -120,9 +122,8 @@ namespace app { return [=](std::vector& audioDataWindow, size_t index, bool useCache, - size_t featuresOverlapIndex) - { - T* tensorData = tflite::GetTensorData(inputTensor); + size_t featuresOverlapIndex) { + T* tensorData = inputTensor->GetData(); std::vector features; /* Reuse features from cache if cache is ready and sliding windows overlap. @@ -143,43 +144,40 @@ namespace app { }; } - template std::function&, size_t , bool, size_t)> - KwsPreProcess::FeatureCalc(TfLiteTensor* inputTensor, - size_t cacheSize, - std::function (std::vector&)> compute); - template std::function&, size_t, bool, size_t)> - KwsPreProcess::FeatureCalc(TfLiteTensor* inputTensor, - size_t cacheSize, - std::function(std::vector&)> compute); - + KwsPreProcess::FeatureCalc( + std::shared_ptr inputTensor, + size_t cacheSize, + std::function(std::vector&)> compute); - std::function&, int, bool, size_t)> - KwsPreProcess::GetFeatureCalculator(audio::MicroNetKwsMFCC& mfcc, TfLiteTensor* inputTensor, size_t cacheSize) + template std::function&, size_t, bool, size_t)> + KwsPreProcess::FeatureCalc( + std::shared_ptr inputTensor, + size_t cacheSize, + std::function(std::vector&)> compute); + + std::function&, int, bool, size_t)> + KwsPreProcess::GetFeatureCalculator(audio::MicroNetKwsMFCC& mfcc, + std::shared_ptr inputTensor, + size_t cacheSize) { std::function&, size_t, bool, size_t)> mfccFeatureCalc = nullptr; - TfLiteQuantization quant = inputTensor->quantization; - - if (kTfLiteAffineQuantization == quant.type) { - auto* quantParams = (TfLiteAffineQuantization*) quant.params; - const float quantScale = quantParams->scale->data[0]; - const int quantOffset = quantParams->zero_point->data[0]; - - switch (inputTensor->type) { - case kTfLiteInt8: { - mfccFeatureCalc = this->FeatureCalc(inputTensor, - cacheSize, - [=, &mfcc](std::vector& audioDataWindow) { - return mfcc.MfccComputeQuant(audioDataWindow, - quantScale, - quantOffset); - } - ); - break; - } - default: - printf_err("Tensor type %s not supported\n", TfLiteTypeGetName(inputTensor->type)); + auto quant = inputTensor->GetQuantParams(); + + if (quant.scale) { + switch (inputTensor->Type()) { + case fwk::iface::TensorType::INT8: { + mfccFeatureCalc = this->FeatureCalc( + inputTensor, cacheSize, [=, &mfcc](std::vector& audioDataWindow) { + return mfcc.MfccComputeQuant( + audioDataWindow, quant.scale, quant.offset); + }); + break; + } + default: + printf_err("Tensor type %s not supported\n", + fwk::iface::GetTensorDataTypeName(inputTensor->Type())); } } else { mfccFeatureCalc = this->FeatureCalc(inputTensor, cacheSize, @@ -190,13 +188,13 @@ namespace app { return mfccFeatureCalc; } - KwsPostProcess::KwsPostProcess(TfLiteTensor* outputTensor, KwsClassifier& classifier, + KwsPostProcess::KwsPostProcess(const std::shared_ptr outputTensor, + KwsClassifier& classifier, const std::vector& labels, - std::vector& results, size_t averagingWindowLen) - :m_outputTensor{outputTensor}, - m_kwsClassifier{classifier}, - m_labels{labels}, - m_results{results} + std::vector& results, + size_t averagingWindowLen) : + m_outputTensor{outputTensor}, m_kwsClassifier{classifier}, m_labels{labels}, + m_results{results} { this->m_resultHistory = {averagingWindowLen, std::vector(labels.size())}; } @@ -209,4 +207,4 @@ namespace app { } } /* namespace app */ -} /* namespace arm */ \ No newline at end of file +} /* namespace arm */ diff --git a/source/application/api/use_case/noise_reduction/CMakeLists.txt b/source/application/api/use_case/noise_reduction/CMakeLists.txt index 7fb00959dc806556813c60f81ec0e563991a97c2..59b40158f2fdb8c37045440b3a40ef25dfebcae4 100644 --- a/source/application/api/use_case/noise_reduction/CMakeLists.txt +++ b/source/application/api/use_case/noise_reduction/CMakeLists.txt @@ -1,5 +1,6 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates +# SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or +# its affiliates # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,8 +28,7 @@ project(${NOISE_REDUCTION_API_TARGET} # Create static library add_library(${NOISE_REDUCTION_API_TARGET} STATIC src/RNNoiseProcessing.cc - src/RNNoiseFeatureProcessor.cc - src/RNNoiseModel.cc) + src/RNNoiseFeatureProcessor.cc) target_include_directories(${NOISE_REDUCTION_API_TARGET} PUBLIC include) diff --git a/source/application/api/use_case/noise_reduction/include/RNNoiseModel.hpp b/source/application/api/use_case/noise_reduction/include/RNNoiseModel.hpp deleted file mode 100644 index 1fdf328b245b46e2f7ab7d1162379cc882e1f617..0000000000000000000000000000000000000000 --- a/source/application/api/use_case/noise_reduction/include/RNNoiseModel.hpp +++ /dev/null @@ -1,79 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 - * - * 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. - */ -#ifndef RNNOISE_MODEL_HPP -#define RNNOISE_MODEL_HPP - -#include "Model.hpp" - -namespace arm { -namespace app { - namespace rnn { - extern const uint32_t g_NumInputFeatures; - extern const uint32_t g_FrameLength; - extern const uint32_t g_FrameStride; - } /* namespace rnn */ - - class RNNoiseModel : public Model { - public: - /** - * @brief Runs inference for RNNoise model. - * - * Call CopyGruStates so GRU state outputs are copied to GRU state inputs before the inference run. - * Run ResetGruState() method to set states to zero before starting processing logically related data. - * @return True if inference succeeded, False - otherwise - */ - bool RunInference() override; - - /** - * @brief Sets GRU input states to zeros. - * Call this method before starting processing the new sequence of logically related data. - */ - void ResetGruState(); - - /** - * @brief Copy current GRU output states to input states. - * Call this method before starting processing the next sequence of logically related data. - */ - bool CopyGruStates(); - - /* Which index of model outputs does the main output (gains) come from. */ - const size_t m_indexForModelOutput = 1; - - protected: - /** @brief Gets the reference to op resolver interface class. */ - const tflite::MicroOpResolver& GetOpResolver() override; - - /** @brief Adds operations to the op resolver instance. */ - bool EnlistOperations() override; - - /* - Each inference after the first needs to copy 3 GRU states from a output index to input index (model dependent): - 0 -> 3, 2 -> 2, 3 -> 1 - */ - const std::vector> m_gruStateMap = {{0,3}, {2, 2}, {3, 1}}; - private: - /* Maximum number of individual operations that can be enlisted. */ - static constexpr int ms_maxOpCnt = 15; - - /* A mutable op resolver instance. */ - tflite::MicroMutableOpResolver m_opResolver; - }; - -} /* namespace app */ -} /* namespace arm */ - -#endif /* RNNOISE_MODEL_HPP */ diff --git a/source/application/api/use_case/noise_reduction/include/RNNoiseProcessing.hpp b/source/application/api/use_case/noise_reduction/include/RNNoiseProcessing.hpp index b7f17bf59e286cb40057fef530d39dcd350daa59..3ca34f6ce8a6cf3e002351a45ed58185696f8146 100644 --- a/source/application/api/use_case/noise_reduction/include/RNNoiseProcessing.hpp +++ b/source/application/api/use_case/noise_reduction/include/RNNoiseProcessing.hpp @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or + * its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,18 +35,18 @@ namespace app { public: /** * @brief Constructor - * @param[in] inputTensor Pointer to the TFLite Micro input Tensor. + * @param[in] inputTensor Shared pointer representing a tensor interface object. * @param[in/out] featureProcessor RNNoise specific feature extractor object. * @param[in/out] frameFeatures RNNoise specific features shared between pre & post-processing. * **/ - explicit RNNoisePreProcess(TfLiteTensor* inputTensor, + explicit RNNoisePreProcess(const std::shared_ptr inputTensor, std::shared_ptr featureProcessor, std::shared_ptr frameFeatures); /** * @brief Should perform pre-processing of 'raw' input audio data and load it into - * TFLite Micro input tensors ready for inference + * input tensors ready for inference * @param[in] input Pointer to the data that pre-processing will work on. * @param[in] inputSize Size of the input data. * @return true if successful, false otherwise. @@ -53,7 +54,7 @@ namespace app { bool DoPreProcess(const void* input, size_t inputSize) override; private: - TfLiteTensor* m_inputTensor; /* Model input tensor. */ + std::shared_ptr m_inputTensor; /* Model input tensor. */ std::shared_ptr m_featureProcessor; /* RNNoise feature processor shared between pre & post-processing. */ std::shared_ptr m_frameFeatures; /* RNNoise features shared between pre & post-processing. */ rnn::vec1D32F m_audioFrame; /* Audio frame cast to FP32 */ @@ -63,11 +64,12 @@ namespace app { * @param[in] inputFeatures Vector of floating point features to quantize. * @param[in] quantScale Quantization scale for the inputTensor. * @param[in] quantOffset Quantization offset for the inputTensor. - * @param[in,out] inputTensor TFLite micro tensor to populate. + * @param[in,out] inputTensor Tensor to populate. **/ static void QuantizeAndPopulateInput(rnn::vec1D32F& inputFeatures, - float quantScale, int quantOffset, - TfLiteTensor* inputTensor); + float quantScale, + int quantOffset, + std::shared_ptr inputTensor); }; /** @@ -80,12 +82,12 @@ namespace app { public: /** * @brief Constructor - * @param[in] outputTensor Pointer to the TFLite Micro output Tensor. + * @param[in] outputTensor Shared pointer representing a tensor interface object * @param[out] denoisedAudioFrame Vector to store the final denoised audio frame. * @param[in/out] featureProcessor RNNoise specific feature extractor object. * @param[in/out] frameFeatures RNNoise specific features shared between pre & post-processing. **/ - RNNoisePostProcess(TfLiteTensor* outputTensor, + RNNoisePostProcess(const std::shared_ptr outputTensor, std::vector& denoisedAudioFrame, std::shared_ptr featureProcessor, std::shared_ptr frameFeatures); @@ -98,7 +100,7 @@ namespace app { bool DoPostProcess() override; private: - TfLiteTensor* m_outputTensor; /* Model output tensor. */ + std::shared_ptr m_outputTensor; /* Model output tensor. */ std::vector& m_denoisedAudioFrame; /* Vector to store the final denoised frame. */ rnn::vec1D32F m_denoisedAudioFrameFloat; /* Internal vector to store the final denoised frame (FP32). */ std::shared_ptr m_featureProcessor; /* RNNoise feature processor shared between pre & post-processing. */ @@ -110,4 +112,4 @@ namespace app { } /* namespace app */ } /* namespace arm */ -#endif /* RNNOISE_PROCESSING_HPP */ \ No newline at end of file +#endif /* RNNOISE_PROCESSING_HPP */ diff --git a/source/application/api/use_case/noise_reduction/src/RNNoiseProcessing.cc b/source/application/api/use_case/noise_reduction/src/RNNoiseProcessing.cc index 6839835f664cc541f59fa992b748883d60996f2c..7a019fd46acff6b3355c9946b483b91ba92eb866 100644 --- a/source/application/api/use_case/noise_reduction/src/RNNoiseProcessing.cc +++ b/source/application/api/use_case/noise_reduction/src/RNNoiseProcessing.cc @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2022 2025 Arm Limited and/or + * its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,10 +21,11 @@ namespace arm { namespace app { - RNNoisePreProcess::RNNoisePreProcess(TfLiteTensor* inputTensor, - std::shared_ptr featureProcessor, std::shared_ptr frameFeatures) - : m_inputTensor{inputTensor}, - m_featureProcessor{featureProcessor}, + RNNoisePreProcess::RNNoisePreProcess( + std::shared_ptr inputTensor, + std::shared_ptr featureProcessor, + std::shared_ptr frameFeatures) : + m_inputTensor{inputTensor}, m_featureProcessor{featureProcessor}, m_frameFeatures{frameFeatures} {} @@ -37,24 +39,26 @@ namespace app { auto input = static_cast(data); this->m_audioFrame = rnn::vec1D32F(input, input + inputSize); m_featureProcessor->PreprocessFrame(this->m_audioFrame.data(), inputSize, *this->m_frameFeatures); + const auto quant = this->m_inputTensor->GetQuantParams(); - QuantizeAndPopulateInput(this->m_frameFeatures->m_featuresVec, - this->m_inputTensor->params.scale, this->m_inputTensor->params.zero_point, - this->m_inputTensor); + QuantizeAndPopulateInput( + this->m_frameFeatures->m_featuresVec, quant.scale, quant.offset, this->m_inputTensor); debug("Input tensor populated \n"); return true; } - void RNNoisePreProcess::QuantizeAndPopulateInput(rnn::vec1D32F& inputFeatures, - const float quantScale, const int quantOffset, - TfLiteTensor* inputTensor) + void RNNoisePreProcess::QuantizeAndPopulateInput( + rnn::vec1D32F& inputFeatures, + const float quantScale, + const int quantOffset, + std::shared_ptr inputTensor) { const float minVal = std::numeric_limits::min(); const float maxVal = std::numeric_limits::max(); - auto* inputTensorData = tflite::GetTensorData(inputTensor); + auto* inputTensorData = inputTensor->GetData(); for (size_t i=0; i < inputFeatures.size(); ++i) { float quantValue = ((inputFeatures[i] / quantScale) + quantOffset); @@ -62,25 +66,24 @@ namespace app { } } - RNNoisePostProcess::RNNoisePostProcess(TfLiteTensor* outputTensor, - std::vector& denoisedAudioFrame, - std::shared_ptr featureProcessor, - std::shared_ptr frameFeatures) - : m_outputTensor{outputTensor}, - m_denoisedAudioFrame{denoisedAudioFrame}, - m_featureProcessor{featureProcessor}, - m_frameFeatures{frameFeatures} - { - this->m_denoisedAudioFrameFloat.reserve(denoisedAudioFrame.size()); - this->m_modelOutputFloat.resize(outputTensor->bytes); - } + RNNoisePostProcess::RNNoisePostProcess( + std::shared_ptr outputTensor, + std::vector& denoisedAudioFrame, + std::shared_ptr featureProcessor, + std::shared_ptr frameFeatures) : + m_outputTensor{outputTensor}, m_denoisedAudioFrame{denoisedAudioFrame}, + m_featureProcessor{featureProcessor}, m_frameFeatures{frameFeatures} + { + this->m_denoisedAudioFrameFloat.reserve(denoisedAudioFrame.size()); + this->m_modelOutputFloat.resize(outputTensor->Bytes()); + } bool RNNoisePostProcess::DoPostProcess() { - const auto* outputData = tflite::GetTensorData(this->m_outputTensor); - auto outputQuantParams = GetTensorQuantParams(this->m_outputTensor); + const auto* outputData = this->m_outputTensor->GetData(); + const auto outputQuantParams = this->m_outputTensor->GetQuantParams(); - for (size_t i = 0; i < this->m_outputTensor->bytes; ++i) { + for (size_t i = 0; i < this->m_outputTensor->Bytes(); ++i) { this->m_modelOutputFloat[i] = (static_cast(outputData[i]) - outputQuantParams.offset) * outputQuantParams.scale; } @@ -97,4 +100,4 @@ namespace app { } } /* namespace app */ -} /* namespace arm */ \ No newline at end of file +} /* namespace arm */ diff --git a/source/application/api/use_case/object_detection/CMakeLists.txt b/source/application/api/use_case/object_detection/CMakeLists.txt index 86d380a0e3611e855d88e4cb2affb8a6877d6492..135569ce2060d09c7e1e51708f3dbd0c8e5a0a00 100644 --- a/source/application/api/use_case/object_detection/CMakeLists.txt +++ b/source/application/api/use_case/object_detection/CMakeLists.txt @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates +# SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or its affiliates # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -27,8 +27,7 @@ project(${OBJECT_DETECTION_API_TARGET} # Create static library add_library(${OBJECT_DETECTION_API_TARGET} STATIC src/DetectorPreProcessing.cc - src/DetectorPostProcessing.cc - src/YoloFastestModel.cc) + src/DetectorPostProcessing.cc) target_include_directories(${OBJECT_DETECTION_API_TARGET} PUBLIC include) diff --git a/source/application/api/use_case/object_detection/include/DetectorPostProcessing.hpp b/source/application/api/use_case/object_detection/include/DetectorPostProcessing.hpp index 3bc8e53bd6e72127e16a4f82ecbedfac1783ce20..492a36fa20d2ff545bae12e18e6b44db53345544 100644 --- a/source/application/api/use_case/object_detection/include/DetectorPostProcessing.hpp +++ b/source/application/api/use_case/object_detection/include/DetectorPostProcessing.hpp @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2022 2025 Arm Limited and/or + * its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,10 +18,11 @@ #ifndef DETECTOR_POST_PROCESSING_HPP #define DETECTOR_POST_PROCESSING_HPP -#include "ImageUtils.hpp" -#include "DetectionResult.hpp" -#include "YoloFastestModel.hpp" #include "BaseProcessing.hpp" +#include "DetectionResult.hpp" +#include "ImageUtils.hpp" +#include "Tensor.hpp" +#include #include @@ -69,13 +71,13 @@ namespace object_detection { public: /** * @brief Constructor. - * @param[in] outputTensor0 Pointer to the TFLite Micro output Tensor at index 0. - * @param[in] outputTensor1 Pointer to the TFLite Micro output Tensor at index 1. + * @param[in] outputTensor0 Shared pointer to output tensor interface object at index 0. + * @param[in] outputTensor1 Shared pointer to output tensor interface object at index 1. * @param[out] results Vector of detected results. * @param[in] postProcessParams Struct of various parameters used in post-processing. **/ - explicit DetectorPostProcess(TfLiteTensor* outputTensor0, - TfLiteTensor* outputTensor1, + explicit DetectorPostProcess(const std::shared_ptr outputTensor0, + std::shared_ptr outputTensor1, std::vector& results, const object_detection::PostProcessParams& postProcessParams); @@ -87,8 +89,8 @@ namespace object_detection { bool DoPostProcess() override; private: - TfLiteTensor* m_outputTensor0; /* Output tensor index 0 */ - TfLiteTensor* m_outputTensor1; /* Output tensor index 1 */ + std::shared_ptr m_outputTensor0; /* Output tensor index 0 */ + std::shared_ptr m_outputTensor1; /* Output tensor index 1 */ std::vector& m_results; /* Single inference results. */ const object_detection::PostProcessParams& m_postProcessParams; /* Post processing param struct. */ object_detection::Network m_net; /* YOLO network object. */ diff --git a/source/application/api/use_case/object_detection/include/DetectorPreProcessing.hpp b/source/application/api/use_case/object_detection/include/DetectorPreProcessing.hpp index 34ce0a797e65c0e2e2b9f655709ba9fae16709d6..83ca37ddaa298428a0f0480ddb26687cf99f4014 100644 --- a/source/application/api/use_case/object_detection/include/DetectorPreProcessing.hpp +++ b/source/application/api/use_case/object_detection/include/DetectorPreProcessing.hpp @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2022 2025 Arm Limited and/or + * its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,15 +34,17 @@ namespace app { public: /** * @brief Constructor - * @param[in] inputTensor Pointer to the TFLite Micro input Tensor. + * @param[in] inputTensor Shared pointer representing a tensor interface object. * @param[in] rgb2Gray Convert image from 3 channel RGB to 1 channel grayscale. * @param[in] convertToInt8 Convert the image from uint8 to int8 range. **/ - explicit DetectorPreProcess(TfLiteTensor* inputTensor, bool rgb2Gray, bool convertToInt8); + explicit DetectorPreProcess(const std::shared_ptr inputTensor, + bool rgb2Gray, + bool convertToInt8); /** * @brief Should perform pre-processing of 'raw' input image data and load it into - * TFLite Micro input tensor ready for inference + * input tensor ready for inference. * @param[in] input Pointer to the data that pre-processing will work on. * @param[in] inputSize Size of the input data. * @return true if successful, false otherwise. @@ -49,7 +52,7 @@ namespace app { bool DoPreProcess(const void* input, size_t inputSize) override; private: - TfLiteTensor* m_inputTensor; + std::shared_ptr m_inputTensor; bool m_rgb2Gray; bool m_convertToInt8; }; @@ -57,4 +60,4 @@ namespace app { } /* namespace app */ } /* namespace arm */ -#endif /* DETECTOR_PRE_PROCESSING_HPP */ \ No newline at end of file +#endif /* DETECTOR_PRE_PROCESSING_HPP */ diff --git a/source/application/api/use_case/object_detection/include/YoloFastestModel.hpp b/source/application/api/use_case/object_detection/include/YoloFastestModel.hpp deleted file mode 100644 index fb92008c27ce7981affc39ebb89ecfe76eaec7e0..0000000000000000000000000000000000000000 --- a/source/application/api/use_case/object_detection/include/YoloFastestModel.hpp +++ /dev/null @@ -1,59 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 - * - * 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. - */ -#ifndef YOLO_FASTEST_MODEL_HPP -#define YOLO_FASTEST_MODEL_HPP - -#include "Model.hpp" - -namespace arm { -namespace app { - namespace object_detection { - extern const int originalImageSize; - extern const int channelsImageDisplayed; - /* NOTE: anchors are different for any given input model size, estimated during training - * phase */ - extern const float anchor1[]; - extern const float anchor2[]; - } /* namespace object_detection */ - - class YoloFastestModel : public Model { - - public: - /* Indices for the expected model - based on input tensor shape */ - static constexpr uint32_t ms_inputRowsIdx = 1; - static constexpr uint32_t ms_inputColsIdx = 2; - static constexpr uint32_t ms_inputChannelsIdx = 3; - - protected: - /** @brief Gets the reference to op resolver interface class. */ - const tflite::MicroOpResolver& GetOpResolver() override; - - /** @brief Adds operations to the op resolver instance. */ - bool EnlistOperations() override; - - private: - /* Maximum number of individual operations that can be enlisted. */ - static constexpr int ms_maxOpCnt = 8; - - /* A mutable op resolver instance. */ - tflite::MicroMutableOpResolver m_opResolver; - }; - -} /* namespace app */ -} /* namespace arm */ - -#endif /* YOLO_FASTEST_MODEL_HPP */ diff --git a/source/application/api/use_case/object_detection/src/DetectorPostProcessing.cc b/source/application/api/use_case/object_detection/src/DetectorPostProcessing.cc index 60b463f20e07f1ff8e0a180cd97c88080204d5c9..d3e4b22c87f393fdade7de1bb151eb6fcda86377 100644 --- a/source/application/api/use_case/object_detection/src/DetectorPostProcessing.cc +++ b/source/application/api/use_case/object_detection/src/DetectorPostProcessing.cc @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2022 2025 Arm Limited and/or + * its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,45 +23,40 @@ namespace arm { namespace app { - DetectorPostProcess::DetectorPostProcess( - TfLiteTensor* modelOutput0, - TfLiteTensor* modelOutput1, - std::vector& results, - const object_detection::PostProcessParams& postProcessParams) - : m_outputTensor0{modelOutput0}, - m_outputTensor1{modelOutput1}, - m_results{results}, - m_postProcessParams{postProcessParams} +DetectorPostProcess::DetectorPostProcess( + std::shared_ptr modelOutput0, + std::shared_ptr modelOutput1, + std::vector& results, + const object_detection::PostProcessParams& postProcessParams) : + m_outputTensor0{modelOutput0}, m_outputTensor1{modelOutput1}, m_results{results}, + m_postProcessParams{postProcessParams} { /* Init PostProcessing */ + const auto out0Quant = m_outputTensor0->GetQuantParams(); + const auto out1Quant = m_outputTensor1->GetQuantParams(); + this->m_net = object_detection::Network{ - .inputWidth = postProcessParams.inputImgCols, - .inputHeight = postProcessParams.inputImgRows, - .numClasses = postProcessParams.numClasses, - .branches = - {object_detection::Branch{.resolution = postProcessParams.inputImgCols / 32, - .numBox = 3, - .anchor = postProcessParams.anchor1, - .modelOutput = this->m_outputTensor0->data.int8, - .scale = (static_cast( - this->m_outputTensor0->quantization.params)) - ->scale->data[0], - .zeroPoint = (static_cast( - this->m_outputTensor0->quantization.params)) - ->zero_point->data[0], - .size = this->m_outputTensor0->bytes}, - object_detection::Branch{.resolution = postProcessParams.inputImgCols / 16, - .numBox = 3, - .anchor = postProcessParams.anchor2, - .modelOutput = this->m_outputTensor1->data.int8, - .scale = (static_cast( - this->m_outputTensor1->quantization.params)) - ->scale->data[0], - .zeroPoint = (static_cast( - this->m_outputTensor1->quantization.params)) - ->zero_point->data[0], - .size = this->m_outputTensor1->bytes}}, - .topN = postProcessParams.topN}; + .inputWidth = postProcessParams.inputImgCols, + .inputHeight = postProcessParams.inputImgRows, + .numClasses = postProcessParams.numClasses, + .branches = {object_detection::Branch{ + .resolution = postProcessParams.inputImgCols / 32, + .numBox = 3, + .anchor = postProcessParams.anchor1, + .modelOutput = this->m_outputTensor0->GetData(), + .scale = out0Quant.scale, + .zeroPoint = out0Quant.offset, + .size = this->m_outputTensor0->Bytes()}, + object_detection::Branch{ + .resolution = postProcessParams.inputImgCols / 16, + .numBox = 3, + .anchor = postProcessParams.anchor2, + .modelOutput = this->m_outputTensor1->GetData(), + .scale = out1Quant.scale, + .zeroPoint = out1Quant.offset, + .size = this->m_outputTensor1->Bytes()}}, + .topN = postProcessParams.topN + }; /* End init */ } diff --git a/source/application/api/use_case/object_detection/src/DetectorPreProcessing.cc b/source/application/api/use_case/object_detection/src/DetectorPreProcessing.cc index 19842497aea4a9f1885cd8d9b08ee364510220de..957ec44db1634a47c7d6bb9c4e21df6924bef401 100644 --- a/source/application/api/use_case/object_detection/src/DetectorPreProcessing.cc +++ b/source/application/api/use_case/object_detection/src/DetectorPreProcessing.cc @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or + * its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,13 +19,15 @@ #include "ImageUtils.hpp" #include "log_macros.h" +#include + namespace arm { namespace app { - DetectorPreProcess::DetectorPreProcess(TfLiteTensor* inputTensor, bool rgb2Gray, bool convertToInt8) - : m_inputTensor{inputTensor}, - m_rgb2Gray{rgb2Gray}, - m_convertToInt8{convertToInt8} + DetectorPreProcess::DetectorPreProcess(std::shared_ptr inputTensor, + bool rgb2Gray, + bool convertToInt8) : + m_inputTensor{inputTensor}, m_rgb2Gray{rgb2Gray}, m_convertToInt8{convertToInt8} {} bool DetectorPreProcess::DoPreProcess(const void* data, size_t inputSize) { @@ -35,18 +38,19 @@ namespace app { auto input = static_cast(data); if (this->m_rgb2Gray) { - image::RgbToGrayscale(input, this->m_inputTensor->data.uint8, this->m_inputTensor->bytes); + image::RgbToGrayscale( + input, this->m_inputTensor->GetData(), this->m_inputTensor->Bytes()); } else { - std::memcpy(this->m_inputTensor->data.data, input, inputSize); + std::memcpy(this->m_inputTensor->GetData(), input, inputSize); } debug("Input tensor populated \n"); if (this->m_convertToInt8) { - image::ConvertImgToInt8(this->m_inputTensor->data.data, this->m_inputTensor->bytes); + image::ConvertUint8ToInt8(this->m_inputTensor->GetData(), this->m_inputTensor->Bytes()); } return true; } } /* namespace app */ -} /* namespace arm */ \ No newline at end of file +} /* namespace arm */ diff --git a/source/application/api/use_case/vww/CMakeLists.txt b/source/application/api/use_case/vww/CMakeLists.txt index f7954658bd060249e48110e95c113c22077a8108..49f6420e3ed739591b71fa5b310d9b55f8598089 100644 --- a/source/application/api/use_case/vww/CMakeLists.txt +++ b/source/application/api/use_case/vww/CMakeLists.txt @@ -1,5 +1,6 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates +# SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or +# its affiliates # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -26,8 +27,7 @@ project(${VWW_API_TARGET} # Create static library add_library(${VWW_API_TARGET} STATIC - src/VisualWakeWordProcessing.cc - src/VisualWakeWordModel.cc) + src/VisualWakeWordProcessing.cc) target_include_directories(${VWW_API_TARGET} PUBLIC include) diff --git a/source/application/api/use_case/vww/include/VisualWakeWordModel.hpp b/source/application/api/use_case/vww/include/VisualWakeWordModel.hpp deleted file mode 100644 index 3a2cbda81ca2d3b52c805fe5c6c501e1a0cfd670..0000000000000000000000000000000000000000 --- a/source/application/api/use_case/vww/include/VisualWakeWordModel.hpp +++ /dev/null @@ -1,50 +0,0 @@ -/* - * SPDX-FileCopyrightText: Copyright 2021 - 2022 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 - * - * 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. - */ -#ifndef VISUAL_WAKE_WORD_MODEL_HPP -#define VISUAL_WAKE_WORD_MODEL_HPP - -#include "Model.hpp" - -namespace arm { -namespace app { - - class VisualWakeWordModel : public Model { - - public: - /* Indices for the expected model - based on input tensor shape */ - static constexpr uint32_t ms_inputRowsIdx = 1; - static constexpr uint32_t ms_inputColsIdx = 2; - static constexpr uint32_t ms_inputChannelsIdx = 3; - - protected: - /** @brief Gets the reference to op resolver interface class. */ - const tflite::MicroOpResolver& GetOpResolver() override; - - /** @brief Adds operations to the op resolver instance. */ - bool EnlistOperations() override; - private: - /* Maximum number of individual operations that can be enlisted. */ - static constexpr int ms_maxOpCnt = 7; - - /* A mutable op resolver instance. */ - tflite::MicroMutableOpResolver m_opResolver; - }; - -} /* namespace app */ -} /* namespace arm */ - -#endif /* VISUAL_WAKE_WORD_MODEL_HPP */ diff --git a/source/application/api/use_case/vww/include/VisualWakeWordProcessing.hpp b/source/application/api/use_case/vww/include/VisualWakeWordProcessing.hpp index ca93f50f33840f0dd7325cc1c2189b89f9dd7d4f..cbc18b4902df781cccfea7dd287b559f3244f21c 100644 --- a/source/application/api/use_case/vww/include/VisualWakeWordProcessing.hpp +++ b/source/application/api/use_case/vww/include/VisualWakeWordProcessing.hpp @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or + * its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,14 +35,15 @@ namespace app { public: /** * @brief Constructor - * @param[in] inputTensor Pointer to the TFLite Micro input Tensor. + * @param[in] inputTensor Shared pointer representing a tensor interface object. * @param[in] rgb2Gray Convert image from 3 channel RGB to 1 channel grayscale. **/ - explicit VisualWakeWordPreProcess(TfLiteTensor* inputTensor, bool rgb2Gray=true); + explicit VisualWakeWordPreProcess( + const std::shared_ptr inputTensor, bool rgb2Gray = true); /** * @brief Should perform pre-processing of 'raw' input image data and load it into - * TFLite Micro input tensors ready for inference + * input tensors ready for inference * @param[in] input Pointer to the data that pre-processing will work on. * @param[in] inputSize Size of the input data. * @return true if successful, false otherwise. @@ -49,7 +51,7 @@ namespace app { bool DoPreProcess(const void* input, size_t inputSize) override; private: - TfLiteTensor* m_inputTensor; + std::shared_ptr m_inputTensor; bool m_rgb2Gray; }; @@ -61,7 +63,7 @@ namespace app { class VisualWakeWordPostProcess : public BasePostProcess { private: - TfLiteTensor* m_outputTensor; + std::shared_ptr m_outputTensor; Classifier& m_vwwClassifier; const std::vector& m_labels; std::vector& m_results; @@ -69,15 +71,16 @@ namespace app { public: /** * @brief Constructor - * @param[in] outputTensor Pointer to the TFLite Micro output Tensor. + * @param[in] outputTensor Shared pointer representing a tensor interface object * @param[in] classifier Classifier object used to get top N results from classification. * @param[in] model Pointer to the VWW classification Model object. * @param[in] labels Vector of string labels to identify each output of the model. * @param[out] results Vector of classification results to store decoded outputs. **/ - VisualWakeWordPostProcess(TfLiteTensor* outputTensor, Classifier& classifier, - const std::vector& labels, - std::vector& results); + VisualWakeWordPostProcess(const std::shared_ptr outputTensor, + Classifier& classifier, + const std::vector& labels, + std::vector& results); /** * @brief Should perform post-processing of the result of inference then @@ -90,4 +93,4 @@ namespace app { } /* namespace app */ } /* namespace arm */ -#endif /* VWW_PROCESSING_HPP */ \ No newline at end of file +#endif /* VWW_PROCESSING_HPP */ diff --git a/source/application/api/use_case/vww/src/VisualWakeWordProcessing.cc b/source/application/api/use_case/vww/src/VisualWakeWordProcessing.cc index 52815e93d02d6140cb9693fd807d89ad7b1e168f..657dc8e8520b76d9906253415f54dc124176ad91 100644 --- a/source/application/api/use_case/vww/src/VisualWakeWordProcessing.cc +++ b/source/application/api/use_case/vww/src/VisualWakeWordProcessing.cc @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or + * its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -17,15 +18,15 @@ #include "VisualWakeWordProcessing.hpp" #include "ImageUtils.hpp" -#include "VisualWakeWordModel.hpp" #include "log_macros.h" +#include namespace arm { namespace app { - VisualWakeWordPreProcess::VisualWakeWordPreProcess(TfLiteTensor* inputTensor, bool rgb2Gray) - :m_inputTensor{inputTensor}, - m_rgb2Gray{rgb2Gray} + VisualWakeWordPreProcess::VisualWakeWordPreProcess( + std::shared_ptr inputTensor, bool rgb2Gray) : + m_inputTensor{inputTensor}, m_rgb2Gray{rgb2Gray} {} bool VisualWakeWordPreProcess::DoPreProcess(const void* data, size_t inputSize) @@ -36,7 +37,7 @@ namespace app { auto input = static_cast(data); - uint8_t* unsignedDstPtr = this->m_inputTensor->data.uint8; + auto* unsignedDstPtr = this->m_inputTensor->GetData(); if (this->m_rgb2Gray) { image::RgbToGrayscale(input, unsignedDstPtr, inputSize); @@ -46,10 +47,10 @@ namespace app { /* VWW model pre-processing is image conversion from uint8 to [0,1] float values, * then quantize them with input quantization info. */ - QuantParams inQuantParams = GetTensorQuantParams(this->m_inputTensor); + auto inQuantParams = this->m_inputTensor->GetQuantParams(); - int8_t* signedDstPtr = this->m_inputTensor->data.int8; - for (size_t i = 0; i < this->m_inputTensor->bytes; i++) { + int8_t* signedDstPtr = this->m_inputTensor->GetData(); + for (size_t i = 0; i < this->m_inputTensor->Bytes(); i++) { auto i_data_int8 = static_cast( ((static_cast(unsignedDstPtr[i]) / 255.0f) / inQuantParams.scale) + inQuantParams.offset ); @@ -61,12 +62,13 @@ namespace app { return true; } - VisualWakeWordPostProcess::VisualWakeWordPostProcess(TfLiteTensor* outputTensor, Classifier& classifier, - const std::vector& labels, std::vector& results) - :m_outputTensor{outputTensor}, - m_vwwClassifier{classifier}, - m_labels{labels}, - m_results{results} + VisualWakeWordPostProcess::VisualWakeWordPostProcess( + std::shared_ptr outputTensor, + Classifier& classifier, + const std::vector& labels, + std::vector& results) : + m_outputTensor{outputTensor}, m_vwwClassifier{classifier}, m_labels{labels}, + m_results{results} {} bool VisualWakeWordPostProcess::DoPostProcess() @@ -77,4 +79,4 @@ namespace app { } } /* namespace app */ -} /* namespace arm */ \ No newline at end of file +} /* namespace arm */ diff --git a/source/application/main/Main.cc b/source/application/main/Main.cc index 4a5370909511fd097c2c506de6dc057b65a5eeac..23538f624cfea3e610050f025b1e54ae79b25f3b 100644 --- a/source/application/main/Main.cc +++ b/source/application/main/Main.cc @@ -22,7 +22,6 @@ #include "hal.h" /* our hardware abstraction api */ #include "log_macros.h" -#include "TensorFlowLiteMicro.hpp" /* our inference logic api */ #include @@ -48,9 +47,6 @@ int main () /* Application information, UART should have been initialised. */ PrintApplicationIntro(); - /* Enable TensorFlow Lite Micro logging. */ - EnableTFLMLog(); - /* Run the application. */ MainLoop(); } diff --git a/source/application/main/UseCaseCommonUtils.cc b/source/application/main/UseCaseCommonUtils.cc index 3f308af18fc28b529c23c4c88c9520148081770c..c788a739341115446369a84d323fb6c77266b581 100644 --- a/source/application/main/UseCaseCommonUtils.cc +++ b/source/application/main/UseCaseCommonUtils.cc @@ -23,7 +23,8 @@ namespace arm { namespace app { - bool PresentInferenceResult(const std::vector& results) + + bool PresentInferenceResult(const std::vector& results) { constexpr uint32_t dataPsnTxtStartX1 = 150; constexpr uint32_t dataPsnTxtStartY1 = 30; @@ -65,7 +66,7 @@ namespace app { return true; } - bool RunInference(arm::app::Model& model, Profiler& profiler) + bool RunInference(fwk::iface::Model& model, Profiler& profiler) { profiler.StartProfiling("Inference"); bool runInf = model.RunInference(); diff --git a/source/application/main/include/BufAttributes.hpp b/source/application/main/include/BufAttributes.hpp index 8431ad3a08c7ae8d089dfb037ae2eeb48ac352de..add6cf4d1a437c50cf9a6a3d834daa351ab2aa26 100644 --- a/source/application/main/include/BufAttributes.hpp +++ b/source/application/main/include/BufAttributes.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -60,14 +60,14 @@ extern "C" { /* Form the attributes, alignment is mandatory. */ #define MAKE_ATTRIBUTE(x) __attribute__((ALIGNMENT_REQ, x)) -#define MODEL_TFLITE_ATTRIBUTE MAKE_ATTRIBUTE(MODEL_SECTION) +#define MODEL_SECTION_ATTRIBUTE MAKE_ATTRIBUTE(MODEL_SECTION) #define ACTIVATION_BUF_ATTRIBUTE MAKE_ATTRIBUTE(ACTIVATION_BUF_SECTION) #define IFM_BUF_ATTRIBUTE MAKE_ATTRIBUTE(IFM_BUF_SECTION) #define LABELS_ATTRIBUTE MAKE_ATTRIBUTE(LABEL_SECTION) #else /* HAVE_ATTRIBUTE(aligned) || (defined(__GNUC__) && !defined(__clang__)) */ -#define MODEL_TFLITE_ATTRIBUTE +#define MODEL_SECTION_ATTRIBUTE #define ACTIVATION_BUF_ATTRIBUTE #define IFM_BUF_ATTRIBUTE #define LABELS_ATTRIBUTE diff --git a/source/application/main/include/UseCaseCommonUtils.hpp b/source/application/main/include/UseCaseCommonUtils.hpp index b4c0cc4e48299cf80d6abe3eef56c1e8bf0fe2de..c344f5d4f7e7b435fdeeb670c416f9e3c9bd4d88 100644 --- a/source/application/main/include/UseCaseCommonUtils.hpp +++ b/source/application/main/include/UseCaseCommonUtils.hpp @@ -30,7 +30,7 @@ namespace app { * @param[in] results Vector of classification results to be displayed. * @return true if successful, false otherwise. **/ - bool PresentInferenceResult(const std::vector& results); + bool PresentInferenceResult(const std::vector& results); /** * @brief Run inference using given model @@ -40,7 +40,7 @@ namespace app { * @param[in] profiler Reference to the initialised profiler. * @return true if inference succeeds, false otherwise. **/ - bool RunInference(Model& model, Profiler& profiler); + bool RunInference(fwk::iface::Model& model, Profiler& profiler); #ifdef INTERACTIVE_MODE /** diff --git a/source/hal/CMakeLists.txt b/source/hal/CMakeLists.txt index 9079550712da13a2a46f67f32b17950b023b68a1..220dbeb7da5e95e09c5f31e5dda9ae4ddd2e13a0 100644 --- a/source/hal/CMakeLists.txt +++ b/source/hal/CMakeLists.txt @@ -57,7 +57,7 @@ add_subdirectory(${PLATFORM_DRIVERS_DIR} ${CMAKE_BINARY_DIR}/platform_driver) # Link time library targets: target_link_libraries(${HAL_TARGET} PUBLIC - log # Logging functions + mlek_log # Logging functions lcd_iface # LCD interface platform_drivers # Platform drivers implementing the required interfaces ) diff --git a/source/hal/source/components/audio/CMakeLists.txt b/source/hal/source/components/audio/CMakeLists.txt index b664c45e57d4637cae0ca9c2ff7d16854ed724fd..dd7950d8962933a405d3f7a5fbc14e718e380605 100644 --- a/source/hal/source/components/audio/CMakeLists.txt +++ b/source/hal/source/components/audio/CMakeLists.txt @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2024 Arm Limited and/or +# SPDX-FileCopyrightText: Copyright 2024-2025 Arm Limited and/or # its affiliates # SPDX-License-Identifier: Apache-2.0 # @@ -34,7 +34,7 @@ target_sources(hal_audio_static_streams PRIVATE source/hal_audio_static.c source/hal_audio_static_external.c) target_include_directories(hal_audio_static_streams PRIVATE source) -target_link_libraries(hal_audio_static_streams PUBLIC hal_audio_interface log) +target_link_libraries(hal_audio_static_streams PUBLIC hal_audio_interface mlek_log) target_compile_definitions(hal_audio_static_streams PRIVATE $<$:HAL_AUDIO_LOOP>) diff --git a/source/hal/source/components/audio/source/hal_audio_static_external.c b/source/hal/source/components/audio/source/hal_audio_static_external.c index c761d3f0a82da9e73481b3cf24c10889f49e894a..db9d3a6d783fb8021d3d4e49bf2e75301ea08687 100644 --- a/source/hal/source/components/audio/source/hal_audio_static_external.c +++ b/source/hal/source/components/audio/source/hal_audio_static_external.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -17,6 +17,7 @@ */ #include "log_macros.h" #include "hal_audio_static_external.h" +#include /** These functions must be provided to this interface */ __attribute__((weak)) const char* get_sample_data_filename(uint32_t idx) diff --git a/source/hal/source/components/camera/CMakeLists.txt b/source/hal/source/components/camera/CMakeLists.txt index b10ac7c4fa5642ef70d00cd0ede61e3829773489..fdb5af6d654c3742339d81b5dc86c635240a5f94 100644 --- a/source/hal/source/components/camera/CMakeLists.txt +++ b/source/hal/source/components/camera/CMakeLists.txt @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2024 Arm Limited and/or +# SPDX-FileCopyrightText: Copyright 2024-2025 Arm Limited and/or # its affiliates # SPDX-License-Identifier: Apache-2.0 # @@ -34,7 +34,7 @@ target_sources(hal_camera_static_images PRIVATE source/hal_camera_static.c source/hal_camera_static_external.c) target_include_directories(hal_camera_static_images PRIVATE source) -target_link_libraries(hal_camera_static_images PUBLIC hal_camera_interface log) +target_link_libraries(hal_camera_static_images PUBLIC hal_camera_interface mlek_log) target_compile_definitions(hal_camera_static_images PRIVATE $<$:HAL_CAMERA_LOOP>) @@ -50,7 +50,7 @@ if (${FVP_VSI_ENABLED}) add_library(hal_camera_vsi STATIC EXCLUDE_FROM_ALL source/hal_camera_vsi.c) target_link_libraries(hal_camera_vsi PUBLIC hal_camera_interface - log + mlek_log virtual_streaming_interface) target_compile_definitions(hal_camera_vsi PRIVATE DYNAMIC_IFM_BASE=${DYNAMIC_IFM_BASE} diff --git a/source/hal/source/components/camera/source/hal_camera_static.c b/source/hal/source/components/camera/source/hal_camera_static.c index f10978eefa1ee285d5bc2ec8407129646da0bdaf..df8d9b2b49e9930c614bb811a617a400f4226869 100644 --- a/source/hal/source/components/camera/source/hal_camera_static.c +++ b/source/hal/source/components/camera/source/hal_camera_static.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -18,8 +18,9 @@ #include "hal_camera.h" #include "log_macros.h" #include "hal_camera_static_external.h" - +#include #include +#include typedef struct hal_camera_device_ { char name[32]; diff --git a/source/hal/source/components/camera/source/hal_camera_static_external.c b/source/hal/source/components/camera/source/hal_camera_static_external.c index b75b83f2685b2f4d7e02c9d24a4854cf38bf4476..b03d45ba144fce70b0bfede3e13f442bff928fb9 100644 --- a/source/hal/source/components/camera/source/hal_camera_static_external.c +++ b/source/hal/source/components/camera/source/hal_camera_static_external.c @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -16,6 +16,8 @@ * limitations under the License. */ #include "log_macros.h" +#include +#include __attribute__((weak)) const char* get_sample_data_filename(uint32_t idx) { diff --git a/source/hal/source/components/lcd/CMakeLists.txt b/source/hal/source/components/lcd/CMakeLists.txt index 6d0aec5ad4d300aaa26f6c1b7aded9ab3c9520d8..d87c4d9ecc0476bba4fe8aefae75228ac87e0153 100644 --- a/source/hal/source/components/lcd/CMakeLists.txt +++ b/source/hal/source/components/lcd/CMakeLists.txt @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2022, 2024 Arm Limited and/or +# SPDX-FileCopyrightText: Copyright 2022, 2024-2025 Arm Limited and/or # its affiliates # SPDX-License-Identifier: Apache-2.0 # @@ -60,7 +60,7 @@ target_compile_definitions(${LCD_MPS3_COMPONENT_TARGET} $<$:LCD_TITLE_STRING=\"${LCD_TITLE_STRING}\">) ## Logging utilities: -if (NOT TARGET log) +if (NOT TARGET mlek_log) if (NOT DEFINED LOG_PROJECT_DIR) message(FATAL_ERROR "LOG_PROJECT_DIR needs to be defined.") endif() @@ -70,7 +70,7 @@ endif() ## Add dependencies target_link_libraries(${LCD_MPS3_COMPONENT_TARGET} PUBLIC ${LCD_IFACE_TARGET} - log) + mlek_log) # Display status message(STATUS "CMAKE_CURRENT_SOURCE_DIR: " ${CMAKE_CURRENT_SOURCE_DIR}) @@ -96,7 +96,7 @@ target_sources(${LCD_STUBS_COMPONENT_TARGET} ## Add dependencies target_link_libraries(${LCD_STUBS_COMPONENT_TARGET} PUBLIC ${LCD_IFACE_TARGET} - log) + mlek_log) # Display status message(STATUS "CMAKE_CURRENT_SOURCE_DIR: " ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/source/hal/source/components/npu/CMakeLists.txt b/source/hal/source/components/npu/CMakeLists.txt index 6671ed7ec49d1d497140892adf71a5d3c4fbef6d..3fd768f0c377ab5af6131a22c51947271cfbea74 100644 --- a/source/hal/source/components/npu/CMakeLists.txt +++ b/source/hal/source/components/npu/CMakeLists.txt @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2022, 2024 Arm Limited and/or its +# SPDX-FileCopyrightText: Copyright 2022, 2024-2025 Arm Limited and/or its # affiliates # SPDX-License-Identifier: Apache-2.0 # @@ -88,7 +88,7 @@ endif () add_subdirectory(${ETHOS_U_NPU_DRIVER_SRC_PATH} ${CMAKE_BINARY_DIR}/ethos-u-driver) ## Logging utilities: -if (NOT TARGET log) +if (NOT TARGET mlek_log) if (NOT DEFINED LOG_PROJECT_DIR) message(FATAL_ERROR "LOG_PROJECT_DIR needs to be defined.") endif() @@ -174,7 +174,7 @@ target_sources(${ETHOS_U_NPU_COMPONENT} ## Add dependencies: target_link_libraries(${ETHOS_U_NPU_COMPONENT} PUBLIC ethosu_core_driver - log) + mlek_log) ## If the rte_components target has been defined, include it as a dependency here. This component ## gives access to certain CPU related functions and definitions that should come from the CMSIS diff --git a/source/hal/source/components/npu/ethosu_profiler.c b/source/hal/source/components/npu/ethosu_profiler.c index 3b0a658b4bfe9888b179bbfd66e2588a9d21118c..fb119e0b99de3537e70dc9faf43cb5a10291ad7c 100644 --- a/source/hal/source/components/npu/ethosu_profiler.c +++ b/source/hal/source/components/npu/ethosu_profiler.c @@ -1,6 +1,7 @@ /* * SPDX-FileCopyrightText: Copyright 2022, 2024 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. diff --git a/source/hal/source/components/npu_ta/CMakeLists.txt b/source/hal/source/components/npu_ta/CMakeLists.txt index 2030849fc5d50227ed3b66a48117b0a8a9fa3f35..7c70f7f530d44107de3bf3b26ac6fe529ff6daa2 100644 --- a/source/hal/source/components/npu_ta/CMakeLists.txt +++ b/source/hal/source/components/npu_ta/CMakeLists.txt @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2022, 2024 Arm Limited and/or its +# SPDX-FileCopyrightText: Copyright 2022, 2024-2025 Arm Limited and/or its # affiliates # SPDX-License-Identifier: Apache-2.0 # @@ -56,7 +56,7 @@ endif() add_subdirectory(${ETHOS_U_NPU_TIMING_ADAPTER_SRC_PATH} ${CMAKE_BINARY_DIR}/timing_adapter) ## Logging utilities: -if (NOT TARGET log) +if (NOT TARGET mlek_log) if (NOT DEFINED LOG_PROJECT_DIR) message(FATAL_ERROR "LOG_PROJECT_DIR needs to be defined.") endif() @@ -85,7 +85,7 @@ target_compile_definitions(${ETHOS_U_NPU_TA_COMPONENT} ## Add dependencies target_link_libraries(${ETHOS_U_NPU_TA_COMPONENT} PUBLIC timing_adapter - log) + mlek_log) # Display status message(STATUS "CMAKE_CURRENT_SOURCE_DIR: " ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/source/hal/source/hal.c b/source/hal/source/hal.c index 4a5d88f159a2929f18ea6c9618c3ae7e42270db9..f2f4df9035849a3e706948ab3389655e23507080 100644 --- a/source/hal/source/hal.c +++ b/source/hal/source/hal.c @@ -1,5 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or + * its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -18,6 +19,7 @@ #include "platform_drivers.h" /* Platform drivers */ #include "log_macros.h" /* Logging macros */ +#include bool hal_platform_init(void) { diff --git a/source/hal/source/platform/mps3/CMakeLists.txt b/source/hal/source/platform/mps3/CMakeLists.txt index 900ce93f1c77185b15dfa8a6ae83a1339b566889..c895beee062b523eb73b16ab13a1af4c46bd79ed 100644 --- a/source/hal/source/platform/mps3/CMakeLists.txt +++ b/source/hal/source/platform/mps3/CMakeLists.txt @@ -136,7 +136,7 @@ target_include_directories(cmsis_device PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/${TARGET_SUBSYSTEM}) ## Logging utilities: -if (NOT TARGET log) +if (NOT TARGET mlek_log) if (NOT DEFINED LOG_PROJECT_DIR) message(FATAL_ERROR "LOG_PROJECT_DIR needs to be defined.") endif() @@ -145,7 +145,7 @@ endif() # Add dependencies: target_link_libraries(${PLATFORM_DRIVERS_TARGET} PUBLIC - log + mlek_log cmsis_device platform_pmu lcd_mps3 diff --git a/source/hal/source/platform/mps4/CMakeLists.txt b/source/hal/source/platform/mps4/CMakeLists.txt index 7b671bab5f90c84b57f3183edecd3e4320122825..6d527a0240a2c354182e08f6ee5bb2fafec92ef7 100644 --- a/source/hal/source/platform/mps4/CMakeLists.txt +++ b/source/hal/source/platform/mps4/CMakeLists.txt @@ -114,7 +114,7 @@ target_include_directories(cmsis_device PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/${TARGET_SUBSYSTEM}) ## Logging utilities: -if (NOT TARGET log) +if (NOT TARGET mlek_log) if (NOT DEFINED LOG_PROJECT_DIR) message(FATAL_ERROR "LOG_PROJECT_DIR needs to be defined.") endif() @@ -123,7 +123,7 @@ endif() # Add dependencies: target_link_libraries(${PLATFORM_DRIVERS_TARGET} PUBLIC - log + mlek_log cmsis_device platform_pmu lcd_stubs diff --git a/source/hal/source/platform/native/CMakeLists.txt b/source/hal/source/platform/native/CMakeLists.txt index 66985ac18e630c3048f8e990d35d5ba32a256922..512fb490152345ab844cbee5f072d0a9f3a74e74 100644 --- a/source/hal/source/platform/native/CMakeLists.txt +++ b/source/hal/source/platform/native/CMakeLists.txt @@ -70,7 +70,7 @@ add_subdirectory(${COMPONENTS_DIR}/audio ${CMAKE_BINARY_DIR}/audio) # Add dependencies: target_link_libraries(${PLATFORM_DRIVERS_TARGET} PUBLIC - log + mlek_log platform_pmu lcd_stubs hal_audio_static_streams diff --git a/source/hal/source/platform/simple/CMakeLists.txt b/source/hal/source/platform/simple/CMakeLists.txt index ca4bb221dbd41122069769649c65aec3db18bd82..83a328d7170869dc73d5ae162a1ade14104d80ba 100644 --- a/source/hal/source/platform/simple/CMakeLists.txt +++ b/source/hal/source/platform/simple/CMakeLists.txt @@ -88,7 +88,7 @@ target_include_directories(cmsis_device PUBLIC ${CMAKE_CURRENT_SOURCE_DIR}/include/${TARGET_SUBSYSTEM}) ## Logging utilities: -if (NOT TARGET log) +if (NOT TARGET mlek_log) if (NOT DEFINED LOG_PROJECT_DIR) message(FATAL_ERROR "LOG_PROJECT_DIR needs to be defined.") endif() @@ -98,7 +98,7 @@ endif() # Add dependencies: target_link_libraries(${PLATFORM_DRIVERS_TARGET} PUBLIC cmsis_device - log + mlek_log platform_pmu lcd_stubs hal_audio_static_streams diff --git a/source/log/CMakeLists.txt b/source/log/CMakeLists.txt index c0478de156665892ecf0254c0935d02154b4e0f2..e5437b7d4008025aaf8dccba30211f7b43fb601c 100644 --- a/source/log/CMakeLists.txt +++ b/source/log/CMakeLists.txt @@ -1,5 +1,6 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates +# SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or +# its affiliates # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -20,24 +21,42 @@ ####################################################### cmake_minimum_required(VERSION 3.21.0) -set(BSP_LOGGING_TARGET log) +set(MLEK_LOG_TARGET mlek_log) -project(${BSP_LOGGING_TARGET} +project(${MLEK_LOG_TARGET} DESCRIPTION "Generic logging formatting header-only interface lib." LANGUAGES C) -add_library(${BSP_LOGGING_TARGET} INTERFACE) +set(MLEK_LOG_ENABLE ON + CACHE BOOL "Enable MLEK logging functions. Set this to off to disable all logging.") -if (DEFINED LOG_LEVEL) - message(STATUS "Setting log level to ${LOG_LEVEL}") - target_compile_definitions(${BSP_LOGGING_TARGET} - INTERFACE - LOG_LEVEL=${LOG_LEVEL}) +set(MLEK_LOG_LEVEL MLEK_LOG_LEVEL_INFO + CACHE STRING "Log level.") + +set_property(CACHE MLEK_LOG_LEVEL PROPERTY STRINGS + MLEK_LOG_LEVEL_TRACE + MLEK_LOG_LEVEL_DEBUG + MLEK_LOG_LEVEL_INFO + MLEK_LOG_LEVEL_WARN + MLEK_LOG_LEVEL_ERROR) + +add_library(${MLEK_LOG_TARGET} INTERFACE) + +if (MLEK_LOG_ENABLE) + target_compile_definitions(${MLEK_LOG_TARGET} INTERFACE + MLEK_LOG_ENABLE) + if (DEFINED MLEK_LOG_LEVEL) + message(STATUS "Setting log level to ${MLEK_LOG_LEVEL}") + target_compile_definitions(${MLEK_LOG_TARGET} INTERFACE + MLEK_LOG_LEVEL=${MLEK_LOG_LEVEL}) + endif() +else() + message(STATUS "MLEK log is disabled") endif() -target_include_directories(${BSP_LOGGING_TARGET} INTERFACE include) +target_include_directories(${MLEK_LOG_TARGET} INTERFACE include) -message(STATUS "*******************************************************") -message(STATUS "Library : " ${BSP_LOGGING_TARGET}) -message(STATUS "CMAKE_SYSTEM_PROCESSOR : " ${CMAKE_SYSTEM_PROCESSOR}) -message(STATUS "*******************************************************") +message(STATUS "**************************************************") +message(STATUS "Library: " ${MLEK_LOG_TARGET}) +message(STATUS "CMAKE_SYSTEM_PROCESSOR: " ${CMAKE_SYSTEM_PROCESSOR}) +message(STATUS "**************************************************") diff --git a/source/log/include/log_macros.h b/source/log/include/log_macros.h index 6c115abcb5f5e39d9c896fd048a85b1891282f48..8a75f76f15e5a3f0dc3fc86795a3d236d066bb47 100644 --- a/source/log/include/log_macros.h +++ b/source/log/include/log_macros.h @@ -1,6 +1,7 @@ /* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or + * its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,8 +15,14 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#ifndef ML_EMBEDDED_CORE_LOG_H -#define ML_EMBEDDED_CORE_LOG_H +#ifndef MLEK_BASIC_LOGGER_H +#define MLEK_BASIC_LOGGER_H + +#if !defined(UNUSED) +#define UNUSED(x) ((void)(x)) +#endif /* #if !defined(UNUSED) */ + +#if defined(MLEK_LOG_ENABLE) #ifdef __cplusplus extern "C" { @@ -24,29 +31,26 @@ extern "C" { #include #include -#define LOG_LEVEL_TRACE 0 -#define LOG_LEVEL_DEBUG 1 -#define LOG_LEVEL_INFO 2 -#define LOG_LEVEL_WARN 3 -#define LOG_LEVEL_ERROR 4 +#define MLEK_LOG_LEVEL_TRACE (0) +#define MLEK_LOG_LEVEL_DEBUG (1) +#define MLEK_LOG_LEVEL_INFO (2) +#define MLEK_LOG_LEVEL_WARN (3) +#define MLEK_LOG_LEVEL_ERROR (4) -#ifndef LOG_LEVEL -#define LOG_LEVEL LOG_LEVEL_INFO -#endif /*LOG_LEVEL*/ +#ifndef MLEK_LOG_LEVEL +#define MLEK_LOG_LEVEL MLEK_LOG_LEVEL_INFO +#endif /* MLEK_LOG_LEVEL */ -#if !defined(UNUSED) -#define UNUSED(x) ((void)(x)) -#endif /* #if !defined(UNUSED) */ -#if (LOG_LEVEL == LOG_LEVEL_TRACE) +#if (MLEK_LOG_LEVEL == MLEK_LOG_LEVEL_TRACE) #define trace(...) \ printf("TRACE - "); \ printf(__VA_ARGS__) #else #define trace(...) -#endif /* LOG_LEVEL == LOG_LEVEL_TRACE */ +#endif /* LOG_LEVEL == MLEK_LOG_LEVEL_TRACE */ -#if (LOG_LEVEL <= LOG_LEVEL_DEBUG) +#if (MLEK_LOG_LEVEL <= MLEK_LOG_LEVEL_DEBUG) #define debug(...) \ printf("DEBUG - "); \ printf(__VA_ARGS__) @@ -54,7 +58,7 @@ extern "C" { #define debug(...) #endif /* LOG_LEVEL > LOG_LEVEL_TRACE */ -#if (LOG_LEVEL <= LOG_LEVEL_INFO) +#if (MLEK_LOG_LEVEL <= MLEK_LOG_LEVEL_INFO) #define info(...) \ printf("INFO - "); \ printf(__VA_ARGS__) @@ -62,7 +66,7 @@ extern "C" { #define info(...) #endif /* LOG_LEVEL > LOG_LEVEL_DEBUG */ -#if (LOG_LEVEL <= LOG_LEVEL_WARN) +#if (MLEK_LOG_LEVEL <= MLEK_LOG_LEVEL_WARN) #define warn(...) \ printf("WARN - "); \ printf(__VA_ARGS__) @@ -70,7 +74,7 @@ extern "C" { #define warn(...) #endif /* LOG_LEVEL > LOG_LEVEL_INFO */ -#if (LOG_LEVEL <= LOG_LEVEL_ERROR) +#if (MLEK_LOG_LEVEL <= MLEK_LOG_LEVEL_ERROR) #define printf_err(...) \ printf("ERROR - "); \ printf(__VA_ARGS__) @@ -82,4 +86,32 @@ extern "C" { } #endif -#endif /* ML_EMBEDDED_CORE_LOG_H */ +#else /* defined(MLEK_LOG_ENABLE) */ + +/** + * Logging macros in this file will not be used. + * Provide stubs if the definitions have not been overridden externally. + */ +#if !defined(trace) + #define trace(...) +#endif + +#if !defined(debug) + #define debug(...) +#endif + +#if !defined(info) + #define info(...) +#endif + +#if !defined(warn) + #define warn(...) +#endif + +#if !defined(printf_err) + #define printf_err(...) +#endif + +#endif /* defined(MLEK_LOG_ENABLE) */ + +#endif /* MLEK_BASIC_LOGGER_H */ diff --git a/source/math/CMakeLists.txt b/source/math/CMakeLists.txt index 009389c6d3675126eb5f07e64b3db15ca7fa4f40..f6003f89c7891a1ab5049175f2df96702b605236 100644 --- a/source/math/CMakeLists.txt +++ b/source/math/CMakeLists.txt @@ -32,10 +32,16 @@ target_sources(arm_math target_include_directories(arm_math PUBLIC include) -target_link_libraries(arm_math PRIVATE log) +target_link_libraries(arm_math PRIVATE mlek_log) if (${CMAKE_CROSSCOMPILING}) - include(cmsis-dsp) + # If CMSIS-DSP target doesn't exist, include the MLEK default + # configuration. An external project that wants to re-use this + # module is expected to declare this target before including + # this project + if (NOT TARGET arm::cmsis-dsp) + include(cmsis-dsp) + endif() target_link_libraries(arm_math PUBLIC arm::cmsis-dsp) endif () diff --git a/source/profiler/CMakeLists.txt b/source/profiler/CMakeLists.txt index 5c0a433f7b5a0b4090436fb086eac80612df8623..f537c62b7c5283da1b7ebcce90f506dd0281d94f 100644 --- a/source/profiler/CMakeLists.txt +++ b/source/profiler/CMakeLists.txt @@ -1,5 +1,6 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates +# SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or its +# affiliates # SPDX-License-Identifier: Apache-2.0 # # Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,7 +33,7 @@ target_sources(profiler target_include_directories(profiler PUBLIC include) # Profiling API depends on the logging interface and the HAL library. -target_link_libraries(profiler PRIVATE log hal) +target_link_libraries(profiler PRIVATE hal mlek_log) # Display status message(STATUS "CMAKE_CURRENT_SOURCE_DIR: " ${CMAKE_CURRENT_SOURCE_DIR}) diff --git a/source/use_case/ad/include/UseCaseHandler.hpp b/source/use_case/ad/include/UseCaseHandler.hpp index 57d2f1a2728266f8c2a3c41ddc63182e489b117e..b4bca10ccd53526fb6d407fb5ac351eea7afea94 100644 --- a/source/use_case/ad/include/UseCaseHandler.hpp +++ b/source/use_case/ad/include/UseCaseHandler.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -29,4 +29,4 @@ namespace app { bool ClassifyVibrationHandler(ApplicationContext& ctx); } /* namespace app */ } /* namespace arm */ -#endif /* AD_EVT_HANDLER_H */ \ No newline at end of file +#endif /* AD_EVT_HANDLER_H */ diff --git a/source/use_case/ad/src/MainLoop.cc b/source/use_case/ad/src/MainLoop.cc index 672627babcf0448366a72d43a3e330577709ea75..061a1f339e27df88c6fad3c4f78c021fbc26dbeb 100644 --- a/source/use_case/ad/src/MainLoop.cc +++ b/source/use_case/ad/src/MainLoop.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2022, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021-2022, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -27,19 +27,20 @@ namespace app { extern uint8_t* GetModelPointer(); extern size_t GetModelLen(); } /* namespace ad */ - static uint8_t tensorArena[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; + static uint8_t activationBuf[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; } /* namespace app */ } /* namespace arm */ void MainLoop() { - arm::app::AdModel model; /* Model wrapper object. */ + arm::app::fwk::tflm::AdModel model; /* Model wrapper object. */ + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::ad::GetModelPointer(), + arm::app::ad::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::activationBuf, + sizeof(arm::app::activationBuf)}; /* Load the model. */ - if (!model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::ad::GetModelPointer(), - arm::app::ad::GetModelLen())) { + if (!model.Init(computeMem, modelMem)) { printf_err("failed to initialise model\n"); return; } @@ -49,7 +50,7 @@ void MainLoop() arm::app::Profiler profiler{"ad"}; caseContext.Set("profiler", profiler); - caseContext.Set("model", model); + caseContext.Set("model", model); caseContext.Set("frameLength", arm::app::ad::g_FrameLength); caseContext.Set("frameStride", arm::app::ad::g_FrameStride); caseContext.Set("scoreThreshold", arm::app::ad::g_ScoreThreshold); diff --git a/source/use_case/ad/src/UseCaseHandler.cc b/source/use_case/ad/src/UseCaseHandler.cc index 15da43df45c0beb86a382795aaab0ccec3fdca02..31f0cf4086a8e49b1cb0656aad0e74f35e6c4c60 100644 --- a/source/use_case/ad/src/UseCaseHandler.cc +++ b/source/use_case/ad/src/UseCaseHandler.cc @@ -18,10 +18,11 @@ #include "UseCaseHandler.hpp" #include "AdMelSpectrogram.hpp" +#include "AdModel.hpp" #include "AdProcessing.hpp" #include "AudioUtils.hpp" -#include "UseCaseCommonUtils.hpp" #include "ImageUtils.hpp" +#include "UseCaseCommonUtils.hpp" #include "hal.h" #include "log_macros.h" @@ -43,7 +44,7 @@ namespace app { constexpr uint32_t dataPsnTxtInfStartX = 20; constexpr uint32_t dataPsnTxtInfStartY = 40; - auto& model = ctx.Get("model"); + auto& model = ctx.Get("model"); if (!model.IsInited()) { printf_err("Model is not initialised! Terminating processing.\n"); return false; @@ -55,15 +56,20 @@ namespace app { const auto scoreThreshold = ctx.Get("scoreThreshold"); const auto trainingMean = ctx.Get("trainingMean"); - TfLiteTensor* outputTensor = model.GetOutputTensor(0); - TfLiteTensor* inputTensor = model.GetInputTensor(0); + auto outputTensor = model.GetOutputTensor(0); + auto inputTensor = model.GetInputTensor(0); - if (!inputTensor->dims) { + if (inputTensor->Shape().empty()) { printf_err("Invalid input tensor dims\n"); return false; } - AdPreProcess preProcess{inputTensor, melSpecFrameLength, melSpecFrameStride, trainingMean}; + AdPreProcess preProcess{inputTensor, + fwk::tflm::AdModel::ms_inputRowsIdx, + fwk::tflm::AdModel::ms_inputColsIdx, + melSpecFrameLength, + melSpecFrameStride, + trainingMean}; AdPostProcess postProcess{outputTensor}; uint32_t machineOutputIndex = 0; /* default sample */ diff --git a/source/use_case/ad/usecase.cmake b/source/use_case/ad/usecase.cmake index 6ea671ed2ff0ddad590e35be85c0beb846c81fe7..5ad725246e2aedc691675cf0b91f16840da83e82 100644 --- a/source/use_case/ad/usecase.cmake +++ b/source/use_case/ad/usecase.cmake @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its +# SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its # affiliates # SPDX-License-Identifier: Apache-2.0 # @@ -16,6 +16,15 @@ # limitations under the License. #---------------------------------------------------------------------------- +# Specify the ML frameworks the use case supports +set(${use_case}_ML_FRAMEWORK "TensorFlowLiteMicro") +if (NOT ${use_case}_ML_FRAMEWORK STREQUAL ${ML_FRAMEWORK}) + set(${use_case}_supports_${ML_FRAMEWORK} OFF) + return() +endif () + +set(${use_case}_supports_${ML_FRAMEWORK} ON) + # Append the API to use for this use case list(APPEND ${use_case}_API_LIST "ad") @@ -76,13 +85,13 @@ set(EXTRA_MODEL_CODE "extern const float g_TrainingMean = -30" ) -USER_OPTION(${use_case}_MODEL_TFLITE_PATH "NN models file to be used in the evaluation application. Model files must be in tflite format." +USER_OPTION(${use_case}_MODEL_PATH "NN models file to be used in the evaluation application. Model files must be in tflite format." ${DEFAULT_MODEL_PATH} FILEPATH) # Generate model file -generate_tflite_code( - MODEL_PATH ${${use_case}_MODEL_TFLITE_PATH} +generate_model_code( + MODEL_PATH ${${use_case}_MODEL_PATH} DESTINATION ${SRC_GEN_DIR} EXPRESSIONS ${EXTRA_MODEL_CODE} NAMESPACE "arm" "app" "ad") diff --git a/source/use_case/asr/src/MainLoop.cc b/source/use_case/asr/src/MainLoop.cc index 9679accbdc0cc0b9f9fd9e927284270c9eaa260e..82609876a02a32a5286d291c02f1ee3829857253 100644 --- a/source/use_case/asr/src/MainLoop.cc +++ b/source/use_case/asr/src/MainLoop.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -25,26 +25,30 @@ namespace arm { namespace app { - static uint8_t tensorArena[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; + static uint8_t activationBuf[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; + namespace asr { extern uint8_t* GetModelPointer(); extern size_t GetModelLen(); } /* namespace asr */ + } /* namespace app */ } /* namespace arm */ /** @brief Verify input and output tensor are of certain min dimensions. */ -static bool VerifyTensorDimensions(const arm::app::Model& model); +static bool VerifyTensorDimensions(const arm::app::fwk::iface::Model& model); void MainLoop() { - arm::app::Wav2LetterModel model; /* Model wrapper object. */ + arm::app::fwk::tflm::Wav2LetterModel model; /* Model wrapper object. */ + + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::asr::GetModelPointer(), + arm::app::asr::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::activationBuf, + sizeof(arm::app::activationBuf)}; /* Load the model. */ - if (!model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::asr::GetModelPointer(), - arm::app::asr::GetModelLen())) { + if (!model.Init(computeMem, modelMem)) { printf_err("Failed to initialise model\n"); return; } else if (!VerifyTensorDimensions(model)) { @@ -56,11 +60,16 @@ void MainLoop() arm::app::ApplicationContext caseContext; std::vector labels; GetLabelsVector(labels); - arm::app::AsrClassifier classifier; /* Classifier wrapper object. */ + + arm::app::AsrClassifier classifier{ + arm::app::fwk::tflm::Wav2LetterModel::ms_inputRowsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_inputColsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputColsIdx}; /* Classifier wrapper object. */ arm::app::Profiler profiler{"asr"}; caseContext.Set("profiler", profiler); - caseContext.Set("model", model); + caseContext.Set("model", model); caseContext.Set("frameLength", arm::app::asr::g_FrameLength); caseContext.Set("frameStride", arm::app::asr::g_FrameStride); caseContext.Set("scoreThreshold", arm::app::asr::g_ScoreThreshold); /* Score threshold. */ @@ -73,23 +82,26 @@ void MainLoop() executionSuccessful ? "successfully" : "with failure"); } -static bool VerifyTensorDimensions(const arm::app::Model& model) +static bool VerifyTensorDimensions(const arm::app::fwk::iface::Model& model) { /* Populate tensor related parameters. */ - TfLiteTensor* inputTensor = model.GetInputTensor(0); - if (!inputTensor->dims) { + auto inputTensor = model.GetInputTensor(0); + if (inputTensor->Shape().empty()) { printf_err("Invalid input tensor dims\n"); return false; - } else if (inputTensor->dims->size < 3) { + } + if (inputTensor->Shape().size() < 3) { printf_err("Input tensor dimension should be >= 3\n"); return false; } - TfLiteTensor* outputTensor = model.GetOutputTensor(0); - if (!outputTensor->dims) { + auto outputTensor = model.GetOutputTensor(0); + if (outputTensor->Shape().empty()) { printf_err("Invalid output tensor dims\n"); return false; - } else if (outputTensor->dims->size < 3) { + } + + if (outputTensor->Shape().size() < 3) { printf_err("Output tensor dimension should be >= 3\n"); return false; } diff --git a/source/use_case/asr/src/UseCaseHandler.cc b/source/use_case/asr/src/UseCaseHandler.cc index f7f4bbef58242afa542a33f6045fb0c1d5cf76f7..01eac3d3a3333fe852cf6d579125f9f7f2a526e7 100644 --- a/source/use_case/asr/src/UseCaseHandler.cc +++ b/source/use_case/asr/src/UseCaseHandler.cc @@ -42,7 +42,7 @@ namespace app { /* ASR inference handler. */ bool ClassifyAudioHandler(ApplicationContext& ctx) { - auto& model = ctx.Get("model"); + auto& model = ctx.Get("model"); auto& profiler = ctx.Get("profiler"); auto mfccFrameLen = ctx.Get("frameLength"); auto mfccFrameStride = ctx.Get("frameStride"); @@ -56,14 +56,14 @@ namespace app { return false; } - TfLiteTensor* inputTensor = model.GetInputTensor(0); - TfLiteTensor* outputTensor = model.GetOutputTensor(0); + const auto inputTensor = model.GetInputTensor(0); + const auto outputTensor = model.GetOutputTensor(0); /* Get input shape. Dimensions of the tensor should have been verified by * the callee. */ - TfLiteIntArray* inputShape = model.GetInputShape(0); + auto inputShape = model.GetInputShape(0); - const uint32_t inputRowsSize = inputShape->data[Wav2LetterModel::ms_inputRowsIdx]; + const uint32_t inputRowsSize = inputShape[fwk::tflm::Wav2LetterModel::ms_inputRowsIdx]; const uint32_t inputInnerLen = inputRowsSize - (2 * inputCtxLen); /* Audio data stride corresponds to inputInnerLen feature vectors. */ @@ -74,21 +74,21 @@ namespace app { const float secondsPerSample = (1.0 / audio::Wav2LetterMFCC::ms_defaultSamplingFreq); /* Set up pre and post-processing objects. */ - AsrPreProcess preProcess = AsrPreProcess(inputTensor, - Wav2LetterModel::ms_numMfccFeatures, - inputShape->data[Wav2LetterModel::ms_inputRowsIdx], - mfccFrameLen, - mfccFrameStride); + AsrPreProcess preProcess = + AsrPreProcess(inputTensor, + fwk::tflm::Wav2LetterModel::ms_numMfccFeatures, + inputShape[fwk::tflm::Wav2LetterModel::ms_inputRowsIdx], + mfccFrameLen, + mfccFrameStride); std::vector singleInfResult; - const uint32_t outputCtxLen = AsrPostProcess::GetOutputContextLen(model, inputCtxLen); - AsrPostProcess postProcess = AsrPostProcess(outputTensor, + AsrPostProcess postProcess = AsrPostProcess(model, ctx.Get("classifier"), ctx.Get&>("labels"), singleInfResult, - outputCtxLen, - Wav2LetterModel::ms_blankTokenIdx, - Wav2LetterModel::ms_outputRowsIdx); + inputCtxLen, + fwk::tflm::Wav2LetterModel::ms_blankTokenIdx, + fwk::tflm::Wav2LetterModel::ms_outputRowsIdx); hal_audio_init(); if (!hal_audio_configure(HAL_AUDIO_MODE_SINGLE_BURST, @@ -239,4 +239,4 @@ namespace app { } } /* namespace app */ -} /* namespace arm */ \ No newline at end of file +} /* namespace arm */ diff --git a/source/use_case/asr/usecase.cmake b/source/use_case/asr/usecase.cmake index 4843a850e58db3a0021c8ea63c1e3404e17b2d6e..dde4f2ba778f8a3cca83c48156264210bffb5bf1 100644 --- a/source/use_case/asr/usecase.cmake +++ b/source/use_case/asr/usecase.cmake @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its +# SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its # affiliates # SPDX-License-Identifier: Apache-2.0 # @@ -15,6 +15,16 @@ # See the License for the specific language governing permissions and # limitations under the License. #---------------------------------------------------------------------------- + +# Specify the ML frameworks the use case supports +set(${use_case}_ML_FRAMEWORK "TensorFlowLiteMicro") +if (NOT ${use_case}_ML_FRAMEWORK STREQUAL ${ML_FRAMEWORK}) + set(${use_case}_supports_${ML_FRAMEWORK} OFF) + return() +endif () + +set(${use_case}_supports_${ML_FRAMEWORK} ON) + # Append the API to use for this use case list(APPEND ${use_case}_API_LIST "asr") @@ -89,14 +99,14 @@ set(EXTRA_MODEL_CODE "extern const float g_ScoreThreshold = ${${use_case}_MODEL_SCORE_THRESHOLD}" ) -USER_OPTION(${use_case}_MODEL_TFLITE_PATH "NN models file to be used in the evaluation application. Model files must be in tflite format." +USER_OPTION(${use_case}_MODEL_PATH "NN models file to be used in the evaluation application. Model files must be in tflite format." ${DEFAULT_MODEL_PATH} FILEPATH ) # Generate model file -generate_tflite_code( - MODEL_PATH ${${use_case}_MODEL_TFLITE_PATH} +generate_model_code( + MODEL_PATH ${${use_case}_MODEL_PATH} DESTINATION ${SRC_GEN_DIR} EXPRESSIONS ${EXTRA_MODEL_CODE} NAMESPACE "arm" "app" "asr") diff --git a/source/use_case/img_class/src/MainLoop.cc b/source/use_case/img_class/src/MainLoop.cc index 271059b2f54242cf373c4f06ce709d9c36c8f0c0..0c9efa76958b0c3755665d3d98f4d228634b1edb 100644 --- a/source/use_case/img_class/src/MainLoop.cc +++ b/source/use_case/img_class/src/MainLoop.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2022, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021-2022, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -15,17 +15,17 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "hal.h" /* Brings in platform definitions. */ -#include "Classifier.hpp" /* Classifier. */ -#include "Labels.hpp" /* For label strings. */ -#include "MobileNetModel.hpp" /* Model class for running inference. */ -#include "UseCaseHandler.hpp" /* Handlers for different user options. */ -#include "UseCaseCommonUtils.hpp" /* Utils functions. */ -#include "BufAttributes.hpp" /* Buffer attributes to be applied */ + +#include "BufAttributes.hpp" /* Buffer attributes to be applied */ +#include "Classifier.hpp" /* Classifier. */ +#include "Labels.hpp" /* For label strings. */ +#include "MobileNetModel.hpp" /* Model class for running inference. */ +#include "UseCaseCommonUtils.hpp" /* Utils functions. */ +#include "UseCaseHandler.hpp" /* Handlers for different user options. */ namespace arm { namespace app { - static uint8_t tensorArena[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; + static uint8_t activationBuf[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; namespace img_class { extern uint8_t* GetModelPointer(); extern size_t GetModelLen(); @@ -35,15 +35,23 @@ namespace app { using ImgClassClassifier = arm::app::Classifier; +/** Based on ML framework, set up the model namespace. */ +#if defined(MLEK_FWK_TFLM) +using arm::app::fwk::tflm::MobileNetModel; +#elif defined(MLEK_FWK_EXECUTORCH) +using arm::app::fwk::et::MobileNetModel; +#endif /** MLEK_FWK_TFLM or MLEK_FWK_EXECUTORCH */ + void MainLoop() { - arm::app::MobileNetModel model; /* Model wrapper object. */ + MobileNetModel model; /* Model wrapper object. */ + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::img_class::GetModelPointer(), + arm::app::img_class::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::activationBuf, + sizeof(arm::app::activationBuf)}; /* Load the model. */ - if (!model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::img_class::GetModelPointer(), - arm::app::img_class::GetModelLen())) { + if (!model.Init(computeMem, modelMem)) { printf_err("Failed to initialise model\n"); return; } @@ -53,7 +61,7 @@ void MainLoop() arm::app::Profiler profiler{"img_class"}; caseContext.Set("profiler", profiler); - caseContext.Set("model", model); + caseContext.Set("model", model); ImgClassClassifier classifier; /* Classifier wrapper object. */ caseContext.Set("classifier", classifier); diff --git a/source/use_case/img_class/src/UseCaseHandler.cc b/source/use_case/img_class/src/UseCaseHandler.cc index 4e69c7e208511f526e39a50237cd6a92c1da616e..b886fc9603a9e1ba7e8e771ae4a100b712ffdcc4 100644 --- a/source/use_case/img_class/src/UseCaseHandler.cc +++ b/source/use_case/img_class/src/UseCaseHandler.cc @@ -32,11 +32,18 @@ using ImgClassClassifier = arm::app::Classifier; namespace arm { namespace app { + /** Based on ML framework, set up the model namespace. */ +#if defined(MLEK_FWK_TFLM) + using fwk::tflm::MobileNetModel; +#elif defined(MLEK_FWK_EXECUTORCH) + using fwk::et::MobileNetModel; +#endif /** MLEK_FWK_TFLM or MLEK_FWK_EXECUTORCH */ + /* Image classification inference handler. */ bool ClassifyImageHandler(ApplicationContext& ctx) { auto& profiler = ctx.Get("profiler"); - auto& model = ctx.Get("model"); + auto& model = ctx.Get("model"); constexpr uint32_t dataPsnImgDownscaleFactor = 2; constexpr uint32_t dataPsnImgStartX = 10; @@ -50,24 +57,21 @@ namespace app { return false; } - TfLiteTensor* inputTensor = model.GetInputTensor(0); - TfLiteTensor* outputTensor = model.GetOutputTensor(0); - if (!inputTensor->dims) { - printf_err("Invalid input tensor dims\n"); - return false; - } else if (inputTensor->dims->size < 4) { + auto inputTensor = model.GetInputTensor(0); + auto outputTensor = model.GetOutputTensor(0); + auto inputShape = inputTensor->Shape(); + if (inputShape.size() < 4) { printf_err("Input tensor dimension should be = 4\n"); return false; } /* Get input shape for displaying the image. */ - TfLiteIntArray* inputShape = model.GetInputShape(0); - const uint32_t nCols = inputShape->data[arm::app::MobileNetModel::ms_inputColsIdx]; - const uint32_t nRows = inputShape->data[arm::app::MobileNetModel::ms_inputRowsIdx]; - const uint32_t nChannels = inputShape->data[arm::app::MobileNetModel::ms_inputChannelsIdx]; + const uint32_t nCols = inputShape[MobileNetModel::ms_inputColsIdx]; + const uint32_t nRows = inputShape[MobileNetModel::ms_inputRowsIdx]; + const uint32_t nChannels = inputShape[MobileNetModel::ms_inputChannelsIdx]; - /* Set up pre and post-processing. */ - ImgClassPreProcess preProcess = ImgClassPreProcess(inputTensor, model.IsDataSigned()); + /* Set up pre- and post-processing. */ + auto preProcess = ImgClassPreProcess(inputTensor); std::vector results; ImgClassPostProcess postProcess = @@ -116,7 +120,7 @@ namespace app { str_inf.c_str(), str_inf.size(), dataPsnTxtInfStartX, dataPsnTxtInfStartY, false); const size_t imgSz = - inputTensor->bytes < capturedFrameSize ? inputTensor->bytes : capturedFrameSize; + inputTensor->Bytes() < capturedFrameSize ? inputTensor->Bytes() : capturedFrameSize; /* Run the pre-processing, inference and post-processing. */ if (!preProcess.DoPreProcess(imgSrc, imgSz)) { @@ -130,7 +134,7 @@ namespace app { } if (!postProcess.DoPostProcess()) { - printf_err("Post-processing failed."); + printf_err("Post-processing failed.\n"); return false; } diff --git a/source/use_case/img_class/usecase.cmake b/source/use_case/img_class/usecase.cmake index 896a47473f4c741fd54c914cd17886ba1f372490..d21b9397dd408fc82a3d38ef89a9a72690d466c6 100644 --- a/source/use_case/img_class/usecase.cmake +++ b/source/use_case/img_class/usecase.cmake @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its +# SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its # affiliates # SPDX-License-Identifier: Apache-2.0 # @@ -15,6 +15,17 @@ # See the License for the specific language governing permissions and # limitations under the License. #---------------------------------------------------------------------------- + +# Specify the ML frameworks the use case supports +set(${use_case}_ML_FRAMEWORK "TensorFlowLiteMicro;ExecuTorch") +if (NOT ${ML_FRAMEWORK} IN_LIST ${use_case}_ML_FRAMEWORK) + message(STATUS "${use_case} does not support ${ML_FRAMEWORK} framework") + set(${use_case}_supports_${ML_FRAMEWORK} OFF) + return() +endif () + +set(${use_case}_supports_${ML_FRAMEWORK} ON) + # Append the API to use for this use case list(APPEND ${use_case}_API_LIST "img_class") @@ -24,41 +35,58 @@ USER_OPTION(${use_case}_IMAGE_SIZE "Square image size in pixels. Images will be 224 STRING) +if (${ML_FRAMEWORK} STREQUAL "TensorFlowLiteMicro") + set(DEFAULT_ACTIVATION_BUF_SZ 0x00200000) + set(LABELS_TXT_FILE ${CMAKE_CURRENT_SOURCE_DIR}/resources/${use_case}/labels/labels_mobilenet_v2_1.0_224.txt) + + if (ETHOS_U_NPU_ENABLED) + set(DEFAULT_MODEL_PATH ${DEFAULT_MODEL_DIR}/mobilenet_v2_1.0_224_INT8_vela_${ETHOS_U_NPU_CONFIG_ID}.tflite) + else() + set(DEFAULT_MODEL_PATH ${DEFAULT_MODEL_DIR}/mobilenet_v2_1.0_224_INT8.tflite) + endif() +elseif(${ML_FRAMEWORK} STREQUAL "ExecuTorch") + set(LABELS_TXT_FILE + ${CMAKE_CURRENT_SOURCE_DIR}/resources/${use_case}/labels/labels_mobilenet_v2_1.IMAGENET1K_V2.txt) + + if (ETHOS_U_NPU_ENABLED) + string(TOLOWER ${ETHOSU_TARGET_NPU_CONFIG} _NPU_CFG_ID) + set(DEFAULT_MODEL_PATH ${DEFAULT_MODEL_DIR}/mv2_arm_delegate_${_NPU_CFG_ID}.pte) + set(DEFAULT_ACTIVATION_BUF_SZ 0x00200000) + else() + set(DEFAULT_MODEL_PATH ${DEFAULT_MODEL_DIR}/mv2_arm_TOSA.pte) + set(DEFAULT_ACTIVATION_BUF_SZ 0x00C00000) + endif() +endif() + +USER_OPTION(${use_case}_MODEL_PATH "Neural network model file to be used in the evaluation application." + ${DEFAULT_MODEL_PATH} + FILEPATH) + +USER_OPTION(${use_case}_ACTIVATION_BUF_SZ "Activation buffer size for the chosen model" + ${DEFAULT_ACTIVATION_BUF_SZ} + STRING) + USER_OPTION(${use_case}_LABELS_TXT_FILE "Labels' txt file for the chosen model" - ${CMAKE_CURRENT_SOURCE_DIR}/resources/${use_case}/labels/labels_mobilenet_v2_1.0_224.txt - FILEPATH) + ${LABELS_TXT_FILE} + FILEPATH) # Generate input files generate_images_code("${${use_case}_FILE_PATH}" - ${SAMPLES_GEN_DIR} - "${${use_case}_IMAGE_SIZE}") + ${SAMPLES_GEN_DIR} + "${${use_case}_IMAGE_SIZE}") # Generate labels file set(${use_case}_LABELS_CPP_FILE Labels) generate_labels_code( - INPUT "${${use_case}_LABELS_TXT_FILE}" - DESTINATION_SRC ${SRC_GEN_DIR} - DESTINATION_HDR ${INC_GEN_DIR} - OUTPUT_FILENAME "${${use_case}_LABELS_CPP_FILE}" + INPUT "${${use_case}_LABELS_TXT_FILE}" + DESTINATION_SRC ${SRC_GEN_DIR} + DESTINATION_HDR ${INC_GEN_DIR} + OUTPUT_FILENAME "${${use_case}_LABELS_CPP_FILE}" ) -USER_OPTION(${use_case}_ACTIVATION_BUF_SZ "Activation buffer size for the chosen model" - 0x00200000 - STRING) - -if (ETHOS_U_NPU_ENABLED) - set(DEFAULT_MODEL_PATH ${DEFAULT_MODEL_DIR}/mobilenet_v2_1.0_224_INT8_vela_${ETHOS_U_NPU_CONFIG_ID}.tflite) -else() - set(DEFAULT_MODEL_PATH ${DEFAULT_MODEL_DIR}/mobilenet_v2_1.0_224_INT8.tflite) -endif() - -USER_OPTION(${use_case}_MODEL_TFLITE_PATH "NN models file to be used in the evaluation application. Model files must be in tflite format." - ${DEFAULT_MODEL_PATH} - FILEPATH - ) # Generate model file -generate_tflite_code( - MODEL_PATH ${${use_case}_MODEL_TFLITE_PATH} +generate_model_code( + MODEL_PATH ${${use_case}_MODEL_PATH} DESTINATION ${SRC_GEN_DIR} NAMESPACE "arm" "app" "img_class") diff --git a/source/use_case/inference_runner/include/UseCaseHandler.hpp b/source/use_case/inference_runner/include/UseCaseHandler.hpp index 9ff73cda41550b9ccfbd91b44dc47cadcd107292..7155a16ed27c90186bedf9915eb6994344bfc1af 100644 --- a/source/use_case/inference_runner/include/UseCaseHandler.hpp +++ b/source/use_case/inference_runner/include/UseCaseHandler.hpp @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,4 +32,4 @@ namespace app { } /* namespace app */ } /* namespace arm */ -#endif /* INF_RUNNER_EVT_HANDLER_HPP */ \ No newline at end of file +#endif /* INF_RUNNER_EVT_HANDLER_HPP */ diff --git a/source/use_case/inference_runner/src/MainLoop.cc b/source/use_case/inference_runner/src/MainLoop.cc index 5caf1df0129ea38e322e3aa2f2ee7275fe9a1f52..05fade581481c37c312e4811af3d232e150be4cd 100644 --- a/source/use_case/inference_runner/src/MainLoop.cc +++ b/source/use_case/inference_runner/src/MainLoop.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -24,7 +24,7 @@ namespace arm { namespace app { - static uint8_t tensorArena[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; + static uint8_t activationBuf[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; namespace inference_runner { #if defined(DYNAMIC_MODEL_BASE) && defined(DYNAMIC_MODEL_SIZE) @@ -54,13 +54,14 @@ extern size_t GetModelLen(); void MainLoop() { - arm::app::TestModel model; /* Model wrapper object. */ + arm::app::fwk::tflm::TestModel model; /* Model wrapper object. */ + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::inference_runner::GetModelPointer(), + arm::app::inference_runner::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::activationBuf, + sizeof(arm::app::activationBuf)}; /* Load the model. */ - if (!model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::inference_runner::GetModelPointer(), - arm::app::inference_runner::GetModelLen())) { + if (!model.Init(computeMem, modelMem)) { printf_err("Failed to initialise model\n"); return; } @@ -70,7 +71,7 @@ void MainLoop() arm::app::Profiler profiler{"inference_runner"}; caseContext.Set("profiler", profiler); - caseContext.Set("model", model); + caseContext.Set("model", model); /* Loop. */ if (RunInferenceHandler(caseContext)) { diff --git a/source/use_case/inference_runner/src/UseCaseHandler.cc b/source/use_case/inference_runner/src/UseCaseHandler.cc index c010f42b00b3a1fa6a3b5e5ee48b642aceac31b6..f876f88a2aaf7515e29e23a9736c18d40d9a0d6b 100644 --- a/source/use_case/inference_runner/src/UseCaseHandler.cc +++ b/source/use_case/inference_runner/src/UseCaseHandler.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -26,9 +26,9 @@ namespace arm { namespace app { -static void PopulateInputTensor(const Model& model) -{ - const size_t numInputs = model.GetNumInputs(); + static void PopulateInputTensor(const fwk::iface::Model& model) + { + const size_t numInputs = model.GetNumInputs(); #if defined(DYNAMIC_IFM_BASE) && defined(DYNAMIC_IFM_SIZE) size_t curInputIdx = 0; @@ -37,14 +37,14 @@ static void PopulateInputTensor(const Model& model) /* Populate each input tensor with random data. */ for (size_t inputIndex = 0; inputIndex < numInputs; inputIndex++) { - TfLiteTensor* inputTensor = model.GetInputTensor(inputIndex); + auto inputTensor = model.GetInputTensor(inputIndex); - debug("Populating input tensor %zu@%p\n", inputIndex, inputTensor); - debug("Total input size to be populated: %zu\n", inputTensor->bytes); + debug("Populating input tensor %zu@%p\n", inputIndex, inputTensor->GetData()); + debug("Total input size to be populated: %zu\n", inputTensor->Bytes()); - if (inputTensor->bytes > 0) { + if (inputTensor->Bytes() > 0) { - uint8_t* tData = tflite::GetTensorData(inputTensor); + uint8_t* tData = inputTensor->GetData(); #if defined(DYNAMIC_IFM_BASE) && defined(DYNAMIC_IFM_SIZE) if (curInputIdx + inputTensor->bytes > DYNAMIC_IFM_SIZE) { @@ -56,7 +56,7 @@ static void PopulateInputTensor(const Model& model) curInputIdx += inputTensor->bytes; #else /* defined(DYNAMIC_IFM_BASE) */ /* Create a random input. */ - for (size_t j = 0; j < inputTensor->bytes; ++j) { + for (size_t j = 0; j < inputTensor->Bytes(); ++j) { tData[j] = static_cast(std::rand() & 0xFF); } #endif /* defined(DYNAMIC_IFM_BASE) && defined(DYNAMIC_IFM_SIZE) */ @@ -67,7 +67,7 @@ static void PopulateInputTensor(const Model& model) info("%d input tensor/s populated with %d bytes with data read from 0x%08x\n", numInputs, curInputIdx, DYNAMIC_IFM_BASE); #endif /* defined(DYNAMIC_IFM_BASE) */ -} + } #if defined (DYNAMIC_OFM_BASE) && defined(DYNAMIC_OFM_SIZE) static void PopulateDynamicOfm(const Model& model) @@ -78,16 +78,16 @@ static void PopulateDynamicOfm(const Model& model) uint8_t* const dstPtr = reinterpret_cast(DYNAMIC_OFM_BASE); for (size_t outputIdx = 0; outputIdx < numOutputs; ++outputIdx) { - TfLiteTensor* outputTensor = model.GetOutputTensor(outputIdx); - uint8_t* const tData = tflite::GetTensorData(outputTensor); + auto* outputTensor = model.GetOutputTensor(outputIdx); + uint8_t* const tData = outputTensor->GetData(); if (tData && outputTensor->bytes > 0) { - if (curCopyIdx + outputTensor->bytes > DYNAMIC_OFM_SIZE) { + if (curCopyIdx + outputTensor->Bytes() > DYNAMIC_OFM_SIZE) { printf_err("OFM reserved buffer size insufficient\n"); return; } - memcpy(dstPtr + curCopyIdx, tData, outputTensor->bytes); - curCopyIdx += outputTensor->bytes; + memcpy(dstPtr + curCopyIdx, tData, outputTensor->Bytes()); + curCopyIdx += outputTensor->Bytes(); } } @@ -117,7 +117,7 @@ static void DumpOutputs(const Model& model, const char* message) bool RunInferenceHandler(ApplicationContext& ctx) { auto& profiler = ctx.Get("profiler"); - auto& model = ctx.Get("model"); + auto& model = ctx.Get("model"); constexpr uint32_t dataPsnTxtInfStartX = 150; constexpr uint32_t dataPsnTxtInfStartY = 40; diff --git a/source/use_case/inference_runner/usecase.cmake b/source/use_case/inference_runner/usecase.cmake index f945b25902cdb4be555be14395f2841c0f652393..37de3555f30d4b95236164fba9b80eb4b329b0b8 100644 --- a/source/use_case/inference_runner/usecase.cmake +++ b/source/use_case/inference_runner/usecase.cmake @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or +# SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or # its affiliates # SPDX-License-Identifier: Apache-2.0 # @@ -15,6 +15,16 @@ # See the License for the specific language governing permissions and # limitations under the License. #---------------------------------------------------------------------------- + +# Specify the ML frameworks the use case supports +set(${use_case}_ML_FRAMEWORK "TensorFlowLiteMicro") +if (NOT ${use_case}_ML_FRAMEWORK STREQUAL ${ML_FRAMEWORK}) + set(${use_case}_supports_${ML_FRAMEWORK} OFF) + return() +endif () + +set(${use_case}_supports_${ML_FRAMEWORK} ON) + # Append the API to use for this use case list(APPEND ${use_case}_API_LIST "inference_runner") @@ -63,13 +73,13 @@ if (${${use_case}_DYNAMIC_MEM_LOAD_ENABLED}) endif() else() - USER_OPTION(${use_case}_MODEL_TFLITE_PATH "NN models file to be used in the evaluation application. Model files must be in tflite format." + USER_OPTION(${use_case}_MODEL_PATH "NN models file to be used in the evaluation application. Model files must be in tflite format." ${DEFAULT_MODEL_PATH} FILEPATH) # Generate model file - generate_tflite_code( - MODEL_PATH ${${use_case}_MODEL_TFLITE_PATH} + generate_model_code( + MODEL_PATH ${${use_case}_MODEL_PATH} DESTINATION ${SRC_GEN_DIR} NAMESPACE "arm" "app" "inference_runner") endif() diff --git a/source/use_case/kws/include/UseCaseHandler.hpp b/source/use_case/kws/include/UseCaseHandler.hpp index 5f36714c83f246a7294ec7c4ca8cbbde21899cbf..ae212e7631d6965dd59583d256510ce673ea732f 100644 --- a/source/use_case/kws/include/UseCaseHandler.hpp +++ b/source/use_case/kws/include/UseCaseHandler.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -33,4 +33,4 @@ namespace app { } /* namespace app */ } /* namespace arm */ -#endif /* KWS_EVT_HANDLER_HPP */ \ No newline at end of file +#endif /* KWS_EVT_HANDLER_HPP */ diff --git a/source/use_case/kws/src/MainLoop.cc b/source/use_case/kws/src/MainLoop.cc index 2a1eae37bddbb31e3c8363697a61f3422ed5679c..a2a8d683c91db0f048a70b6e3c9896c6dbf697f9 100644 --- a/source/use_case/kws/src/MainLoop.cc +++ b/source/use_case/kws/src/MainLoop.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2022, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021-2022, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -26,7 +26,7 @@ namespace arm { namespace app { - static uint8_t tensorArena[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; + static uint8_t activationBuf[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; namespace kws { extern uint8_t* GetModelPointer(); extern size_t GetModelLen(); @@ -36,13 +36,15 @@ namespace app { void MainLoop() { - arm::app::MicroNetKwsModel model; /* Model wrapper object. */ + arm::app::fwk::tflm::MicroNetKwsModel model; /* Model wrapper object. */ + + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::kws::GetModelPointer(), + arm::app::kws::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::activationBuf, + sizeof(arm::app::activationBuf)}; /* Load the model. */ - if (!model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::kws::GetModelPointer(), - arm::app::kws::GetModelLen())) { + if (!model.Init(computeMem, modelMem)) { printf_err("Failed to initialise model\n"); return; } @@ -52,7 +54,7 @@ void MainLoop() arm::app::Profiler profiler{"kws"}; caseContext.Set("profiler", profiler); - caseContext.Set("model", model); + caseContext.Set("model", model); caseContext.Set("frameLength", arm::app::kws::g_FrameLength); caseContext.Set("frameStride", arm::app::kws::g_FrameStride); caseContext.Set("scoreThreshold", arm::app::kws::g_ScoreThreshold); /* Normalised score threshold. */ diff --git a/source/use_case/kws/src/UseCaseHandler.cc b/source/use_case/kws/src/UseCaseHandler.cc index f346909cd0a73189316962e4a32c08e2280cf495..e8da502ad422d2281d1c1d401dad1be44e611b77 100644 --- a/source/use_case/kws/src/UseCaseHandler.cc +++ b/source/use_case/kws/src/UseCaseHandler.cc @@ -43,7 +43,7 @@ namespace app { bool ClassifyAudioHandler(ApplicationContext& ctx) { auto& profiler = ctx.Get("profiler"); - auto& model = ctx.Get("model"); + auto& model = ctx.Get("model"); const auto mfccFrameLength = ctx.Get("frameLength"); const auto mfccFrameStride = ctx.Get("frameStride"); const auto scoreThreshold = ctx.Get("scoreThreshold"); @@ -51,9 +51,10 @@ namespace app { constexpr uint32_t dataPsnTxtInfStartX = 20; constexpr uint32_t dataPsnTxtInfStartY = 40; constexpr int minTensorDims = - static_cast((MicroNetKwsModel::ms_inputRowsIdx > MicroNetKwsModel::ms_inputColsIdx) - ? MicroNetKwsModel::ms_inputRowsIdx - : MicroNetKwsModel::ms_inputColsIdx); + static_cast((fwk::tflm::MicroNetKwsModel::ms_inputRowsIdx > + fwk::tflm::MicroNetKwsModel::ms_inputColsIdx) + ? fwk::tflm::MicroNetKwsModel::ms_inputRowsIdx + : fwk::tflm::MicroNetKwsModel::ms_inputColsIdx); if (!model.IsInited()) { printf_err("Model is not initialised! Terminating processing.\n"); @@ -61,21 +62,21 @@ namespace app { } /* Get Input and Output tensors for pre/post processing. */ - TfLiteTensor* inputTensor = model.GetInputTensor(0); - TfLiteTensor* outputTensor = model.GetOutputTensor(0); - if (!inputTensor->dims) { + auto inputTensor = model.GetInputTensor(0); + auto outputTensor = model.GetOutputTensor(0); + + const auto inputShape = inputTensor->Shape(); + if (inputShape.empty()) { printf_err("Invalid input tensor dims\n"); return false; - } else if (inputTensor->dims->size < minTensorDims) { + } else if (inputShape.size() < minTensorDims) { printf_err("Input tensor dimension should be >= %d\n", minTensorDims); return false; } /* Get input shape for feature extraction. */ - TfLiteIntArray* inputShape = model.GetInputShape(0); - const uint32_t numMfccFeatures = inputShape->data[MicroNetKwsModel::ms_inputColsIdx]; - const uint32_t numMfccFrames = - inputShape->data[arm::app::MicroNetKwsModel::ms_inputRowsIdx]; + const uint32_t numMfccFeatures = inputShape[fwk::tflm::MicroNetKwsModel::ms_inputColsIdx]; + const uint32_t numMfccFrames = inputShape[fwk::tflm::MicroNetKwsModel::ms_inputRowsIdx]; /* We expect to be sampling 1 second worth of data at a time. * NOTE: This is only used for time stamp calculation. */ diff --git a/source/use_case/kws/usecase.cmake b/source/use_case/kws/usecase.cmake index 8181b3d0ae1659388a8feaae2a118702475307b3..6504e8401ab3e36013218a4bf839701e4b8ef288 100644 --- a/source/use_case/kws/usecase.cmake +++ b/source/use_case/kws/usecase.cmake @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its +# SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its # affiliates # SPDX-License-Identifier: Apache-2.0 # @@ -15,6 +15,16 @@ # See the License for the specific language governing permissions and # limitations under the License. #---------------------------------------------------------------------------- + +# Specify the ML frameworks the use case supports +set(${use_case}_ML_FRAMEWORK "TensorFlowLiteMicro") +if (NOT ${use_case}_ML_FRAMEWORK STREQUAL ${ML_FRAMEWORK}) + set(${use_case}_supports_${ML_FRAMEWORK} OFF) + return() +endif () + +set(${use_case}_supports_${ML_FRAMEWORK} ON) + # Append the API to use for this use case list(APPEND ${use_case}_API_LIST "kws") @@ -88,13 +98,13 @@ set(EXTRA_MODEL_CODE "extern const float g_ScoreThreshold = ${${use_case}_MODEL_SCORE_THRESHOLD}" ) -USER_OPTION(${use_case}_MODEL_TFLITE_PATH "NN models file to be used in the evaluation application. Model files must be in tflite format." +USER_OPTION(${use_case}_MODEL_PATH "NN models file to be used in the evaluation application. Model files must be in tflite format." ${DEFAULT_MODEL_PATH} FILEPATH) # Generate model file -generate_tflite_code( - MODEL_PATH ${${use_case}_MODEL_TFLITE_PATH} +generate_model_code( + MODEL_PATH ${${use_case}_MODEL_PATH} DESTINATION ${SRC_GEN_DIR} EXPRESSIONS ${EXTRA_MODEL_CODE} NAMESPACE "arm" "app" "kws" diff --git a/source/use_case/kws_asr/src/MainLoop.cc b/source/use_case/kws_asr/src/MainLoop.cc index 97e4f58b229624411f4d32ac6aedd1b6da1a4c6d..3046f711374306270a1a55c9bfed9ff17d0c7724 100644 --- a/source/use_case/kws_asr/src/MainLoop.cc +++ b/source/use_case/kws_asr/src/MainLoop.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2022, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021-2022, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -38,35 +38,35 @@ namespace app { extern uint8_t* GetModelPointer(); extern size_t GetModelLen(); } /* namespace kws */ - static uint8_t tensorArena[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; + static uint8_t activationBuf[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; } /* namespace app */ } /* namespace arm */ /** @brief Verify input and output tensor are of certain min dimensions. */ -static bool VerifyTensorDimensions(const arm::app::Model& model); +static bool VerifyTensorDimensions(const arm::app::fwk::iface::Model& model); void MainLoop() { /* Model wrapper objects. */ - arm::app::MicroNetKwsModel kwsModel; - arm::app::Wav2LetterModel asrModel; - - /* Load the models. */ - if (!kwsModel.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::kws::GetModelPointer(), - arm::app::kws::GetModelLen())) { + arm::app::fwk::tflm::MicroNetKwsModel kwsModel; + arm::app::fwk::tflm::Wav2LetterModel asrModel; + + arm::app::fwk::iface::MemoryRegion kwsModelMem{arm::app::kws::GetModelPointer(), + arm::app::kws::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion asrModelMem{arm::app::asr::GetModelPointer(), + arm::app::asr::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::activationBuf, + sizeof(arm::app::activationBuf)}; + + /* Load the model. */ + if (!kwsModel.Init(computeMem, kwsModelMem)) { printf_err("Failed to initialise KWS model\n"); return; } /* Initialise the asr model using the same allocator from KWS * to re-use the tensor arena. */ - if (!asrModel.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::asr::GetModelPointer(), - arm::app::asr::GetModelLen(), - kwsModel.GetAllocator())) { + if (!asrModel.Init(computeMem, asrModelMem, &kwsModel.GetBackendData())) { printf_err("Failed to initialise ASR model\n"); return; } else if (!VerifyTensorDimensions(asrModel)) { @@ -79,8 +79,8 @@ void MainLoop() arm::app::Profiler profiler{"kws_asr"}; caseContext.Set("profiler", profiler); - caseContext.Set("kwsModel", kwsModel); - caseContext.Set("asrModel", asrModel); + caseContext.Set("kwsModel", kwsModel); + caseContext.Set("asrModel", asrModel); caseContext.Set("ctxLen", arm::app::asr::g_ctxLen); /* Left and right context length (MFCC feat vectors). */ caseContext.Set("kwsFrameLength", arm::app::kws::g_FrameLength); caseContext.Set("kwsFrameStride", arm::app::kws::g_FrameStride); @@ -93,7 +93,11 @@ void MainLoop() caseContext.Set("asrScoreThreshold", arm::app::asr::g_ScoreThreshold); /* Normalised score threshold. */ arm::app::KwsClassifier kwsClassifier; /* Classifier wrapper object. */ - arm::app::AsrClassifier asrClassifier; /* Classifier wrapper object. */ + arm::app::AsrClassifier asrClassifier{ + arm::app::fwk::tflm::Wav2LetterModel::ms_inputRowsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_inputColsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputColsIdx}; /* Classifier wrapper object. */ caseContext.Set("kwsClassifier", kwsClassifier); caseContext.Set("asrClassifier", asrClassifier); @@ -120,23 +124,26 @@ void MainLoop() executionSuccessful ? "successfully" : "with failure"); } -static bool VerifyTensorDimensions(const arm::app::Model& model) +static bool VerifyTensorDimensions(const arm::app::fwk::iface::Model& model) { /* Populate tensor related parameters. */ - TfLiteTensor* inputTensor = model.GetInputTensor(0); - if (!inputTensor->dims) { + auto inputTensor = model.GetInputTensor(0); + if (inputTensor->Shape().empty()) { printf_err("Invalid input tensor dims\n"); return false; - } else if (inputTensor->dims->size < 3) { + } + if (inputTensor->Shape().size() < 3) { printf_err("Input tensor dimension should be >= 3\n"); return false; } - TfLiteTensor* outputTensor = model.GetOutputTensor(0); - if (!outputTensor->dims) { + auto outputTensor = model.GetOutputTensor(0); + if (outputTensor->Shape().empty()) { printf_err("Invalid output tensor dims\n"); return false; - } else if (outputTensor->dims->size < 3) { + } + + if (outputTensor->Shape().size() < 3) { printf_err("Output tensor dimension should be >= 3\n"); return false; } diff --git a/source/use_case/kws_asr/src/UseCaseHandler.cc b/source/use_case/kws_asr/src/UseCaseHandler.cc index b45c652f81b6aa1189ddbfcf9df7af218382d2fd..244d5234e0a5c6f0dcfa6f0b63da60c06d31f024 100644 --- a/source/use_case/kws_asr/src/UseCaseHandler.cc +++ b/source/use_case/kws_asr/src/UseCaseHandler.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2022, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021-2022, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -71,7 +71,7 @@ namespace app { static KWSOutput doKws(ApplicationContext& ctx, const int16_t* audioBuffer, uint32_t nElements) { auto& profiler = ctx.Get("profiler"); - auto& kwsModel = ctx.Get("kwsModel"); + auto& kwsModel = ctx.Get("kwsModel"); const auto kwsMfccFrameLength = ctx.Get("kwsFrameLength"); const auto kwsMfccFrameStride = ctx.Get("kwsFrameStride"); const auto kwsScoreThreshold = ctx.Get("kwsScoreThreshold"); @@ -80,9 +80,10 @@ namespace app { constexpr uint32_t dataPsnTxtInfStartY = 40; constexpr int minTensorDims = - static_cast((MicroNetKwsModel::ms_inputRowsIdx > MicroNetKwsModel::ms_inputColsIdx) - ? MicroNetKwsModel::ms_inputRowsIdx - : MicroNetKwsModel::ms_inputColsIdx); + static_cast((fwk::tflm::MicroNetKwsModel::ms_inputRowsIdx > + fwk::tflm::MicroNetKwsModel::ms_inputColsIdx) + ? fwk::tflm::MicroNetKwsModel::ms_inputRowsIdx + : fwk::tflm::MicroNetKwsModel::ms_inputColsIdx); /* Output struct from doing KWS. */ KWSOutput output{}; @@ -93,20 +94,22 @@ namespace app { } /* Get Input and Output tensors for pre/post processing. */ - TfLiteTensor* kwsInputTensor = kwsModel.GetInputTensor(0); - TfLiteTensor* kwsOutputTensor = kwsModel.GetOutputTensor(0); - if (!kwsInputTensor->dims) { + auto kwsInputTensor = kwsModel.GetInputTensor(0); + auto kwsOutputTensor = kwsModel.GetOutputTensor(0); + const auto kwsInputTensorShape = kwsInputTensor->Shape(); + if (kwsInputTensorShape.empty()) { printf_err("Invalid input tensor dims\n"); return output; - } else if (kwsInputTensor->dims->size < minTensorDims) { + } else if (kwsInputTensorShape.size() < minTensorDims) { printf_err("Input tensor dimension should be >= %d\n", minTensorDims); return output; } /* Get input shape for feature extraction. */ - TfLiteIntArray* inputShape = kwsModel.GetInputShape(0); - const uint32_t numMfccFeatures = inputShape->data[MicroNetKwsModel::ms_inputColsIdx]; - const uint32_t numMfccFrames = inputShape->data[MicroNetKwsModel::ms_inputRowsIdx]; + const uint32_t numMfccFeatures = + kwsInputTensorShape[fwk::tflm::MicroNetKwsModel::ms_inputColsIdx]; + const uint32_t numMfccFrames = + kwsInputTensorShape[fwk::tflm::MicroNetKwsModel::ms_inputRowsIdx]; /* We expect to be sampling 1 second worth of data at a time * NOTE: This is only used for time stamp calculation. */ @@ -210,7 +213,7 @@ namespace app { **/ static bool doAsr(ApplicationContext& ctx, const KWSOutput& kwsOutput) { - auto& asrModel = ctx.Get("asrModel"); + auto& asrModel = ctx.Get("asrModel"); auto& profiler = ctx.Get("profiler"); auto asrMfccFrameLen = ctx.Get("asrFrameLength"); auto asrMfccFrameStride = ctx.Get("asrFrameStride"); @@ -228,14 +231,15 @@ namespace app { hal_lcd_clear(COLOR_BLACK); /* Get Input and Output tensors for pre/post processing. */ - TfLiteTensor* asrInputTensor = asrModel.GetInputTensor(0); - TfLiteTensor* asrOutputTensor = asrModel.GetOutputTensor(0); + auto asrInputTensor = asrModel.GetInputTensor(0); + auto asrOutputTensor = asrModel.GetOutputTensor(0); /* Get input shape. Dimensions of the tensor should have been verified by * the callee. */ - TfLiteIntArray* inputShape = asrModel.GetInputShape(0); + auto inputShape = asrModel.GetInputShape(0); - const uint32_t asrInputRows = asrInputTensor->dims->data[Wav2LetterModel::ms_inputRowsIdx]; + const uint32_t asrInputRows = + asrInputTensor->Shape()[fwk::tflm::Wav2LetterModel::ms_inputRowsIdx]; const uint32_t asrInputInnerLen = asrInputRows - (2 * asrInputCtxLen); /* Make sure the input tensor supports the above context and inner lengths. */ @@ -284,21 +288,20 @@ namespace app { /* Set up pre and post-processing objects. */ AsrPreProcess asrPreProcess = AsrPreProcess(asrInputTensor, - arm::app::Wav2LetterModel::ms_numMfccFeatures, - inputShape->data[Wav2LetterModel::ms_inputRowsIdx], + fwk::tflm::Wav2LetterModel::ms_numMfccFeatures, + inputShape[fwk::tflm::Wav2LetterModel::ms_inputRowsIdx], asrMfccFrameLen, asrMfccFrameStride); std::vector singleInfResult; - const uint32_t outputCtxLen = AsrPostProcess::GetOutputContextLen(asrModel, asrInputCtxLen); AsrPostProcess asrPostProcess = - AsrPostProcess(asrOutputTensor, + AsrPostProcess(asrModel, ctx.Get("asrClassifier"), ctx.Get&>("asrLabels"), singleInfResult, - outputCtxLen, - Wav2LetterModel::ms_blankTokenIdx, - Wav2LetterModel::ms_outputRowsIdx); + asrInputCtxLen, + fwk::tflm::Wav2LetterModel::ms_blankTokenIdx, + fwk::tflm::Wav2LetterModel::ms_outputRowsIdx); /* Start sliding through audio clip. */ while (audioDataSlider.HasNext()) { @@ -349,8 +352,9 @@ namespace app { asrScoreThreshold)); #if VERIFY_TEST_OUTPUT - armDumpTensor(asrOutputTensor, - asrOutputTensor->dims->data[Wav2LetterModel::ms_outputColsIdx]); + armDumpTensor( + asrOutputTensor, + asrOutputTensor->dims->data[fwk::tflm::Wav2LetterModel::ms_outputColsIdx]); #endif /* VERIFY_TEST_OUTPUT */ /* Erase */ diff --git a/source/use_case/kws_asr/usecase.cmake b/source/use_case/kws_asr/usecase.cmake index fbf34a26ba9e05cd10ded8afaff2deff0c956463..c0fac2e9e0aab0ccc4e1f4d9a65502321de81a57 100644 --- a/source/use_case/kws_asr/usecase.cmake +++ b/source/use_case/kws_asr/usecase.cmake @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2021-2022, 2024 Arm Limited and/or its +# SPDX-FileCopyrightText: Copyright 2021-2022, 2024-2025 Arm Limited and/or its # affiliates # SPDX-License-Identifier: Apache-2.0 # @@ -15,6 +15,16 @@ # See the License for the specific language governing permissions and # limitations under the License. #---------------------------------------------------------------------------- + +# Specify the ML frameworks the use case supports +set(${use_case}_ML_FRAMEWORK "TensorFlowLiteMicro") +if (NOT ${use_case}_ML_FRAMEWORK STREQUAL ${ML_FRAMEWORK}) + set(${use_case}_supports_${ML_FRAMEWORK} OFF) + return() +endif () + +set(${use_case}_supports_${ML_FRAMEWORK} ON) + # Append the APIs to use for this use case list(APPEND ${use_case}_API_LIST "kws" "asr") @@ -75,12 +85,12 @@ else() set(DEFAULT_MODEL_PATH_ASR ${DEFAULT_MODEL_DIR}/wav2letter_pruned_int8.tflite) endif() -USER_OPTION(${use_case}_MODEL_TFLITE_PATH_KWS "NN models file to be used for KWS in the evaluation application. Model files must be in tflite format." +USER_OPTION(${use_case}_MODEL_PATH_KWS "NN models file to be used for KWS in the evaluation application. Model files must be in tflite format." ${DEFAULT_MODEL_PATH_KWS} FILEPATH ) -USER_OPTION(${use_case}_MODEL_TFLITE_PATH_ASR "NN models file to be used for ASR in the evaluation application. Model files must be in tflite format." +USER_OPTION(${use_case}_MODEL_PATH_ASR "NN models file to be used for ASR in the evaluation application. Model files must be in tflite format." ${DEFAULT_MODEL_PATH_ASR} FILEPATH ) @@ -108,16 +118,16 @@ set(EXTRA_MODEL_CODE_ASR ) # Generate model file for KWS -generate_tflite_code( - MODEL_PATH ${${use_case}_MODEL_TFLITE_PATH_KWS} +generate_model_code( + MODEL_PATH ${${use_case}_MODEL_PATH_KWS} DESTINATION ${SRC_GEN_DIR} EXPRESSIONS ${EXTRA_MODEL_CODE_KWS} NAMESPACE "arm" "app" "kws" ) # and for ASR -generate_tflite_code( - MODEL_PATH ${${use_case}_MODEL_TFLITE_PATH_ASR} +generate_model_code( + MODEL_PATH ${${use_case}_MODEL_PATH_ASR} DESTINATION ${SRC_GEN_DIR} EXPRESSIONS ${EXTRA_MODEL_CODE_ASR} NAMESPACE "arm" "app" "asr" diff --git a/source/use_case/noise_reduction/include/UseCaseHandler.hpp b/source/use_case/noise_reduction/include/UseCaseHandler.hpp index 0c18e4db5200bed3597ec061fd43d54a358f0a25..7d2608701b631a0ba7891efd9a782efd0f24b836 100644 --- a/source/use_case/noise_reduction/include/UseCaseHandler.hpp +++ b/source/use_case/noise_reduction/include/UseCaseHandler.hpp @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -45,16 +45,15 @@ namespace app { * * @return number of bytes written to memory. */ - size_t DumpOutputTensorsToMemory(Model& model, uint8_t* memAddress, - size_t memSize); + size_t DumpOutputTensorsToMemory(fwk::iface::Model& model, uint8_t* memAddress, size_t memSize); /** * @brief Dumps the audio file header. * This functionality is required for RNNoise use case as we want to * save the inference output to a file. Dumping out the header to a * memory location will allow the Arm FVP or MPS3 to extract the - * contents of this memory location to a file. - * The header contains the following information + * contents of this memory location to a file. + * The header contains the following information * int32_t filenameLength: filename length * uint8_t[] filename: the string containing the file name (without trailing \0) * int32_t dumpSizeByte: audiofile buffer size in bytes @@ -94,4 +93,4 @@ namespace app { } /* namespace app */ } /* namespace arm */ -#endif /* NOISE_REDUCTION_EVT_HANDLER_HPP */ \ No newline at end of file +#endif /* NOISE_REDUCTION_EVT_HANDLER_HPP */ diff --git a/source/use_case/noise_reduction/src/MainLoop.cc b/source/use_case/noise_reduction/src/MainLoop.cc index dd1634a1b814c23d72a91ec12490f61b9429e8ae..a41df11eb815e2b76f3bb9715045129a868080bd 100644 --- a/source/use_case/noise_reduction/src/MainLoop.cc +++ b/source/use_case/noise_reduction/src/MainLoop.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021-2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -23,7 +23,7 @@ namespace arm { namespace app { - static uint8_t tensorArena[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; + static uint8_t activationBuf[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; namespace rnn { extern uint8_t* GetModelPointer(); extern size_t GetModelLen(); @@ -33,13 +33,14 @@ namespace app { void MainLoop() { - arm::app::RNNoiseModel model; /* Model wrapper object. */ + arm::app::fwk::tflm::RNNoiseModel model; /* Model wrapper object. */ + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::rnn::GetModelPointer(), + arm::app::rnn::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::activationBuf, + sizeof(arm::app::activationBuf)}; /* Load the model. */ - if (!model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::rnn::GetModelPointer(), - arm::app::rnn::GetModelLen())) { + if (!model.Init(computeMem, modelMem)) { printf_err("Failed to initialise model\n"); return; } @@ -52,7 +53,7 @@ void MainLoop() caseContext.Set("numInputFeatures", arm::app::rnn::g_NumInputFeatures); caseContext.Set("frameLength", arm::app::rnn::g_FrameLength); caseContext.Set("frameStride", arm::app::rnn::g_FrameStride); - caseContext.Set("model", model); + caseContext.Set("model", model); #if defined(MEM_DUMP_BASE_ADDR) /* For this use case, for valid targets, we dump contents diff --git a/source/use_case/noise_reduction/src/UseCaseHandler.cc b/source/use_case/noise_reduction/src/UseCaseHandler.cc index 7d3a30dacb01b4666988900c52680b491d9bccce..2675f1ade688c4efa052b6063fe9b1175fcf7592 100644 --- a/source/use_case/noise_reduction/src/UseCaseHandler.cc +++ b/source/use_case/noise_reduction/src/UseCaseHandler.cc @@ -49,7 +49,7 @@ namespace app { auto& profiler = ctx.Get("profiler"); /* Get model reference. */ - auto& model = ctx.Get("model"); + auto& model = ctx.Get("model"); if (!model.IsInited()) { printf_err("Model is not initialised! Terminating processing.\n"); return false; @@ -60,12 +60,12 @@ namespace app { auto audioFrameStride = ctx.Get("frameStride"); auto nrNumInputFeatures = ctx.Get("numInputFeatures"); - TfLiteTensor* inputTensor = model.GetInputTensor(0); - if (nrNumInputFeatures != inputTensor->bytes) { + auto inputTensor = model.GetInputTensor(0); + if (nrNumInputFeatures != inputTensor->Bytes()) { printf_err("Input features size must be equal to input tensor size." " Feature size = %" PRIu32 ", Tensor size = %zu.\n", nrNumInputFeatures, - inputTensor->bytes); + inputTensor->Bytes()); return false; } @@ -73,7 +73,7 @@ namespace app { return false; } - TfLiteTensor* outputTensor = model.GetOutputTensor(model.m_indexForModelOutput); + auto outputTensor = model.GetOutputTensor(model.m_indexForModelOutput); hal_audio_init(); if (!hal_audio_configure(HAL_AUDIO_MODE_SINGLE_BURST, @@ -317,7 +317,8 @@ namespace app { return numByteToBeWritten; } - size_t DumpOutputTensorsToMemory(Model& model, uint8_t* memAddress, const size_t memSize) + size_t + DumpOutputTensorsToMemory(fwk::iface::Model& model, uint8_t* memAddress, const size_t memSize) { const size_t numOutputs = model.GetNumOutputs(); size_t numBytesWritten = 0; @@ -325,20 +326,21 @@ namespace app { /* Iterate over all output tensors. */ for (size_t i = 0; i < numOutputs; ++i) { - const TfLiteTensor* tensor = model.GetOutputTensor(i); - const auto* tData = tflite::GetTensorData(tensor); + auto tensor = model.GetOutputTensor(i); + const auto* tData = tensor->GetData(); + const size_t nBytes = tensor->Bytes(); #if VERIFY_TEST_OUTPUT DumpTensor(tensor); #endif /* VERIFY_TEST_OUTPUT */ /* Ensure that we don't overflow the allowed limit. */ - if (numBytesWritten + tensor->bytes <= memSize) { - if (tensor->bytes > 0) { - std::memcpy(ptr, tData, tensor->bytes); + if (numBytesWritten + nBytes <= memSize) { + if (nBytes > 0) { + std::memcpy(ptr, tData, nBytes); - info("Copied %zu bytes for tensor %zu to 0x%p\n", tensor->bytes, i, ptr); + info("Copied %zu bytes for tensor %zu to 0x%p\n", nBytes, i, ptr); - numBytesWritten += tensor->bytes; - ptr += tensor->bytes; + numBytesWritten += nBytes; + ptr += nBytes; } } else { printf_err("Error writing tensor %zu to memory @ 0x%p\n", i, memAddress); diff --git a/source/use_case/noise_reduction/usecase.cmake b/source/use_case/noise_reduction/usecase.cmake index d1116332c5f46d72c89dcb7433311c3315bf7da2..2023e148c0e516c9f8e7b365b8f1bec38baa6a25 100644 --- a/source/use_case/noise_reduction/usecase.cmake +++ b/source/use_case/noise_reduction/usecase.cmake @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its +# SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its # affiliates # SPDX-License-Identifier: Apache-2.0 # @@ -15,6 +15,16 @@ # See the License for the specific language governing permissions and # limitations under the License. #---------------------------------------------------------------------------- + +# Specify the ML frameworks the use case supports +set(${use_case}_ML_FRAMEWORK "TensorFlowLiteMicro") +if (NOT ${use_case}_ML_FRAMEWORK STREQUAL ${ML_FRAMEWORK}) + set(${use_case}_supports_${ML_FRAMEWORK} OFF) + return() +endif () + +set(${use_case}_supports_${ML_FRAMEWORK} ON) + # Append the API to use for this use case list(APPEND ${use_case}_API_LIST "noise_reduction") @@ -28,7 +38,7 @@ else() set(DEFAULT_MODEL_PATH ${DEFAULT_MODEL_DIR}/rnnoise_INT8.tflite) endif() -USER_OPTION(${use_case}_MODEL_TFLITE_PATH "NN models file to be used in the evaluation application. Model files must be in tflite format." +USER_OPTION(${use_case}_MODEL_PATH "NN models file to be used in the evaluation application. Model files must be in tflite format." ${DEFAULT_MODEL_PATH} FILEPATH) @@ -76,8 +86,8 @@ set(EXTRA_MODEL_CODE ) # Generate model file. -generate_tflite_code( - MODEL_PATH ${${use_case}_MODEL_TFLITE_PATH} +generate_model_code( + MODEL_PATH ${${use_case}_MODEL_PATH} DESTINATION ${SRC_GEN_DIR} EXPRESSIONS ${EXTRA_MODEL_CODE} NAMESPACE "arm" "app" "rnn") diff --git a/source/use_case/object_detection/src/MainLoop.cc b/source/use_case/object_detection/src/MainLoop.cc index 946ff46a786d0100514368f603ac2e0952d21f0d..e3e181f33ec57ee7f906c50e2a4c52e09e139b1d 100644 --- a/source/use_case/object_detection/src/MainLoop.cc +++ b/source/use_case/object_detection/src/MainLoop.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2022, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2022, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -24,7 +24,7 @@ namespace arm { namespace app { - static uint8_t tensorArena[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; + static uint8_t activationBuf[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; namespace object_detection { extern uint8_t* GetModelPointer(); extern size_t GetModelLen(); @@ -34,13 +34,15 @@ namespace app { void MainLoop() { - arm::app::YoloFastestModel model; /* Model wrapper object. */ + arm::app::fwk::tflm::YoloFastestModel model; /* Model wrapper object. */ + + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::object_detection::GetModelPointer(), + arm::app::object_detection::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::activationBuf, + sizeof(arm::app::activationBuf)}; /* Load the model. */ - if (!model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::object_detection::GetModelPointer(), - arm::app::object_detection::GetModelLen())) { + if (!model.Init(computeMem, modelMem)) { printf_err("Failed to initialise model\n"); return; } @@ -50,7 +52,7 @@ void MainLoop() arm::app::Profiler profiler{"object_detection"}; caseContext.Set("profiler", profiler); - caseContext.Set("model", model); + caseContext.Set("model", model); bool executionSuccessful = ObjectDetectionHandler(caseContext); info("Main loop terminated %s.\n", diff --git a/source/use_case/object_detection/src/UseCaseHandler.cc b/source/use_case/object_detection/src/UseCaseHandler.cc index eea42c0ee9954f3c76b31becaa6a3a73bfb6a1cb..e960839b9153ed9b268ba3d5360789ddaaa8d90b 100644 --- a/source/use_case/object_detection/src/UseCaseHandler.cc +++ b/source/use_case/object_detection/src/UseCaseHandler.cc @@ -63,28 +63,28 @@ namespace app { hal_lcd_clear(COLOR_BLACK); - auto& model = ctx.Get("model"); + auto& model = ctx.Get("model"); if (!model.IsInited()) { printf_err("Model is not initialised! Terminating processing.\n"); return false; } - TfLiteTensor* inputTensor = model.GetInputTensor(0); - TfLiteTensor* outputTensor0 = model.GetOutputTensor(0); - TfLiteTensor* outputTensor1 = model.GetOutputTensor(1); + auto inputTensor = model.GetInputTensor(0); + auto outputTensor0 = model.GetOutputTensor(0); + auto outputTensor1 = model.GetOutputTensor(1); - if (!inputTensor->dims) { + const auto inputShape = inputTensor->Shape(); + + if (inputShape.empty()) { printf_err("Invalid input tensor dims\n"); return false; - } else if (inputTensor->dims->size < 3) { + } else if (inputShape.size() < 3) { printf_err("Input tensor dimension should be >= 3\n"); return false; } - TfLiteIntArray* inputShape = model.GetInputShape(0); - - const int inputImgCols = inputShape->data[YoloFastestModel::ms_inputColsIdx]; - const int inputImgRows = inputShape->data[YoloFastestModel::ms_inputRowsIdx]; + const int inputImgCols = inputShape[fwk::tflm::YoloFastestModel::ms_inputColsIdx]; + const int inputImgRows = inputShape[fwk::tflm::YoloFastestModel::ms_inputRowsIdx]; /* Set up pre and post-processing. */ DetectorPreProcess preProcess = DetectorPreProcess(inputTensor, true, model.IsDataSigned()); @@ -127,9 +127,9 @@ namespace app { break; } - auto dstPtr = static_cast(inputTensor->data.uint8); + auto* dstPtr = inputTensor->GetData(); const size_t copySz = - inputTensor->bytes < capturedFrameSize ? inputTensor->bytes : capturedFrameSize; + inputTensor->Bytes() < capturedFrameSize ? inputTensor->Bytes() : capturedFrameSize; /* Run the pre-processing, inference and post-processing. */ if (!preProcess.DoPreProcess(currImage, copySz)) { diff --git a/source/use_case/object_detection/usecase.cmake b/source/use_case/object_detection/usecase.cmake index b94bbeb6ce72aac278f18ec5defb15fb0368b573..66f0c7ca38c342467d1fb5fb60e61d726b1c1368 100644 --- a/source/use_case/object_detection/usecase.cmake +++ b/source/use_case/object_detection/usecase.cmake @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2022, 2024 Arm Limited and/or its +# SPDX-FileCopyrightText: Copyright 2022, 2024-2025 Arm Limited and/or its # affiliates # SPDX-License-Identifier: Apache-2.0 # @@ -15,6 +15,16 @@ # See the License for the specific language governing permissions and # limitations under the License. #---------------------------------------------------------------------------- + +# Specify the ML frameworks the use case supports +set(${use_case}_ML_FRAMEWORK "TensorFlowLiteMicro") +if (NOT ${use_case}_ML_FRAMEWORK STREQUAL ${ML_FRAMEWORK}) + set(${use_case}_supports_${ML_FRAMEWORK} OFF) + return() +endif () + +set(${use_case}_supports_${ML_FRAMEWORK} ON) + # Append the API to use for this use case list(APPEND ${use_case}_API_LIST "object_detection") @@ -64,14 +74,14 @@ set(EXTRA_MODEL_CODE "extern const float anchor2[] = ${${use_case}_ANCHOR_2};" ) -USER_OPTION(${use_case}_MODEL_TFLITE_PATH "NN models file to be used in the evaluation application. Model files must be in tflite format." +USER_OPTION(${use_case}_MODEL_PATH "NN models file to be used in the evaluation application. Model files must be in tflite format." ${DEFAULT_MODEL_PATH} FILEPATH ) # Generate model file -generate_tflite_code( - MODEL_PATH ${${use_case}_MODEL_TFLITE_PATH} +generate_model_code( + MODEL_PATH ${${use_case}_MODEL_PATH} DESTINATION ${SRC_GEN_DIR} EXPRESSIONS ${EXTRA_MODEL_CODE} NAMESPACE "arm" "app" "object_detection") diff --git a/source/use_case/vww/src/MainLoop.cc b/source/use_case/vww/src/MainLoop.cc index 24ef755d0a593c375487d8bc4377b1b0c6e5f145..2deb101359fc49246ab69da5a3fe42e9196f3571 100644 --- a/source/use_case/vww/src/MainLoop.cc +++ b/source/use_case/vww/src/MainLoop.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -26,7 +26,7 @@ namespace arm { namespace app { - static uint8_t tensorArena[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; + static uint8_t activationBuf[ACTIVATION_BUF_SZ] ACTIVATION_BUF_ATTRIBUTE; namespace vww { extern uint8_t* GetModelPointer(); extern size_t GetModelLen(); @@ -38,13 +38,15 @@ using ViusalWakeWordClassifier = arm::app::Classifier; void MainLoop() { - arm::app::VisualWakeWordModel model; /* Model wrapper object. */ + arm::app::fwk::tflm::VisualWakeWordModel model; /* Model wrapper object. */ + + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::vww::GetModelPointer(), + arm::app::vww::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::activationBuf, + sizeof(arm::app::activationBuf)}; /* Load the model. */ - if (!model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::vww::GetModelPointer(), - arm::app::vww::GetModelLen())) { + if (!model.Init(computeMem, modelMem)) { printf_err("Failed to initialise model\n"); return; } @@ -54,7 +56,7 @@ void MainLoop() arm::app::Profiler profiler{"vww"}; caseContext.Set("profiler", profiler); - caseContext.Set("model", model); + caseContext.Set("model", model); ViusalWakeWordClassifier classifier; /* Classifier wrapper object. */ caseContext.Set("classifier", classifier); diff --git a/source/use_case/vww/src/UseCaseHandler.cc b/source/use_case/vww/src/UseCaseHandler.cc index 75e9ff2b624b30da3892931fb80fceee1034b708..dd8b30b1d152760124677012bd20c3dc1d48380b 100644 --- a/source/use_case/vww/src/UseCaseHandler.cc +++ b/source/use_case/vww/src/UseCaseHandler.cc @@ -31,7 +31,7 @@ namespace app { bool ClassifyImageHandler(ApplicationContext& ctx) { auto& profiler = ctx.Get("profiler"); - auto& model = ctx.Get("model"); + auto& model = ctx.Get("model"); constexpr uint32_t dataPsnImgDownscaleFactor = 1; constexpr uint32_t dataPsnImgStartX = 10; @@ -45,22 +45,24 @@ namespace app { return false; } - TfLiteTensor* inputTensor = model.GetInputTensor(0); - TfLiteTensor* outputTensor = model.GetOutputTensor(0); - if (!inputTensor->dims) { + auto inputTensor = model.GetInputTensor(0); + auto outputTensor = model.GetOutputTensor(0); + const auto inputShape = inputTensor->Shape(); + if (inputShape.empty()) { printf_err("Invalid input tensor dims\n"); return false; - } else if (inputTensor->dims->size < 4) { + } else if (inputShape.size() < 4) { printf_err("Input tensor dimension should be = 4\n"); return false; } /* Get input shape for displaying the image. */ - TfLiteIntArray* inputShape = model.GetInputShape(0); - const uint32_t nCols = inputShape->data[arm::app::VisualWakeWordModel::ms_inputColsIdx]; - const uint32_t nRows = inputShape->data[arm::app::VisualWakeWordModel::ms_inputRowsIdx]; - if (arm::app::VisualWakeWordModel::ms_inputChannelsIdx >= - static_cast(inputShape->size)) { + const uint32_t nCols = + inputShape[arm::app::fwk::tflm::VisualWakeWordModel::ms_inputColsIdx]; + const uint32_t nRows = + inputShape[arm::app::fwk::tflm::VisualWakeWordModel::ms_inputRowsIdx]; + if (arm::app::fwk::tflm::VisualWakeWordModel::ms_inputChannelsIdx >= + static_cast(inputShape.size())) { printf_err("Invalid channel index.\n"); return false; } @@ -117,7 +119,7 @@ namespace app { str_inf.c_str(), str_inf.size(), dataPsnTxtInfStartX, dataPsnTxtInfStartY, 0); const size_t imgSz = - inputTensor->bytes < capturedFrameSize ? inputTensor->bytes : capturedFrameSize; + inputTensor->Bytes() < capturedFrameSize ? inputTensor->Bytes() : capturedFrameSize; /* Run the pre-processing, inference and post-processing. */ if (!preProcess.DoPreProcess(imgSrc, imgSz)) { diff --git a/source/use_case/vww/usecase.cmake b/source/use_case/vww/usecase.cmake index bedd07e0490c9ba00f1831dcacad779e32d4275b..9e11d807ac2ad289020b2edd2d21a8aeb8b32f38 100644 --- a/source/use_case/vww/usecase.cmake +++ b/source/use_case/vww/usecase.cmake @@ -1,5 +1,5 @@ #---------------------------------------------------------------------------- -# SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its +# SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its # affiliates # SPDX-License-Identifier: Apache-2.0 # @@ -16,6 +16,15 @@ # limitations under the License. #---------------------------------------------------------------------------- +# Specify the ML frameworks the use case supports +set(${use_case}_ML_FRAMEWORK "TensorFlowLiteMicro") +if (NOT ${use_case}_ML_FRAMEWORK STREQUAL ${ML_FRAMEWORK}) + set(${use_case}_supports_${ML_FRAMEWORK} OFF) + return() +endif () + +set(${use_case}_supports_${ML_FRAMEWORK} ON) + # Append the API to use for this use case list(APPEND ${use_case}_API_LIST "vww") @@ -39,13 +48,13 @@ else() set(DEFAULT_MODEL_PATH ${DEFAULT_MODEL_DIR}/vww4_128_128_INT8.tflite) endif() -USER_OPTION(${use_case}_MODEL_TFLITE_PATH "NN models file to be used in the evaluation application. Model files must be in tflite format." +USER_OPTION(${use_case}_MODEL_PATH "NN models file to be used in the evaluation application. Model files must be in tflite format." ${DEFAULT_MODEL_PATH} FILEPATH) # Generate model file -generate_tflite_code( - MODEL_PATH ${${use_case}_MODEL_TFLITE_PATH} +generate_model_code( + MODEL_PATH ${${use_case}_MODEL_PATH} DESTINATION ${SRC_GEN_DIR} NAMESPACE "arm" "app" "vww") diff --git a/tests/common/AppContextTest.cc b/tests/common/AppContextTest.cc index ab5b68730396f18a59656712ffd6aac27ce6656d..64c3348a25b4c95ac08e12b99c7ee32b2535fef8 100644 --- a/tests/common/AppContextTest.cc +++ b/tests/common/AppContextTest.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -60,4 +60,4 @@ TEST_CASE("Common: Application context") REQUIRE(vect == data); delete(vect); } -} \ No newline at end of file +} diff --git a/tests/common/ClassifierTests.cc b/tests/common/ClassifierTests.cc index b15ffd4dec99ba4fec7153c4c9ca919de79faf0a..a59c9d20b350147192e6f0ae4bdc8616a82e3d90 100644 --- a/tests/common/ClassifierTests.cc +++ b/tests/common/ClassifierTests.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021,2023 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021,2023, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,17 +15,21 @@ * limitations under the License. */ #include "Classifier.hpp" +#include "TensorFlowLiteMicro.hpp" +#include "TflmTensor.hpp" #include -template -void test_classifier_result(std::vector>& selectedResults, T defaultTensorValue) { +template +void TestClassifierResult(std::vector>& selectedResults, + T defaultTensorValue) +{ int dimArray[] = {1, 1001}; std::vector labels(1001); std::vector outputVec(1001, defaultTensorValue); TfLiteIntArray* dims= tflite::testing::IntArrayFromInts(dimArray); TfLiteTensor tfTensor = tflite::testing::CreateQuantizedTensor(outputVec.data(), dims, 1, 0); - TfLiteTensor* outputTensor = &tfTensor; + auto outputTensor = std::make_shared(&tfTensor); std::vector resultVec; @@ -44,14 +48,6 @@ void test_classifier_result(std::vector>& selectedResults TEST_CASE("Common classifier") { - SECTION("Test invalid classifier") - { - TfLiteTensor* outputTens = nullptr; - std::vector resultVec; - arm::app::Classifier classifier; - REQUIRE(!classifier.GetClassificationResults(outputTens, resultVec, {}, 5, true)); - } - SECTION("Test classification results") { SECTION("uint8") { @@ -59,7 +55,7 @@ TEST_CASE("Common classifier") std::vector> selectedResults { {1000, 10}, {15, 9}, {0, 8}, {20, 7}, {10, 7} }; - test_classifier_result(selectedResults, static_cast(5)); + TestClassifierResult(selectedResults, static_cast(5)); } SECTION("int8") { @@ -67,7 +63,7 @@ TEST_CASE("Common classifier") std::vector> selectedResults { {1000, 10}, {15, 9}, {0, 8}, {20, -7}, {10, -7} }; - test_classifier_result(selectedResults, static_cast(-100)); + TestClassifierResult(selectedResults, static_cast(-100)); } SECTION("float") { @@ -75,8 +71,7 @@ TEST_CASE("Common classifier") std::vector> selectedResults { {1000, 10.9f}, {15, 9.8f}, {0, 8.7f}, {20, -7.0f}, {10, -7.1f} }; - test_classifier_result(selectedResults, -100.0f); + TestClassifierResult(selectedResults, -100.0f); } - } } diff --git a/tests/common/TflmTensorTests.cc b/tests/common/TflmTensorTests.cc new file mode 100644 index 0000000000000000000000000000000000000000..29cf0f46a02f460534d11cc544e20a19f7c3ebad --- /dev/null +++ b/tests/common/TflmTensorTests.cc @@ -0,0 +1,71 @@ +/* + * SPDX-FileCopyrightText: Copyright 2025 Arm Limited and/or its affiliates + * + * SPDX-License-Identifier: Apache-2.0 + * + * 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. + */ +#include "TflmTensor.hpp" +#include "log_macros.h" + +#include +#include + +template +static void TestQuantizedTensorFunctions() +{ + static_assert(std::is_integral_v, "T must be integral"); + + std::vector vectorT(10, 0); + int dimArray[] = {2 /* n dims */, 1 /* dim0 */, 10 /* dim1 */}; + constexpr float scale = 0.5; + constexpr int offset = 1; + TfLiteIntArray* dims = tflite::testing::IntArrayFromInts(dimArray); + TfLiteTensor tfTensor = tflite::testing::CreateQuantizedTensor(vectorT.data(), + dims, + scale, + offset); + const auto tensor = std::make_shared(&tfTensor); + + REQUIRE(tensor); + const auto shape = tensor->Shape(); + CHECK(shape.size() == 2); + CHECK(shape[0] == 1); + CHECK(shape[1] == 10); + const auto quant = tensor->GetQuantParams(); + CHECK(quant.scale == Approx(scale).epsilon(0.1)); + CHECK(quant.offset == offset); + CHECK(tensor->GetNumElements() == 10); + CHECK(tensor->Bytes() == 10 * sizeof(T)); + + arm::app::fwk::iface::TensorType type; + switch(sizeof(T)) { + case 1: + if (std::is_signed_v) { + type = arm::app::fwk::iface::TensorType::INT8; break; + } else { + type = arm::app::fwk::iface::TensorType::UINT8; break; + } + case 2: type = arm::app::fwk::iface::TensorType::INT16; break; + default: type = arm::app::fwk::iface::TensorType::INVALID; + } + info("Tests for tensor type %s: OK\n", arm::app::fwk::iface::GetTensorDataTypeName(type)); + CHECK(tensor->Type() == type); +} + +TEST_CASE("TensorFlow Lite Micro Tensor wrapper") +{ + TestQuantizedTensorFunctions(); + TestQuantizedTensorFunctions(); + TestQuantizedTensorFunctions(); +} diff --git a/tests/use_case/ad/InferenceTestAD.cc b/tests/use_case/ad/InferenceTestAD.cc index b1184d7a7ddc8e7e2ba5648f113fb69d000b8829..e13c3e8e12f07adadef6bc78abb68c1d22bf9984 100644 --- a/tests/use_case/ad/InferenceTestAD.cc +++ b/tests/use_case/ad/InferenceTestAD.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -40,23 +40,23 @@ namespace app { using namespace test; -bool RunInference(arm::app::Model& model, const int8_t vec[]) +bool RunInference(arm::app::fwk::iface::Model& model, const int8_t vec[]) { - TfLiteTensor* inputTensor = model.GetInputTensor(0); + auto inputTensor = model.GetInputTensor(0); REQUIRE(inputTensor); - const size_t copySz = inputTensor->bytes < AD_IN_FEATURE_VEC_DATA_SIZE - ? inputTensor->bytes + const size_t copySz = inputTensor->Bytes() < AD_IN_FEATURE_VEC_DATA_SIZE + ? inputTensor->Bytes() : AD_IN_FEATURE_VEC_DATA_SIZE; - memcpy(inputTensor->data.data, vec, copySz); + memcpy(inputTensor->GetData(), vec, copySz); return model.RunInference(); } -bool RunInferenceRandom(arm::app::Model& model) +bool RunInferenceRandom(arm::app::fwk::iface::Model& model) { - TfLiteTensor* inputTensor = model.GetInputTensor(0); + auto inputTensor = model.GetInputTensor(0); REQUIRE(inputTensor); std::random_device rndDevice; @@ -65,7 +65,7 @@ bool RunInferenceRandom(arm::app::Model& model) auto gen = [&dist, &mersenneGen]() { return dist(mersenneGen); }; - std::vector randomInput(inputTensor->bytes); + std::vector randomInput(inputTensor->Bytes()); std::generate(std::begin(randomInput), std::end(randomInput), gen); REQUIRE(RunInference(model, randomInput.data())); @@ -73,33 +73,36 @@ bool RunInferenceRandom(arm::app::Model& model) } template -void TestInference(const T* input_goldenFV, const T* output_goldenFV, arm::app::Model& model) +void TestInference(const T* input_goldenFV, + const T* output_goldenFV, + arm::app::fwk::iface::Model& model) { REQUIRE(RunInference(model, static_cast(input_goldenFV))); - TfLiteTensor* outputTensor = model.GetOutputTensor(0); + auto outputTensor = model.GetOutputTensor(0); REQUIRE(outputTensor); - REQUIRE(outputTensor->bytes == OFM_0_DATA_SIZE); - auto tensorData = tflite::GetTensorData(outputTensor); + REQUIRE(outputTensor->Bytes() == OFM_0_DATA_SIZE); + auto tensorData = outputTensor->GetData(); REQUIRE(tensorData); - for (size_t i = 0; i < outputTensor->bytes; i++) { + for (size_t i = 0; i < outputTensor->Bytes(); i++) { REQUIRE(static_cast(tensorData[i]) == static_cast(((T)output_goldenFV[i]))); } } TEST_CASE("Running random inference with TensorFlow Lite Micro and AdModel Int8", "[AD]") { - arm::app::AdModel model{}; - + arm::app::fwk::tflm::AdModel model{}; REQUIRE_FALSE(model.IsInited()); - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::ad::GetModelPointer(), - arm::app::ad::GetModelLen())); - REQUIRE(model.IsInited()); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::ad::GetModelPointer(), + arm::app::ad::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + /* Load the model. */ + REQUIRE(model.Init(computeMem, modelMem)); + REQUIRE(model.IsInited()); REQUIRE(RunInferenceRandom(model)); } @@ -114,13 +117,15 @@ TEST_CASE("Running golden vector inference with TensorFlow Lite Micro and AdMode DYNAMIC_SECTION("Executing inference with re-init") { - arm::app::AdModel model{}; - + arm::app::fwk::tflm::AdModel model{}; REQUIRE_FALSE(model.IsInited()); - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::ad::GetModelPointer(), - arm::app::ad::GetModelLen())); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::ad::GetModelPointer(), + arm::app::ad::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + + /* Load the model. */ + REQUIRE(model.Init(computeMem, modelMem)); REQUIRE(model.IsInited()); TestInference(input_goldenFV, output_goldenFV, model); diff --git a/tests/use_case/asr/AsrClassifierTests.cc b/tests/use_case/asr/AsrClassifierTests.cc index 4c4a2bee9462d6861d2057ef3d44397ab2904d5c..a8606e68e889b8eeac9eb153b7c9d3f466f77354 100644 --- a/tests/use_case/asr/AsrClassifierTests.cc +++ b/tests/use_case/asr/AsrClassifierTests.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,20 +15,11 @@ * limitations under the License. */ #include "AsrClassifier.hpp" +#include "TflmTensor.hpp" #include "Wav2LetterModel.hpp" #include -TEST_CASE("Test invalid classifier") -{ - TfLiteTensor* outputTens = nullptr; - std::vector resultVec; - arm::app::AsrClassifier classifier; - - REQUIRE(!classifier.GetClassificationResults(outputTens, resultVec, {}, 1)); -} - - TEST_CASE("Test valid classifier UINT8") { int dimArray[] = {4, 1, 1, 246, 29}; std::vector labels(29); @@ -36,9 +27,13 @@ TEST_CASE("Test valid classifier UINT8") { TfLiteIntArray* dims= tflite::testing::IntArrayFromInts(dimArray); TfLiteTensor tfTensor = tflite::testing::CreateQuantizedTensor( outputVec.data(), dims, 1, 0); - TfLiteTensor* outputTensor = &tfTensor; + auto outputTensor = std::make_shared(&tfTensor); std::vector resultVec; - arm::app::AsrClassifier classifier; + arm::app::AsrClassifier classifier{ + arm::app::fwk::tflm::Wav2LetterModel::ms_inputRowsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_inputColsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputColsIdx}; /* classifier */ REQUIRE(classifier.GetClassificationResults(outputTensor, resultVec, labels, 1)); REQUIRE(246 == resultVec.size()); @@ -52,7 +47,7 @@ TEST_CASE("Get classification results") { TfLiteIntArray* dims= tflite::testing::IntArrayFromInts(dimArray); TfLiteTensor tfTensor = tflite::testing::CreateQuantizedTensor( outputVec.data(), dims, 1, 0); - TfLiteTensor* outputTensor = &tfTensor; + auto outputTensor = std::make_shared(&tfTensor); std::vector resultVec(10); @@ -74,7 +69,8 @@ TEST_CASE("Get classification results") { {9, {1, 10}} }; - const uint32_t nCols = outputTensor->dims->data[arm::app::Wav2LetterModel::ms_outputColsIdx]; + const uint32_t nCols = + outputTensor->Shape()[arm::app::fwk::tflm::Wav2LetterModel::ms_outputColsIdx]; for (size_t i = 0; i < selectedResults.size(); ++i) { uint32_t rIndex = selectedResults[i].first; uint32_t cIndex = selectedResults[i].second.first; @@ -82,7 +78,11 @@ TEST_CASE("Get classification results") { outputVec[rIndex * nCols + cIndex] = value; } - arm::app::AsrClassifier classifier; + arm::app::AsrClassifier classifier{ + arm::app::fwk::tflm::Wav2LetterModel::ms_inputRowsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_inputColsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputColsIdx}; /* classifier */ REQUIRE(classifier.GetClassificationResults(outputTensor, resultVec, labels, 1)); REQUIRE(resultVec[0].m_labelIdx == 3); diff --git a/tests/use_case/asr/InferenceTestWav2Letter.cc b/tests/use_case/asr/InferenceTestWav2Letter.cc index 591c22af27d3918e945011e41b0fcf839a326ea9..53a85ff72557dbb583cc134d7ddf566ce9c3bca9 100644 --- a/tests/use_case/asr/InferenceTestWav2Letter.cc +++ b/tests/use_case/asr/InferenceTestWav2Letter.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,90 +32,97 @@ namespace app { } /* namespace app */ } /* namespace arm */ -using namespace test; +namespace test { +namespace asr { -bool RunInference(arm::app::Model& model, const int8_t vec[], const size_t copySz) -{ - TfLiteTensor* inputTensor = model.GetInputTensor(0); - REQUIRE(inputTensor); + bool RunInference(arm::app::fwk::iface::Model& model, const int8_t vec[], const size_t copySz) + { + auto inputTensor = model.GetInputTensor(0); + REQUIRE(inputTensor); - memcpy(inputTensor->data.data, vec, copySz); + memcpy(inputTensor->GetData(), vec, copySz); - return model.RunInference(); -} + return model.RunInference(); + } -bool RunInferenceRandom(arm::app::Model& model) -{ - TfLiteTensor* inputTensor = model.GetInputTensor(0); - REQUIRE(inputTensor); + bool RunInferenceRandom(arm::app::fwk::iface::Model& model) + { + auto inputTensor = model.GetInputTensor(0); + REQUIRE(inputTensor); - std::random_device rndDevice; - std::mt19937 mersenneGen{rndDevice()}; - std::uniform_int_distribution dist{-128, 127}; + std::random_device rndDevice; + std::mt19937 mersenneGen{rndDevice()}; + std::uniform_int_distribution dist{-128, 127}; - auto gen = [&dist, &mersenneGen]() { return dist(mersenneGen); }; + auto gen = [&dist, &mersenneGen]() { return dist(mersenneGen); }; - std::vector randomAudio(inputTensor->bytes); - std::generate(std::begin(randomAudio), std::end(randomAudio), gen); + std::vector randomAudio(inputTensor->Bytes()); + std::generate(std::begin(randomAudio), std::end(randomAudio), gen); - REQUIRE(RunInference(model, randomAudio.data(), inputTensor->bytes)); - return true; -} + REQUIRE(RunInference(model, randomAudio.data(), inputTensor->Bytes())); + return true; + } -TEST_CASE("Running random inference with TensorFlow Lite Micro and Wav2LetterModel Int8", - "[Wav2Letter]") -{ - arm::app::Wav2LetterModel model{}; + TEST_CASE("Running random inference with Tflu and Wav2LetterModel Int8", "[Wav2Letter]") + { + arm::app::fwk::tflm::Wav2LetterModel model{}; - REQUIRE_FALSE(model.IsInited()); - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::asr::GetModelPointer(), - arm::app::asr::GetModelLen())); - REQUIRE(model.IsInited()); + REQUIRE_FALSE(model.IsInited()); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::asr::GetModelPointer(), + arm::app::asr::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); + REQUIRE(model.IsInited()); - REQUIRE(RunInferenceRandom(model)); -} + REQUIRE(RunInferenceRandom(model)); + } -template -void TestInference(const T* input_goldenFV, const T* output_goldenFV, arm::app::Model& model) -{ - TfLiteTensor* inputTensor = model.GetInputTensor(0); - REQUIRE(inputTensor); + template + void TestInference(const T* input_goldenFV, + const T* output_goldenFV, + arm::app::fwk::iface::Model& model) + { + auto inputTensor = model.GetInputTensor(0); + REQUIRE(inputTensor); - REQUIRE(RunInference(model, input_goldenFV, inputTensor->bytes)); + REQUIRE(RunInference(model, input_goldenFV, inputTensor->Bytes())); - TfLiteTensor* outputTensor = model.GetOutputTensor(0); + auto outputTensor = model.GetOutputTensor(0); - REQUIRE(outputTensor); - REQUIRE(outputTensor->bytes == OFM_0_DATA_SIZE); - auto tensorData = tflite::GetTensorData(outputTensor); - REQUIRE(tensorData); + REQUIRE(outputTensor); + REQUIRE(outputTensor->Bytes() == OFM_0_DATA_SIZE); + auto tensorData = outputTensor->GetData(); + REQUIRE(tensorData); - for (size_t i = 0; i < outputTensor->bytes; i++) { - REQUIRE(static_cast(tensorData[i]) == static_cast(((T)output_goldenFV[i]))); + for (size_t i = 0; i < outputTensor->Bytes(); i++) { + REQUIRE(static_cast(tensorData[i]) == static_cast(((T)output_goldenFV[i]))); + } } -} - -TEST_CASE("Running inference with Tflu and Wav2LetterModel Int8", "[Wav2Letter]") -{ - REQUIRE(NUMBER_OF_IFM_FILES == NUMBER_OF_OFM_FILES); - for (uint32_t i = 0; i < NUMBER_OF_IFM_FILES; ++i) { - auto input_goldenFV = reinterpret_cast(GetIfmDataArray(i)); - auto output_goldenFV = reinterpret_cast(GetOfmDataArray(i)); - - DYNAMIC_SECTION("Executing inference with re-init") - { - arm::app::Wav2LetterModel model{}; - - REQUIRE_FALSE(model.IsInited()); - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::asr::GetModelPointer(), - arm::app::asr::GetModelLen())); - REQUIRE(model.IsInited()); - - TestInference(input_goldenFV, output_goldenFV, model); + + TEST_CASE("Running inference with Tflu and Wav2LetterModel Int8", "[Wav2Letter]") + { + REQUIRE(NUMBER_OF_IFM_FILES == NUMBER_OF_OFM_FILES); + for (uint32_t i = 0; i < NUMBER_OF_IFM_FILES; ++i) { + auto input_goldenFV = reinterpret_cast(GetIfmDataArray(i)); + auto output_goldenFV = reinterpret_cast(GetOfmDataArray(i)); + + DYNAMIC_SECTION("Executing inference with re-init") + { + arm::app::fwk::tflm::Wav2LetterModel model{}; + + REQUIRE_FALSE(model.IsInited()); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::asr::GetModelPointer(), + arm::app::asr::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); + REQUIRE(model.IsInited()); + + TestInference(input_goldenFV, output_goldenFV, model); + } } } -} + +} // namespace asr +} // namespace test diff --git a/tests/use_case/asr/Wav2LetterPostprocessingTest.cc b/tests/use_case/asr/Wav2LetterPostprocessingTest.cc index bc711a6bfe3b3c7141c702bcc9a8fe59e75ec620..6f165f1d649a9e30a9d66dca9cfb4edf079d95d8 100644 --- a/tests/use_case/asr/Wav2LetterPostprocessingTest.cc +++ b/tests/use_case/asr/Wav2LetterPostprocessingTest.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2023 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021-2023, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "Wav2LetterPostprocess.hpp" -#include "Wav2LetterModel.hpp" -#include "ClassificationResult.hpp" #include "BufAttributes.hpp" +#include "ClassificationResult.hpp" +#include "TflmTensor.hpp" +#include "Wav2LetterModel.hpp" +#include "Wav2LetterPostprocess.hpp" #include #include @@ -30,109 +31,87 @@ namespace app { namespace asr { extern uint8_t* GetModelPointer(); extern size_t GetModelLen(); - } /* namespace asr */ + } // namespace asr + namespace kws { + extern uint8_t* GetModelPointer(); + extern size_t GetModelLen(); + } // namespace kws } /* namespace app */ } /* namespace arm */ template -static TfLiteTensor GetTestTensor( - std::vector& shape, - T initVal, - std::vector& vectorBuf) +static std::vector +VectorFromTensor(const std::shared_ptr& tensor) { - REQUIRE(0 != shape.size()); - - shape.insert(shape.begin(), shape.size()); - uint32_t sizeInBytes = sizeof(T); - for (size_t i = 1; i < shape.size(); ++i) { - sizeInBytes *= shape[i]; - } - - /* Allocate mem. */ - vectorBuf = std::vector(sizeInBytes, initVal); - TfLiteIntArray* dims = tflite::testing::IntArrayFromInts(shape.data()); - return tflite::testing::CreateQuantizedTensor( - vectorBuf.data(), dims, - 1, 0, "test-tensor"); + auto* tData = tensor->GetData(); + return std::vector(tData, tData + tensor->GetNumElements()); } TEST_CASE("Checking return value") { - SECTION("Mismatched post processing parameters and tensor size") - { - const uint32_t outputCtxLen = 5; - arm::app::AsrClassifier classifier; - arm::app::Wav2LetterModel model; - model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::asr::GetModelPointer(), - arm::app::asr::GetModelLen()); - std::vector placeholderLabels = {"a", "b", "$"}; - const uint32_t blankTokenIdx = 2; - std::vector placeholderResult; - std::vector tensorShape = {1, 1, 1, 13}; - std::vector tensorVec; - TfLiteTensor tensor = GetTestTensor( - tensorShape, 100, tensorVec); - - arm::app::AsrPostProcess post{&tensor, classifier, placeholderLabels, placeholderResult, outputCtxLen, - blankTokenIdx, arm::app::Wav2LetterModel::ms_outputRowsIdx}; - - REQUIRE(!post.DoPostProcess()); - } - SECTION("Post processing succeeds") { - const uint32_t outputCtxLen = 5; - arm::app::AsrClassifier classifier; - arm::app::Wav2LetterModel model; - model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::asr::GetModelPointer(), - arm::app::asr::GetModelLen()); + const uint32_t inputContexLen = 98; + arm::app::AsrClassifier classifier{ + arm::app::fwk::tflm::Wav2LetterModel::ms_inputRowsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_inputColsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputColsIdx}; /* classifier */ + + arm::app::fwk::tflm::Wav2LetterModel model; + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::asr::GetModelPointer(), + arm::app::asr::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); std::vector placeholderLabels = {"a", "b", "$"}; const uint32_t blankTokenIdx = 2; std::vector placeholderResult; - std::vector tensorShape = {1, 1, 13, 1}; - std::vector tensorVec; - TfLiteTensor tensor = GetTestTensor( - tensorShape, 100, tensorVec); - arm::app::AsrPostProcess post{&tensor, classifier, placeholderLabels, placeholderResult, outputCtxLen, - blankTokenIdx, arm::app::Wav2LetterModel::ms_outputRowsIdx}; - - /* Copy elements to compare later. */ - std::vector originalVec = tensorVec; + arm::app::AsrPostProcess post{model, + classifier, + placeholderLabels, + placeholderResult, + inputContexLen, + blankTokenIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx}; /* This step should not erase anything. */ REQUIRE(post.DoPostProcess()); } } - TEST_CASE("Postprocessing - erasing required elements") { - constexpr uint32_t outputCtxLen = 5; - constexpr uint32_t innerLen = 3; - constexpr uint32_t nRows = 2*outputCtxLen + innerLen; - constexpr uint32_t nCols = 10; - constexpr uint32_t blankTokenIdx = nCols - 1; - std::vector tensorShape = {1, 1, nRows, nCols}; - arm::app::AsrClassifier classifier; - arm::app::Wav2LetterModel model; - model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::asr::GetModelPointer(), - arm::app::asr::GetModelLen()); + constexpr uint32_t inputCtxLen = 98; + constexpr uint32_t outputCtxLen = 49; + constexpr uint32_t innerLen = 50; + constexpr uint32_t nCols = 29; + constexpr uint32_t blankTokenIdx = arm::app::fwk::tflm::Wav2LetterModel::ms_blankTokenIdx; + arm::app::AsrClassifier classifier{ + arm::app::fwk::tflm::Wav2LetterModel::ms_inputRowsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_inputColsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputColsIdx}; /* classifier */ + arm::app::fwk::tflm::Wav2LetterModel model; + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::asr::GetModelPointer(), + arm::app::asr::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); std::vector placeholderLabels = {"a", "b", "$"}; std::vector placeholderResult; SECTION("First and last iteration") { - std::vector tensorVec; - TfLiteTensor tensor = GetTestTensor(tensorShape, 100, tensorVec); - arm::app::AsrPostProcess post{&tensor, classifier, placeholderLabels, placeholderResult, outputCtxLen, - blankTokenIdx, arm::app::Wav2LetterModel::ms_outputRowsIdx}; + std::vector tensorVec = VectorFromTensor(model.GetOutputTensor(0)); + arm::app::AsrPostProcess post{model, + classifier, + placeholderLabels, + placeholderResult, + inputCtxLen, + blankTokenIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx}; /* Copy elements to compare later. */ std::vectororiginalVec = tensorVec; @@ -140,27 +119,31 @@ TEST_CASE("Postprocessing - erasing required elements") /* This step should not erase anything. */ post.m_lastIteration = true; REQUIRE(post.DoPostProcess()); + tensorVec = VectorFromTensor(model.GetOutputTensor(0)); REQUIRE(originalVec == tensorVec); } SECTION("Right context erase") { - std::vector tensorVec; - TfLiteTensor tensor = GetTestTensor( - tensorShape, 100, tensorVec); - arm::app::AsrPostProcess post{&tensor, classifier, placeholderLabels, placeholderResult, outputCtxLen, - blankTokenIdx, arm::app::Wav2LetterModel::ms_outputRowsIdx}; + std::vector tensorVec = VectorFromTensor(model.GetOutputTensor(0)); + arm::app::AsrPostProcess post{model, + classifier, + placeholderLabels, + placeholderResult, + inputCtxLen, + blankTokenIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx}; /* Copy elements to compare later. */ std::vector originalVec = tensorVec; - //auto tensorData = tflite::GetTensorData(tensor); /* This step should erase the right context only. */ post.m_lastIteration = false; REQUIRE(post.DoPostProcess()); + tensorVec = VectorFromTensor(model.GetOutputTensor(0)); REQUIRE(originalVec != tensorVec); - /* The last ctxLen * 10 elements should be gone. */ + /* The last ctxLen * nCols elements should be gone. */ for (size_t i = 0; i < outputCtxLen; ++i) { for (size_t j = 0; j < nCols; ++j) { /* Check right context elements are zeroed. Blank token idx should be set to 1 when erasing. */ @@ -183,11 +166,14 @@ TEST_CASE("Postprocessing - erasing required elements") SECTION("Left and right context erase") { - std::vector tensorVec; - TfLiteTensor tensor = GetTestTensor( - tensorShape, 100, tensorVec); - arm::app::AsrPostProcess post{&tensor, classifier, placeholderLabels, placeholderResult, outputCtxLen, - blankTokenIdx, arm::app::Wav2LetterModel::ms_outputRowsIdx}; + std::vector tensorVec = VectorFromTensor(model.GetOutputTensor(0)); + arm::app::AsrPostProcess post{model, + classifier, + placeholderLabels, + placeholderResult, + inputCtxLen, + blankTokenIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx}; /* Copy elements to compare later. */ std::vector originalVec = tensorVec; @@ -199,9 +185,10 @@ TEST_CASE("Postprocessing - erasing required elements") /* Calling it the second time should erase the left context. */ REQUIRE(post.DoPostProcess()); + tensorVec = VectorFromTensor(model.GetOutputTensor(0)); REQUIRE(originalVec != tensorVec); - /* The first and last ctxLen * 10 elements should be gone. */ + /* The first and last ctxLen * nCols elements should be gone. */ for (size_t i = 0; i < outputCtxLen; ++i) { for (size_t j = 0; j < nCols; ++j) { /* Check left and right context elements are zeroed. */ @@ -224,13 +211,16 @@ TEST_CASE("Postprocessing - erasing required elements") SECTION("Try left context erase") { - std::vector tensorVec; - TfLiteTensor tensor = GetTestTensor( - tensorShape, 100, tensorVec); + std::vector tensorVec = VectorFromTensor(model.GetOutputTensor(0)); /* Should not be able to erase the left context if it is the first iteration. */ - arm::app::AsrPostProcess post{&tensor, classifier, placeholderLabels, placeholderResult, outputCtxLen, - blankTokenIdx, arm::app::Wav2LetterModel::ms_outputRowsIdx}; + arm::app::AsrPostProcess post{model, + classifier, + placeholderLabels, + placeholderResult, + inputCtxLen, + blankTokenIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx}; /* Copy elements to compare later. */ std::vector originalVec = tensorVec; @@ -239,6 +229,7 @@ TEST_CASE("Postprocessing - erasing required elements") post.m_lastIteration = true; REQUIRE(post.DoPostProcess()); + tensorVec = VectorFromTensor(model.GetOutputTensor(0)); REQUIRE(originalVec == tensorVec); } } diff --git a/tests/use_case/asr/Wav2LetterPreprocessingTest.cc b/tests/use_case/asr/Wav2LetterPreprocessingTest.cc index 273476ef01143ee52adbd12732b11f45cc9c2b8d..ba46d592743366917d3b8503cd1b94906b288d6e 100644 --- a/tests/use_case/asr/Wav2LetterPreprocessingTest.cc +++ b/tests/use_case/asr/Wav2LetterPreprocessingTest.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2023 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021-2023, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,57 +14,58 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "TflmTensor.hpp" #include "Wav2LetterPreprocess.hpp" -#include #include +#include +#include constexpr uint32_t numMfccFeatures = 13; constexpr uint32_t numMfccVectors = 10; /* Test vector output: generated using test-asr-preprocessing.py. */ int8_t expectedResult[numMfccVectors][numMfccFeatures * 3] = { - /* Feature vec 0. */ - {-32, 4, -9, -8, -10, -10, -11, -11, -11, -11, -12, -11, -11, /* MFCCs. */ - -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, /* Delta 1. */ - -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10}, /* Delta 2. */ - /* Feature vec 1. */ - {-31, 4, -9, -8, -10, -10, -11, -11, -11, -11, -12, -11, -11, - -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, - -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10}, - /* Feature vec 2. */ - {-31, 4, -9, -9, -10, -10, -11, -11, -11, -11, -12, -12, -12, - -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, - -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10}, - /* Feature vec 3. */ - {-31, 4, -9, -9, -10, -10, -11, -11, -11, -11, -11, -12, -12, - -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, - -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10}, - /* Feature vec 4 : this should have valid delta 1 and delta 2. */ - {-31, 4, -9, -9, -10, -10, -11, -11, -11, -11, -11, -12, -12, - -38, -29, -9, 1, -2, -7, -8, -8, -12, -16, -14, -5, 5, - -68, -50, -13, 5, 0, -9, -9, -8, -13, -20, -19, -3, 15}, - /* Feature vec 5 : this should have valid delta 1 and delta 2. */ - {-31, 4, -9, -8, -10, -10, -11, -11, -11, -11, -11, -12, -12, - -62, -45, -11, 5, 0, -8, -9, -8, -12, -19, -17, -3, 13, - -27, -22, -13, -9, -11, -12, -12, -11, -11, -13, -13, -10, -6}, - /* Feature vec 6. */ - {-31, 4, -9, -8, -10, -10, -11, -11, -11, -11, -12, -11, -11, - -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, - -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10}, - /* Feature vec 7. */ - {-32, 4, -9, -8, -10, -10, -11, -11, -11, -12, -12, -11, -11, - -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, - -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10}, - /* Feature vec 8. */ - {-32, 4, -9, -8, -10, -10, -11, -11, -11, -12, -12, -11, -11, - -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, - -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10}, - /* Feature vec 9. */ - {-31, 4, -9, -8, -10, -10, -11, -11, -11, -11, -12, -11, -11, - -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, - -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10} -}; + /* Feature vec 0. */ + {-32, 4, -9, -8, -10, -10, -11, -11, -11, -11, -12, -11, -11, /* MFCCs. */ + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, /* Delta 1. */ + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10}, /* Delta 2. */ + /* Feature vec 1. */ + {-31, 4, -9, -8, -10, -10, -11, -11, -11, -11, -12, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10}, + /* Feature vec 2. */ + {-31, 4, -9, -9, -10, -10, -11, -11, -11, -11, -12, -12, -12, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10}, + /* Feature vec 3. */ + {-31, 4, -9, -9, -10, -10, -11, -11, -11, -11, -11, -12, -12, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10}, + /* Feature vec 4 : this should have valid delta 1 and delta 2. */ + {-31, 4, -9, -9, -10, -10, -11, -11, -11, -11, -11, -12, -12, + -38, -29, -9, 1, -2, -7, -8, -8, -12, -16, -14, -5, 5, + -68, -50, -13, 5, 0, -9, -9, -8, -13, -20, -19, -3, 15}, + /* Feature vec 5 : this should have valid delta 1 and delta 2. */ + {-31, 4, -9, -8, -10, -10, -11, -11, -11, -11, -11, -12, -12, + -62, -45, -11, 5, 0, -8, -9, -8, -12, -19, -17, -3, 13, + -27, -22, -13, -9, -11, -12, -12, -11, -11, -13, -13, -10, -6}, + /* Feature vec 6. */ + {-31, 4, -9, -8, -10, -10, -11, -11, -11, -11, -12, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10}, + /* Feature vec 7. */ + {-32, 4, -9, -8, -10, -10, -11, -11, -11, -12, -12, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10}, + /* Feature vec 8. */ + {-32, 4, -9, -8, -10, -10, -11, -11, -11, -12, -12, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10}, + /* Feature vec 9. */ + {-31, 4, -9, -8, -10, -10, -11, -11, -11, -11, -12, -11, -11, + -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, -11, + -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10, -10}}; void PopulateTestWavVector(std::vector& vec) { @@ -96,8 +97,7 @@ TEST_CASE("Preprocessing calculation INT8") /* Test wav memory. */ std::vector testWav((mfccWindowStride * numMfccVectors) + - (mfccWindowLen - mfccWindowStride) - ); + (mfccWindowLen - mfccWindowStride)); /* Populate with placeholder input. */ PopulateTestWavVector(testWav); @@ -109,18 +109,20 @@ TEST_CASE("Preprocessing calculation INT8") TfLiteIntArray* dims= tflite::testing::IntArrayFromInts(dimArray); TfLiteTensor inputTensor = tflite::testing::CreateQuantizedTensor( tensorVec.data(), dims, quantScale, quantOffset, "preprocessedInput"); + std::shared_ptr inputTensorPtr = + std::make_shared(&inputTensor); /* Initialise pre-processing module. */ - arm::app::AsrPreProcess prep{&inputTensor, - numMfccFeatures, numMfccVectors, mfccWindowLen, mfccWindowStride}; + arm::app::AsrPreProcess prep{ + inputTensorPtr, numMfccFeatures, numMfccVectors, mfccWindowLen, mfccWindowStride}; /* Invoke pre-processing. */ REQUIRE(prep.DoPreProcess(testWav.data(), testWav.size())); /* Wrap the tensor with a std::vector for ease. */ - auto* tensorData = tflite::GetTensorData(&inputTensor); - std::vector vecResults = - std::vector(tensorData, tensorData + inputTensor.bytes); + auto* tensorData = inputTensorPtr->GetData(); + std::vector vecResults = + std::vector(tensorData, tensorData + inputTensorPtr->Bytes()); /* Check sizes. */ REQUIRE(vecResults.size() == sizeof(expectedResult)); diff --git a/tests/use_case/img_class/ImgClassificationUCTest.cc b/tests/use_case/img_class/ImgClassificationUCTest.cc index 73abdc57edd233ddbaf880859cf56640d66cb3a0..bf2c2ebdf9d26f9735d6d5209de03aa56d0abbb5 100644 --- a/tests/use_case/img_class/ImgClassificationUCTest.cc +++ b/tests/use_case/img_class/ImgClassificationUCTest.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2022, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021-2022, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -39,20 +39,22 @@ namespace app { TEST_CASE("Model info") { /* Model wrapper object. */ - arm::app::MobileNetModel model; + arm::app::fwk::tflm::MobileNetModel model; /* Load the model. */ - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::img_class::GetModelPointer(), - arm::app::img_class::GetModelLen())); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::img_class::GetModelPointer(), + arm::app::img_class::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); /* Instantiate application context. */ arm::app::ApplicationContext caseContext; - caseContext.Set("model", model); + caseContext.Set("model", model); - REQUIRE(model.ShowModelInfoHandler()); + model.LogInterpreterInfo(); + REQUIRE(model.IsInited()); } @@ -62,20 +64,21 @@ TEST_CASE("Inference by index", "[.]") hal_platform_init(); /* Model wrapper object. */ - arm::app::MobileNetModel model; + arm::app::fwk::tflm::MobileNetModel model; /* Load the model. */ - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::img_class::GetModelPointer(), - arm::app::img_class::GetModelLen())); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::img_class::GetModelPointer(), + arm::app::img_class::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); /* Instantiate application context. */ arm::app::ApplicationContext caseContext; arm::app::Profiler profiler{"img_class"}; caseContext.Set("profiler", profiler); - caseContext.Set("model", model); + caseContext.Set("model", model); arm::app::Classifier classifier; /* Classifier wrapper object. */ caseContext.Set("classifier", classifier); @@ -97,20 +100,21 @@ TEST_CASE("Inference run all images", "[.]") hal_platform_init(); /* Model wrapper object. */ - arm::app::MobileNetModel model; + arm::app::fwk::tflm::MobileNetModel model; /* Load the model. */ - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::img_class::GetModelPointer(), - arm::app::img_class::GetModelLen())); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::img_class::GetModelPointer(), + arm::app::img_class::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); /* Instantiate application context. */ arm::app::ApplicationContext caseContext; arm::app::Profiler profiler{"img_class"}; caseContext.Set("profiler", profiler); - caseContext.Set("model", model); + caseContext.Set("model", model); arm::app::Classifier classifier; /* classifier wrapper object. */ caseContext.Set("classifier", classifier); diff --git a/tests/use_case/img_class/InferenceTestMobilenetV2.cc b/tests/use_case/img_class/InferenceTestMobilenetV2.cc index 7e22cddd975e4b96054c462710a785298bcdbc0a..79d25eda2d02627cadf5496daf2d4644770cb076 100644 --- a/tests/use_case/img_class/InferenceTestMobilenetV2.cc +++ b/tests/use_case/img_class/InferenceTestMobilenetV2.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2022, 2024 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021-2022, 2024-2025 Arm Limited and/or its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -34,38 +34,38 @@ namespace app { using namespace test; -bool RunInference(arm::app::Model& model, const int8_t imageData[]) +bool RunInference(arm::app::fwk::iface::Model& model, const int8_t imageData[]) { - TfLiteTensor* inputTensor = model.GetInputTensor(0); + auto inputTensor = model.GetInputTensor(0); REQUIRE(inputTensor); const size_t copySz = - inputTensor->bytes < IFM_0_DATA_SIZE ? inputTensor->bytes : IFM_0_DATA_SIZE; - memcpy(inputTensor->data.data, imageData, copySz); + inputTensor->Bytes() < IFM_0_DATA_SIZE ? inputTensor->Bytes() : IFM_0_DATA_SIZE; + memcpy(inputTensor->GetData(), imageData, copySz); if (model.IsDataSigned()) { - arm::app::image::ConvertImgToInt8(inputTensor->data.data, copySz); + arm::app::image::ConvertUint8ToInt8(inputTensor->GetData(), copySz); } return model.RunInference(); } template -void TestInference(int imageIdx, arm::app::Model& model, T tolerance) +void TestInference(int imageIdx, arm::app::fwk::iface::Model& model, T tolerance) { auto image = reinterpret_cast(GetIfmDataArray(imageIdx)); auto goldenFV = reinterpret_cast(GetOfmDataArray(imageIdx)); REQUIRE(RunInference(model, image)); - TfLiteTensor* outputTensor = model.GetOutputTensor(0); + auto outputTensor = model.GetOutputTensor(0); REQUIRE(outputTensor); - REQUIRE(outputTensor->bytes == OFM_0_DATA_SIZE); - auto tensorData = tflite::GetTensorData(outputTensor); + REQUIRE(outputTensor->Bytes() == OFM_0_DATA_SIZE); + auto tensorData = outputTensor->GetData(); REQUIRE(tensorData); - for (size_t i = 0; i < outputTensor->bytes; i++) { + for (size_t i = 0; i < outputTensor->Bytes(); i++) { REQUIRE(static_cast(tensorData[i]) == Approx(static_cast((T)goldenFV[i])).epsilon(tolerance)); } @@ -75,14 +75,15 @@ TEST_CASE("Running inference with TensorFlow Lite Micro and MobileNeV2 Uint8", " { SECTION("Executing inferences sequentially") { - arm::app::MobileNetModel model{}; + arm::app::fwk::tflm::MobileNetModel model{}; REQUIRE_FALSE(model.IsInited()); - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::img_class::GetModelPointer(), - arm::app::img_class::GetModelLen())); - REQUIRE(model.IsInited()); + + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::img_class::GetModelPointer(), + arm::app::img_class::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); for (uint32_t i = 0; i < NUMBER_OF_IFM_FILES; ++i) { TestInference(i, model, 1); @@ -92,13 +93,14 @@ TEST_CASE("Running inference with TensorFlow Lite Micro and MobileNeV2 Uint8", " for (uint32_t i = 0; i < NUMBER_OF_IFM_FILES; ++i) { DYNAMIC_SECTION("Executing inference with re-init") { - arm::app::MobileNetModel model{}; + arm::app::fwk::tflm::MobileNetModel model{}; REQUIRE_FALSE(model.IsInited()); - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::img_class::GetModelPointer(), - arm::app::img_class::GetModelLen())); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::img_class::GetModelPointer(), + arm::app::img_class::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); REQUIRE(model.IsInited()); TestInference(i, model, 1); diff --git a/tests/use_case/inference_runner/InferenceTestInferenceRunner.cc b/tests/use_case/inference_runner/InferenceTestInferenceRunner.cc index 974d2c808fd18875fc2f831d3967368b74b346c5..429ad067644c4df21bc39a99027a9190f74cc238 100644 --- a/tests/use_case/inference_runner/InferenceTestInferenceRunner.cc +++ b/tests/use_case/inference_runner/InferenceTestInferenceRunner.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2023 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2023, 2025 Arm Limited and/or its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -33,12 +33,12 @@ namespace app { TEST_CASE("Testing Init failure due to insufficient tensor arena inf runner", "[inf runner]") { - arm::app::TestModel model{}; + arm::app::fwk::tflm::TestModel model{}; REQUIRE_FALSE(model.IsInited()); size_t insufficientTensorArenaSz = 1000; - REQUIRE_FALSE(model.Init(arm::app::tensorArena, - insufficientTensorArenaSz, - arm::app::inference_runner::GetModelPointer(), - arm::app::inference_runner::GetModelLen())); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::inference_runner::GetModelPointer(), + arm::app::inference_runner::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, insufficientTensorArenaSz}; + REQUIRE_FALSE(model.Init(computeMem, modelMem)); REQUIRE_FALSE(model.IsInited()); } diff --git a/tests/use_case/kws/InferenceTestMicroNetKws.cc b/tests/use_case/kws/InferenceTestMicroNetKws.cc index a286e10bcaf59ba48761eb1a07337205209c044d..80c3193ee6c971fdab92ba0f5f0d355307f451f8 100644 --- a/tests/use_case/kws/InferenceTestMicroNetKws.cc +++ b/tests/use_case/kws/InferenceTestMicroNetKws.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -32,89 +32,96 @@ namespace app { } /* namespace app */ } /* namespace arm */ -using namespace test; +namespace test { +namespace kws { -bool RunInference(arm::app::Model& model, const int8_t vec[]) -{ - TfLiteTensor* inputTensor = model.GetInputTensor(0); - REQUIRE(inputTensor); + bool RunInference(arm::app::fwk::iface::Model& model, const int8_t vec[]) + { + auto inputTensor = model.GetInputTensor(0); + REQUIRE(inputTensor); - const size_t copySz = - inputTensor->bytes < IFM_0_DATA_SIZE ? inputTensor->bytes : IFM_0_DATA_SIZE; - memcpy(inputTensor->data.data, vec, copySz); + const size_t copySz = + inputTensor->Bytes() < IFM_0_DATA_SIZE ? inputTensor->Bytes() : IFM_0_DATA_SIZE; + memcpy(inputTensor->GetData(), vec, copySz); - return model.RunInference(); -} + return model.RunInference(); + } -bool RunInferenceRandom(arm::app::Model& model) -{ - TfLiteTensor* inputTensor = model.GetInputTensor(0); - REQUIRE(inputTensor); + bool RunInferenceRandom(arm::app::fwk::iface::Model& model) + { + auto inputTensor = model.GetInputTensor(0); + REQUIRE(inputTensor); - std::random_device rndDevice; - std::mt19937 mersenneGen{rndDevice()}; - std::uniform_int_distribution dist{-128, 127}; + std::random_device rndDevice; + std::mt19937 mersenneGen{rndDevice()}; + std::uniform_int_distribution dist{-128, 127}; - auto gen = [&dist, &mersenneGen]() { return dist(mersenneGen); }; + auto gen = [&dist, &mersenneGen]() { return dist(mersenneGen); }; - std::vector randomAudio(inputTensor->bytes); - std::generate(std::begin(randomAudio), std::end(randomAudio), gen); + std::vector randomAudio(inputTensor->Bytes()); + std::generate(std::begin(randomAudio), std::end(randomAudio), gen); - REQUIRE(RunInference(model, randomAudio.data())); - return true; -} + REQUIRE(RunInference(model, randomAudio.data())); + return true; + } -template -void TestInference(const T* input_goldenFV, const T* output_goldenFV, arm::app::Model& model) -{ - REQUIRE(RunInference(model, input_goldenFV)); + template + void TestInference(const T* input_goldenFV, + const T* output_goldenFV, + arm::app::fwk::iface::Model& model) + { + REQUIRE(RunInference(model, input_goldenFV)); - TfLiteTensor* outputTensor = model.GetOutputTensor(0); + auto outputTensor = model.GetOutputTensor(0); - REQUIRE(outputTensor); - REQUIRE(outputTensor->bytes == OFM_0_DATA_SIZE); - auto tensorData = tflite::GetTensorData(outputTensor); - REQUIRE(tensorData); + REQUIRE(outputTensor); + REQUIRE(outputTensor->Bytes() == OFM_0_DATA_SIZE); + auto tensorData = outputTensor->GetData(); + REQUIRE(tensorData); - for (size_t i = 0; i < outputTensor->bytes; i++) { - REQUIRE(static_cast(tensorData[i]) == static_cast(((T)output_goldenFV[i]))); + for (size_t i = 0; i < outputTensor->Bytes(); i++) { + REQUIRE(static_cast(tensorData[i]) == static_cast((T)output_goldenFV[i])); + } } -} - -TEST_CASE("Running random inference with TensorFlow Lite Micro and MicroNetKwsModel Int8", - "[MicroNetKws]") -{ - arm::app::MicroNetKwsModel model{}; - - REQUIRE_FALSE(model.IsInited()); - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::kws::GetModelPointer(), - arm::app::kws::GetModelLen())); - REQUIRE(model.IsInited()); - - REQUIRE(RunInferenceRandom(model)); -} - -TEST_CASE("Running inference with TensorFlow Lite Micro and MicroNetKwsModel int8", "[MicroNetKws]") -{ - REQUIRE(NUMBER_OF_IFM_FILES == NUMBER_OF_OFM_FILES); - for (uint32_t i = 0; i < NUMBER_OF_IFM_FILES; ++i) { - auto input_goldenFV = reinterpret_cast(GetIfmDataArray(i)); - auto output_goldenFV = reinterpret_cast(GetOfmDataArray(i)); - - DYNAMIC_SECTION("Executing inference with re-init " << i) - { - arm::app::MicroNetKwsModel model{}; - - REQUIRE_FALSE(model.IsInited()); - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::kws::GetModelPointer(), - arm::app::kws::GetModelLen())); - REQUIRE(model.IsInited()); - - TestInference(input_goldenFV, output_goldenFV, model); + + TEST_CASE("Running random inference with Tflu and MicroNetKwsModel Int8", "[MicroNetKws]") + { + arm::app::fwk::tflm::MicroNetKwsModel model{}; + + REQUIRE_FALSE(model.IsInited()); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::kws::GetModelPointer(), + arm::app::kws::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); + REQUIRE(model.IsInited()); + + REQUIRE(RunInferenceRandom(model)); + } + + TEST_CASE("Running inference with Tflu and MicroNetKwsModel Int8", "[MicroNetKws]") + { + REQUIRE(NUMBER_OF_IFM_FILES == NUMBER_OF_OFM_FILES); + for (uint32_t i = 0; i < NUMBER_OF_IFM_FILES; ++i) { + auto input_goldenFV = reinterpret_cast(GetIfmDataArray(i)); + auto output_goldenFV = reinterpret_cast(GetOfmDataArray(i)); + + DYNAMIC_SECTION("Executing inference with re-init") + { + arm::app::fwk::tflm::MicroNetKwsModel model{}; + + REQUIRE_FALSE(model.IsInited()); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::kws::GetModelPointer(), + arm::app::kws::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); + REQUIRE(model.IsInited()); + + TestInference(input_goldenFV, output_goldenFV, model); + } } } -} + +} // namespace kws +} // namespace test diff --git a/tests/use_case/kws/KWSHandlerTest.cc b/tests/use_case/kws/KWSHandlerTest.cc index da16d21123cf65bcd6389914a3965b99e3644942..c5890ffbcc286aaeca3f39fb42bdbcf5f5228203 100644 --- a/tests/use_case/kws/KWSHandlerTest.cc +++ b/tests/use_case/kws/KWSHandlerTest.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2022, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021-2022, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -39,20 +39,22 @@ namespace arm { TEST_CASE("Model info") { /* Model wrapper object. */ - arm::app::MicroNetKwsModel model; + arm::app::fwk::tflm::MicroNetKwsModel model{}; - /* Load the model. */ - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::kws::GetModelPointer(), - arm::app::kws::GetModelLen())); + REQUIRE_FALSE(model.IsInited()); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::kws::GetModelPointer(), + arm::app::kws::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); /* Instantiate application context. */ arm::app::ApplicationContext caseContext; - caseContext.Set("model", model); + caseContext.Set("model", model); - REQUIRE(model.ShowModelInfoHandler()); + model.LogInterpreterInfo(); + REQUIRE(model.IsInited()); } TEST_CASE("Inference run all clips") @@ -61,20 +63,19 @@ TEST_CASE("Inference run all clips") hal_platform_init(); /* Model wrapper object. */ - arm::app::MicroNetKwsModel model; - - /* Load the model. */ - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::kws::GetModelPointer(), - arm::app::kws::GetModelLen())); + arm::app::fwk::tflm::MicroNetKwsModel model{}; + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::kws::GetModelPointer(), + arm::app::kws::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); /* Instantiate application context. */ arm::app::ApplicationContext caseContext; arm::app::Profiler profiler{"kws"}; caseContext.Set("profiler", profiler); - caseContext.Set("model", model); + caseContext.Set("model", model); caseContext.Set("frameLength", arm::app::kws::g_FrameLength); /* 640 sample length for MicroNet. */ caseContext.Set("frameStride", arm::app::kws::g_FrameStride); /* 320 sample stride for MicroNet. */ caseContext.Set("scoreThreshold", 0.7); /* Normalised score threshold. */ diff --git a/tests/use_case/kws/KwsClassifierTests.cc b/tests/use_case/kws/KwsClassifierTests.cc index 4e2fe17e10b8466ed8443eb2a46bf4490106e41c..8976f88ba06f5b5da6dc2004007505f41b920c95 100644 --- a/tests/use_case/kws/KwsClassifierTests.cc +++ b/tests/use_case/kws/KwsClassifierTests.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2022 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2022, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -15,18 +15,11 @@ * limitations under the License. */ #include "KwsClassifier.hpp" +#include "TensorFlowLiteMicro.hpp" +#include "TflmTensor.hpp" #include -TEST_CASE("Test invalid classifier") -{ - TfLiteTensor* outputTens = nullptr; - std::vector resultVec; - arm::app::KwsClassifier classifier; - std::vector> resultHistory; - REQUIRE(!classifier.GetClassificationResults(outputTens, resultVec, {}, 5, true, resultHistory)); -} - TEST_CASE("Test valid classifier, average=0 should be same as 1)") { int dimArray[] = {1, 5}; @@ -36,7 +29,7 @@ TEST_CASE("Test valid classifier, average=0 should be same as 1)") TfLiteIntArray* dims= tflite::testing::IntArrayFromInts(dimArray); TfLiteTensor tfTensor = tflite::testing::CreateQuantizedTensor( outputVec.data(), dims, 1, 0); - TfLiteTensor* outputTensor = &tfTensor; + auto outputTensor = std::make_shared(&tfTensor); std::vector resultVec; arm::app::KwsClassifier classifier; @@ -61,7 +54,7 @@ TEST_CASE("Test valid classifier UINT8, average=1, softmax=false") TfLiteIntArray* dims= tflite::testing::IntArrayFromInts(dimArray); TfLiteTensor tfTensor = tflite::testing::CreateQuantizedTensor( outputVec.data(), dims, 1, 0); - TfLiteTensor* outputTensor = &tfTensor; + auto outputTensor = std::make_shared(&tfTensor); std::vector resultVec; arm::app::KwsClassifier classifier; @@ -83,7 +76,7 @@ TEST_CASE("Test valid classifier UINT8, average=2") TfLiteIntArray* dims= tflite::testing::IntArrayFromInts(dimArray); TfLiteTensor tfTensor = tflite::testing::CreateQuantizedTensor( outputVec.data(), dims, 1, 0); - TfLiteTensor* outputTensor = &tfTensor; + auto outputTensor = std::make_shared(&tfTensor); std::vector resultVec; arm::app::KwsClassifier classifier; @@ -108,7 +101,7 @@ TEST_CASE("Test valid classifier int8, average=0") TfLiteIntArray* dims= tflite::testing::IntArrayFromInts(dimArray); TfLiteTensor tfTensor = tflite::testing::CreateQuantizedTensor( outputVec.data(), dims, 1, 0); - TfLiteTensor* outputTensor = &tfTensor; + auto outputTensor = std::make_shared(&tfTensor); std::vector resultVec; arm::app::KwsClassifier classifier; @@ -122,4 +115,4 @@ TEST_CASE("Test valid classifier int8, average=0") std::vector> expectedHistory = {}; REQUIRE(resultHistory == expectedHistory); -} \ No newline at end of file +} diff --git a/tests/use_case/kws_asr/InferenceTestMicroNetKws.cc b/tests/use_case/kws_asr/InferenceTestMicroNetKws.cc index 14e4ce2cca8f43f255959c837c5c4d7b566897b5..8c502ef849d2bb3f93799015fe8067f0730e0160 100644 --- a/tests/use_case/kws_asr/InferenceTestMicroNetKws.cc +++ b/tests/use_case/kws_asr/InferenceTestMicroNetKws.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,21 +35,21 @@ namespace app { namespace test { namespace kws { - bool RunInference(arm::app::Model& model, const int8_t vec[]) + bool RunInference(arm::app::fwk::iface::Model& model, const int8_t vec[]) { - TfLiteTensor* inputTensor = model.GetInputTensor(0); + auto inputTensor = model.GetInputTensor(0); REQUIRE(inputTensor); const size_t copySz = - inputTensor->bytes < IFM_0_DATA_SIZE ? inputTensor->bytes : IFM_0_DATA_SIZE; - memcpy(inputTensor->data.data, vec, copySz); + inputTensor->Bytes() < IFM_0_DATA_SIZE ? inputTensor->Bytes() : IFM_0_DATA_SIZE; + memcpy(inputTensor->GetData(), vec, copySz); return model.RunInference(); } - bool RunInferenceRandom(arm::app::Model& model) + bool RunInferenceRandom(arm::app::fwk::iface::Model& model) { - TfLiteTensor* inputTensor = model.GetInputTensor(0); + auto inputTensor = model.GetInputTensor(0); REQUIRE(inputTensor); std::random_device rndDevice; @@ -58,7 +58,7 @@ namespace kws { auto gen = [&dist, &mersenneGen]() { return dist(mersenneGen); }; - std::vector randomAudio(inputTensor->bytes); + std::vector randomAudio(inputTensor->Bytes()); std::generate(std::begin(randomAudio), std::end(randomAudio), gen); REQUIRE(RunInference(model, randomAudio.data())); @@ -66,31 +66,34 @@ namespace kws { } template - void TestInference(const T* input_goldenFV, const T* output_goldenFV, arm::app::Model& model) + void TestInference(const T* input_goldenFV, + const T* output_goldenFV, + arm::app::fwk::iface::Model& model) { REQUIRE(RunInference(model, input_goldenFV)); - TfLiteTensor* outputTensor = model.GetOutputTensor(0); + auto outputTensor = model.GetOutputTensor(0); REQUIRE(outputTensor); - REQUIRE(outputTensor->bytes == OFM_0_DATA_SIZE); - auto tensorData = tflite::GetTensorData(outputTensor); + REQUIRE(outputTensor->Bytes() == OFM_0_DATA_SIZE); + auto tensorData = outputTensor->GetData(); REQUIRE(tensorData); - for (size_t i = 0; i < outputTensor->bytes; i++) { + for (size_t i = 0; i < outputTensor->Bytes(); i++) { REQUIRE(static_cast(tensorData[i]) == static_cast((T)output_goldenFV[i])); } } TEST_CASE("Running random inference with Tflu and MicroNetKwsModel Int8", "[MicroNetKws]") { - arm::app::MicroNetKwsModel model{}; + arm::app::fwk::tflm::MicroNetKwsModel model{}; REQUIRE_FALSE(model.IsInited()); - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::kws::GetModelPointer(), - arm::app::kws::GetModelLen())); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::kws::GetModelPointer(), + arm::app::kws::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); REQUIRE(model.IsInited()); REQUIRE(RunInferenceRandom(model)); @@ -105,13 +108,14 @@ namespace kws { DYNAMIC_SECTION("Executing inference with re-init") { - arm::app::MicroNetKwsModel model{}; + arm::app::fwk::tflm::MicroNetKwsModel model{}; REQUIRE_FALSE(model.IsInited()); - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::kws::GetModelPointer(), - arm::app::kws::GetModelLen())); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::kws::GetModelPointer(), + arm::app::kws::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); REQUIRE(model.IsInited()); TestInference(input_goldenFV, output_goldenFV, model); diff --git a/tests/use_case/kws_asr/InferenceTestWav2Letter.cc b/tests/use_case/kws_asr/InferenceTestWav2Letter.cc index 29e04eb30e21f16341426f020b304050e9ae2550..96792c9ae8f94c30169cdc98c7d764632e526502 100644 --- a/tests/use_case/kws_asr/InferenceTestWav2Letter.cc +++ b/tests/use_case/kws_asr/InferenceTestWav2Letter.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021, 2024 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021, 2024-2025 Arm Limited and/or its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,19 +35,19 @@ namespace app { namespace test { namespace asr { - bool RunInference(arm::app::Model& model, const int8_t vec[], const size_t copySz) + bool RunInference(arm::app::fwk::iface::Model& model, const int8_t vec[], const size_t copySz) { - TfLiteTensor* inputTensor = model.GetInputTensor(0); + auto inputTensor = model.GetInputTensor(0); REQUIRE(inputTensor); - memcpy(inputTensor->data.data, vec, copySz); + memcpy(inputTensor->GetData(), vec, copySz); return model.RunInference(); } - bool RunInferenceRandom(arm::app::Model& model) + bool RunInferenceRandom(arm::app::fwk::iface::Model& model) { - TfLiteTensor* inputTensor = model.GetInputTensor(0); + auto inputTensor = model.GetInputTensor(0); REQUIRE(inputTensor); std::random_device rndDevice; @@ -56,43 +56,46 @@ namespace asr { auto gen = [&dist, &mersenneGen]() { return dist(mersenneGen); }; - std::vector randomAudio(inputTensor->bytes); + std::vector randomAudio(inputTensor->Bytes()); std::generate(std::begin(randomAudio), std::end(randomAudio), gen); - REQUIRE(RunInference(model, randomAudio.data(), inputTensor->bytes)); + REQUIRE(RunInference(model, randomAudio.data(), inputTensor->Bytes())); return true; } TEST_CASE("Running random inference with Tflu and Wav2LetterModel Int8", "[Wav2Letter]") { - arm::app::Wav2LetterModel model{}; + arm::app::fwk::tflm::Wav2LetterModel model{}; REQUIRE_FALSE(model.IsInited()); - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::asr::GetModelPointer(), - arm::app::asr::GetModelLen())); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::asr::GetModelPointer(), + arm::app::asr::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); REQUIRE(model.IsInited()); REQUIRE(RunInferenceRandom(model)); } template - void TestInference(const T* input_goldenFV, const T* output_goldenFV, arm::app::Model& model) + void TestInference(const T* input_goldenFV, + const T* output_goldenFV, + arm::app::fwk::iface::Model& model) { - TfLiteTensor* inputTensor = model.GetInputTensor(0); + auto inputTensor = model.GetInputTensor(0); REQUIRE(inputTensor); - REQUIRE(RunInference(model, input_goldenFV, inputTensor->bytes)); + REQUIRE(RunInference(model, input_goldenFV, inputTensor->Bytes())); - TfLiteTensor* outputTensor = model.GetOutputTensor(0); + auto outputTensor = model.GetOutputTensor(0); REQUIRE(outputTensor); - REQUIRE(outputTensor->bytes == OFM_0_DATA_SIZE); - auto tensorData = tflite::GetTensorData(outputTensor); + REQUIRE(outputTensor->Bytes() == OFM_0_DATA_SIZE); + auto tensorData = outputTensor->GetData(); REQUIRE(tensorData); - for (size_t i = 0; i < outputTensor->bytes; i++) { + for (size_t i = 0; i < outputTensor->Bytes(); i++) { REQUIRE(static_cast(tensorData[i]) == static_cast(((T)output_goldenFV[i]))); } } @@ -106,13 +109,14 @@ namespace asr { DYNAMIC_SECTION("Executing inference with re-init") { - arm::app::Wav2LetterModel model{}; + arm::app::fwk::tflm::Wav2LetterModel model{}; REQUIRE_FALSE(model.IsInited()); - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::asr::GetModelPointer(), - arm::app::asr::GetModelLen())); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::asr::GetModelPointer(), + arm::app::asr::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); REQUIRE(model.IsInited()); TestInference(input_goldenFV, output_goldenFV, model); diff --git a/tests/use_case/kws_asr/InitModels.cc b/tests/use_case/kws_asr/InitModels.cc index 439d67fb1de2013c81dc106dd2bed13cb9192331..2b557a93fac5690682454b94aa9655267d51df5e 100644 --- a/tests/use_case/kws_asr/InitModels.cc +++ b/tests/use_case/kws_asr/InitModels.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -38,36 +38,23 @@ namespace arm { /* Skip this test, Wav2LetterModel if not Vela optimized but only from ML-zoo will fail. */ TEST_CASE("Init two Models", "[.]") { - arm::app::MicroNetKwsModel model1; - arm::app::MicroNetKwsModel model2; - - /* Ideally we should load the wav2letter model here, but there is - * none available to run on native (ops not supported on unoptimised - * version). However, we can certainly create two instances of the - * same type of model to see if our tensor arena re-use works as - * intended. - * - * @TODO: uncomment this when this model can run on native pipeline. */ - //arm::app::Wav2LetterModel model2; /* model2. */ - - /* Load/initialise the first model. */ - REQUIRE(model1.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::kws::GetModelPointer(), - arm::app::kws::GetModelLen())); - - /* Allocator instance should have been created. */ - REQUIRE(nullptr != model1.GetAllocator()); - - /* Load the second model using the same allocator as model 1. */ - REQUIRE(model2.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::asr::GetModelPointer(), - arm::app::asr::GetModelLen(), - model1.GetAllocator())); - - /* Make sure they point to the same allocator object. */ - REQUIRE(model1.GetAllocator() == model2.GetAllocator()); + arm::app::fwk::tflm::MicroNetKwsModel model1; + arm::app::fwk::tflm::Wav2LetterModel model2; + { + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::kws::GetModelPointer(), + arm::app::kws::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model1.Init(computeMem, modelMem)); + } + + { + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::asr::GetModelPointer(), + arm::app::asr::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model2.Init(computeMem, modelMem, &model1.GetBackendData())); + } /* Both models should report being initialised. */ REQUIRE(true == model1.IsInited()); diff --git a/tests/use_case/kws_asr/Wav2LetterPostprocessingTest.cc b/tests/use_case/kws_asr/Wav2LetterPostprocessingTest.cc index d19e0c4ff8e55777526cba483281506ed6bfb465..f9c7666ffa087ea916b3521801ae2a0dd6844aca 100644 --- a/tests/use_case/kws_asr/Wav2LetterPostprocessingTest.cc +++ b/tests/use_case/kws_asr/Wav2LetterPostprocessingTest.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2023 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021-2023, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,11 @@ * See the License for the specific language governing permissions and * limitations under the License. */ -#include "Wav2LetterPostprocess.hpp" -#include "Wav2LetterModel.hpp" -#include "ClassificationResult.hpp" #include "BufAttributes.hpp" +#include "ClassificationResult.hpp" +#include "TflmTensor.hpp" +#include "Wav2LetterModel.hpp" +#include "Wav2LetterPostprocess.hpp" #include #include @@ -39,104 +40,78 @@ namespace arm { } /* namespace arm */ template -static TfLiteTensor GetTestTensor( - std::vector& shape, - T initVal, - std::vector& vectorBuf) +static std::vector +VectorFromTensor(const std::shared_ptr& tensor) { - REQUIRE(0 != shape.size()); - - shape.insert(shape.begin(), shape.size()); - uint32_t sizeInBytes = sizeof(T); - for (size_t i = 1; i < shape.size(); ++i) { - sizeInBytes *= shape[i]; - } - - /* Allocate mem. */ - vectorBuf = std::vector(sizeInBytes, initVal); - TfLiteIntArray* dims = tflite::testing::IntArrayFromInts(shape.data()); - return tflite::testing::CreateQuantizedTensor( - vectorBuf.data(), dims, - 1, 0, "test-tensor"); + auto* tData = tensor->GetData(); + return std::vector(tData, tData + tensor->GetNumElements()); } TEST_CASE("Checking return value") { - SECTION("Mismatched post processing parameters and tensor size") - { - const uint32_t outputCtxLen = 5; - arm::app::AsrClassifier classifier; - arm::app::Wav2LetterModel model; - model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::asr::GetModelPointer(), - arm::app::asr::GetModelLen()); - std::vector placeholderLabels = {"a", "b", "$"}; - const uint32_t blankTokenIdx = 2; - std::vector placeholderResult; - std::vector tensorShape = {1, 1, 1, 13}; - std::vector tensorVec; - TfLiteTensor tensor = GetTestTensor( - tensorShape, 100, tensorVec); - - arm::app::AsrPostProcess post{&tensor, classifier, placeholderLabels, placeholderResult, outputCtxLen, - blankTokenIdx, arm::app::Wav2LetterModel::ms_outputRowsIdx}; - - REQUIRE(!post.DoPostProcess()); - } - SECTION("Post processing succeeds") { - const uint32_t outputCtxLen = 5; - arm::app::AsrClassifier classifier; - arm::app::Wav2LetterModel model; - model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::asr::GetModelPointer(), - arm::app::asr::GetModelLen()); + const uint32_t inputContexLen = 98; + arm::app::AsrClassifier classifier{ + arm::app::fwk::tflm::Wav2LetterModel::ms_inputRowsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_inputColsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputColsIdx}; /* classifier */ + + arm::app::fwk::tflm::Wav2LetterModel model; + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::asr::GetModelPointer(), + arm::app::asr::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); std::vector placeholderLabels = {"a", "b", "$"}; const uint32_t blankTokenIdx = 2; std::vector placeholderResult; - std::vector tensorShape = {1, 1, 13, 1}; - std::vector tensorVec; - TfLiteTensor tensor = GetTestTensor( - tensorShape, 100, tensorVec); - arm::app::AsrPostProcess post{&tensor, classifier, placeholderLabels, placeholderResult, outputCtxLen, - blankTokenIdx, arm::app::Wav2LetterModel::ms_outputRowsIdx}; - - /* Copy elements to compare later. */ - std::vector originalVec = tensorVec; + arm::app::AsrPostProcess post{model, + classifier, + placeholderLabels, + placeholderResult, + inputContexLen, + blankTokenIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx}; /* This step should not erase anything. */ REQUIRE(post.DoPostProcess()); } } - TEST_CASE("Postprocessing - erasing required elements") { - constexpr uint32_t outputCtxLen = 5; - constexpr uint32_t innerLen = 3; - constexpr uint32_t nRows = 2*outputCtxLen + innerLen; - constexpr uint32_t nCols = 10; - constexpr uint32_t blankTokenIdx = nCols - 1; - std::vector tensorShape = {1, 1, nRows, nCols}; - arm::app::AsrClassifier classifier; - arm::app::Wav2LetterModel model; - model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::asr::GetModelPointer(), - arm::app::asr::GetModelLen()); + constexpr uint32_t inputCtxLen = 98; + constexpr uint32_t outputCtxLen = 49; + constexpr uint32_t innerLen = 50; + constexpr uint32_t nCols = 29; + constexpr uint32_t blankTokenIdx = arm::app::fwk::tflm::Wav2LetterModel::ms_blankTokenIdx; + arm::app::AsrClassifier classifier{ + arm::app::fwk::tflm::Wav2LetterModel::ms_inputRowsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_inputColsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputColsIdx}; /* classifier */ + arm::app::fwk::tflm::Wav2LetterModel model; + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::asr::GetModelPointer(), + arm::app::asr::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); std::vector placeholderLabels = {"a", "b", "$"}; std::vector placeholderResult; SECTION("First and last iteration") { - std::vector tensorVec; - TfLiteTensor tensor = GetTestTensor(tensorShape, 100, tensorVec); - arm::app::AsrPostProcess post{&tensor, classifier, placeholderLabels, placeholderResult, outputCtxLen, - blankTokenIdx, arm::app::Wav2LetterModel::ms_outputRowsIdx}; + std::vector tensorVec = VectorFromTensor(model.GetOutputTensor(0)); + arm::app::AsrPostProcess post{model, + classifier, + placeholderLabels, + placeholderResult, + inputCtxLen, + blankTokenIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx}; /* Copy elements to compare later. */ std::vectororiginalVec = tensorVec; @@ -144,16 +119,20 @@ TEST_CASE("Postprocessing - erasing required elements") /* This step should not erase anything. */ post.m_lastIteration = true; REQUIRE(post.DoPostProcess()); + tensorVec = VectorFromTensor(model.GetOutputTensor(0)); REQUIRE(originalVec == tensorVec); } SECTION("Right context erase") { - std::vector tensorVec; - TfLiteTensor tensor = GetTestTensor( - tensorShape, 100, tensorVec); - arm::app::AsrPostProcess post{&tensor, classifier, placeholderLabels, placeholderResult, outputCtxLen, - blankTokenIdx, arm::app::Wav2LetterModel::ms_outputRowsIdx}; + std::vector tensorVec = VectorFromTensor(model.GetOutputTensor(0)); + arm::app::AsrPostProcess post{model, + classifier, + placeholderLabels, + placeholderResult, + inputCtxLen, + blankTokenIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx}; /* Copy elements to compare later. */ std::vector originalVec = tensorVec; @@ -161,9 +140,10 @@ TEST_CASE("Postprocessing - erasing required elements") /* This step should erase the right context only. */ post.m_lastIteration = false; REQUIRE(post.DoPostProcess()); + tensorVec = VectorFromTensor(model.GetOutputTensor(0)); REQUIRE(originalVec != tensorVec); - /* The last ctxLen * 10 elements should be gone. */ + /* The last ctxLen * nCols elements should be gone. */ for (size_t i = 0; i < outputCtxLen; ++i) { for (size_t j = 0; j < nCols; ++j) { /* Check right context elements are zeroed. Blank token idx should be set to 1 when erasing. */ @@ -186,11 +166,14 @@ TEST_CASE("Postprocessing - erasing required elements") SECTION("Left and right context erase") { - std::vector tensorVec; - TfLiteTensor tensor = GetTestTensor( - tensorShape, 100, tensorVec); - arm::app::AsrPostProcess post{&tensor, classifier, placeholderLabels, placeholderResult, outputCtxLen, - blankTokenIdx, arm::app::Wav2LetterModel::ms_outputRowsIdx}; + std::vector tensorVec = VectorFromTensor(model.GetOutputTensor(0)); + arm::app::AsrPostProcess post{model, + classifier, + placeholderLabels, + placeholderResult, + inputCtxLen, + blankTokenIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx}; /* Copy elements to compare later. */ std::vector originalVec = tensorVec; @@ -202,9 +185,10 @@ TEST_CASE("Postprocessing - erasing required elements") /* Calling it the second time should erase the left context. */ REQUIRE(post.DoPostProcess()); + tensorVec = VectorFromTensor(model.GetOutputTensor(0)); REQUIRE(originalVec != tensorVec); - /* The first and last ctxLen * 10 elements should be gone. */ + /* The first and last ctxLen * nCols elements should be gone. */ for (size_t i = 0; i < outputCtxLen; ++i) { for (size_t j = 0; j < nCols; ++j) { /* Check left and right context elements are zeroed. */ @@ -227,13 +211,16 @@ TEST_CASE("Postprocessing - erasing required elements") SECTION("Try left context erase") { - std::vector tensorVec; - TfLiteTensor tensor = GetTestTensor( - tensorShape, 100, tensorVec); + std::vector tensorVec = VectorFromTensor(model.GetOutputTensor(0)); /* Should not be able to erase the left context if it is the first iteration. */ - arm::app::AsrPostProcess post{&tensor, classifier, placeholderLabels, placeholderResult, outputCtxLen, - blankTokenIdx, arm::app::Wav2LetterModel::ms_outputRowsIdx}; + arm::app::AsrPostProcess post{model, + classifier, + placeholderLabels, + placeholderResult, + inputCtxLen, + blankTokenIdx, + arm::app::fwk::tflm::Wav2LetterModel::ms_outputRowsIdx}; /* Copy elements to compare later. */ std::vector originalVec = tensorVec; @@ -242,6 +229,7 @@ TEST_CASE("Postprocessing - erasing required elements") post.m_lastIteration = true; REQUIRE(post.DoPostProcess()); + tensorVec = VectorFromTensor(model.GetOutputTensor(0)); REQUIRE(originalVec == tensorVec); } } diff --git a/tests/use_case/kws_asr/Wav2LetterPreprocessingTest.cc b/tests/use_case/kws_asr/Wav2LetterPreprocessingTest.cc index b86556add0e73f6ea6d00c3965daba2a435880f6..88de9be5e5ebeeba69f2a92dc4a721a876d5cd13 100644 --- a/tests/use_case/kws_asr/Wav2LetterPreprocessingTest.cc +++ b/tests/use_case/kws_asr/Wav2LetterPreprocessingTest.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2023 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021-2023, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -14,10 +14,12 @@ * See the License for the specific language governing permissions and * limitations under the License. */ +#include "TflmTensor.hpp" #include "Wav2LetterPreprocess.hpp" -#include #include +#include +#include constexpr uint32_t numMfccFeatures = 13; constexpr uint32_t numMfccVectors = 10; @@ -109,18 +111,20 @@ TEST_CASE("Preprocessing calculation INT8") TfLiteIntArray* dims= tflite::testing::IntArrayFromInts(dimArray); TfLiteTensor inputTensor = tflite::testing::CreateQuantizedTensor( tensorVec.data(), dims, quantScale, quantOffset, "preprocessedInput"); + std::shared_ptr inputTensorPtr = + std::make_shared(&inputTensor); /* Initialise pre-processing module. */ - arm::app::AsrPreProcess prep{&inputTensor, - numMfccFeatures, numMfccVectors, mfccWindowLen, mfccWindowStride}; + arm::app::AsrPreProcess prep{ + inputTensorPtr, numMfccFeatures, numMfccVectors, mfccWindowLen, mfccWindowStride}; /* Invoke pre-processing. */ REQUIRE(prep.DoPreProcess(testWav.data(), testWav.size())); /* Wrap the tensor with a std::vector for ease. */ - auto* tensorData = tflite::GetTensorData(&inputTensor); - std::vector vecResults = - std::vector(tensorData, tensorData + inputTensor.bytes); + auto* tensorData = inputTensorPtr->GetData(); + std::vector vecResults = + std::vector(tensorData, tensorData + inputTensorPtr->Bytes()); /* Check sizes. */ REQUIRE(vecResults.size() == sizeof(expectedResult)); diff --git a/tests/use_case/noise_reduction/InferenceTestRNNoise.cc b/tests/use_case/noise_reduction/InferenceTestRNNoise.cc index 17ce9ac2bac8ac1121a27e1b4805fbc8e38373cf..d2d08e57e26770b9c457b0a5f86e1eb86c33eb5d 100644 --- a/tests/use_case/noise_reduction/InferenceTestRNNoise.cc +++ b/tests/use_case/noise_reduction/InferenceTestRNNoise.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021, 2025 Arm Limited and/or its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -35,18 +35,19 @@ namespace app { namespace test { namespace noise_reduction { - bool RunInference(arm::app::Model& model, const std::vector> inData) + bool RunInference(arm::app::fwk::iface::Model& model, + const std::vector> inData) { for (size_t i = 0; i < model.GetNumInputs(); ++i) { - TfLiteTensor* inputTensor = model.GetInputTensor(i); + auto inputTensor = model.GetInputTensor(i); REQUIRE(inputTensor); - memcpy(inputTensor->data.data, inData[i].data(), inData[i].size()); + memcpy(inputTensor->GetData(), inData[i].data(), inData[i].size()); } return model.RunInference(); } - bool RunInferenceRandom(arm::app::Model& model) + bool RunInferenceRandom(arm::app::fwk::iface::Model& model) { std::random_device rndDevice; std::mt19937 mersenneGen{rndDevice()}; @@ -56,9 +57,9 @@ namespace noise_reduction { std::vector> randomInput{NUMBER_OF_IFM_FILES}; for (size_t i = 0; i < model.GetNumInputs(); ++i) { - TfLiteTensor* inputTensor = model.GetInputTensor(i); + auto inputTensor = model.GetInputTensor(i); REQUIRE(inputTensor); - randomInput[i].resize(inputTensor->bytes); + randomInput[i].resize(inputTensor->Bytes()); std::generate(std::begin(randomInput[i]), std::end(randomInput[i]), gen); } @@ -68,38 +69,37 @@ namespace noise_reduction { TEST_CASE("Running random inference with Tflu and RNNoise Int8", "[RNNoise]") { - arm::app::RNNoiseModel model{}; - + arm::app::fwk::tflm::RNNoiseModel model; /* Model wrapper object. */ REQUIRE_FALSE(model.IsInited()); - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::rnn::GetModelPointer(), - arm::app::rnn::GetModelLen())); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::rnn::GetModelPointer(), + arm::app::rnn::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); REQUIRE(model.IsInited()); - REQUIRE(RunInferenceRandom(model)); } template void TestInference(const std::vector> input_goldenFV, const std::vector> output_goldenFV, - arm::app::Model& model) + arm::app::fwk::iface::Model& model) { for (size_t i = 0; i < model.GetNumInputs(); ++i) { - TfLiteTensor* inputTensor = model.GetInputTensor(i); + auto inputTensor = model.GetInputTensor(i); REQUIRE(inputTensor); } REQUIRE(RunInference(model, input_goldenFV)); for (size_t i = 0; i < model.GetNumOutputs(); ++i) { - TfLiteTensor* outputTensor = model.GetOutputTensor(i); + auto outputTensor = model.GetOutputTensor(i); REQUIRE(outputTensor); - auto tensorData = tflite::GetTensorData(outputTensor); + auto tensorData = outputTensor->GetData(); REQUIRE(tensorData); - for (size_t j = 0; j < outputTensor->bytes; j++) { + for (size_t j = 0; j < outputTensor->Bytes(); j++) { REQUIRE(static_cast(tensorData[j]) == static_cast((output_goldenFV[i][j]))); } @@ -128,13 +128,13 @@ namespace noise_reduction { DYNAMIC_SECTION("Executing inference with re-init") { - arm::app::RNNoiseModel model{}; - + arm::app::fwk::tflm::RNNoiseModel model; /* Model wrapper object. */ REQUIRE_FALSE(model.IsInited()); - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::rnn::GetModelPointer(), - arm::app::rnn::GetModelLen())); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::rnn::GetModelPointer(), + arm::app::rnn::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); REQUIRE(model.IsInited()); TestInference(goldenInputFV, goldenOutputFV, model); diff --git a/tests/use_case/noise_reduction/RNNNoiseUCTests.cc b/tests/use_case/noise_reduction/RNNNoiseUCTests.cc index 83d5a6ccc164fd492985a2d86fa13b6ac3f4c2a7..762f3b40af72ba2138e08d5aa7fe1f7a30fe7053 100644 --- a/tests/use_case/noise_reduction/RNNNoiseUCTests.cc +++ b/tests/use_case/noise_reduction/RNNNoiseUCTests.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2022, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021-2022, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -39,48 +39,51 @@ namespace app { arm::app::ApplicationContext caseContext; \ arm::app::Profiler profiler{"noise_reduction"}; \ caseContext.Set("profiler", profiler); \ - caseContext.Set("model", model); + caseContext.Set("model", model); TEST_CASE("Verify output tensor memory dump") { constexpr size_t maxMemDumpSz = 0x100000; /* 1 MiB worth of space */ std::vector memPool(maxMemDumpSz); /* Memory pool */ - arm::app::RNNoiseModel model{}; + arm::app::fwk::tflm::RNNoiseModel model; /* Model wrapper object. */ + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::rnn::GetModelPointer(), + arm::app::rnn::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::rnn::GetModelPointer(), - arm::app::rnn::GetModelLen())); + /* Load the model. */ + REQUIRE(model.Init(computeMem, modelMem)); REQUIRE(model.IsInited()); /* Populate the output tensors */ const size_t numOutputs = model.GetNumOutputs(); size_t sizeToWrite = 0; - size_t lastTensorSize = model.GetOutputTensor(numOutputs - 1)->bytes; + size_t lastTensorSize = model.GetOutputTensor(numOutputs - 1)->Bytes(); for (size_t i = 0; i < numOutputs; ++i) { - TfLiteTensor* tensor = model.GetOutputTensor(i); - auto* tData = tflite::GetTensorData(tensor); + auto tensor = model.GetOutputTensor(i); + auto* tData = tensor->GetData(); - if (tensor->bytes > 0) { - memset(tData, static_cast(i), tensor->bytes); - sizeToWrite += tensor->bytes; + if (tensor->Bytes() > 0) { + memset(tData, static_cast(i), tensor->Bytes()); + sizeToWrite += tensor->Bytes(); } } SECTION("Positive use case") { /* Run the memory dump */ - auto bytesWritten = DumpOutputTensorsToMemory(model, memPool.data(), memPool.size()); + auto bytesWritten = + arm::app::DumpOutputTensorsToMemory(model, memPool.data(), memPool.size()); REQUIRE(sizeToWrite == bytesWritten); /* Verify the dump */ size_t k = 0; for (size_t i = 0; i < numOutputs && k < memPool.size(); ++i) { - TfLiteTensor* tensor = model.GetOutputTensor(i); - auto* tData = tflite::GetTensorData(tensor); + auto tensor = model.GetOutputTensor(i); + auto* tData = tensor->GetData(); - for (size_t j = 0; j < tensor->bytes && k < memPool.size(); ++j) { + for (size_t j = 0; j < tensor->Bytes() && k < memPool.size(); ++j) { REQUIRE(tData[j] == memPool[k++]); } } @@ -89,7 +92,8 @@ TEST_CASE("Verify output tensor memory dump") SECTION("Limited memory - skipping last tensor") { /* Run the memory dump */ - auto bytesWritten = DumpOutputTensorsToMemory(model, memPool.data(), sizeToWrite - 1); + auto bytesWritten = + arm::app::DumpOutputTensorsToMemory(model, memPool.data(), sizeToWrite - 1); REQUIRE(lastTensorSize > 0); REQUIRE(bytesWritten == sizeToWrite - lastTensorSize); } @@ -97,7 +101,7 @@ TEST_CASE("Verify output tensor memory dump") SECTION("Zero memory") { /* Run the memory dump */ - auto bytesWritten = DumpOutputTensorsToMemory(model, memPool.data(), 0); + auto bytesWritten = arm::app::DumpOutputTensorsToMemory(model, memPool.data(), 0); REQUIRE(bytesWritten == 0); } } @@ -106,7 +110,7 @@ TEST_CASE("Inference run all clips", "[RNNoise]") { PLATFORM - arm::app::RNNoiseModel model; + arm::app::fwk::tflm::RNNoiseModel model; /* Model wrapper object. */ CONTEXT @@ -114,11 +118,14 @@ TEST_CASE("Inference run all clips", "[RNNoise]") caseContext.Set("frameLength", arm::app::rnn::g_FrameLength); caseContext.Set("frameStride", arm::app::rnn::g_FrameStride); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::rnn::GetModelPointer(), + arm::app::rnn::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + /* Load the model. */ - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::rnn::GetModelPointer(), - arm::app::rnn::GetModelLen())); + REQUIRE(model.Init(computeMem, modelMem)); + REQUIRE(model.IsInited()); REQUIRE(arm::app::NoiseReductionHandler(caseContext)); } diff --git a/tests/use_case/noise_reduction/RNNoiseModelTests.cc b/tests/use_case/noise_reduction/RNNoiseModelTests.cc index d08c5f3962f218210e786f57ef30358815b15b68..3f05bd28248ff036fe944ac85a7c86f23aea9d88 100644 --- a/tests/use_case/noise_reduction/RNNoiseModelTests.cc +++ b/tests/use_case/noise_reduction/RNNoiseModelTests.cc @@ -1,6 +1,6 @@ /* - * SPDX-FileCopyrightText: Copyright 2021,2023 Arm Limited and/or its affiliates - * SPDX-License-Identifier: Apache-2.0 + * SPDX-FileCopyrightText: Copyright 2021,2023, 2025 Arm Limited and/or its affiliates + * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. @@ -32,14 +32,16 @@ namespace app { } /* namespace app */ } /* namespace arm */ -bool RunInference(arm::app::Model& model, std::vector vec, - const size_t sizeRequired, const size_t dataInputIndex) +bool RunInference(arm::app::fwk::iface::Model& model, + std::vector vec, + const size_t sizeRequired, + const size_t dataInputIndex) { - TfLiteTensor* inputTensor = model.GetInputTensor(dataInputIndex); + auto inputTensor = model.GetInputTensor(dataInputIndex); REQUIRE(inputTensor); - size_t copySz = inputTensor->bytes < sizeRequired ? inputTensor->bytes : sizeRequired; + size_t copySz = inputTensor->Bytes() < sizeRequired ? inputTensor->Bytes() : sizeRequired; const int8_t* vecData = vec.data(); - memcpy(inputTensor->data.data, vecData, copySz); + memcpy(inputTensor->GetData(), vecData, copySz); return model.RunInference(); } @@ -55,13 +57,13 @@ void genRandom(size_t bytes, std::vector& randomAudio) std::generate(std::begin(randomAudio), std::end(randomAudio), gen); } -bool RunInferenceRandom(arm::app::Model& model, const size_t dataInputIndex) +bool RunInferenceRandom(arm::app::fwk::iface::Model& model, const size_t dataInputIndex) { std::array inputSizes = {IFM_0_DATA_SIZE, IFM_1_DATA_SIZE, IFM_2_DATA_SIZE, IFM_3_DATA_SIZE}; std::vector randomAudio; - TfLiteTensor* inputTensor = model.GetInputTensor(dataInputIndex); + auto inputTensor = model.GetInputTensor(dataInputIndex); REQUIRE(inputTensor); - genRandom(inputTensor->bytes, randomAudio); + genRandom(inputTensor->Bytes(), randomAudio); REQUIRE(RunInference(model, randomAudio, inputSizes[dataInputIndex], dataInputIndex)); return true; @@ -69,56 +71,58 @@ bool RunInferenceRandom(arm::app::Model& model, const size_t dataInputIndex) TEST_CASE("Running random inference with TensorFlow Lite Micro and RNNoiseModel Int8", "[RNNoise]") { - arm::app::RNNoiseModel model{}; - + arm::app::fwk::tflm::RNNoiseModel model; /* Model wrapper object. */ REQUIRE_FALSE(model.IsInited()); - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::rnn::GetModelPointer(), - arm::app::rnn::GetModelLen())); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::rnn::GetModelPointer(), + arm::app::rnn::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + REQUIRE(model.Init(computeMem, modelMem)); REQUIRE(model.IsInited()); model.ResetGruState(); for (int i = 1; i < 4; i++ ) { - TfLiteTensor* inputGruStateTensor = model.GetInputTensor(i); - auto* inputGruState = tflite::GetTensorData(inputGruStateTensor); - for (size_t tIndex = 0; tIndex < inputGruStateTensor->bytes; tIndex++) { - REQUIRE(inputGruState[tIndex] == arm::app::GetTensorQuantParams(inputGruStateTensor).offset); + auto inputGruStateTensor = model.GetInputTensor(i); + auto* inputGruState = inputGruStateTensor->GetData(); + for (size_t tIndex = 0; tIndex < inputGruStateTensor->Bytes(); tIndex++) { + REQUIRE(inputGruState[tIndex] == inputGruStateTensor->GetQuantParams().offset); } } REQUIRE(RunInferenceRandom(model, 0)); } -class TestRNNoiseModel : public arm::app::RNNoiseModel -{ +class TestRNNoiseModel : public arm::app::fwk::tflm::RNNoiseModel { public: bool CopyGruStatesTest() { - return RNNoiseModel::CopyGruStates(); + return this->CopyGruStates(); } std::vector> GetStateMap() { - return m_gruStateMap; + return m_gruStateMap; } - }; /* This is true for gcc x86 platform, not guaranteed for other compilers and platforms. */ TEST_CASE("Test initial GRU out state is 0", "[RNNoise]") { TestRNNoiseModel model{}; - model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::rnn::GetModelPointer(), - arm::app::rnn::GetModelLen()); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::rnn::GetModelPointer(), + arm::app::rnn::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + + /* Load the model. */ + REQUIRE(model.Init(computeMem, modelMem)); + REQUIRE(model.IsInited()); auto map = model.GetStateMap(); for(auto& mapping: map) { - TfLiteTensor* gruOut = model.GetOutputTensor(mapping.first); - auto* outGruState = tflite::GetTensorData(gruOut); + auto gruOut = model.GetOutputTensor(mapping.first); + auto* outGruState = gruOut->GetData(); - for (size_t tIndex = 0; tIndex < gruOut->bytes; tIndex++) { + for (size_t tIndex = 0; tIndex < gruOut->Bytes(); tIndex++) { REQUIRE(outGruState[tIndex] == 0); } } @@ -128,10 +132,13 @@ TEST_CASE("Test initial GRU out state is 0", "[RNNoise]") TEST_CASE("Test GRU state copy", "[RNNoise]") { TestRNNoiseModel model{}; - model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::rnn::GetModelPointer(), - arm::app::rnn::GetModelLen()); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::rnn::GetModelPointer(), + arm::app::rnn::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + + /* Load the model. */ + REQUIRE(model.Init(computeMem, modelMem)); REQUIRE(RunInferenceRandom(model, 0)); auto map = model.GetStateMap(); @@ -139,23 +146,22 @@ TEST_CASE("Test GRU state copy", "[RNNoise]") std::vector> oldStates; for(auto& mapping: map) { - TfLiteTensor* gruOut = model.GetOutputTensor(mapping.first); - auto* outGruState = tflite::GetTensorData(gruOut); + auto gruOut = model.GetOutputTensor(mapping.first); + auto* outGruState = gruOut->GetData(); /* Save old output state. */ - std::vector oldState(gruOut->bytes); - memcpy(oldState.data(), outGruState, gruOut->bytes); + std::vector oldState(gruOut->Bytes()); + memcpy(oldState.data(), outGruState, gruOut->Bytes()); oldStates.push_back(oldState); } model.CopyGruStatesTest(); auto statesIter = oldStates.begin(); for(auto& mapping: map) { - TfLiteTensor* gruInput = model.GetInputTensor(mapping.second); - auto* inGruState = tflite::GetTensorData(gruInput); - for (size_t tIndex = 0; tIndex < gruInput->bytes; tIndex++) { + auto gruInput = model.GetInputTensor(mapping.second); + auto* inGruState = gruInput->GetData(); + for (size_t tIndex = 0; tIndex < gruInput->Bytes(); tIndex++) { REQUIRE((*statesIter)[tIndex] == inGruState[tIndex]); } statesIter++; } - } diff --git a/tests/use_case/object_detection/InferenceTestYoloFastest.cc b/tests/use_case/object_detection/InferenceTestYoloFastest.cc index ad46b43810071c6ced57b18b640053c2682dd9ff..9cbecbe98670bed217914bdf61ca408bb6060380 100644 --- a/tests/use_case/object_detection/InferenceTestYoloFastest.cc +++ b/tests/use_case/object_detection/InferenceTestYoloFastest.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2022, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2022, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -62,24 +62,24 @@ void GetExpectedResults( arm::app::object_detection::DetectionResult(0.99, 63, 60, 38, 45)}); } -bool RunInference(arm::app::Model& model, const uint8_t imageData[]) +bool RunInference(arm::app::fwk::iface::Model& model, const uint8_t imageData[]) { - TfLiteTensor* inputTensor = model.GetInputTensor(0); + auto inputTensor = model.GetInputTensor(0); REQUIRE(inputTensor); - const size_t copySz = inputTensor->bytes; + const size_t copySz = inputTensor->Bytes(); - arm::app::image::RgbToGrayscale(imageData, inputTensor->data.uint8, copySz); + arm::app::image::RgbToGrayscale(imageData, inputTensor->GetData(), copySz); if (model.IsDataSigned()) { - arm::app::image::ConvertImgToInt8(inputTensor->data.data, copySz); + arm::app::image::ConvertUint8ToInt8(inputTensor->GetData(), copySz); } return model.RunInference(); } template -bool TestInferenceDetectionResults(size_t imageIdx, arm::app::Model& model, T tolerance) +bool TestInferenceDetectionResults(size_t imageIdx, arm::app::fwk::iface::Model& model, T tolerance) { std::vector results; uint32_t capturedFrameSize = 0; @@ -89,21 +89,21 @@ bool TestInferenceDetectionResults(size_t imageIdx, arm::app::Model& model, T to return false; } - TfLiteIntArray* inputShape = model.GetInputShape(0); - auto nCols = inputShape->data[arm::app::YoloFastestModel::ms_inputColsIdx]; - auto nRows = inputShape->data[arm::app::YoloFastestModel::ms_inputRowsIdx]; + auto inputShape = model.GetInputShape(0); + auto nCols = inputShape[arm::app::fwk::tflm::YoloFastestModel::ms_inputColsIdx]; + auto nRows = inputShape[arm::app::fwk::tflm::YoloFastestModel::ms_inputRowsIdx]; REQUIRE(RunInference(model, image)); - std::vector output_arr{model.GetOutputTensor(0), model.GetOutputTensor(1)}; + std::vector output_arr{model.GetOutputTensor(0), model.GetOutputTensor(1)}; for (size_t i = 0; i < output_arr.size(); i++) { REQUIRE(output_arr[i]); - REQUIRE(tflite::GetTensorData(output_arr[i])); + REQUIRE(output_arr[i]->GetData()); } const arm::app::object_detection::PostProcessParams postProcessParams{ - nRows, - nCols, + static_cast(nRows), + static_cast(nCols), arm::app::object_detection::originalImageSize, arm::app::object_detection::anchor1, arm::app::object_detection::anchor2}; @@ -137,19 +137,22 @@ TEST_CASE("Running inference with TensorFlow Lite Micro and YoloFastest", "[Yolo { SECTION("Executing inferences sequentially") { - arm::app::YoloFastestModel model{}; + arm::app::fwk::tflm::YoloFastestModel model{}; REQUIRE_FALSE(model.IsInited()); - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::object_detection::GetModelPointer(), - arm::app::object_detection::GetModelLen())); + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::object_detection::GetModelPointer(), + arm::app::object_detection::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; + + /* Load the model. */ + REQUIRE(model.Init(computeMem, modelMem)); REQUIRE(model.IsInited()); - TfLiteIntArray* inputShape = model.GetInputShape(0); + auto inputShape = model.GetInputShape(0); - const int inputImgCols = inputShape->data[arm::app::YoloFastestModel::ms_inputColsIdx]; - const int inputImgRows = inputShape->data[arm::app::YoloFastestModel::ms_inputRowsIdx]; + const int inputImgCols = inputShape[arm::app::fwk::tflm::YoloFastestModel::ms_inputColsIdx]; + const int inputImgRows = inputShape[arm::app::fwk::tflm::YoloFastestModel::ms_inputRowsIdx]; hal_camera_init(); auto bCamera = hal_camera_configure(inputImgCols, diff --git a/tests/use_case/object_detection/ObjectDetectionUCTest.cc b/tests/use_case/object_detection/ObjectDetectionUCTest.cc index a1e0b4a2a1f33f65353dd0bbc17c81cf60ecc4e6..68386c0cb86464e86b980e6838ba9452f1d20588 100644 --- a/tests/use_case/object_detection/ObjectDetectionUCTest.cc +++ b/tests/use_case/object_detection/ObjectDetectionUCTest.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2022, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2022, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -38,20 +38,23 @@ namespace arm { TEST_CASE("Model info") { /* Model wrapper object. */ - arm::app::YoloFastestModel model; + arm::app::fwk::tflm::YoloFastestModel model; + + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::object_detection::GetModelPointer(), + arm::app::object_detection::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; /* Load the model. */ - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::object_detection::GetModelPointer(), - arm::app::object_detection::GetModelLen())); + REQUIRE(model.Init(computeMem, modelMem)); /* Instantiate application context. */ arm::app::ApplicationContext caseContext; - caseContext.Set("model", model); + caseContext.Set("model", model); - REQUIRE(model.ShowModelInfoHandler()); + model.LogInterpreterInfo(); + REQUIRE(model.IsInited()); } @@ -61,20 +64,22 @@ TEST_CASE("Inference by index") hal_platform_init(); /* Model wrapper object. */ - arm::app::YoloFastestModel model; + arm::app::fwk::tflm::YoloFastestModel model; + + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::object_detection::GetModelPointer(), + arm::app::object_detection::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; /* Load the model. */ - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::object_detection::GetModelPointer(), - arm::app::object_detection::GetModelLen())); + REQUIRE(model.Init(computeMem, modelMem)); /* Instantiate application context. */ arm::app::ApplicationContext caseContext; arm::app::Profiler profiler{"object_detection"}; caseContext.Set("profiler", profiler); - caseContext.Set("model", model); + caseContext.Set("model", model); REQUIRE(arm::app::ObjectDetectionHandler(caseContext)); } @@ -86,20 +91,21 @@ TEST_CASE("Inference run all images") hal_platform_init(); /* Model wrapper object. */ - arm::app::YoloFastestModel model; + arm::app::fwk::tflm::YoloFastestModel model; + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::object_detection::GetModelPointer(), + arm::app::object_detection::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; /* Load the model. */ - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::object_detection::GetModelPointer(), - arm::app::object_detection::GetModelLen())); + REQUIRE(model.Init(computeMem, modelMem)); /* Instantiate application context. */ arm::app::ApplicationContext caseContext; arm::app::Profiler profiler{"object_detection"}; caseContext.Set("profiler", profiler); - caseContext.Set("model", model); + caseContext.Set("model", model); REQUIRE(arm::app::ObjectDetectionHandler(caseContext)); } diff --git a/tests/use_case/vww/InferenceVisualWakeWordModelTests.cc b/tests/use_case/vww/InferenceVisualWakeWordModelTests.cc index 4c9703c327c1f892570a3e9a3dfbea610ee61bf6..e61190963417917416935899dc8c3b1a7291d7d6 100644 --- a/tests/use_case/vww/InferenceVisualWakeWordModelTests.cc +++ b/tests/use_case/vww/InferenceVisualWakeWordModelTests.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2022, 2024 Arm Limited and/or its affiliates + * SPDX-FileCopyrightText: Copyright 2021-2022, 2024-2025 Arm Limited and/or its affiliates * SPDX-License-Identifier: Apache-2.0 * * Licensed under the Apache License, Version 2.0 (the "License"); @@ -22,39 +22,39 @@ #include -bool RunInference(arm::app::Model& model, const int8_t* imageData) +bool RunInference(arm::app::fwk::iface::Model& model, const int8_t* imageData) { - TfLiteTensor* inputTensor = model.GetInputTensor(0); + auto inputTensor = model.GetInputTensor(0); REQUIRE(inputTensor); const size_t copySz = - inputTensor->bytes < IFM_0_DATA_SIZE ? inputTensor->bytes : IFM_0_DATA_SIZE; + inputTensor->Bytes() < IFM_0_DATA_SIZE ? inputTensor->Bytes() : IFM_0_DATA_SIZE; - memcpy(inputTensor->data.data, imageData, copySz); + memcpy(inputTensor->GetData(), imageData, copySz); if (model.IsDataSigned()) { - arm::app::image::ConvertImgToInt8(inputTensor->data.data, copySz); + arm::app::image::ConvertUint8ToInt8(inputTensor->GetData(), copySz); } return model.RunInference(); } template -void TestInference(int imageIdx, arm::app::Model& model) +void TestInference(int imageIdx, arm::app::fwk::iface::Model& model) { auto image = reinterpret_cast(test::GetIfmDataArray(imageIdx)); auto goldenFV = reinterpret_cast(test::GetOfmDataArray(imageIdx)); REQUIRE(RunInference(model, image)); - TfLiteTensor* outputTensor = model.GetOutputTensor(0); + auto outputTensor = model.GetOutputTensor(0); REQUIRE(outputTensor); - REQUIRE(outputTensor->bytes == OFM_0_DATA_SIZE); + REQUIRE(outputTensor->Bytes() == OFM_0_DATA_SIZE); auto tensorData = tflite::GetTensorData(outputTensor); REQUIRE(tensorData); - for (size_t i = 0; i < outputTensor->bytes; i++) { + for (size_t i = 0; i < outputTensor->Bytes(); i++) { auto testVal = static_cast(tensorData[i]); auto goldenVal = static_cast(goldenFV[i]); CHECK(testVal == goldenVal); diff --git a/tests/use_case/vww/VisualWakeWordUCTests.cc b/tests/use_case/vww/VisualWakeWordUCTests.cc index 3237fef3a3e527074bbddfb30739190b224b3098..3335c7a9fe80afb7ed090a38aa1a5bf2b59bc0e6 100644 --- a/tests/use_case/vww/VisualWakeWordUCTests.cc +++ b/tests/use_case/vww/VisualWakeWordUCTests.cc @@ -1,5 +1,5 @@ /* - * SPDX-FileCopyrightText: Copyright 2021-2022, 2024 Arm Limited and/or its + * SPDX-FileCopyrightText: Copyright 2021-2022, 2024-2025 Arm Limited and/or its * affiliates * SPDX-License-Identifier: Apache-2.0 * @@ -38,20 +38,22 @@ namespace app { TEST_CASE("Model info") { - arm::app::VisualWakeWordModel model; /* model wrapper object */ + arm::app::fwk::tflm::VisualWakeWordModel model; /* model wrapper object */ + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::vww::GetModelPointer(), + arm::app::vww::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; - /* Load the model */ - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::vww::GetModelPointer(), - arm::app::vww::GetModelLen())); + /* Load the model. */ + REQUIRE(model.Init(computeMem, modelMem)); /* Instantiate application context */ arm::app::ApplicationContext caseContext; - caseContext.Set("model", model); + caseContext.Set("model", model); - REQUIRE(model.ShowModelInfoHandler()); + model.LogInterpreterInfo(); + REQUIRE(model.IsInited()); } TEST_CASE("Inference run all images") @@ -59,19 +61,20 @@ TEST_CASE("Inference run all images") /* Initialise the HAL and platform */ hal_platform_init(); - arm::app::VisualWakeWordModel model; /* model wrapper object */ + arm::app::fwk::tflm::VisualWakeWordModel model; /* model wrapper object */ + arm::app::fwk::iface::MemoryRegion modelMem{arm::app::vww::GetModelPointer(), + arm::app::vww::GetModelLen()}; + arm::app::fwk::iface::MemoryRegion computeMem{arm::app::tensorArena, + sizeof(arm::app::tensorArena)}; - /* Load the model */ - REQUIRE(model.Init(arm::app::tensorArena, - sizeof(arm::app::tensorArena), - arm::app::vww::GetModelPointer(), - arm::app::vww::GetModelLen())); + /* Load the model. */ + REQUIRE(model.Init(computeMem, modelMem)); /* Instantiate application context */ arm::app::ApplicationContext caseContext; arm::app::Profiler profiler{"pd"}; caseContext.Set("profiler", profiler); - caseContext.Set("model", model); + caseContext.Set("model", model); arm::app::Classifier classifier; /* classifier wrapper object */ caseContext.Set("classifier", classifier);