You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tvm.apache.org by tl...@apache.org on 2026/06/16 16:04:13 UTC
(tvm) branch main updated: [REFACTOR][HEXAGON] Phase out Hexagon app and test wrappers (#19796)
This is an automated email from the ASF dual-hosted git repository.
tlopex pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/tvm.git
The following commit(s) were added to refs/heads/main by this push:
new 54b7e4dad0 [REFACTOR][HEXAGON] Phase out Hexagon app and test wrappers (#19796)
54b7e4dad0 is described below
commit 54b7e4dad0a9d661eca4fcf1fd9576a1021e62f6
Author: Tianqi Chen <tq...@users.noreply.github.com>
AuthorDate: Tue Jun 16 12:04:07 2026 -0400
[REFACTOR][HEXAGON] Phase out Hexagon app and test wrappers (#19796)
## Summary
The old Hexagon app and test wrappers depend on RPC helper artifacts
that are no longer part of the supported app flow. This PR removes those
wrappers and related helper references while keeping the core Hexagon
target, codegen, and runtime implementation in place.
## Changes
- Remove the obsolete Hexagon app wrapper directories.
- Remove the Hexagon contrib test directory and its dedicated pytest/RPC
launcher helpers.
- Drop stale CI/docs references to the removed app and test helper
paths.
---
apps/hexagon_api/CMakeLists.txt | 166 ----
apps/hexagon_api/README.md | 58 --
apps/hexagon_launcher/CMakeLists.txt | 81 --
apps/hexagon_launcher/README.md | 145 ---
apps/hexagon_launcher/cmake/HexagonLauncher.cmake | 75 --
apps/hexagon_launcher/cmake/android/CMakeLists.txt | 100 ---
apps/hexagon_launcher/cmake/hexagon/CMakeLists.txt | 105 ---
apps/hexagon_launcher/launcher_android.cc | 170 ----
apps/hexagon_launcher/launcher_core.cc | 231 -----
apps/hexagon_launcher/launcher_core.h | 133 ---
apps/hexagon_launcher/launcher_hexagon.cc | 237 -----
apps/hexagon_launcher/launcher_main.cc | 159 ----
apps/hexagon_launcher/launcher_rpc.idl | 33 -
apps/hexagon_launcher/launcher_util.cc | 68 --
apps/hexagon_launcher/launcher_util.h | 34 -
ci/jenkins/data.py | 4 -
python/tvm/contrib/hexagon/_ci_env_check.py | 4 +-
python/tvm/contrib/hexagon/build.py | 830 -----------------
python/tvm/contrib/hexagon/hexagon_profiler.py | 125 ---
python/tvm/contrib/hexagon/meta_schedule.py | 195 ----
.../contrib/hexagon/profiling/process_lwp_data.py | 388 --------
python/tvm/contrib/hexagon/pytest_plugin.py | 384 --------
python/tvm/contrib/hexagon/session.py | 287 ------
src/backend/hexagon/runtime/profiler/README.md | 85 --
.../hexagon/runtime/rpc/android_bash.sh.template | 31 -
tests/python/contrib/test_hexagon/README.md | 130 ---
tests/python/contrib/test_hexagon/README_RPC.md | 371 --------
tests/python/contrib/test_hexagon/__init__.py | 19 -
.../python/contrib/test_hexagon/benchmark_util.py | 277 ------
tests/python/contrib/test_hexagon/conftest.py | 27 -
tests/python/contrib/test_hexagon/conv2d/README.md | 37 -
.../python/contrib/test_hexagon/conv2d/__init__.py | 19 -
.../test_hexagon/conv2d/test_conv2d_blocked.md | 494 -----------
.../test_hexagon/conv2d/test_conv2d_conv2d.md | 986 ---------------------
.../python/contrib/test_hexagon/infrastructure.py | 376 --------
tests/python/contrib/test_hexagon/pytest_util.py | 176 ----
.../test_hexagon/test_async_dma_pipeline.py | 889 -------------------
.../test_hexagon/test_benchmark_elemwise_add.py | 425 ---------
.../test_hexagon/test_benchmark_maxpool2d.py | 358 --------
.../contrib/test_hexagon/test_dma_builtin.py | 190 ----
.../contrib/test_hexagon/test_memory_alloc.py | 84 --
.../contrib/test_hexagon/test_meta_schedule.py | 370 --------
.../contrib/test_hexagon/test_parallel_hvx.py | 240 -----
.../test_hexagon/test_parallel_hvx_load_vtcm.py | 562 ------------
.../contrib/test_hexagon/test_parallel_scalar.py | 177 ----
.../test_relax_2d_buffer_allocation.py | 93 --
.../contrib/test_hexagon/test_relax_integration.py | 115 ---
.../contrib/test_hexagon/test_run_unit_tests.py | 183 ----
tests/python/contrib/test_hexagon/test_sigmoid.py | 120 ---
.../test_hexagon/test_software_pipeline_async.py | 206 -----
tests/python/contrib/test_hexagon/test_take.py | 397 ---------
.../contrib/test_hexagon/test_thread_pool.py | 108 ---
tests/python/contrib/test_hexagon/test_vtcm.py | 94 --
.../contrib/test_hexagon/test_vtcm_bandwidth.py | 194 ----
tests/python/testing/test_env.py | 2 +-
55 files changed, 2 insertions(+), 11845 deletions(-)
diff --git a/apps/hexagon_api/CMakeLists.txt b/apps/hexagon_api/CMakeLists.txt
deleted file mode 100644
index 62dca9d4e6..0000000000
--- a/apps/hexagon_api/CMakeLists.txt
+++ /dev/null
@@ -1,166 +0,0 @@
-cmake_minimum_required(VERSION 3.2)
-
-project(hexagon_api)
-
-include(ExternalProject)
-
-# Required variables:
-# ANDROID_ABI
-# ANDROID_PLATFORM
-# USE_ANDROID_TOOLCHAIN (Android toolchain .cmake file)
-# USE_HEXAGON_ARCH
-# USE_HEXAGON_SDK
-# USE_HEXAGON_TOOLCHAIN (Path to Hexagon toolchain ending with "Tools")
-# Optional variable:
-# USE_OUTPUT_BINARY_DIR (Path to copy the output binaries to)
-# USE_HEXAGON_GTEST (Path to Hexagon specific gtest version)
-
-set(TVM_SOURCE_DIR "${CMAKE_SOURCE_DIR}/../..")
-
-if(DEFINED USE_OUTPUT_BINARY_DIR)
- set(HEXAGON_API_BINARY_DIR "${USE_OUTPUT_BINARY_DIR}")
-else()
- set(HEXAGON_API_BINARY_DIR "${CMAKE_BINARY_DIR}/hexagon_rpc")
-endif()
-file(MAKE_DIRECTORY ${HEXAGON_API_BINARY_DIR})
-
-if(DEFINED USE_HEXAGON_GTEST)
- if(EXISTS ${USE_HEXAGON_GTEST})
- message(STATUS "Found Hexagon gtest at ${USE_HEXAGON_GTEST}")
- else()
- message(WARNING "Could not find Hexagon gtest at ${USE_HEXAGON_GTEST}. Disabling Hexagon gtest support.")
- unset(USE_HEXAGON_GTEST)
- endif()
-endif()
-
-# Build X86 binaries:
-# - tvm_rpc_x86
-
-ExternalProject_Add(x86_tvm_runtime_rpc
- SOURCE_DIR "${TVM_SOURCE_DIR}"
- BUILD_COMMAND $(MAKE) runtime tvm_rpc
- CMAKE_ARGS
- "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}"
- "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
- "-DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER}"
- "-DCMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER}"
- "-DUSE_HEXAGON_TOOLCHAIN=${USE_HEXAGON_TOOLCHAIN}"
- "-DCMAKE_CXX_STANDARD=17"
- "-DTVM_FFI_USE_LIBBACKTRACE=OFF"
- "-DTVM_FFI_USE_THREADS=OFF"
- "-DTVM_FFI_USE_DL_LIBS=OFF"
- "-DUSE_RPC=ON"
- "-DUSE_CPP_RPC=ON"
- "-DUSE_HEXAGON=ON"
- "-DUSE_HEXAGON_RPC=ON"
- "-DBUILD_STATIC_RUNTIME=ON"
- "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
- INSTALL_COMMAND ""
- BUILD_ALWAYS ON
-)
-ExternalProject_Get_Property(x86_tvm_runtime_rpc BINARY_DIR)
-ExternalProject_Add_Step(x86_tvm_runtime_rpc copy_rpc_server
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- ${BINARY_DIR}/tvm_rpc
- ${HEXAGON_API_BINARY_DIR}/tvm_rpc_x86
- DEPENDEES install
-)
-
-# Build Android binaries:
-# - libtvm_runtime.so
-# - tvm_rpc_android
-
-ExternalProject_Add(android_tvm_runtime_rpc
- SOURCE_DIR "${TVM_SOURCE_DIR}"
- BUILD_COMMAND $(MAKE) runtime tvm_rpc
- CMAKE_ARGS
- "-DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER}"
- "-DCMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER}"
- "-DCMAKE_TOOLCHAIN_FILE=${USE_ANDROID_TOOLCHAIN}"
- "-DANDROID_PLATFORM=${ANDROID_PLATFORM}"
- "-DANDROID_ABI=${ANDROID_ABI}"
- "-DUSE_HEXAGON_SDK=${USE_HEXAGON_SDK}"
- "-DUSE_HEXAGON_ARCH=${USE_HEXAGON_ARCH}"
- "-DCMAKE_CXX_STANDARD=17"
- "-DTVM_FFI_USE_LIBBACKTRACE=OFF"
- "-DTVM_FFI_USE_THREADS=OFF"
- "-DTVM_FFI_USE_DL_LIBS=OFF"
- "-DUSE_RPC=ON"
- "-DUSE_CPP_RPC=ON"
- "-DUSE_HEXAGON=ON"
- "-DUSE_HEXAGON_RPC=ON"
- "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
- "-DUSE_ALTERNATIVE_LINKER=OFF"
- "-DUSE_RANDOM=ON"
- INSTALL_COMMAND ""
- BUILD_ALWAYS ON
-)
-
-ExternalProject_Get_Property(android_tvm_runtime_rpc BINARY_DIR)
-ExternalProject_Add_Step(android_tvm_runtime_rpc copy_runtime
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- ${BINARY_DIR}/lib/libtvm_runtime.so
- ${HEXAGON_API_BINARY_DIR}
- DEPENDEES install
-)
-ExternalProject_Add_Step(android_tvm_runtime_rpc copy_rpc_server
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- ${BINARY_DIR}/tvm_rpc
- ${HEXAGON_API_BINARY_DIR}/tvm_rpc_android
- DEPENDEES install
-)
-
-
-# Build Hexagon binaries:
-# - libhexagon_rpc_skel.so
-# - libtvm_runtime.a
-if(DEFINED USE_HEXAGON_GTEST)
- set(GTEST_FLAG "-DUSE_HEXAGON_GTEST=${USE_HEXAGON_GTEST}")
-endif()
-
-if(NOT DEFINED USE_HEXAGON_QHL)
- # USE_HEXAGON_QHL defaults to ON for rpc runtime if not explicitly set to OFF
- set(USE_HEXAGON_QHL ON)
-endif()
-
-ExternalProject_Add(hexagon_tvm_runtime_rpc
- SOURCE_DIR "${TVM_SOURCE_DIR}"
- BUILD_COMMAND $(MAKE) runtime hexagon_rpc_sim
- CMAKE_ARGS
- "-DCMAKE_C_COMPILER_LAUNCHER=${CMAKE_C_COMPILER_LAUNCHER}"
- "-DCMAKE_CXX_COMPILER_LAUNCHER=${CMAKE_CXX_COMPILER_LAUNCHER}"
- "-DCMAKE_C_COMPILER=${USE_HEXAGON_TOOLCHAIN}/bin/hexagon-clang"
- "-DCMAKE_CXX_COMPILER=${USE_HEXAGON_TOOLCHAIN}/bin/hexagon-clang++"
- "-DUSE_HEXAGON_SDK=${USE_HEXAGON_SDK}"
- "-DUSE_HEXAGON_ARCH=${USE_HEXAGON_ARCH}"
- "-DUSE_HEXAGON_EXTERNAL_LIBS=${USE_HEXAGON_EXTERNAL_LIBS}"
- "-DHEXAGON_EXTERNAL_LIBS_SHA=${HEXAGON_EXTERNAL_LIBS_SHA}"
- "-DCMAKE_CXX_STANDARD=17"
- "-DTVM_FFI_USE_LIBBACKTRACE=OFF"
- "-DTVM_FFI_USE_THREADS=OFF"
- "-DTVM_FFI_USE_DL_LIBS=OFF"
- "-DUSE_RPC=OFF"
- "-DUSE_HEXAGON=ON"
- "-DUSE_HEXAGON_RPC=ON"
- "-DBUILD_STATIC_RUNTIME=ON"
- "-DCMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}"
- "-DUSE_ALTERNATIVE_LINKER=OFF"
- "-DUSE_CUSTOM_LOGGING=ON"
- "-DUSE_HEXAGON_QHL=${USE_HEXAGON_QHL}"
- "-DUSE_RANDOM=ON"
- "${GTEST_FLAG}"
- INSTALL_COMMAND ""
- BUILD_ALWAYS ON
-)
-ExternalProject_Get_Property(hexagon_tvm_runtime_rpc BINARY_DIR)
-ExternalProject_Add_Step(hexagon_tvm_runtime_rpc copy_binaries
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- ${BINARY_DIR}/lib/libtvm_runtime.a
- ${BINARY_DIR}/libhexagon_rpc_skel.so
- ${BINARY_DIR}/libhexagon_rpc_sim.so
- ${HEXAGON_API_BINARY_DIR}
- DEPENDEES install
-)
-
-configure_file("${TVM_SOURCE_DIR}/src/runtime/hexagon/rpc/android_bash.sh.template"
- ${HEXAGON_API_BINARY_DIR} COPYONLY)
diff --git a/apps/hexagon_api/README.md b/apps/hexagon_api/README.md
deleted file mode 100644
index f8f5f792af..0000000000
--- a/apps/hexagon_api/README.md
+++ /dev/null
@@ -1,58 +0,0 @@
-<!--- Licensed to the Apache Software Foundation (ASF) under one -->
-<!--- or more contributor license agreements. See the NOTICE file -->
-<!--- distributed with this work for additional information -->
-<!--- regarding copyright ownership. The ASF licenses this file -->
-<!--- to you under the Apache License, Version 2.0 (the -->
-<!--- "License"); you may not use this file except in compliance -->
-<!--- with the License. You may obtain a copy of the License at -->
-
-<!--- http://www.apache.org/licenses/LICENSE-2.0 -->
-
-<!--- Unless required by applicable law or agreed to in writing, -->
-<!--- software distributed under the License is distributed on an -->
-<!--- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -->
-<!--- KIND, either express or implied. See the License for the -->
-<!--- specific language governing permissions and limitations -->
-<!--- under the License. -->
-
-# Hexagon API app
-
-This is a meta-app that build the necessary binaries for use with
-the `HexagonLauncher` utility from `tvm.contrib.hexagon`.
-
-It will build the TVM runtime for Android, the RPC server application
-for Android, and the RPC library for Hexagon with the TVM runtime for
-Hexagon built into it.
-
-## Configuration
-
-There is a set of configuration variables that are required for CMake:
-- `ANDROID_ABI`: Set this to `arm64-v8a`.
-- `ANDROID_PLATFORM`: This can be `android-28`.
-- `USE_ANDROID_TOOLCHAIN`: The path to the Android toolchain file, i.e.
-`android.toolchain.cmake`. This file is a part of the Android NDK.
-- `USE_HEXAGON_ARCH`: The version string of the Hexagon architecture
-to use, i.e. vNN. The typical setting would be `v68` or later.
-- `USE_HEXAGON_SDK`: The path to the Hexagon SDK. Set this path in such
-a way that `${USE_HEXAGON_SDK}/setup_sdk_env.source` exists.
-- `USE_HEXAGON_TOOLCHAIN`: Path to Hexagon toolchain. It can be the
-Hexagon toolchain included in the SDK, for example
-`${USE_HEXAGON_TOOLCHAIN}/tools/HEXAGON_Tools/x.y.z/Tools`. The `x.y.z`
-in the path is the toolchain version number, which is specific to the
-version of the SDK.
-
-Additionally, the variable `USE_OUTPUT_BINARY_DIR` can be set to indicate
-the location where the generated binaries will be placed. If not set, it
-defaults to `hexagon_rpc` subdirectory in the current build directory.
-
-
-## Build
-
-The build will generate the following binaries:
-- `tvm_runtime.so`: TVM runtime for Android (shared library).
-- `tvm_rpc_android`: RPC server for Android.
-- `libhexagon_rpc_skel.so`: RPC library for Hexagon.
-- `libtvm_runtime.a`: TVM runtime for Hexagon (static library).
-
-The RPC library for Hexagon contains the TVM runtime, so the static
-TVM runtime for Hexagon is not strictly necessary.
diff --git a/apps/hexagon_launcher/CMakeLists.txt b/apps/hexagon_launcher/CMakeLists.txt
deleted file mode 100644
index c08e743a25..0000000000
--- a/apps/hexagon_launcher/CMakeLists.txt
+++ /dev/null
@@ -1,81 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-cmake_minimum_required(VERSION 3.2)
-project(HexagonLauncher C CXX)
-
-include(ExternalProject)
-
-set(LAUNCHER_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
-
-set(VARS_NEEDED
- ANDROID_ABI
- ANDROID_PLATFORM
- USE_ANDROID_TOOLCHAIN
- USE_HEXAGON_ARCH
- USE_HEXAGON_SDK
- USE_HEXAGON_TOOLCHAIN
-)
-foreach(V IN LISTS VARS_NEEDED)
- if(NOT ${V})
- message(SEND_ERROR "Please set ${V}")
- endif()
-endforeach()
-
-
-ExternalProject_Add(android_launcher_binaries
- SOURCE_DIR "${LAUNCHER_SOURCE_DIR}/cmake/android"
- BUILD_COMMAND $(MAKE)
- CMAKE_ARGS
- "-DCMAKE_TOOLCHAIN_FILE=${USE_ANDROID_TOOLCHAIN}"
- "-DANDROID_PLATFORM=${ANDROID_PLATFORM}"
- "-DANDROID_ABI=${ANDROID_ABI}"
- "-DCMAKE_CXX_STANDARD=17"
- "-DUSE_HEXAGON_SDK=${USE_HEXAGON_SDK}"
- "-DUSE_HEXAGON_ARCH=${USE_HEXAGON_ARCH}"
- INSTALL_COMMAND ""
- BUILD_ALWAYS ON
-)
-ExternalProject_Get_Property(android_launcher_binaries BINARY_DIR)
-ExternalProject_Add_Step(android_launcher_binaries copy_binaries
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- ${BINARY_DIR}/launcher_android
- ${BINARY_DIR}/lib/libtvm_runtime.so
- ${CMAKE_CURRENT_BINARY_DIR}
- DEPENDEES install
-)
-
-ExternalProject_Add(hexagon_launcher_binaries
- SOURCE_DIR "${LAUNCHER_SOURCE_DIR}/cmake/hexagon"
- BUILD_COMMAND $(MAKE)
- CMAKE_ARGS
- "-DCMAKE_C_COMPILER=${USE_HEXAGON_TOOLCHAIN}/bin/hexagon-clang"
- "-DCMAKE_CXX_COMPILER=${USE_HEXAGON_TOOLCHAIN}/bin/hexagon-clang++"
- "-DCMAKE_CXX_STANDARD=17"
- "-DUSE_HEXAGON_ARCH=${USE_HEXAGON_ARCH}"
- "-DUSE_HEXAGON_SDK=${USE_HEXAGON_SDK}"
- "-DUSE_CUSTOM_LOGGING=ON"
- INSTALL_COMMAND ""
- BUILD_ALWAYS ON
-)
-ExternalProject_Get_Property(hexagon_launcher_binaries BINARY_DIR)
-ExternalProject_Add_Step(hexagon_launcher_binaries copy_binaries
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- ${BINARY_DIR}/liblauncher_rpc_skel.so
- ${CMAKE_CURRENT_BINARY_DIR}
- DEPENDEES install
-)
diff --git a/apps/hexagon_launcher/README.md b/apps/hexagon_launcher/README.md
deleted file mode 100644
index f3c3519ea7..0000000000
--- a/apps/hexagon_launcher/README.md
+++ /dev/null
@@ -1,145 +0,0 @@
-<!--- Licensed to the Apache Software Foundation (ASF) under one -->
-<!--- or more contributor license agreements. See the NOTICE file -->
-<!--- distributed with this work for additional information -->
-<!--- regarding copyright ownership. The ASF licenses this file -->
-<!--- to you under the Apache License, Version 2.0 (the -->
-<!--- "License"); you may not use this file except in compliance -->
-<!--- with the License. You may obtain a copy of the License at -->
-
-<!--- http://www.apache.org/licenses/LICENSE-2.0 -->
-
-<!--- Unless required by applicable law or agreed to in writing, -->
-<!--- software distributed under the License is distributed on an -->
-<!--- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -->
-<!--- KIND, either express or implied. See the License for the -->
-<!--- specific language governing permissions and limitations -->
-<!--- under the License. -->
-# Hexagon Graph Launcher
-
-## Compilation
-
-The launcher consists of two parts: part running on Hexagon, and part running
-on Android. Each component must be compiled separately.
-
-The supported Snapdragon architectures are 855, 865, and 888.
-
-### Prerequisites
-
-1. Android NDK version r19c or later.
-2. Hexagon SDK version 4.0.0 or later.
-
-Android NDK can be downloaded from https://developer.android.com/ndk.
-Hexagon SDK is available at https://developer.qualcomm.com/software/hexagon-dsp-sdk.
-
-### Manual compilation
-
-Since some source files are shared between the Hexagon and Android builds,
-make sure to delete all object files between compilations. Compile the Hexagon
-code first.
-
-#### Compilation of the Hexagon part
-
-Create a subdirectory for the build files, and run `cmake` with the
-following variables set:
-
-```
-cmake -DCMAKE_C_COMPILER=/path/to/hexagon-clang \
- -DCMAKE_CXX_COMPILER=/path/to/hexagon-clang++ \
- -DUSE_HEXAGON_ARCH=v65|v66|v68|v69|v73|v75 \
- -DUSE_HEXAGON_SDK=/path/to/hexagon/SDK \
- /path/to/apps/hexagon_launcher/cmake/hexagon
-```
-
-Run `make`. This will create `liblauncher_rpc_skel.so`. The static version of
-the TVM runtime for Hexagon will be built as a part of the process.
-
-#### Compilation of the Android part
-
-2. Create a subdirectory for the build files (different from the one used for
- Hexagon files), and run `cmake` with the following variables set:
-
-```
-cmake -DCMAKE_TOOLCHAIN_FILE=/path/to/android-ndk/build/cmake/android.toolchain.cmake \
- -DANDROID_ABI=arm64-v8a \
- -DANDROID_PLATFORM=android-28 \
- -DUSE_HEXAGON_SDK=/p/Hexagon_SDK/4.3.0.0 \
- -DUSE_HEXAGON_ARCH=v65|v66|v68|v69|v73|v75 \
- /path/to/apps/hexagon_launcher/cmake/android
-```
-
-Run `make`. This will create `launcher_android`. The TVM runtime for Android will
-be built as a part of the process. Depending on the version of CMake that you are
-using, you may see the following warnings---they can be ignored.
-
-```
-An old version of CMake is being used that cannot automatically detect
-compiler attributes. Compiler identification is being bypassed. Some
-values may be wrong or missing. Update to CMake 3.19 or newer to use
-CMake's built-in compiler identification.
-```
-
-## Execution
-
-From the Android shell, do
-```
-./launcher_android --in_config input.json --out_config output.json
-```
-
-You may need to add the location of `libtvm_runtime.so` to `LD_LIBRARY_PATH`.
-See below for more information about the setup and launcher's inputs.
-
-### Preparation steps
-
-Copy the following binaries to the device:
-- `liblauncher_rpc_skel.so`: created by the compilation step for Hexagon,
-- `libgcc.so`: take this one from the Hexagon toolchain,
-- `launcher_android`: created by the compilation step for Android,
-- `libtvm_runtime.so`: built for Android.
-
-These are only the binaries related to the launcher itself. To run a model
-copy the shared object with the model and the model JSON file over to the
-device (both are obtained from relay). Also, copy all input files for the
-model as well.
-
-## Profiling using hexagon launcher
-
-### Enabling lightweight profiling (LWP) instrumentation
-
-This profiling option can be used to get function and loop level processor cycles.
-This needs to be enabled explicitly while compiling a model.
-
-Here, `instrument_lwp` is used to enable the tir pass which instruments the code with the builtin calls.
-
-During codegen, profiling builtin calls can be replaced with a target specific handler to record runtime
-information into a buffer. This buffer is written into a JSON file which is processed to construct
-function and loop level profiling information.
-
-To generate LWP JSON file, add `--gen_lwp_json` flag to launcher_android:
-
-```
-./launcher_android --in_config input.json --out_config output.json --gen_lwp_json
-```
-
-Please note that `--gen_lwp_json` flag by itself doesn't enable profiling and is only used to dump
-the profiling data into a json file called lwp.json. This file will be created at the same location
-on the device where launcher_android is executed from. To generate the data, profiling instrumentation
-must be enabled while compiling a model as mentioned above.
-
-Use this command to pull `lwp.json` from the device:
-
-```
-adb -s <DEVICE-ID> pull /path/to/lwp.json
-```
-
-**Note:** Please refer to src/runtime/hexagon/profiler/README.md for information on how
-to enable profiling using Hexagon RPC launcher and also to learn about additional profiling related
-config options.
-
-# Disclaimer
-
-The launcher does not perform any correctness verification. In order to verify
-correctness, the user needs to copy the output files from the device and
-verify their contents.
-
-This launcher is intended for use with prototyping and does not utilize any
-performance acceleration, as such the measured performance may be very poor.
diff --git a/apps/hexagon_launcher/cmake/HexagonLauncher.cmake b/apps/hexagon_launcher/cmake/HexagonLauncher.cmake
deleted file mode 100644
index 52e2cff1f8..0000000000
--- a/apps/hexagon_launcher/cmake/HexagonLauncher.cmake
+++ /dev/null
@@ -1,75 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-# On successful execution, sets
-# SDK_INCLUDE_DIRS
-# QAIC_EXE_PATH
-# and
-# QAIC_FLAGS
-# LAUNCHER_SRC
-# LAUNCHER_RPC_IDL
-# LAUNCHER_RPC_H
-# LAUNCHER_RPC_SKEL_C
-# LAUNCHER_RPC_STUB_C
-
-if(USE_CUSTOM_LOGGING)
- add_definitions(-DTVM_LOG_CUSTOMIZE=1)
-endif()
-if(NOT DEFINED USE_HEXAGON_SDK)
- message(SEND_ERROR "Please set USE_HEXAGON_SDK to the location of Hexagon SDK")
-endif()
-if (NOT DEFINED USE_HEXAGON_ARCH)
- message(SEND_ERROR "Please set USE_HEXAGON_ARCH to the Hexagon architecture version")
-endif()
-
-set(TVM_SOURCE_DIR "${CMAKE_CURRENT_SOURCE_DIR}/../../../../")
-
-include(ExternalProject)
-include("${TVM_SOURCE_DIR}/cmake/utils/Utils.cmake")
-include("${TVM_SOURCE_DIR}/cmake/modules/HexagonSDK.cmake")
-
-get_hexagon_sdk_property("${USE_HEXAGON_SDK}" "${USE_HEXAGON_ARCH}"
- SDK_INCLUDE SDK_INCLUDE_DIRS
- QAIC_EXE QAIC_EXE_PATH
-)
-if(NOT SDK_INCLUDE_DIRS OR NOT QAIC_EXE_PATH)
- message(WARNING "Could not locate some Hexagon SDK components")
-endif()
-
-include_directories(SYSTEM ${SDK_INCLUDE_DIRS})
-
-foreach(INCDIR IN LISTS SDK_INCLUDE_DIRS)
- list(APPEND QAIC_FLAGS "-I${INCDIR}")
-endforeach()
-
-set(LAUNCHER_SRC "${CMAKE_CURRENT_SOURCE_DIR}/../../")
-set(CMAKE_SKIP_RPATH TRUE)
-
-# Qaic for the domain header.
-#
-# Don't add paths to these filenames, or otherwise CMake may spontaneously
-# add -o option to the qaic invocation (with an undesirable path).
-set(LAUNCHER_RPC_IDL "launcher_rpc.idl")
-set(LAUNCHER_RPC_H "launcher_rpc.h")
-set(LAUNCHER_RPC_SKEL_C "launcher_rpc_skel.c")
-set(LAUNCHER_RPC_STUB_C "launcher_rpc_stub.c")
-
-include_directories(
- "${LAUNCHER_SRC}"
- "${TVM_SOURCE_DIR}/include"
- "${TVM_SOURCE_DIR}/3rdparty/dlpack/include"
-)
diff --git a/apps/hexagon_launcher/cmake/android/CMakeLists.txt b/apps/hexagon_launcher/cmake/android/CMakeLists.txt
deleted file mode 100644
index 0846ce7869..0000000000
--- a/apps/hexagon_launcher/cmake/android/CMakeLists.txt
+++ /dev/null
@@ -1,100 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-cmake_minimum_required(VERSION 3.2)
-project(HexagonAndroidLauncher C CXX)
-
-include("${CMAKE_CURRENT_SOURCE_DIR}/../HexagonLauncher.cmake")
-# From the include above, get
-# SDK_INCLUDE_DIRS
-# QAIC_EXE_PATH
-# and
-# QAIC_FLAGS
-# LAUNCHER_SRC
-# LAUNCHER_RPC_IDL
-# LAUNCHER_RPC_H
-# LAUNCHER_RPC_SKEL_C
-# LAUNCHER_RPC_STUB_C
-
-add_custom_command(
- OUTPUT ${LAUNCHER_RPC_STUB_C} ${LAUNCHER_RPC_H}
- COMMAND ${QAIC_EXE_PATH} ${QAIC_FLAGS} "${LAUNCHER_SRC}/${LAUNCHER_RPC_IDL}"
- MAIN_DEPENDENCY "${LAUNCHER_SRC}/${LAUNCHER_RPC_IDL}"
-)
-
-get_hexagon_sdk_property("${USE_HEXAGON_SDK}" "${USE_HEXAGON_ARCH}"
- RPCMEM_ROOT RPCMEM_ROOT_DIR
- DSPRPC_LIB DSPRPC_LIB_DIRS
-)
-if(NOT RPCMEM_ROOT_DIR)
- message(WARNING "Could not locate some Hexagon SDK components")
-endif()
-
-include_directories(SYSTEM
- "${SDK_INCLUDE_DIRS}"
- "${RPCMEM_ROOT_DIR}/inc"
- "${CMAKE_CURRENT_BINARY_DIR}" # Output of qaic will go here
-)
-
-link_directories(${DSPRPC_LIB_DIRS})
-
-
-set(STUB_SRCS
- "${LAUNCHER_SRC}/launcher_android.cc"
- "${LAUNCHER_SRC}/launcher_core.cc"
- "${LAUNCHER_SRC}/launcher_main.cc"
- "${LAUNCHER_SRC}/launcher_util.cc"
-)
-
-add_executable(launcher_android
- "${LAUNCHER_RPC_H}"
- "${LAUNCHER_RPC_STUB_C}"
- "${STUB_SRCS}"
-)
-
-ExternalProject_Add(android_tvm_runtime
- SOURCE_DIR "${TVM_SOURCE_DIR}"
- BUILD_COMMAND $(MAKE) runtime
- CMAKE_ARGS
- "-DANDROID_ABI=${ANDROID_ABI}"
- "-DANDROID_PLATFORM=${ANDROID_PLATFORM}"
- "-DCMAKE_CXX_STANDARD=17"
- "-DCMAKE_TOOLCHAIN_FILE=${CMAKE_TOOLCHAIN_FILE}"
- "-DUSE_HEXAGON=ON"
- "-DUSE_HEXAGON_ARCH=${USE_HEXAGON_ARCH}"
- "-DUSE_HEXAGON_SDK=${USE_HEXAGON_SDK}"
- "-DTVM_FFI_USE_LIBBACKTRACE=OFF"
- "-DTVM_FFI_USE_THREADS=OFF"
- "-DTVM_FFI_USE_DL_LIBS=OFF"
- "-DUSE_LLVM=OFF"
- "-DUSE_RPC=OFF"
- INSTALL_COMMAND ""
- BUILD_ALWAYS ON
-)
-ExternalProject_Get_Property(android_tvm_runtime BINARY_DIR)
-ExternalProject_Add_Step(android_tvm_runtime copy_binaries
- COMMAND ${CMAKE_COMMAND} -E copy_if_different
- ${BINARY_DIR}/lib/libtvm_runtime.so
- ${CMAKE_CURRENT_BINARY_DIR}
- DEPENDEES install
-)
-
-add_dependencies(launcher_android android_tvm_runtime)
-add_library(a_tvm_runtime SHARED IMPORTED)
-set_target_properties(a_tvm_runtime PROPERTIES IMPORTED_LOCATION "${BINARY_DIR}/lib/libtvm_runtime.so")
-
-target_link_libraries(launcher_android cdsprpc log a_tvm_runtime)
diff --git a/apps/hexagon_launcher/cmake/hexagon/CMakeLists.txt b/apps/hexagon_launcher/cmake/hexagon/CMakeLists.txt
deleted file mode 100644
index a0557307ba..0000000000
--- a/apps/hexagon_launcher/cmake/hexagon/CMakeLists.txt
+++ /dev/null
@@ -1,105 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-cmake_minimum_required(VERSION 3.2)
-project(HexagonLauncherRPCSkel C CXX ASM)
-
-include("${CMAKE_CURRENT_SOURCE_DIR}/../HexagonLauncher.cmake")
-# From the include above get
-# SDK_INCLUDE_DIRS
-# QAIC_EXE_PATH
-# and
-# QAIC_FLAGS
-# LAUNCHER_SRC
-# LAUNCHER_RPC_IDL
-# LAUNCHER_RPC_H
-# LAUNCHER_RPC_SKEL_C
-# LAUNCHER_RPC_STUB_C
-
-add_custom_command(
- OUTPUT ${LAUNCHER_RPC_SKEL_C} ${LAUNCHER_RPC_H}
- COMMAND ${QAIC_EXE_PATH} ${QAIC_FLAGS} "${LAUNCHER_SRC}/${LAUNCHER_RPC_IDL}"
- MAIN_DEPENDENCY "${LAUNCHER_SRC}/${LAUNCHER_RPC_IDL}"
-)
-
-get_hexagon_sdk_property("${USE_HEXAGON_SDK}" "${USE_HEXAGON_ARCH}"
- QURT_INCLUDE QURT_INCLUDE_DIRS
- QURT_LIB QURT_LIB_DIRS
-)
-if(NOT QURT_INCLUDE_DIRS OR NOT QURT_LIB_DIRS)
- message(WARNING "Could not locate some Hexagon SDK components")
-endif()
-
-include_directories(SYSTEM
- ${QURT_INCLUDE_DIRS}
- ${CMAKE_CURRENT_BINARY_DIR} # Output of qaic will go here
-)
-
-link_directories(${QURT_LIB_DIRS})
-
-add_definitions(-D_MACH_I32=int)
-
-# Extra compile flags (both C and C++).
-set(EXTRA_COMP_FLAGS
- "-O3"
- "-m${USE_HEXAGON_ARCH}"
-)
-string(REGEX REPLACE ";" " " EXTRA_COMP_FLAGS_STR "${EXTRA_COMP_FLAGS}")
-set(CMAKE_C_FLAGS "${EXTRA_COMP_FLAGS_STR} ${CMAKE_C_FLAGS}")
-set(CMAKE_CXX_FLAGS "${EXTRA_COMP_FLAGS_STR} ${CMAKE_CXX_FLAGS}")
-
-set(SKEL_SRCS
- "${LAUNCHER_SRC}/launcher_core.cc"
- "${LAUNCHER_SRC}/launcher_hexagon.cc"
-)
-set(PROFILER_DIR "${TVM_SOURCE_DIR}/src/runtime/hexagon/profiler")
-
-add_library(launcher_rpc_skel SHARED
- "${LAUNCHER_RPC_H}"
- "${LAUNCHER_RPC_SKEL_C}"
- "${SKEL_SRCS}"
- "${PROFILER_DIR}/prof_utils.cc"
- "${PROFILER_DIR}/lwp_handler.S"
-)
-
-ExternalProject_Add(static_hexagon_tvm_runtime
- SOURCE_DIR "${TVM_SOURCE_DIR}"
- BUILD_COMMAND $(MAKE) runtime
- CMAKE_ARGS
- "-DBUILD_STATIC_RUNTIME=ON"
- "-DCMAKE_C_COMPILER=${CMAKE_C_COMPILER}"
- "-DCMAKE_CXX_COMPILER=${CMAKE_CXX_COMPILER}"
- "-DCMAKE_CXX_STANDARD=17"
- "-DUSE_HEXAGON=ON"
- "-DUSE_HEXAGON_ARCH=${USE_HEXAGON_ARCH}"
- "-DUSE_HEXAGON_SDK=${USE_HEXAGON_SDK}"
- "-DTVM_FFI_USE_LIBBACKTRACE=OFF"
- "-DTVM_FFI_USE_THREADS=OFF"
- "-DTVM_FFI_USE_DL_LIBS=OFF"
- "-DUSE_LLVM=OFF"
- "-DUSE_RPC=OFF"
- "-DUSE_CUSTOM_LOGGING=ON"
- INSTALL_COMMAND ""
- BUILD_ALWAYS ON
-)
-ExternalProject_Get_Property(static_hexagon_tvm_runtime BINARY_DIR)
-
-add_dependencies(launcher_rpc_skel static_hexagon_tvm_runtime)
-add_library(h_tvm_runtime STATIC IMPORTED)
-set_target_properties(h_tvm_runtime PROPERTIES IMPORTED_LOCATION "${BINARY_DIR}/libtvm_runtime.a")
-
-target_link_libraries(launcher_rpc_skel -Wl,--whole-archive h_tvm_runtime -Wl,--no-whole-archive)
diff --git a/apps/hexagon_launcher/launcher_android.cc b/apps/hexagon_launcher/launcher_android.cc
deleted file mode 100644
index 34db0bdacb..0000000000
--- a/apps/hexagon_launcher/launcher_android.cc
+++ /dev/null
@@ -1,170 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-#include <AEEStdDef.h>
-#include <AEEStdErr.h>
-#include <remote.h>
-#include <rpcmem.h>
-
-#include <algorithm>
-#include <ios>
-#include <iostream>
-#include <string>
-#include <vector>
-
-#include "launcher_core.h"
-#include "launcher_rpc.h"
-
-AEEResult enable_unsigned_pd(bool enable) {
- remote_rpc_control_unsigned_module data;
- data.domain = CDSP_DOMAIN_ID;
- data.enable = static_cast<int>(enable);
- AEEResult rc = remote_session_control(DSPRPC_CONTROL_UNSIGNED_MODULE, &data, sizeof(data));
- if (rc != AEE_SUCCESS) {
- std::cout << "error " << (enable ? "enabling" : "disabling") << " unsigned PD\n";
- }
- return rc;
-}
-
-AEEResult set_remote_stack_size(int size) {
- remote_rpc_thread_params data;
- data.domain = CDSP_DOMAIN_ID;
- data.prio = -1;
- data.stack_size = size;
- AEEResult rc = remote_session_control(FASTRPC_THREAD_PARAMS, &data, sizeof(data));
- if (rc != AEE_SUCCESS) {
- std::cout << "error setting remote stack size: " << std::hex << rc << '\n';
- }
- return rc;
-}
-
-struct RPCChannel : public ExecutionSession {
- explicit RPCChannel(const std::string& uri, bool gen_lwp_json = false)
- : ExecutionSession(gen_lwp_json) {
- enable_unsigned_pd(true);
- set_remote_stack_size(128 * 1024);
-
- int rc = launcher_rpc_open(uri.c_str(), &handle);
- if (rc != AEE_SUCCESS) {
- handle = -1;
- }
- }
-
- ~RPCChannel() {
- if (handle == -1) {
- return;
- }
-
- for (void* ptr : allocations) {
- rpcmem_free(ptr);
- }
- if (model_loaded) {
- unload_model();
- }
- launcher_rpc_close(handle);
- handle = -1;
- }
-
- void* alloc_mem(size_t nbytes, size_t align) override {
- void* host_ptr = rpcmem_alloc(RPCMEM_HEAP_ID_SYSTEM, RPCMEM_DEFAULT_FLAGS, nbytes);
- if (host_ptr != nullptr) {
- allocations.push_back(host_ptr);
- }
- return host_ptr;
- }
-
- void free_mem(void* addr) override {
- auto f = std::find(allocations.begin(), allocations.end(), addr);
- if (f != allocations.end()) {
- allocations.erase(f);
- rpcmem_free(addr);
- }
- }
-
- bool load_model(const std::string& model_path, const std::string& model_json) override {
- AEEResult rc = launcher_rpc_load(handle, model_path.c_str(), model_json.c_str());
- if (rc != AEE_SUCCESS) {
- std::cout << "error loading graph module: " << std::hex << rc << '\n';
- } else {
- model_loaded = true;
- }
- return rc == AEE_SUCCESS;
- }
-
- bool unload_model() override {
- AEEResult rc = launcher_rpc_unload(handle);
- if (rc != AEE_SUCCESS) {
- std::cout << "error unloading model: " << std::hex << rc << '\n';
- }
- model_loaded = false;
- return rc == AEE_SUCCESS;
- }
-
- bool set_input(int input_idx, const tensor_meta* input_meta, const void* input_data) override {
- AEEResult rc = launcher_rpc_set_input(
- handle, input_idx, reinterpret_cast<const unsigned char*>(input_meta),
- input_meta->meta_size(), reinterpret_cast<const unsigned char*>(input_data),
- input_meta->data_size());
- if (rc != AEE_SUCCESS) {
- std::cout << "error setting model input no." << input_idx << ": " << std::hex << rc << '\n';
- }
- return rc == AEE_SUCCESS;
- }
-
- bool run(uint64_t* pcycles, uint64_t* usecs) override {
- AEEResult rc = launcher_rpc_run(handle, pcycles, usecs, gen_lwp_json);
- if (rc != AEE_SUCCESS) {
- std::cout << "error running model: " << std::hex << rc << '\n';
- }
- return rc == AEE_SUCCESS;
- }
-
- bool get_num_outputs(int* num_outputs) override {
- AEEResult rc = launcher_rpc_get_num_outputs(handle, num_outputs);
- if (rc != AEE_SUCCESS) {
- std::cout << "error getting number of outputs: " << std::hex << rc << '\n';
- }
- return rc == AEE_SUCCESS;
- }
-
- bool get_output(int output_idx, tensor_meta* output_meta, int meta_size, void* output_data,
- int data_size) override {
- AEEResult rc = launcher_rpc_get_output(
- handle, output_idx, reinterpret_cast<unsigned char*>(output_meta), meta_size,
- reinterpret_cast<unsigned char*>(output_data), data_size);
- if (rc != AEE_SUCCESS) {
- std::cout << "error getting output no." << output_idx << ": " << std::hex << rc << '\n';
- }
- return rc == AEE_SUCCESS;
- }
-
- bool model_loaded = false;
- remote_handle64 handle = -1;
- std::vector<void*> allocations;
-};
-
-ExecutionSession* create_execution_session(bool gen_lwp_json) {
- auto* session = new RPCChannel(launcher_rpc_URI CDSP_DOMAIN, gen_lwp_json);
- if (session->handle == -1) {
- delete session;
- session = nullptr;
- std::cout << "Error opening FastRPC channel\n";
- }
- return session;
-}
diff --git a/apps/hexagon_launcher/launcher_core.cc b/apps/hexagon_launcher/launcher_core.cc
deleted file mode 100644
index 44fc48c927..0000000000
--- a/apps/hexagon_launcher/launcher_core.cc
+++ /dev/null
@@ -1,231 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-#include "launcher_core.h"
-
-#include <tvm/ffi/extra/json.h>
-#include <tvm/ffi/function.h>
-#include <tvm/runtime/c_backend_api.h>
-
-#include <algorithm>
-#include <fstream>
-#include <ios>
-#include <iterator>
-#include <ostream>
-#include <sstream>
-#include <string>
-#include <vector>
-
-const std::string TensorConfig::file_key = "file"; // NOLINT(runtime/string)
-const std::string TensorConfig::shape_key = "shape"; // NOLINT(runtime/string)
-const std::string TensorConfig::dtype_key = "dtype"; // NOLINT(runtime/string)
-
-std::string tensor_meta::to_string() const {
- std::stringstream out;
- out << "ndim=" << ndim << ", dtype=" << tvm::runtime::DLDataTypeToString(dtype) << ", shape=";
- for (int i = 0; i != ndim; ++i) {
- out << shape[i];
- if (i + 1 < ndim) {
- out << 'x';
- }
- }
- return out.str();
-}
-
-void TensorConfig::Load(tvm::ffi::json::Object obj) {
- namespace json = ::tvm::ffi::json;
- for (const auto& kv : obj) {
- std::string key = std::string(kv.first.cast<tvm::ffi::String>());
- if (key == file_key) {
- file_name = std::string(kv.second.cast<tvm::ffi::String>());
- } else if (key == shape_key) {
- auto arr = kv.second.cast<json::Array>();
- shape.clear();
- shape.reserve(arr.size());
- for (const auto& elem : arr) {
- shape.push_back(static_cast<int>(elem.cast<int64_t>()));
- }
- if (shape.empty()) {
- std::cout << "error: empty shape\n";
- bad = true;
- }
- } else if (key == dtype_key) {
- dtype = std::string(kv.second.cast<tvm::ffi::String>());
- } else {
- std::cout << "unknown tensor config key: " << key << '\n';
- bad = true;
- }
- }
-}
-
-tvm::ffi::json::Value TensorConfig::SaveToJSON() const {
- namespace json = ::tvm::ffi::json;
- json::Object obj;
- obj.Set(tvm::ffi::String(file_key), tvm::ffi::String(file_name));
- json::Array shape_arr;
- for (int v : shape) {
- shape_arr.push_back(static_cast<int64_t>(v));
- }
- obj.Set(tvm::ffi::String(shape_key), std::move(shape_arr));
- obj.Set(tvm::ffi::String(dtype_key), tvm::ffi::String(dtype));
- return obj;
-}
-
-void ModelConfig::Load(tvm::ffi::json::Object obj) {
- namespace json = ::tvm::ffi::json;
- for (const auto& kv : obj) {
- std::string key = std::string(kv.first.cast<tvm::ffi::String>());
- if (key == "model-library") {
- model_library = std::string(kv.second.cast<tvm::ffi::String>());
- } else if (key == "model-json") {
- model_json = std::string(kv.second.cast<tvm::ffi::String>());
- } else if (key == "inputs") {
- auto arr = kv.second.cast<json::Array>();
- inputs.clear();
- inputs.reserve(arr.size());
- for (const auto& elem : arr) {
- TensorConfig tc;
- tc.Load(elem.cast<json::Object>());
- inputs.push_back(std::move(tc));
- }
- bad = std::any_of(inputs.begin(), inputs.end(), [](auto t) { return t.bad; });
- } else {
- std::cout << "unknown model config key: " << key << '\n';
- bad = true;
- }
- }
-}
-
-tvm::ffi::json::Value OutputConfig::SaveToJSON() const {
- namespace json = ::tvm::ffi::json;
- json::Object obj;
- obj.Set(tvm::ffi::String("pcycles"), static_cast<int64_t>(pcycles));
- obj.Set(tvm::ffi::String("usecs"), static_cast<int64_t>(usecs));
- json::Array outputs_arr;
- for (const auto& tc : outputs) {
- outputs_arr.push_back(tc.SaveToJSON());
- }
- obj.Set(tvm::ffi::String("outputs"), std::move(outputs_arr));
- return obj;
-}
-
-bool read_model_config(const std::string& file_name, ModelConfig* model_config) {
- namespace json = ::tvm::ffi::json;
- if (model_config == nullptr) {
- return false;
- }
- std::ifstream mfc(file_name);
- if (!mfc.is_open()) {
- return false;
- }
- std::string content((std::istreambuf_iterator<char>(mfc)), std::istreambuf_iterator<char>());
- auto parsed = json::Parse(content);
- model_config->Load(parsed.cast<json::Object>());
- if (model_config->bad) {
- return false;
- }
- return true;
-}
-
-bool write_output_config(const std::string& file_name, OutputConfig* output_config) {
- namespace json = ::tvm::ffi::json;
- std::ofstream ofc(file_name);
- if (!ofc.is_open()) {
- return false;
- }
- ofc << std::string(json::Stringify(output_config->SaveToJSON(), 2));
- if (!ofc) {
- return false;
- }
- return true;
-}
-
-Model::Model(tvm::runtime::Module executor, tvm::runtime::Module module, std::string json)
- : model_executor(executor), graph_module(module), graph_json(json) {
- // Lookup "run" ahead of time to reduce overhead in the model execution.
- run = get_module_func(model_executor, "run");
-}
-
-const tvm::ffi::Function get_runtime_func(const std::string& name) {
- if (auto pf = tvm::ffi::Function::GetGlobal(name)) {
- return *pf;
- }
- return tvm::ffi::Function();
-}
-
-const tvm::ffi::Function get_module_func(tvm::runtime::Module module, const std::string& name) {
- return module->GetFunction(name, false).value_or(tvm::ffi::Function());
-}
-
-void reset_device_api() {
- const tvm::ffi::Function api = get_runtime_func("device_api.hexagon");
- tvm::ffi::Function::SetGlobal("device_api.cpu", api, true);
-}
-
-tvm::runtime::Module load_module(const std::string& file_name) {
- static const tvm::ffi::Function loader = get_runtime_func("ffi.Module.load_from_file.hexagon");
- tvm::ffi::Any rv = loader(file_name);
- if (rv.type_code() == kTVMModuleHandle) {
- TVM_FFI_ICHECK_EQ(rv.type_code(), kTVMModuleHandle)
- << __func__ << ": loaded " << file_name << ", but did not get module handle";
- return rv.operator tvm::runtime::Module();
- }
- return tvm::runtime::Module();
-}
-
-std::ostream& operator<<(std::ostream& os, const tvm::ffi::Array<tvm::ffi::String>& strings) {
- os << '[';
- for (int i = 0, e = strings.size(); i != e; ++i) {
- if (i != 0) os << ',';
- os << static_cast<std::string>(strings[i]);
- }
- os << ']';
- return os;
-}
-
-tvm::runtime::Module create_graph_executor(const std::string& graph_json,
- tvm::runtime::Module graph_module, tvm::Device device) {
- std::string launcher_name = "tvm.graph_executor.create";
-
- const tvm::ffi::Function create_executor = get_runtime_func(launcher_name);
- uint64_t device_type = device.device_type;
- uint64_t device_id = device.device_id;
-
- if (graph_json.empty()) {
- LOG(ERROR) << __func__ << ": graph executor requires graph JSON";
- return tvm::runtime::Module();
- }
- tvm::ffi::Any rv = create_executor(graph_json, graph_module, device_type, device_id);
- return rv.operator tvm::runtime::Module();
-}
-
-tvm::runtime::Module create_aot_executor(tvm::runtime::Module factory_module, tvm::Device device) {
- tvm::ffi::Function list_modules = get_module_func(factory_module, "list_module_names");
- tvm::ffi::Array<tvm::ffi::String> module_names = list_modules();
- if (module_names.size() != 1) {
- LOG(WARNING) << __func__ << ": expecting single module, got: " << module_names << ", using "
- << module_names[0];
- }
- tvm::ffi::Function f = get_module_func(factory_module, module_names[0]);
- if (f.get() == nullptr) {
- LOG(ERROR) << __func__ << ": failed to obtain function " << module_names[0];
- return tvm::runtime::Module();
- }
- return f(device);
-}
diff --git a/apps/hexagon_launcher/launcher_core.h b/apps/hexagon_launcher/launcher_core.h
deleted file mode 100644
index 1af8a1fbf8..0000000000
--- a/apps/hexagon_launcher/launcher_core.h
+++ /dev/null
@@ -1,133 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-#ifndef TVM_RUNTIME_HEXAGON_LAUNCHER_LAUNCHER_CORE_H_
-#define TVM_RUNTIME_HEXAGON_LAUNCHER_LAUNCHER_CORE_H_
-
-#include <dlpack/dlpack.h>
-#include <tvm/ffi/extra/json.h>
-#include <tvm/ffi/extra/module.h>
-#include <tvm/ffi/function.h>
-#include <tvm/runtime/data_type.h>
-#include <tvm/runtime/tensor.h>
-
-#include <string>
-#include <vector>
-
-struct tensor_meta {
- int ndim;
- DLDataType dtype;
- int64_t shape[];
-
- int meta_size() const { return meta_size(ndim); }
- int data_size() const {
- int size = tvm::runtime::DataType(dtype).bytes();
- for (int d = 0; d != ndim; ++d) {
- size *= shape[d];
- }
- return size;
- }
-
- static int meta_size(int ndim) { return sizeof(tensor_meta) + ndim * sizeof(int64_t); }
-
- std::string to_string() const;
-};
-
-struct TensorConfig {
- static const std::string file_key;
- static const std::string shape_key;
- static const std::string dtype_key;
-
- std::string file_name;
- std::vector<int> shape;
- std::string dtype;
- bool bad = false;
-
- void Load(tvm::ffi::json::Object obj);
- tvm::ffi::json::Value SaveToJSON() const;
-};
-
-struct ModelConfig {
- std::string model_library;
- std::string model_json;
- std::vector<TensorConfig> inputs;
- bool bad = false;
-
- void Load(tvm::ffi::json::Object obj);
-};
-
-struct OutputConfig {
- uint64_t pcycles;
- uint64_t usecs;
- std::vector<TensorConfig> outputs;
-
- tvm::ffi::json::Value SaveToJSON() const;
-};
-
-struct Model {
- Model(tvm::ffi::Module executor, tvm::ffi::Module module, std::string json);
-
- tvm::ffi::Module model_executor;
- tvm::ffi::Module graph_module;
- std::string graph_json;
-
- static tvm::Device device() { return tvm::Device{static_cast<DLDeviceType>(kDLHexagon), 0}; }
- static tvm::Device external() { return tvm::Device{static_cast<DLDeviceType>(kDLCPU), 0}; }
-
- tvm::ffi::Function run;
-};
-
-struct ExecutionSession {
- explicit ExecutionSession(bool lwp_json = false) : gen_lwp_json(lwp_json) {}
-
- template <typename T>
- T* alloc(size_t bytes, size_t align = 1) {
- return reinterpret_cast<T*>(alloc_mem(bytes, align));
- }
- void free(void* ptr) { free_mem(ptr); }
-
- virtual void* alloc_mem(size_t bytes, size_t align) = 0;
- virtual void free_mem(void* ptr) = 0;
-
- virtual bool load_model(const std::string& model_path, const std::string& model_json) = 0;
- virtual bool unload_model() = 0;
-
- virtual bool set_input(int input_idx, const tensor_meta* input_meta, const void* input_data) = 0;
- virtual bool run(uint64_t* pcycles, uint64_t* usecs) = 0;
- virtual bool get_num_outputs(int* num_outputs) = 0;
- virtual bool get_output(int output_idx, tensor_meta* output_meta, int meta_size,
- void* output_data, int data_size) = 0;
- bool gen_lwp_json = false;
-};
-
-bool read_model_config(const std::string& file_name, ModelConfig* model_config);
-bool write_output_config(const std::string& file_name, OutputConfig* output_config);
-
-void reset_device_api();
-
-tvm::ffi::Module load_module(const std::string& file_name);
-
-const tvm::ffi::Function get_runtime_func(const std::string& name);
-const tvm::ffi::Function get_module_func(tvm::ffi::Module module, const std::string& name);
-
-tvm::ffi::Module create_aot_executor(tvm::ffi::Module factory_module, tvm::Device device);
-tvm::ffi::Module create_graph_executor(const std::string& graph_json, tvm::ffi::Module graph_module,
- tvm::Device device);
-
-#endif // TVM_RUNTIME_HEXAGON_LAUNCHER_LAUNCHER_CORE_H_
diff --git a/apps/hexagon_launcher/launcher_hexagon.cc b/apps/hexagon_launcher/launcher_hexagon.cc
deleted file mode 100644
index 32f1e62198..0000000000
--- a/apps/hexagon_launcher/launcher_hexagon.cc
+++ /dev/null
@@ -1,237 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-extern "C" {
-#include <AEEStdDef.h>
-#include <AEEStdErr.h>
-#include <HAP_farf.h>
-#include <HAP_perf.h>
-#include <qurt_error.h>
-}
-
-#include <tvm/ffi/cast.h>
-#include <tvm/ffi/object.h>
-
-#include <algorithm>
-#include <memory>
-#include <string>
-
-#include "launcher_core.h"
-#include "launcher_rpc.h"
-
-static std::unique_ptr<Model> TheModel;
-bool WriteLWPOutput(const std::string&);
-
-static AEEResult error_too_small(const std::string& func_name, const std::string& value_name,
- int given, int needed) {
- LOG(ERROR) << func_name.c_str() << ": " << value_name.c_str() << " value too small (" << given
- << "), need at least " << needed;
- return AEE_EBADPARM;
-}
-
-int __QAIC_HEADER(launcher_rpc_open)(const char* uri, remote_handle64* handle) {
- *handle = 0; // Just use any value.
- reset_device_api();
- static const tvm::ffi::Function acq_res =
- get_runtime_func("device_api.hexagon.acquire_resources");
- acq_res();
- return AEE_SUCCESS;
-}
-
-int __QAIC_HEADER(launcher_rpc_close)(remote_handle64 handle) {
- // Comment to stop clang-format from single-lining this function.
- static const tvm::ffi::Function rel_res =
- get_runtime_func("device_api.hexagon.release_resources");
- rel_res();
- return AEE_SUCCESS;
-}
-
-AEEResult __QAIC_HEADER(launcher_rpc_load)(remote_handle64 handle, const char* module_path,
- const char* graph_json) {
- if (TheModel) {
- // Need to unload first.
- LOG(ERROR) << __func__ << ": model already loaded, unload first";
- return AEE_EUNABLETOLOAD;
- }
-
- tvm::runtime::Module module = load_module(module_path);
- std::string module_type = module->type_key();
- tvm::runtime::Module executor;
- if (module_type == "AotExecutorFactory") {
- executor = create_aot_executor(module, Model::external());
- } else if (module_type == "library") {
- // We're not expecting "GraphExecutorFactory" here.
- executor = create_graph_executor(graph_json, module, Model::device());
- } else {
- LOG(ERROR) << __func__ << ": unexpected module type: " << module_type;
- // Fall through.
- }
-
- if (executor.get() == nullptr) {
- LOG(ERROR) << __func__ << ": failed to create executor for module" << module_path;
- return AEE_EUNABLETOLOAD;
- }
-
- TheModel = std::make_unique<Model>(executor, module, graph_json);
- return AEE_SUCCESS;
-}
-
-AEEResult __QAIC_HEADER(launcher_rpc_unload)(remote_handle64 handle) {
- if (TheModel) {
- TheModel.reset();
- }
- return AEE_SUCCESS;
-}
-
-AEEResult __QAIC_HEADER(launcher_rpc_get_num_inputs)(remote_handle64 handle, int* num_inputs) {
- if (!TheModel) {
- // No model created.
- return AEE_EBADSTATE;
- }
-
- tvm::ffi::Function get_num_inputs = get_module_func(TheModel->model_executor, "get_num_inputs");
- *num_inputs = get_num_inputs();
- return AEE_SUCCESS;
-}
-
-AEEResult __QAIC_HEADER(launcher_rpc_set_input)(remote_handle64 handle, int input_idx,
- const unsigned char* input_meta, int meta_size,
- const unsigned char* input_value, int value_size) {
- if (!TheModel) {
- // No model created.
- LOG(ERROR) << __func__ << ": no model created";
- return AEE_EBADSTATE;
- }
-
- const auto* meta = reinterpret_cast<const tensor_meta*>(input_meta);
- if (meta_size < meta->meta_size()) {
- return error_too_small(__func__, "meta_size", meta_size, meta->meta_size());
- }
- if (value_size < meta->data_size()) {
- return error_too_small(__func__, "value_size", value_size, meta->data_size());
- }
-
- DLTensor tensor{
- const_cast<unsigned char*>(input_value),
- Model::external(),
- meta->ndim,
- meta->dtype,
- const_cast<int64_t*>(meta->shape),
- /*strides*/ nullptr,
- /*byte_offset*/ 0,
- };
- DLManagedTensor managed{tensor, /*manager_ctx*/ nullptr, /*deleter*/ nullptr};
-
- auto input = tvm::runtime::Tensor::FromDLPack(&managed);
-
- tvm::ffi::Function set_input = get_module_func(TheModel->model_executor, "set_input");
- set_input(input_idx, input);
-
- return AEE_SUCCESS;
-}
-
-AEEResult __QAIC_HEADER(launcher_rpc_get_num_outputs)(remote_handle64 handle, int* num_outputs) {
- if (!TheModel) {
- // No model created.
- return AEE_EBADSTATE;
- }
-
- tvm::ffi::Function get_num_outputs = get_module_func(TheModel->model_executor, "get_num_outputs");
- *num_outputs = get_num_outputs();
- return AEE_SUCCESS;
-}
-
-AEEResult __QAIC_HEADER(launcher_rpc_get_output)(remote_handle64 handle, int output_idx,
- unsigned char* output_meta, int meta_size,
- unsigned char* output_value, int value_size) {
- if (!TheModel) {
- // No model created.
- return AEE_EBADSTATE;
- }
- if (meta_size < 0 || value_size < 0) {
- return AEE_EBADPARM;
- }
- if ((output_meta == nullptr && meta_size != 0) || (output_value == nullptr && value_size != 0)) {
- // If the pointer is null, the size must be 0.
- return AEE_EBADPARM;
- }
-
- tvm::ffi::Function get_output = get_module_func(TheModel->model_executor, "get_output");
- tvm::runtime::Tensor output = get_output(output_idx);
-
- std::vector<int64_t> shape_vec{output->shape, output->shape + output->ndim};
-
- auto* container = new tvm::runtime::Tensor::Container(static_cast<void*>(output_value), shape_vec,
- output->dtype, Model::external());
- container->SetDeleter([](tvm::ffi::Object* container) {
- delete static_cast<tvm::runtime::Tensor::Container*>(container);
- });
-
- tvm::runtime::Tensor host_output(tvm::ffi::GetObjectPtr<tvm::ffi::Object>(container));
-
- if (meta_size != 0) {
- auto* meta = reinterpret_cast<tensor_meta*>(output_meta);
- if (meta_size < meta->meta_size(output->ndim)) {
- return error_too_small(__func__, "meta_size", meta_size, meta->meta_size(output->ndim));
- }
-
- meta->ndim = output->ndim;
- meta->dtype = output->dtype;
- std::copy(&output->shape[0], &output->shape[output->ndim], meta->shape);
- }
-
- if (value_size != 0) {
- size_t data_size = tvm::runtime::GetDataSize(*output.operator->());
- if (value_size < data_size) {
- return error_too_small(__func__, "value_size", value_size, data_size);
- }
-
- host_output.CopyFrom(output);
- }
-
- return AEE_SUCCESS;
-}
-
-AEEResult __QAIC_HEADER(launcher_rpc_run)(remote_handle64 handle, uint64_t* pcycles,
- uint64_t* usecs, int gen_lwp_json) {
- if (!TheModel) {
- // No model created.
- LOG(ERROR) << __func__ << ": no model created";
- return AEE_EBADSTATE;
- }
-
- uint64_t us_begin = HAP_perf_get_time_us();
- uint64_t pc_begin = HAP_perf_get_pcycles();
-
- TheModel->run();
-
- uint64_t pc_end = HAP_perf_get_pcycles();
- uint64_t us_end = HAP_perf_get_time_us();
- *pcycles = pc_end - pc_begin;
- *usecs = us_end - us_begin;
-
- if (gen_lwp_json) {
- if (!WriteLWPOutput("lwp.json")) {
- LOG(ERROR) << "ERROR: failed to generate lwp json file";
- return AEE_EFAILED;
- }
- }
-
- return AEE_SUCCESS;
-}
diff --git a/apps/hexagon_launcher/launcher_main.cc b/apps/hexagon_launcher/launcher_main.cc
deleted file mode 100644
index 8690996684..0000000000
--- a/apps/hexagon_launcher/launcher_main.cc
+++ /dev/null
@@ -1,159 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-#include <getopt.h>
-
-#include <algorithm>
-#include <iostream>
-#include <string>
-#include <utility>
-
-#include "launcher_core.h"
-#include "launcher_util.h"
-
-ExecutionSession* create_execution_session(bool gen_lwp_json);
-
-int parse_command_line(int argc, char* argv[], std::string* in_path, std::string* out_path,
- bool* gen_lwp_json) {
- static option long_options[] = {
- {"in_config", required_argument, nullptr, 0},
- {"out_config", required_argument, nullptr, 0},
- {"gen_lwp_json", optional_argument, nullptr, 0},
- };
-
- bool show_usage = false;
- int opt, long_index = 0;
- while ((opt = getopt_long(argc, argv, "i:o:u:", long_options, &long_index)) != -1) {
- if (opt != 0) {
- show_usage = true;
- continue;
- }
- switch (long_index) {
- case 0:
- *in_path = std::string(optarg);
- break;
- case 1:
- *out_path = std::string(optarg);
- break;
- case 2:
- *gen_lwp_json = true;
- break;
- }
- }
- if (in_path->empty() || out_path->empty() || show_usage) {
- std::cout << "Usage: " << argv[0] << " --" << long_options[0].name << " input.json --"
- << long_options[1].name << " output.json\n";
- return 1;
- }
- return 0;
-}
-
-int main(int argc, char* argv[]) {
- std::string in_path, out_path;
- bool gen_lwp_json;
- if (parse_command_line(argc, argv, &in_path, &out_path, &gen_lwp_json) != 0) {
- return 1;
- }
-
- ModelConfig config;
- if (!read_model_config(in_path, &config)) {
- return 1;
- }
-
- ExecutionSession* session_ptr = create_execution_session(gen_lwp_json);
- if (session_ptr == nullptr) {
- return 1;
- }
- ExecutionSession& session = *session_ptr;
-
- std::cout << "loading model files: ";
- if (!config.model_json.empty()) {
- std::cout << config.model_json << ", ";
- }
- std::cout << config.model_library << '\n';
-
- std::string json = !config.model_json.empty() ? load_text_file(config.model_json) : "";
- if (!session.load_model(config.model_library, json.c_str())) {
- return 1;
- }
-
- int max_ndim = 0;
- for (const TensorConfig& tc : config.inputs) {
- max_ndim = std::max<int>(max_ndim, tc.shape.size());
- }
- auto* input_meta = session.alloc<tensor_meta>(tensor_meta::meta_size(max_ndim));
-
- for (int i = 0, e = config.inputs.size(); i != e; ++i) {
- const TensorConfig& tc = config.inputs[i];
- input_meta->ndim = tc.shape.size();
- input_meta->dtype = tvm::ffi::StringToDLDataType(tc.dtype);
- std::copy(tc.shape.begin(), tc.shape.end(), input_meta->shape);
-
- auto* input_data = session.alloc<unsigned char>(input_meta->data_size());
- std::cout << "loading input file #" << i << ": " << tc.file_name << '\n';
- load_binary_file(tc.file_name, input_data, input_meta->data_size());
- if (!session.set_input(i, input_meta, input_data)) {
- return 1;
- }
- }
-
- OutputConfig output_config;
-
- std::cout << "running..." << std::flush;
- if (!session.run(&output_config.pcycles, &output_config.usecs)) {
- std::cout << '\n';
- return 1;
- }
- std::cout << '\n';
- std::cout << "Finished in " << output_config.pcycles << " pcycles, (" << output_config.usecs
- << "us)\n";
-
- auto* output_meta = session.alloc<tensor_meta>(128);
- int num_outputs = 0;
- if (!session.get_num_outputs(&num_outputs)) {
- return 1;
- }
-
- for (int i = 0; i != num_outputs; ++i) {
- if (!session.get_output(i, output_meta, 128, nullptr, 0)) {
- return 1;
- }
- int data_size = output_meta->data_size();
- auto* output_data = session.alloc<unsigned char>(data_size);
- if (!session.get_output(i, output_meta, 128, output_data, data_size)) {
- return 1;
- }
-
- TensorConfig oc;
- oc.file_name = "output" + std::to_string(i) + ".dat";
- for (int i = 0, e = output_meta->ndim; i != e; ++i) {
- oc.shape.push_back(output_meta->shape[i]);
- }
- oc.dtype = tvm::runtime::DLDataTypeToString(output_meta->dtype);
- write_binary_file(oc.file_name, output_data, data_size);
- output_config.outputs.push_back(std::move(oc));
-
- session.free(output_data);
- }
-
- if (!write_output_config(out_path, &output_config)) {
- return 1;
- }
- return 0;
-}
diff --git a/apps/hexagon_launcher/launcher_rpc.idl b/apps/hexagon_launcher/launcher_rpc.idl
deleted file mode 100644
index 27e5d1d15d..0000000000
--- a/apps/hexagon_launcher/launcher_rpc.idl
+++ /dev/null
@@ -1,33 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-#include "remote.idl"
-#include "AEEStdDef.idl"
-
-typedef sequence<octet> buffer;
-
-interface launcher_rpc : remote_handle64 {
- AEEResult load(in string module_path, in string model_json);
- AEEResult unload();
- AEEResult get_num_inputs(rout long num_inputs);
- AEEResult set_input(in long input_idx, in buffer input_meta, in buffer input_value);
- AEEResult get_num_outputs(rout long num_outputs);
- AEEResult get_output(in long output_idx, rout buffer output_meta, rout buffer output_value);
- AEEResult run(rout uint64_t pcycles, rout uint64_t usecs, in long gen_lwp_json);
-};
diff --git a/apps/hexagon_launcher/launcher_util.cc b/apps/hexagon_launcher/launcher_util.cc
deleted file mode 100644
index ddbafb3c84..0000000000
--- a/apps/hexagon_launcher/launcher_util.cc
+++ /dev/null
@@ -1,68 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-#include "launcher_util.h"
-
-#include <tvm/runtime/logging.h>
-
-#include <algorithm>
-#include <fstream>
-#include <ios>
-#include <iostream>
-#include <string>
-#include <utility>
-
-size_t get_file_size(std::ifstream& in_file) {
- std::ifstream::pos_type pos = in_file.tellg();
- size_t size = in_file.seekg(0, std::ios::end).tellg();
- in_file.seekg(pos, std::ios::beg);
- return size;
-}
-
-size_t get_file_size(std::ifstream&& in_file) {
- return get_file_size(in_file); // calls the & version
-}
-
-std::string load_text_file(const std::string& file_name) {
- constexpr size_t block_size = 1024 * 1024; // 1MB
- std::ifstream in_file(file_name);
- TVM_FFI_ICHECK(in_file.is_open()) << "cannot open file " << file_name;
- size_t file_size = get_file_size(in_file);
- std::string buffer(file_size + 1, 0);
-
- in_file.read(&buffer[0], file_size);
- return buffer;
-}
-
-void* load_binary_file(const std::string& file_name, void* buffer, size_t buffer_size) {
- std::ifstream in_file(file_name);
- TVM_FFI_ICHECK(in_file.is_open()) << "cannot open file " << file_name;
- size_t file_size = get_file_size(in_file);
-
- in_file.read(reinterpret_cast<std::ifstream::char_type*>(buffer),
- std::min(buffer_size, file_size));
- return buffer;
-}
-
-void write_binary_file(const std::string& file_name, void* buffer, size_t buffer_size) {
- std::ofstream out_file(file_name);
- TVM_FFI_ICHECK(out_file.is_open()) << "cannot open file " << file_name;
-
- out_file.write(reinterpret_cast<std::ofstream::char_type*>(buffer), buffer_size);
-}
diff --git a/apps/hexagon_launcher/launcher_util.h b/apps/hexagon_launcher/launcher_util.h
deleted file mode 100644
index 13db89d052..0000000000
--- a/apps/hexagon_launcher/launcher_util.h
+++ /dev/null
@@ -1,34 +0,0 @@
-/*
- * Licensed to the Apache Software Foundation (ASF) under one
- * or more contributor license agreements. See the NOTICE file
- * distributed with this work for additional information
- * regarding copyright ownership. The ASF licenses this file
- * to you under the Apache License, Version 2.0 (the
- * "License"); you may not use this file except in compliance
- * with the License. You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing,
- * software distributed under the License is distributed on an
- * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
- * KIND, either express or implied. See the License for the
- * specific language governing permissions and limitations
- * under the License.
- */
-
-#ifndef TVM_RUNTIME_HEXAGON_LAUNCHER_LAUNCHER_UTIL_H_
-#define TVM_RUNTIME_HEXAGON_LAUNCHER_LAUNCHER_UTIL_H_
-
-#include <cstddef>
-#include <fstream>
-#include <string>
-
-size_t get_file_size(std::ifstream& in_file);
-size_t get_file_size(std::ifstream&& in_file);
-
-std::string load_text_file(const std::string& file_name);
-void* load_binary_file(const std::string& file_name, void* buffer, size_t buffer_size);
-void write_binary_file(const std::string& file_name, void* buffer, size_t buffer_size);
-
-#endif // TVM_RUNTIME_HEXAGON_LAUNCHER_LAUNCHER_UTIL_H_
diff --git a/ci/jenkins/data.py b/ci/jenkins/data.py
index 44cdba1d02..0811451b53 100644
--- a/ci/jenkins/data.py
+++ b/ci/jenkins/data.py
@@ -33,10 +33,6 @@ import sys
files_to_stash = {
# Executables and build files needed to run c++ tests
"cpptest": ["build/cpptest", "build/build.ninja", "build/CMakeFiles/rules.ninja"],
- # Folder for hexagon build
- "hexagon_api": [
- "build/hexagon_api_output",
- ],
# runtime files
"tvm_runtime": ["build/lib/libtvm_runtime.so", "build/config.cmake"],
# compiler files
diff --git a/python/tvm/contrib/hexagon/_ci_env_check.py b/python/tvm/contrib/hexagon/_ci_env_check.py
index f7f14a2395..ed7beb246a 100644
--- a/python/tvm/contrib/hexagon/_ci_env_check.py
+++ b/python/tvm/contrib/hexagon/_ci_env_check.py
@@ -17,9 +17,7 @@
"""Hexagon environment checks for CI usage
-These may be required by either tvm.testing or
-tvm.contrib.hexagon.pytest_plugin, and are separated here to avoid a
-circular dependency.
+These are required by tvm.testing and live here to avoid a circular dependency.
"""
import os
diff --git a/python/tvm/contrib/hexagon/build.py b/python/tvm/contrib/hexagon/build.py
deleted file mode 100644
index 30b9c48458..0000000000
--- a/python/tvm/contrib/hexagon/build.py
+++ /dev/null
@@ -1,830 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-# pylint: disable=consider-using-with, unnecessary-ellipsis
-# ruff: noqa: RUF005, RUF012
-
-"""Defines top-level glue functions for building Hexagon."""
-
-import abc
-import datetime
-import logging
-import multiprocessing as mp
-import os
-import pathlib
-import random
-import signal
-import socket
-import stat
-import string
-import subprocess
-import sys
-import tempfile
-
-from tvm.contrib.hexagon.hexagon_profiler import HexagonProfiler
-
-from .session import Session
-from .tools import HEXAGON_SIMULATOR_NAME
-
-HEXAGON_RPC_LIB_DIR = os.environ.get("HEXAGON_RPC_LIB_DIR")
-ANDROID_BASH_FILE_NAME = "android_bash.sh"
-HEXAGON_REMOTE_DEVICE_KEY = "hexagon-dev"
-
-
-def _check_call_verbose(cmd, **kwargs) -> None:
- """
- Similar to subprocess.check_call(cmd), but if the exit code is non-zero
- then the raised Exception's message provides more detail, including
- the stdout/stderr provided by the subprocess.
- """
- try:
- subprocess.run(
- cmd,
- check=True,
- encoding="UTF-8",
- capture_output=True,
- **kwargs,
- )
- except subprocess.CalledProcessError as err:
- error_msg = f"{err}\nstdout:\n{err.stdout}\nstderr:\n{err.stderr}"
- raise Exception(error_msg)
-
-
-def _get_hexagon_rpc_lib_dir() -> pathlib.Path:
- """Find the Hexagon API binaries.
-
- Returns
- -------
- pathlib.Path :
- The path to the Hexagon API directory.
- """
- global HEXAGON_RPC_LIB_DIR
- HEXAGON_RPC_LIB_DIR = os.environ.get("HEXAGON_RPC_LIB_DIR")
- if HEXAGON_RPC_LIB_DIR is None:
- raise RuntimeError("hexagon_api binaries not found, please define HEXAGON_RPC_LIB_DIR")
- return pathlib.Path(HEXAGON_RPC_LIB_DIR)
-
-
-def _get_test_directory_name() -> str:
- """Generate a time-stamped name for use as a test directory name."""
- date_str = datetime.datetime.now().strftime("%Y-%m-%dT%H-%M-%S")
- random_str = "".join(random.choice(string.ascii_lowercase) for _ in range(10))
- return f"{date_str}-{random_str}"
-
-
-def _get_adb_path() -> str:
- """Define path to adb
-
- Order of search:
- 1. From PATH
- 2. From ANDROID_SDK_ROOT
- 3. From ANDROID_HOME
- 3. From default android sdk installation directory (platform specific)
- """
-
- def check_execution(exe_path):
- try:
- ret_code = subprocess.call(
- [exe_path, "--version"], stdout=subprocess.PIPE, stderr=subprocess.PIPE
- )
- except FileNotFoundError:
- ret_code = -1
-
- return ret_code == 0
-
- # Check if adb available via PATH
- if check_execution("adb"):
- return "adb"
-
- # Check if adb available via env vars or default directories
- list_of_paths = [
- os.environ.get("ANDROID_SDK_ROOT", default=""),
- os.environ.get("ANDROID_HOME", default=""),
- ]
-
- if sys.platform == "darwin":
- list_of_paths += [
- os.path.join(pathlib.Path.home(), "Library", "Android", "sdk", "platform-tools")
- ]
- if sys.platform == "win32":
- list_of_paths += [
- os.path.join(
- pathlib.Path.home(), "AppData", "Local", "Android", "sdk", "platform-tools"
- )
- ]
- if sys.platform == "linux":
- list_of_paths += [os.path.join(pathlib.Path.home(), "Android", "Sdk", "platform-tools")]
-
- list_of_paths = [path for path in list_of_paths if path != ""]
-
- found_path = None
- for candidate_path in list_of_paths:
- adb_path = os.path.join(candidate_path, "adb")
- if os.path.isfile(adb_path) and check_execution(adb_path):
- found_path = adb_path
- break
-
- if found_path is None:
- raise RuntimeError(
- "ADB was not found. It should be available via PATH, ANDROID_SDK_ROOT "
- "or ANDROID_HOME env var."
- )
-
- return found_path
-
-
-class HexagonLauncherRPC(metaclass=abc.ABCMeta):
- """Base class for RPC-based launchers.
-
- This is an abstract class intended to be a base class for specific
- implementations of RPC launchers. There are two public methods that
- each launcher needs to implement:
- - start_server
- - stop server
- and two "private" methods used in setting up the environment:
- - _copy_to_remote
- - _create_remote_directory
-
- The basic flow of interaction with the launcher is
- launcher = HexagonLauncher(...)
- launcher.start_server()
- with launcher.create_session() as session:
- # Do something with the session
- launcher.stop_server()
-
- Parameters
- ----------
- rpc_info : dict
- Description of the RPC setup. Recognized keys:
- "rpc_tracker_host" : str name of the host running the tracker (default "0.0.0.0")
- "rpc_tracker_port" : int port number of the tracker (default: 9190)
- "rpc_server_port" : int port number for the RPC server to use (default 7070)
- "workspace_base" : str name of base test directory (default ".")
- workspace : str or patlib.Path
- The server's remote working directory. If this directory does not
- exist, it will be created. If it does exist, the servermust have
- write permissions to it.
- If this parameter is None, a subdirectory in the `workspace_base`
- directory will be created, otherwise the `workspace_base` is not
- used.
- """
-
- def __init__(
- self,
- rpc_info: dict,
- workspace: str | pathlib.Path | None = None,
- serial_number: str | None = None,
- ):
- self._rpc_info = {
- "rpc_tracker_host": "0.0.0.0",
- "rpc_tracker_port": 9190,
- "rpc_server_port": 7070,
- "workspace_base": ".",
- }
- self._rpc_info.update(rpc_info)
- self._workspace = self._create_workspace(workspace)
- self._serial_number = serial_number
-
- @abc.abstractmethod
- def start_server(self):
- """Start the RPC server"""
- ...
-
- @abc.abstractmethod
- def stop_server(self):
- """Stop the RPC server"""
- ...
-
- @abc.abstractmethod
- def cleanup_directory(self):
- """Cleanup working directory"""
- ...
-
- @abc.abstractmethod
- def _copy_to_remote(self, local_path: str | pathlib.Path, remote_path: str | pathlib.Path):
- """Copy a local file to a remote location.
-
- Parameters
- ----------
- local_path : str or pathlib.Path
- Path to the local file.
- remote_path : str or pathlib.Path
- Path to the remote file (to be written).
- """
- ...
-
- @abc.abstractmethod
- def _create_remote_directory(self, remote_path: str | pathlib.Path) -> pathlib.Path:
- """Create a directory in the remote location.
-
- Parameters
- ----------
- remote_path : str or pathlib.Path
- Name of the directory to be created.
-
- Returns
- -------
- pathlib.Path :
- Absolute path of the remote workspace.
- """
- ...
-
- def _create_workspace(self, workspace: str | pathlib.Path) -> pathlib.Path:
- """Create a working directory for the server.
-
- Parameters
- ----------
- workspace : str or pathlib.Path or NoneType
- Name of the directory to create. If None, a new name is constructed
- using workspace_base.
-
- Returns
- -------
- pathlib.Path :
- Created workspace.
- """
- if not workspace:
- base_dir = self._rpc_info["workspace_base"]
- workspace = os.path.join(base_dir, _get_test_directory_name())
- return self._create_remote_directory(workspace)
-
- @abc.abstractmethod
- def get_profile_output(
- self,
- hex_profiler: HexagonProfiler,
- session: Session,
- ) -> str:
- """Extract profile output.
-
- Parameters
- ----------
- hex_profiler : HexagonProfiler
- HexagonProfiler object that contains the profiling related information.
- session : Session
- Remote session. The session must be established (via __enter__)
- prior to calling this function.
-
- Returns
- -------
- profile_data : str
- Path of the profiling data file
- """
- ...
-
- def create_session(self, session_name: str = "hexagon-rpc") -> Session:
- """Create an RPC session.
-
- Parameters
- ----------
- session_name : str
- RPC session name.
-
- Returns
- -------
- Session :
- The session object.
- """
- hexagon_session_kw = {
- "remote_workspace": self._workspace,
- "rpc_tracker": (self._rpc_info["rpc_tracker_host"], self._rpc_info["rpc_tracker_port"]),
- "rpc_server_key": self._rpc_info["device_key"],
- "serial_number": self._serial_number,
- "session_name": session_name,
- }
- return Session(**hexagon_session_kw)
-
- def is_simulator(self):
- return self._serial_number == HEXAGON_SIMULATOR_NAME
-
-
-class HexagonLauncherAndroid(HexagonLauncherRPC):
- """Hexagon Launcher for Android."""
-
- ANDROID_HEXAGON_TEST_BASE_DIR = pathlib.Path("/data/local/tmp/hexagon_test")
- ANDROID_HEXAGON_RPC_FILES = [
- "libhexagon_rpc_skel.so",
- "libtvm_runtime.so",
- "tvm_rpc_android",
- ]
-
- def __init__(
- self,
- serial_number: str,
- rpc_info: dict,
- workspace: str | pathlib.Path | None = None,
- hexagon_debug: bool = False,
- clear_logcat: bool = False,
- sysmon_profile: bool = False,
- farf_config: str = "0x1e",
- ):
- """Configure a new HexagonLauncherAndroid
-
- Parameters
- ----------
- serial_number : str
- Android device serial number.
- rpc_info : dict
- Same as in HexagonLauncherRPC, except if the "workspace_base"
- key is not present or is None, ANDROID_HEXAGON_TEST_BASE_DIR
- is used as the base directory.
- workspace : str or pathlib.Path, optional
- Test workspace path on android.
- hexagon_debug: bool, optional
- Should the server run debug options.
- clear_logcat: bool, optional
- Should the server clear logcat before running.
- sysmon_profile: bool, optional
- Should the server run sysmon profiler in the background.
- farf_config: str, optional
- Configuration string for runtime log level filtering.
- Use farf_config_from_python_log_level to generate a bitmask
- string from a Python logging level (e.g., logging.INFO)
- """
- if not rpc_info.get("workspace_base"):
- rpc_info["workspace_base"] = self.ANDROID_HEXAGON_TEST_BASE_DIR
- self._serial_number = serial_number
- assert self._serial_number != "", "Android serial number is not set."
-
- adb_socket = rpc_info["adb_server_socket"] if rpc_info["adb_server_socket"] else "tcp:5037"
- adb_exe = _get_adb_path()
- self._adb_device_sub_cmd = [adb_exe, "-L", adb_socket, "-s", self._serial_number]
- self.forwarded_ports_ = []
- self._hexagon_debug = hexagon_debug
- self._clear_logcat = clear_logcat
- self._sysmon_profile = sysmon_profile
- self._sysmon_process = None
- self._farf_config = farf_config
- rpc_info["device_key"] = HEXAGON_REMOTE_DEVICE_KEY + "." + self._serial_number
-
- super().__init__(rpc_info, workspace, self._serial_number)
-
- def _copy_to_remote(self, local_path: str | pathlib.Path, remote_path: str | pathlib.Path):
- """Abstract method implementation. See description in HexagonLauncherRPC."""
-
- _check_call_verbose(self._adb_device_sub_cmd + ["push", str(local_path), str(remote_path)])
-
- def _create_remote_directory(self, remote_path: str | pathlib.Path) -> pathlib.Path:
- """Abstract method implementation. See description in HexagonLauncherRPC."""
- _check_call_verbose(self._adb_device_sub_cmd + ["shell", "mkdir", "-p", str(remote_path)])
- return pathlib.Path(remote_path)
-
- def _copy_binaries(self):
- """Upload Android server binaries."""
-
- # Create bash script
- with open(_get_hexagon_rpc_lib_dir() / f"{ANDROID_BASH_FILE_NAME}.template") as src_f:
- with tempfile.TemporaryDirectory() as temp_dir:
- android_bash_script_path = pathlib.Path(temp_dir) / ANDROID_BASH_FILE_NAME
- with open(android_bash_script_path, "w") as dest_f:
- for line in src_f.readlines():
- if "<RPC_TRACKER_HOST>" in line:
- line = line.replace(
- "<RPC_TRACKER_HOST>", str(self._rpc_info["rpc_tracker_host"])
- )
- if "<RPC_TRACKER_PORT>" in line:
- line = line.replace(
- "<RPC_TRACKER_PORT>", str(self._rpc_info["rpc_tracker_port"])
- )
- if "<HEXAGON_REMOTE_DEVICE_KEY>" in line:
- line = line.replace(
- "<HEXAGON_REMOTE_DEVICE_KEY>", self._rpc_info["device_key"]
- )
- if "<RPC_SERVER_PORT>" in line:
- line = line.replace(
- "<RPC_SERVER_PORT>", str(self._rpc_info["rpc_server_port"])
- )
- if "<FARF_CONFIG>" in line:
- line = line.replace("<FARF_CONFIG>", str(self._farf_config))
- dest_f.write(line)
-
- # Make shell script executable
- android_bash_stat = os.stat(android_bash_script_path)
- os.chmod(android_bash_script_path, android_bash_stat.st_mode | stat.S_IEXEC)
- self._copy_to_remote(
- android_bash_script_path, self._workspace / android_bash_script_path.name
- )
-
- # Push files
- lib_dir = _get_hexagon_rpc_lib_dir()
- for item in self.ANDROID_HEXAGON_RPC_FILES:
- self._copy_to_remote(lib_dir / item, self._workspace / item)
-
- def _process_forwarded_ports(self):
- forwarded_ports = subprocess.check_output(self._adb_device_sub_cmd + ["forward", "--list"])
- existing_forwards = []
- for forward in str(forwarded_ports).split("\\n"):
- entry = forward.split()
- if len(entry) == 3:
- _, local, _ = entry
- existing_forwards.append(int(local.strip("tcp:")))
- return existing_forwards
-
- def _forward_ports(self, rpc_server_port, existing_forwards):
- # Enable port forward for RPC server. We forward the first ten open ports
- # starting from the rpc_server_port
- port = rpc_server_port
- while len(self.forwarded_ports_) < 10:
- if port not in existing_forwards and not _is_port_in_use(port):
- _check_call_verbose(
- self._adb_device_sub_cmd + ["forward", f"tcp:{port}", f"tcp:{port}"]
- )
- self.forwarded_ports_.append(port)
- port += 1
-
- def _reverse_ports(self, rpc_tracker_port):
- _check_call_verbose(
- self._adb_device_sub_cmd
- + ["reverse", f"tcp:{rpc_tracker_port}", f"tcp:{rpc_tracker_port}"]
- )
-
- def _run_server_script(self):
- """Setup the ADB connection and execute the server script."""
-
- # Collect any existing adb port forwarding to avoid duplication
- # with another running process
- existing_forwards = self._process_forwarded_ports()
- # Enable port reverse for RPC tracker
- rpc_tracker_port = self._rpc_info["rpc_tracker_port"]
- rpc_server_port = self._rpc_info["rpc_server_port"]
-
- self._reverse_ports(rpc_tracker_port)
- self._forward_ports(rpc_server_port, existing_forwards)
-
- # Run server and connect to tracker
- subprocess.Popen(
- self._adb_device_sub_cmd
- + ["shell", f"cd {self._workspace} && ./{ANDROID_BASH_FILE_NAME}"],
- stdout=subprocess.PIPE,
- stdin=subprocess.PIPE,
- stderr=subprocess.PIPE,
- )
-
- def _cleanup_port_forwarding(self):
- # Removed pre-defined forward/reverse rules
- rpc_tracker_port = self._rpc_info["rpc_tracker_port"]
- _check_call_verbose(
- self._adb_device_sub_cmd + ["reverse", "--remove", f"tcp:{rpc_tracker_port}"]
- )
- for port in self.forwarded_ports_:
- _check_call_verbose(self._adb_device_sub_cmd + ["forward", "--remove", f"tcp:{port}"])
-
- def _terminate_remote(self):
- # Send interupt to main and child processes
- subprocess.Popen(
- self._adb_device_sub_cmd
- + ["shell", f"pkill -l sigint -P `cat {self._workspace}/rpc_pid.txt`"]
- )
- subprocess.Popen(
- self._adb_device_sub_cmd
- + ["shell", f"kill -s sigint `cat {self._workspace}/rpc_pid.txt`"]
- )
- # Wait for processes to destruct cleanly after receiving the intrupt
- subprocess.Popen(self._adb_device_sub_cmd + ["shell", "sleep", "0.1s"])
- # Kill process children
- subprocess.Popen(
- self._adb_device_sub_cmd + ["shell", f"pkill -P `cat {self._workspace}/rpc_pid.txt`"]
- )
- # Kill main process
- subprocess.Popen(
- self._adb_device_sub_cmd + ["shell", f"kill `cat {self._workspace}/rpc_pid.txt`"]
- )
-
- def cleanup_directory(self):
- """Abstract method implementation. See description in HexagonLauncherRPC."""
- subprocess.Popen(self._adb_device_sub_cmd + ["shell", f"rm -rf {self._workspace}"])
-
- def _start_sysmon(self):
- hexagon_sdk_root = os.environ.get("HEXAGON_SDK_ROOT", default="")
- subprocess.call(
- self._adb_device_sub_cmd
- + ["push", f"{hexagon_sdk_root}/tools/utils/sysmon/sysMonApp", "/data/local/tmp/"]
- )
- sysmon_process = subprocess.Popen(
- self._adb_device_sub_cmd
- + [
- "shell",
- "/data/local/tmp/sysMonApp profiler --debugLevel 0 --samplePeriod 1 --q6 cdsp",
- ],
- stdin=subprocess.PIPE,
- )
- return sysmon_process
-
- def _stop_sysmon(self):
- if self._sysmon_process is not None:
- self._sysmon_process.communicate(input=b"\n")
- self._sysmon_process = None
-
- def _retrieve_sysmon(self):
- pathlib.Path("./sysmon_output/").mkdir(exist_ok=True)
- subprocess.call(
- self._adb_device_sub_cmd + ["pull", "/sdcard/sysmon_cdsp.bin", "./sysmon_output/"]
- )
- subprocess.call(self._adb_device_sub_cmd + ["root"])
- hexagon_sdk_root = os.environ.get("HEXAGON_SDK_ROOT", default="")
- subprocess.call(
- f"{hexagon_sdk_root}/tools/utils/sysmon/parser_linux_v2/HTML_Parser/sysmon_parser "
- + "./sysmon_output/sysmon_cdsp.bin --outdir ./sysmon_output/",
- shell=True,
- )
-
- def _clear_debug_logs(self):
- subprocess.call(self._adb_device_sub_cmd + ["shell", "logcat", "-c"])
-
- def _retrieve_debug_logs(self):
- run_start_time = subprocess.check_output(
- self._adb_device_sub_cmd
- + [
- "shell",
- "stat",
- f"{self._workspace}/android_bash.sh | grep 'Change' | grep -oe '[0-9].*'",
- ]
- )
- run_start_time = run_start_time[:-1].decode("UTF-8")
- subprocess.call(
- self._adb_device_sub_cmd
- + [
- "shell",
- "logcat",
- "-t",
- f'"{run_start_time}"',
- "-f",
- f"{self._workspace}/logcat.txt",
- ]
- )
- subprocess.call(self._adb_device_sub_cmd + ["pull", f"{self._workspace}/logcat.txt", "."])
-
- def _print_cdsp_logs(self):
- crash_count = 0
- context_lines = 0
- print_buffer = ""
- try:
- with open("./logcat.txt") as f:
- for line in f:
- if "Process on cDSP CRASHED" in line:
- if crash_count <= 5:
- print(print_buffer, "\n")
- context_lines = 40
- print_buffer = ""
- crash_count += 1
- if context_lines > 0 and "platform_qdi_driver" in line:
- context_lines -= 1
- print_buffer += line[80:]
-
- if crash_count <= 5:
- print(print_buffer, "\n")
-
- print(
- f"There were {crash_count} crashes on the cDSP during execution... "
- + "Crash printing is limited to the first 5."
- )
- except FileNotFoundError:
- print("Unable to parse logcat file.")
-
- def start_server(self):
- """Abstract method implementation. See description in HexagonLauncherRPC."""
- self._copy_binaries()
- if self._sysmon_profile:
- self._sysmon_process = self._start_sysmon()
- self._run_server_script()
- if self._clear_logcat:
- self._clear_debug_logs()
-
- def stop_server(self):
- """Abstract method implementation. See description in HexagonLauncherRPC."""
- if self._sysmon_profile and self._sysmon_process is not None:
- self._stop_sysmon()
- self._retrieve_sysmon()
- if self._hexagon_debug:
- self._retrieve_debug_logs()
- self._print_cdsp_logs()
- self._cleanup_port_forwarding()
- self._terminate_remote()
- if not self._hexagon_debug:
- self.cleanup_directory()
-
- def get_profile_output(
- self,
- hex_profiler: HexagonProfiler,
- session: Session,
- ):
- """Abstract method implementation. See description in HexagonLauncherRPC."""
- profile_data = ""
- if hex_profiler.is_lwp_enabled():
- temp_dir = hex_profiler.get_temp_dir()
- remote_path = hex_profiler.get_remote_path()
- if not temp_dir:
- raise RuntimeError("tempdir not passed")
- fname = "lwp.json"
- out_path = os.path.join(remote_path, fname)
- profile_data = temp_dir.relpath(fname)
- ret = session.get_profile_output(hex_profiler.get_mode(), fname)
- if ret:
- subprocess.check_call(self._adb_device_sub_cmd + ["pull", out_path, profile_data])
- else:
- raise RuntimeError("Error generating profile output")
- elif hex_profiler.profiling_mode == "etm":
- hex_profiler.pull_files_for_etm_processing(self._workspace)
- else:
- raise RuntimeError("Profiling not enabled")
- return profile_data
-
-
-class HexagonLauncherSimulator(HexagonLauncherRPC):
- """Hexagon Launcher for Hexagon simulator."""
-
- SIMULATOR_HEXAGON_RPC_FILES = ["tvm_rpc_x86", "libhexagon_rpc_sim.so"]
-
- def __init__(self, rpc_info: dict, workspace: str | pathlib.Path | None = None):
- """Configure a new HexagonLauncherSimulator
-
- Parameters are same as for HexagonLauncherRPC.
- """
-
- self._toolchain = os.environ.get("HEXAGON_TOOLCHAIN")
- if not self._toolchain:
- raise RuntimeError("Please set HEXAGON_TOOLCHAIN env variable")
- self._serial_number = HEXAGON_SIMULATOR_NAME
-
- super().__init__(rpc_info, workspace, self._serial_number)
-
- def _copy_to_remote(self, local_path: str | pathlib.Path, remote_path: str | pathlib.Path):
- """Abstract method implementation. See description in HexagonLauncherRPC."""
- _check_call_verbose(["cp", str(local_path), str(remote_path)])
-
- def _create_remote_directory(self, remote_path: str | pathlib.Path) -> pathlib.Path:
- """Abstract method implementation. See description in HexagonLauncherRPC."""
- _check_call_verbose(["mkdir", "-p", str(remote_path)])
- return pathlib.Path(os.path.abspath(remote_path))
-
- def _copy_libcxx(self, dest_dir: str | pathlib.Path):
- """Copy libc++ libraries to the remote workspace."""
- # Copy the v68 versions, since we don't have target information.
- # The v68 ones should work everywhere on v68+.
- lib_dir = os.path.join(self._toolchain, "target/hexagon/lib/v68/G0/pic")
-
- libcxx_files = []
- for entry in os.scandir(lib_dir):
- if entry.is_dir() or entry.name.find(".so") == -1:
- continue
- if entry.name.startswith("libc++"):
- libcxx_files.append(entry.name)
-
- # Use tar to preserve the symbolic links. Libc++ libraries use the
- # typical .so versioning, so that libc++.so may be a symlink to
- # something else. Also, shared libraries using libc++ could be
- # directly linked against some version, e.g. libc++.so.1, so make
- # sure that all files are copied over. The preservation of symbolic
- # links is to save disk space.
- tar_in = f"tar -cf - -C {lib_dir} " + " ".join(libcxx_files)
- tar_out = f"tar -xf - -C {dest_dir!s}"
- _check_call_verbose(tar_in + " | " + tar_out, shell=True)
-
- def start_server(self):
- """Abstract method implementation. See description in HexagonLauncherRPC."""
- # Copy binaries
- lib_dir = _get_hexagon_rpc_lib_dir()
- for item in self.SIMULATOR_HEXAGON_RPC_FILES:
- self._copy_to_remote(lib_dir / item, self._workspace / item)
- # Copy libc++ from the toolchain to the workspace
- self._copy_libcxx(self._workspace)
- self._rpc_info["device_key"] = HEXAGON_REMOTE_DEVICE_KEY + "." + str(os.getpid())
-
- rpc_tracker_host = self._rpc_info["rpc_tracker_host"]
- rpc_tracker_port = self._rpc_info["rpc_tracker_port"]
- rpc_server_port = self._rpc_info["rpc_server_port"]
- device_key = self._rpc_info["device_key"]
- server_exe = os.path.join(".", "tvm_rpc_x86")
-
- args = [
- "server",
- f"--tracker={rpc_tracker_host}:{rpc_tracker_port}",
- f"--port={rpc_server_port}",
- f"--key={device_key}",
- "--timeout=0",
- ]
-
- # pylint: disable=unused-argument
- def _terminate_handler(self, signum, *rest):
- # Terminate the Popen'ed (sub)process.
- os.kill(self._subprocess_pid, signal.SIGTERM)
-
- def _start(self):
- # This function will be running in a new process. It will start the RPC
- # (x86) server as a subprocess of itself.
- log_out = self._workspace / "stdout.txt"
- log_err = self._workspace / "stderr.txt"
- # Intercept the TERM signal so we can also terminate the subprocess.
- signal.signal(signal.SIGTERM, lambda *a: _terminate_handler(self, *a))
-
- with open(log_out, "w") as out, open(log_err, "w") as err:
- p = subprocess.Popen(
- [server_exe, *args], stdout=out, stderr=err, cwd=self._workspace
- )
- # Insert the pid of the subprocess in the self object.
- self._subprocess_pid = p.pid
- p.wait()
-
- self._server_process = mp.Process(target=lambda *a: _start(self, *a))
- self._server_process.start()
-
- def cleanup_directory(self):
- """Abstract method implementation. See description in HexagonLauncherRPC."""
-
- def stop_server(self):
- """Abstract method implementation. See description in HexagonLauncherRPC."""
- self._server_process.terminate()
-
- def get_profile_output(
- self,
- hex_profiler: HexagonProfiler,
- session: Session,
- ):
- """Abstract method implementation. See description in HexagonLauncherRPC."""
- profile_data = ""
- if hex_profiler.is_lwp_enabled():
- fname = "lwp.json"
- profile_data = f"{self._workspace}/{fname}"
- ret = session.get_profile_output(hex_profiler.get_mode(), fname)
- if not ret:
- raise RuntimeError("Error generating profile output")
- elif hex_profiler.profiling_mode == "etm":
- raise RuntimeError("ETM Profiling not supported on the simulator")
- else:
- raise RuntimeError("Profiling not enabled")
-
- return profile_data
-
-
-# https://stackoverflow.com/a/52872579/2689797
-def _is_port_in_use(port: int) -> bool:
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as s:
- return s.connect_ex(("localhost", port)) == 0
-
-
-def farf_config_from_python_log_level(level) -> str:
- """Generates a FARF configuration string enabling logging at the specified level
-
- Parameters
- ----------
- level : str or int
- Minimum level to log at. Must be a known Python logging level or string
- (e.g., logging.INFO or "INFO")
- """
-
- # Runtime log levels can be selectively enabled by computing a bitmask
- # corresponding to the levels you want to enable. These get forwarded to
- # logcat by the DSP RPC daemon. The bits for each level are:
-
- # 0x01 - Hexagon LOW / TVM DEBUG / Python DEBUG
- # 0x02 - Hexagon MEDIUM / TVM INFO / Python INFO
- # 0x04 - Hexagon HIGH / TVM WARN / Python WARNING
- # 0x08 - Hexagon ERROR / TVM ERROR / Python ERROR
- # 0x10 - Hexagon FATAL / TVM FATAL / Python CRITICAL
-
- # Runtime logging can also be filtered on filenames by appending a
- # comma-separated list of filenames. For more information, see
- # the Hexagon SDK documentation.
-
- if level in (logging.DEBUG, "DEBUG"):
- return "0x1F"
- if level in (logging.INFO, "INFO"):
- return "0x1E"
- if level in (logging.WARNING, "WARNING"):
- return "0x1C"
- if level in (logging.ERROR, "ERROR"):
- return "0x18"
- if level in (logging.CRITICAL, "CRITICAL"):
- return "0x10"
-
- raise ValueError("Argument must be a known Python logging level or string")
-
-
-# pylint: disable=invalid-name
-def HexagonLauncher(
- serial_number: str,
- rpc_info: dict,
- workspace: str | pathlib.Path | None = None,
- hexagon_debug: bool = False,
- clear_logcat: bool = False,
- sysmon_profile: bool = False,
- farf_config: str = farf_config_from_python_log_level(logging.INFO),
-):
- """Creates a HexagonLauncher"""
- if serial_number == HEXAGON_SIMULATOR_NAME:
- return HexagonLauncherSimulator(rpc_info, workspace)
- return HexagonLauncherAndroid(
- serial_number, rpc_info, workspace, hexagon_debug, clear_logcat, sysmon_profile, farf_config
- )
diff --git a/python/tvm/contrib/hexagon/hexagon_profiler.py b/python/tvm/contrib/hexagon/hexagon_profiler.py
deleted file mode 100644
index 44a66ef7be..0000000000
--- a/python/tvm/contrib/hexagon/hexagon_profiler.py
+++ /dev/null
@@ -1,125 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-# pylint: disable=consider-using-with
-# ruff: noqa: RUF005
-
-"""Define HexagonProfiler class to enable profiling for Hexagon"""
-
-import os
-import subprocess
-
-from tvm.contrib.hexagon.profiling.process_lwp_data import process_lwp_output
-from tvm.ir.transform import PassContext
-from tvm.support import utils
-
-
-class HexagonProfiler:
- """Hexagon Profiler"""
-
- def __init__(
- self,
- dso_binary: str,
- module,
- hexagon_server_process,
- enable_debug,
- ):
- """Configure HexagonProfiler"""
- # Save test .so to process profiling data
- self._temp_dir = utils.tempdir(keep_for_debug=enable_debug)
- self._dso_binary_path = self._temp_dir.relpath(dso_binary)
- module.save(self._dso_binary_path)
-
- self._android_serial_number = os.environ.get("ANDROID_SERIAL_NUMBER")
- self._remote_path = ""
- self._logcat_path = ""
-
- self._profiling_mode = None
- config = PassContext.current().config
- if self._android_serial_number is None:
- raise RuntimeError("ANDROID_SERIAL_NUMBER must be set for profiling")
-
- if ("tirx.instrument_lwp", True) in config.items():
- # Set profiling mode
- self._profiling_mode = "lwp"
-
- if self._android_serial_number != "simulator":
- # Clear the logcat buffer and create a child process to redirect logcat output
- # into a file.
- launcher = hexagon_server_process["launcher"]
- subprocess.check_call(launcher._adb_device_sub_cmd + ["logcat", "-c"])
- self._logcat_path = self._temp_dir.relpath("logcat.log")
- self._fo = open(self._logcat_path, "w")
- self._proc = subprocess.Popen(
- launcher._adb_device_sub_cmd + ["logcat"], stdout=self._fo
- )
-
- # Get the remote workspace on the device from where the lwp data needs to be copied.
- self._remote_path = launcher._workspace
-
- if self._profiling_mode is None:
- raise RuntimeError("Profiling mode was not set or was not a valid one.")
-
- def get_mode(self):
- return self._profiling_mode
-
- def is_lwp_enabled(self):
- return self._profiling_mode == "lwp"
-
- def get_temp_dir(self):
- return self._temp_dir
-
- def get_remote_path(self):
- return self._remote_path
-
- def get_profile_output(self, hexagon_launcher, hexagon_session):
- """Get runtime profiling data"""
- prof_out = hexagon_launcher.get_profile_output(self, hexagon_session)
-
- print("lwp json can be found at -- ", prof_out)
-
- # Process lightweight profiling output into an easily readable csv file
- # The post-processing requires following parameters:
- # 1) Path of the binary file
- # 2) android_serial_number
- # 3) Path of the lwp json file (lwp.json) which gets created in the current directory
- # 4) Path to the run log depending on the environment:
- # a) For on-device runs:
- # Use logcat output as the run log
- # b) For simulator runs:
- # Use "stdout.txt" as the run log. There is no need to specify the full path to
- # "stdout.txt" as it will be inferred based on 'prof_out' location.
- # 5) lwp processed output file - "lwp.csv"
- #
- lwp_csv = self._temp_dir.relpath("lwp.csv")
- if self._android_serial_number == "simulator":
- process_lwp_output(
- self._dso_binary_path, self._android_serial_number, prof_out, "stdout.txt", lwp_csv
- )
- else:
- # For on-device run
- self._proc.kill() # End the child process for logcat
- self._fo.close()
- if os.path.exists(self._logcat_path):
- process_lwp_output(
- self._dso_binary_path,
- self._android_serial_number,
- prof_out,
- self._logcat_path,
- lwp_csv,
- )
- else:
- raise RuntimeError("Error processing lwp output - missing logcat file")
diff --git a/python/tvm/contrib/hexagon/meta_schedule.py b/python/tvm/contrib/hexagon/meta_schedule.py
deleted file mode 100644
index 0084d1da7f..0000000000
--- a/python/tvm/contrib/hexagon/meta_schedule.py
+++ /dev/null
@@ -1,195 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-"""Meta schedule tuning utilities for Hexagon."""
-
-import os
-import tempfile
-from collections.abc import Callable
-
-import tvm
-from tvm.driver import build as tvm_build
-from tvm.ir.module import IRModule
-from tvm.ir.utils import derived_object
-from tvm.runtime import Module, Tensor
-from tvm.s_tir.meta_schedule.builder import LocalBuilder
-from tvm.s_tir.meta_schedule.runner import (
- EvaluatorConfig,
- PyRunner,
- RunnerFuture,
- RunnerInput,
-)
-from tvm.s_tir.meta_schedule.runner.rpc_runner import (
- RPCRunnerFuture,
- default_alloc_argument,
- default_run_evaluator,
-)
-from tvm.s_tir.meta_schedule.utils import cpu_count
-from tvm.s_tir.transform import RemoveWeightLayoutRewriteBlock
-from tvm.support.popen_pool import PopenPoolExecutor
-from tvm.target import Target
-
-from .build import HexagonLauncherRPC
-from .tools import export_module
-
-
-@derived_object
-class HexagonRPCRunner(PyRunner):
- """RPCRunner for Hexagon. See the documentation of RPCRunner for more details."""
-
- def __init__(
- self,
- hexagon_launcher: HexagonLauncherRPC,
- evaluator_config: EvaluatorConfig | None = None,
- cooldown_sec: float = 0.0,
- alloc_repeat: int = 1,
- max_workers: int | None = None,
- initializer: Callable[[], None] | None = None,
- ):
- """
- Parameters
- ----------
- hexagon_launcher : HexagonLauncherRPC
- The RPC launcher for Hexagon. It is needed for creating hexagon.Session
- object inside the worker function.
- evaluator_config: EvaluatorConfig
- The evaluator configuration.
- cooldown_sec: float
- The cooldown in seconds.
- alloc_repeat: int
- The number of times to random fill the allocation.
- max_workers: Optional[int] = None
- The maximum number of connections. Defaults to number of logical CPU cores.
- initializer: Optional[Callable[[], None]]
- The initializer function.
- """
-
- super().__init__()
- self.hexagon_launcher = hexagon_launcher
- self.evaluator_config = EvaluatorConfig._normalized(evaluator_config)
- self.cooldown_sec = cooldown_sec
- self.alloc_repeat = alloc_repeat
- if max_workers is None:
- max_workers = cpu_count(logical=True)
- self.pool = PopenPoolExecutor(
- max_workers=max_workers,
- timeout=100,
- initializer=initializer,
- )
-
- def run(self, runner_inputs: list[RunnerInput]) -> list[RunnerFuture]:
- results = []
- for runner_input in runner_inputs:
- future = RPCRunnerFuture(
- future=self.pool.submit(
- _worker_func,
- self.hexagon_launcher,
- self.evaluator_config,
- self.alloc_repeat,
- str(runner_input.artifact_path),
- tuple(arg_info.as_json() for arg_info in runner_input.args_info),
- ),
- timeout_sec=100,
- )
- results.append(future)
- return results
-
-
-def _worker_func(hexagon_launcher, evaluator_config, alloc_repeat, artifact_path, args_info):
- with hexagon_launcher.create_session() as session:
- device = session.device
- _, remote_path = os.path.split(artifact_path)
- uploaded = session.upload(artifact_path, remote_path)
- rt_mod = session.load_module(uploaded)
- repeated_args = default_alloc_argument(
- session,
- device,
- args_info,
- alloc_repeat,
- )
- costs = default_run_evaluator(
- session,
- rt_mod,
- device,
- evaluator_config,
- repeated_args,
- )
- return costs
-
-
-def get_hexagon_local_builder(
- pass_context: tvm.transform.PassContext = None,
- max_workers: int | None = None,
- timeout_sec: float = 30.0,
-):
- """Return Hexagon-compatible Builder for meta schedule."""
-
- def export_func(mod):
- binary_path = export_module(mod, tempfile.mkdtemp())
- return str(binary_path)
-
- def default_build_with_context(
- mod: IRModule, target: Target, _params: dict[str, Tensor] | None
- ) -> Module:
- with pass_context:
- mod = RemoveWeightLayoutRewriteBlock(skip_tensor_rewrite=True)(mod)
- return tvm_build(mod, target=target)
-
- if pass_context is not None:
- return LocalBuilder(
- f_build=default_build_with_context,
- f_export=export_func,
- max_workers=max_workers,
- timeout_sec=timeout_sec,
- )
- else:
- return LocalBuilder(f_export=export_func, max_workers=max_workers, timeout_sec=timeout_sec)
-
-
-def get_hexagon_rpc_runner(
- hexagon_launcher: HexagonLauncherRPC,
- number=3,
- repeat=1,
- min_repeat_ms=100,
- max_workers: int | None = None,
-):
- """Return Hexagon-compatible RPC Runner for meta schedule.
-
- Parameters
- ----------
- hexagon_launcher : HexagonLauncherRPC
- The RPC launcher for Hexagon.
- number: int
- The number of times to run this function for taking average.
- We call these runs as one `repeat` of measurement.
- repeat: int
- The number of times to repeat the measurement.
- In total, the function will be invoked (1 + number x repeat) times,
- where the first one is warm up and will be discarded.
- The returned result contains `repeat` costs,
- each of which is an average of `number` costs.
- min_repeat_ms: int
- Minimum repeat time in ms. if the execution latency is too short,
- increase the number of runs to the given time (in ms) to reduce the measurement error.
- """
- evaluator_config = EvaluatorConfig(
- number=number,
- repeat=repeat,
- min_repeat_ms=min_repeat_ms,
- enable_cpu_cache_flush=False,
- )
-
- return HexagonRPCRunner(hexagon_launcher, evaluator_config, max_workers=max_workers)
diff --git a/python/tvm/contrib/hexagon/profiling/process_lwp_data.py b/python/tvm/contrib/hexagon/profiling/process_lwp_data.py
deleted file mode 100644
index b82fbd4f88..0000000000
--- a/python/tvm/contrib/hexagon/profiling/process_lwp_data.py
+++ /dev/null
@@ -1,388 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-# ruff: noqa: E501, E741
-
-import argparse
-import csv
-import json
-import os
-import subprocess
-from collections import OrderedDict
-from re import compile, search
-
-ENABLE_DEBUG = False
-"""
-Process lightweight profiling output and generate a CSV file with processor
-cycles for the instrumented functions and loops.
-
-Please note that some assumptions have been made while processing
-the lightweight profiling output. They are as follows:
-
-1) We don't expect profiled functions to call another profiled function.
- This constraint can be relaxed if needed but it simplifies the processing
- significantly without introducing any limitations for our use case.
-2) For now, it's also assumed that every unique section (loop) ID has same start
- and end offset which will not be true while a loop gets unrolled as it will
- create multiple profiling section with the same ID. The current
- implementation doesn't handle this case.
-
-"""
-
-
-def get_func_info(model_so):
- """Get all the .text sections along with their start and end offset values"""
- hexagon_nm_path = os.environ["HEXAGON_TOOLCHAIN"] + "/bin/hexagon-nm"
- out = subprocess.Popen(
- [hexagon_nm_path, "--print-size", model_so],
- stdout=subprocess.PIPE,
- stderr=subprocess.STDOUT,
- )
- stdo, stde = out.communicate()
- stdo = stdo.decode("utf-8")
-
- func_info = []
- for l in stdo.split("\n"):
- info = {}
- if search(" (T|t) ", l): # If .text section
- parts = l.split(" ")
- assert len(parts) == 4
- info["start"] = int(parts[0], base=16)
- info["end"] = int(parts[0], base=16) + int(parts[1], base=16)
- info["name"] = parts[3]
- func_info.append(info)
-
- # Sort the entries in the increasing order of the start offset value.
- func_info = sorted(func_info, key=lambda d: d["start"])
-
- if ENABLE_DEBUG:
- print("func_info :\n ")
- for f in func_info:
- print(f)
- return func_info
-
-
-def find_func(func_info, offset):
- """For a given offset, find the function it belongs to."""
- fidx = 0
- lidx = len(func_info) - 1
- while fidx <= lidx:
- midx = (fidx + lidx) // 2
- ms = func_info[midx]["start"]
- me = func_info[midx]["end"]
- if fidx == lidx:
- assert offset >= ms and offset <= me, (
- f"Couldn't find a function for this offset: {offset}"
- )
- return fidx
- else:
- if offset > me:
- fidx = midx + 1
- elif offset < ms:
- lidx = midx - 1
- else:
- return midx
- assert False, "Possible mismatch between model .so and LWP data"
-
-
-def accumulate_cycles(overall_cycles, func_cycles, func_name):
- """Accumulate function cycles"""
- acc_cycles = overall_cycles[func_name]
- for id in func_cycles:
- assert id in acc_cycles, f"id [{id}] missing in the existing function record"
- assert acc_cycles[id]["start"] == func_cycles[id]["start"], (
- "Offset value doesn't match with the existing function record."
- )
- acc_cycles[id]["cycles"] += func_cycles[id]["cycles"]
- acc_cycles[id]["count"] += func_cycles[id]["count"]
- overall_cycles.update({func_name: acc_cycles})
- return overall_cycles
-
-
-def adjust_per_loop_counts(overall_cycles, data):
- """
- Use execution count and the number of entries recorded for each function/loop
- to compute the overall cycles spent on them.
- """
- for func in overall_cycles:
- func_cycles = overall_cycles[func]
- for id in func_cycles:
- exec_count = data["loop_counts"][id]
- rec_count = func_cycles[id]["count"]
- assert exec_count != 0, "Execution count should have been non-zero."
- assert rec_count != 0, "Entry count should have been non-zero."
- exec_cycles = ((int(func_cycles[id]["cycles"])) * exec_count) // rec_count
- func_cycles[id]["cycles"] = exec_cycles
- func_cycles[id]["count"] = exec_count
- overall_cycles.update({func: OrderedDict(sorted(func_cycles.items()))})
- return overall_cycles
-
-
-def create_csv_report(overall_cycles, fname):
- """Create csv report"""
- header = [
- "function name",
- "loop/function id",
- "loop depth",
- "start offset",
- "end offset",
- "pcycles",
- "parent count",
- ]
- with open(fname, "w") as f:
- writer = csv.writer(f)
- writer.writerow(header)
- for func in overall_cycles:
- func_cycles = overall_cycles[func]
- data = []
- root = -1
- outer_most = -1
- for key, value in func_cycles.items():
- if value["parent"] == -1:
- assert root == -1, "Can't have multiple root nodes."
- root = key
-
- data.append(func)
- data.append(key)
- if value["parent"] == -1:
- data.append("-") # Total cycles over all invocations of this function.
- elif value["parent"] == root:
- data.append(0)
- outer_most = key
- else:
- if outer_most > -1:
- data.append(key - outer_most)
- else:
- data.append(key - value["parent"])
- data.append(hex(value["start"]))
- data.append(hex(value["end"]))
- data.append(value["cycles"])
- data.append(value["count"])
- writer.writerow(data)
- data.clear()
-
-
-def process_data(data, func_info, so_ld_addr):
- """Process data"""
- # Keep an ordered list of loop IDs as they are being visited. This is used
- # to match entry and exit pairs. Once the function/loop is processed, it's
- # removed from the list.
- ordered_visited_list = []
- # Store information regarding visited nodes as they are being processed. Once
- # the function/loop is processed, it's removed from the set.
- visited_set = {}
- # Dictionary to store cycles for the entire model which is grouped into functions.
- overall_cycles = {}
- func_cycles = {}
-
- func_idx = -1
- func_name = ""
- prev_func_name = ""
- func_start = 0
- func_end = 0
- save_data = False
- # Iterate over all the entries in the LWP data file and process them
- # to construct a report.
- for entry in data["entries"]:
- id = entry["id"]
- offset = entry["ret"] - so_ld_addr
-
- # Recorded return address should fall within the function begin and end
- # offsets. If not, find the function it belongs to.
- if offset < func_start or offset > func_end:
- prev_func_name = func_name
- if ENABLE_DEBUG:
- print("offset : ", offset)
- print("id : ", id)
-
- func_idx = find_func(func_info, offset)
- func_name = func_info[func_idx]["name"]
- func_start = func_info[func_idx]["start"]
- func_end = func_info[func_idx]["end"]
- if ENABLE_DEBUG:
- print("func_name : ", func_name)
-
- if save_data:
- # overall_cycles = save_func_cycles(prev_func_name, overall_cycles, func_cycles, ordered_visited_list)
- # Done processing the previous function, copy its info into 'overall_cycles'.
- if prev_func_name not in overall_cycles:
- overall_cycles[prev_func_name] = func_cycles.copy()
- else:
- # Accumulate cycles into existing function entry.
- overall_cycles = accumulate_cycles(overall_cycles, func_cycles, prev_func_name)
- # We don't allow for fused operators (functions) calling another operator.
- if ENABLE_DEBUG:
- print("ordered_visited_list : ", ordered_visited_list)
-
- assert len(ordered_visited_list) == 0, (
- f"\nDone processing function [{prev_func_name}] but ordered_visited_list not empty.\n"
- f"\t Possible reasons -- \n"
- f"\t\t1) Mismatch between model .so and json file.\n"
- f"\t\t2) LWP buffer may have overflowed resulting into missing entries!"
- )
- func_cycles.clear()
-
- save_data = True
-
- if id not in visited_set: # Found 'entry' record
- visited_info = {"func_idx": func_idx, "ret": offset, "cyc": entry["cyc"]}
- visited_set[id] = visited_info
- ordered_visited_list.append(id)
- else: # Found 'exit' record
- # This should be the last entry in the ordered_visited_list. If not, error out.
- assert ordered_visited_list[-1] == id, (
- "Problem with LWP output - Interleaved handler calls found."
- f"Loop [{ordered_visited_list[-1]}] hasn't exited yet."
- )
- ordered_visited_list.pop()
- entry_node = visited_set.pop(id)
- assert entry_node["func_idx"] == func_idx, (
- f"Error - Found under a different function name : {entry_node['func_idx']}"
- )
- cycles = entry["cyc"] - entry_node["cyc"]
- parent = -1
- if ordered_visited_list:
- parent = int(ordered_visited_list[-1])
- if id in func_cycles:
- fcycles = func_cycles[id]
- fcycles["cycles"] += cycles
- fcycles["count"] += 1
- func_cycles[id] = fcycles
- else:
- func_cycles[id] = {
- "cycles": cycles,
- "start": entry_node["ret"],
- "end": offset,
- "parent": parent,
- "count": 1,
- }
-
- # Done processing the previous function, copy its info into 'overall_cycles'.
- if func_name not in overall_cycles:
- overall_cycles[func_name] = func_cycles.copy()
- else:
- # Accumulate cycles into existing function entry.
- overall_cycles = accumulate_cycles(overall_cycles, func_cycles, func_name)
- # We don't allow for fused operators (functions) calling another operator.
- if ENABLE_DEBUG:
- print("ordered_visited_list : ", ordered_visited_list)
-
- assert len(ordered_visited_list) == 0, (
- f"\nDone processing function [{prev_func_name}] but ordered_visited_list not empty.\n"
- f"\t Possible reasons -- \n"
- f"\t\t1) Mismatch between model .so and json file.\n"
- f"\t\t2) LWP buffer may have overflowed resulting into missing entries!"
- )
-
- overall_cycles = adjust_per_loop_counts(overall_cycles, data)
- return overall_cycles
-
-
-def get_load_addr(serial_number: str, lwp_json: str, run_log: str):
- """Get load address of the binary file"""
- if serial_number == "simulator":
- basedir = os.path.dirname(lwp_json)
- if run_log is None:
- run_log = os.path.join(basedir, "stdout.txt")
- else:
- # If the directory name is specified for the run_log of the
- # simulator (stdout.txt) then it must be same as lwp_json.
- run_log_dir = os.path.dirname(run_log)
- assert run_log_dir == "" or run_log_dir == basedir, (
- f"stdout.txt and {os.path.basename(lwp_json)} must be in the same directory"
- )
- run_log = os.path.join(basedir, os.path.basename(run_log))
- # To extract load address for the simulator run
- pattern = compile(r"Model.*: (\w+):")
- else:
- # To extract load address for on-device run
- pattern = compile(r"Model.*: (\w+)")
-
- with open(run_log) as f:
- lines = f.read()
- a = pattern.search(lines)
- load_addr = int(a.group(1), 16)
- if ENABLE_DEBUG:
- print("load_addr : ", load_addr)
- return load_addr
-
-
-def process_lwp_output(
- binary_path: str,
- serial_number: str,
- lwp_json: str,
- run_log: str,
- lwp_out: str,
- enable_debug: bool = False,
-):
- """Process lightweight profiling data"""
- # Enable debug messages
- global ENABLE_DEBUG
- ENABLE_DEBUG = enable_debug
-
- # Get load address for the binary
- load_addr = get_load_addr(serial_number, lwp_json, run_log)
- # Opening JSON file
- with open(lwp_json) as f:
- # Returns JSON object as a dictionary
- data = json.load(f)
-
- # Get function names, and their start and end offsets from the model .so
- func_info = get_func_info(binary_path)
-
- # Get the load address for model .so.
- so_ld_addr = load_addr
-
- # Process profiling data to construct a CSV report.
- overall_cycles = process_data(data, func_info, so_ld_addr)
- create_csv_report(overall_cycles, lwp_out)
- print("lwp processed output written to -- ", lwp_out)
- print("[NOTE: Use '--hexagon-debug' to keep the temp directory]")
-
-
-def get_args():
- """Add commandline arguments to run the script manually if needed"""
- parser = argparse.ArgumentParser()
- parser.add_argument("--lwp-json", help="LWP json file", required=True)
- parser.add_argument("--serial-num", help="device-id/simulator", required=True)
- parser.add_argument("--test-so", help="Test shared library", required=True)
- parser.add_argument(
- "--run-log",
- help="Logcat file for on-device run and stdout.txt for simulator run",
- required=True,
- )
- parser.add_argument("--lwp-out", help="LWP output file name", required=True)
- parser.add_argument(
- "--debug",
- help="Enable debug output from the script",
- dest="debug",
- action="store_true",
- required=False,
- )
- parser.set_defaults(debug=False)
- args = parser.parse_args()
-
- global ENABLE_DEBUG
- ENABLE_DEBUG = args.debug
-
- return args
-
-
-if __name__ == "__main__":
- args = get_args()
- process_lwp_output(
- args.test_so, args.serial_num, args.lwp_json, args.run_log, args.lwp_out, args.debug
- )
diff --git a/python/tvm/contrib/hexagon/pytest_plugin.py b/python/tvm/contrib/hexagon/pytest_plugin.py
deleted file mode 100644
index ac1bc7af99..0000000000
--- a/python/tvm/contrib/hexagon/pytest_plugin.py
+++ /dev/null
@@ -1,384 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-# pylint: disable=invalid-name,redefined-outer-name
-"""Hexagon testing fixtures used to deduce testing argument
-values from testing parameters"""
-
-from __future__ import annotations
-
-import os
-import random
-import socket
-from typing import TYPE_CHECKING
-
-import pytest
-
-import tvm
-import tvm.rpc
-import tvm.testing
-
-if TYPE_CHECKING:
- from tvm.contrib.hexagon.build import HexagonLauncherRPC
- from tvm.contrib.hexagon.session import Session
-
-HEXAGON_TOOLCHAIN = "HEXAGON_TOOLCHAIN"
-TVM_TRACKER_HOST = "TVM_TRACKER_HOST"
-TVM_TRACKER_PORT = "TVM_TRACKER_PORT"
-ANDROID_REMOTE_DIR = "ANDROID_REMOTE_DIR"
-ANDROID_SERIAL_NUMBER = "ANDROID_SERIAL_NUMBER"
-ADB_SERVER_SOCKET = "ADB_SERVER_SOCKET"
-HEXAGON_SIMULATOR_NAME = "simulator"
-RNG_SEEDED = False
-
-HEXAGON_AOT_LLVM_TARGET = {
- "kind": "llvm",
- "keys": ["hexagon", "cpu"],
- "mattr": ["+hvxv68", "+hvx-length128b", "+hvx-qfloat", "-hvx-ieee-fp"],
- "mcpu": "hexagonv68",
- "mtriple": "hexagon",
-}
-
-
-@tvm.testing.fixture
-def shape_nhwc(batch, in_channel, in_size):
- return (batch, in_size, in_size, in_channel)
-
-
-def android_serial_number() -> str | None:
- """Return the android serial number"""
- serial = os.getenv(ANDROID_SERIAL_NUMBER, default="")
- # Setting ANDROID_SERIAL_NUMBER to an empty string should be
- # equivalent to having it unset.
- if not serial.strip():
- return None
-
- # Split android serial numbers into a list
- serial = serial.split(",")
- return serial
-
-
-# NOTE on server ports:
-# These tests use different port numbers for the RPC server (7070 + ...).
-# The reason is that an RPC session cannot be gracefully closed without
-# triggering TIME_WAIT state on the server socket. This prevents another
-# server to bind to the same port until the wait time elapses.
-
-LISTEN_PORT_MIN = 6000 # Avoid hitting well-known Android debug ports
-LISTEN_PORT_MAX = 9000 # Below the search range end (port_end=9199) of RPC server
-PREVIOUS_PORT = None
-
-
-def get_free_port() -> int:
- """Return the next port that is available to listen on"""
- global PREVIOUS_PORT
- global RNG_SEEDED
-
- if tvm.testing.utils.IS_IN_CI and not RNG_SEEDED:
- random.seed(0)
- RNG_SEEDED = True
-
- if PREVIOUS_PORT is None:
- port = random.randint(LISTEN_PORT_MIN, LISTEN_PORT_MAX)
- else:
- port = PREVIOUS_PORT + 1
- if port > LISTEN_PORT_MAX:
- port = LISTEN_PORT_MIN
-
- while _is_port_in_use(port):
- port = port + 1 if port < LISTEN_PORT_MAX else LISTEN_PORT_MIN
-
- PREVIOUS_PORT = port
- return port
-
-
-def _is_port_in_use(port: int) -> bool:
- with socket.socket(socket.AF_INET, socket.SOCK_STREAM) as sock:
- return sock.connect_ex(("localhost", port)) == 0
-
-
-@pytest.fixture(scope="session")
-def _tracker_info() -> str | int:
- env_tracker_host = os.getenv(TVM_TRACKER_HOST, default="")
- env_tracker_port = os.getenv(TVM_TRACKER_PORT, default="")
-
- if env_tracker_host or env_tracker_port:
- # A tracker is already running, and we should connect to it
- # when running tests.
- assert env_tracker_host, "TVM_TRACKER_PORT is defined, but TVM_TRACKER_HOST is not"
- assert env_tracker_port, "TVM_TRACKER_HOST is defined, but TVM_TRACKER_PORT is not"
- env_tracker_port = int(env_tracker_port)
-
- try:
- tvm.rpc.connect_tracker(env_tracker_host, env_tracker_port)
- except RuntimeError as exc:
- message = (
- "Could not connect to external tracker "
- "specified by $TVM_TRACKER_HOST and $TVM_TRACKER_PORT "
- f"({env_tracker_host}:{env_tracker_port})"
- )
- raise RuntimeError(message) from exc
-
- yield (env_tracker_host, env_tracker_port)
-
- else:
- # No tracker is provided to the tests, so we should start one
- # for the tests to use. Import tvm.rpc.tracker lazily since it
- # requires the optional tornado package.
- pytest.importorskip("tornado", reason="tvm.rpc.tracker requires tornado")
- from tvm.rpc.tracker import Tracker
-
- tracker = Tracker("127.0.0.1", get_free_port())
- try:
- yield (tracker.host, tracker.port)
- finally:
- tracker.terminate()
-
-
-@pytest.fixture(scope="session")
-def tvm_tracker_host(_tracker_info) -> str:
- host, _ = _tracker_info
- return host
-
-
-@pytest.fixture(scope="session")
-def tvm_tracker_port(_tracker_info) -> int:
- _, port = _tracker_info
- return port
-
-
-@pytest.fixture(scope="session")
-def rpc_server_port_for_session() -> int:
- return get_free_port()
-
-
-@pytest.fixture()
-def rpc_server_port() -> int:
- return get_free_port()
-
-
-@pytest.fixture(scope="session")
-def adb_server_socket() -> str:
- return os.getenv(ADB_SERVER_SOCKET, default="tcp:5037")
-
-
-@pytest.fixture(scope="session")
-def hexagon_server_process(
- request,
- rpc_server_port_for_session,
- adb_server_socket,
- skip_rpc,
- hexagon_debug,
- sysmon_profile,
- clear_logcat,
-) -> HexagonLauncherRPC:
- """Initials and returns hexagon launcher if ANDROID_SERIAL_NUMBER is defined.
- This launcher is started only once per test session.
- """
- android_serial_num = android_serial_number()
-
- if android_serial_num is None:
- pytest.skip("ANDROID_SERIAL_NUMBER is not set.")
- if android_serial_num == [HEXAGON_SIMULATOR_NAME]:
- yield None
- else:
- from tvm.contrib.hexagon.build import HexagonLauncher
-
- # Requesting these fixtures sets up a local tracker, if one
- # hasn't been provided to us. Delaying the evaluation of
- # these fixtures avoids starting a tracker unless necessary.
- tvm_tracker_host = request.getfixturevalue("tvm_tracker_host")
- tvm_tracker_port = request.getfixturevalue("tvm_tracker_port")
-
- rpc_info = {
- "rpc_tracker_host": tvm_tracker_host,
- "rpc_tracker_port": tvm_tracker_port,
- "rpc_server_port": rpc_server_port_for_session,
- "adb_server_socket": adb_server_socket,
- }
- workerinput = getattr(request.config, "workerinput", None)
- if workerinput is None: # single-process execution
- device_adr = read_device_list()[0]
- else: # running in a subprocess here
- device_adr = workerinput["device_adr"]
- launcher = HexagonLauncher(
- serial_number=device_adr,
- rpc_info=rpc_info,
- hexagon_debug=hexagon_debug,
- sysmon_profile=sysmon_profile,
- clear_logcat=clear_logcat,
- )
- try:
- if not skip_rpc:
- launcher.start_server()
- yield {"launcher": launcher, "device_adr": device_adr}
- finally:
- if not skip_rpc:
- launcher.stop_server()
-
-
-def read_device_list():
- return android_serial_number()
-
-
-def pytest_configure(config):
- # read device list if we are on the master
- if not hasattr(config, "workerinput"):
- config.iplist = read_device_list()
-
-
-def pytest_configure_node(node):
- # the master for each node fills node input dictionary
- # which pytest-xdist will transfer to the subprocess
- if node.config.iplist is not None:
- node.workerinput["device_adr"] = node.config.iplist.pop()
-
-
-@pytest.fixture
-def hexagon_launcher(
- hexagon_server_process,
- rpc_server_port,
- tvm_tracker_host,
- tvm_tracker_port,
- adb_server_socket,
- hexagon_debug,
- sysmon_profile,
- clear_logcat,
-) -> HexagonLauncherRPC:
- """Initials and returns hexagon launcher which reuses RPC info and Android serial number."""
- android_serial_num = android_serial_number()
-
- if android_serial_num != [HEXAGON_SIMULATOR_NAME]:
- rpc_info = hexagon_server_process["launcher"]._rpc_info
- else:
- rpc_info = {
- "rpc_tracker_host": tvm_tracker_host,
- "rpc_tracker_port": tvm_tracker_port,
- "rpc_server_port": rpc_server_port,
- "adb_server_socket": adb_server_socket,
- }
- from tvm.contrib.hexagon.build import HexagonLauncher
-
- launcher = None
- try:
- if android_serial_num == [HEXAGON_SIMULATOR_NAME]:
- launcher = HexagonLauncher(serial_number=android_serial_num[0], rpc_info=rpc_info)
- launcher.start_server()
- else:
- launcher = HexagonLauncher(
- serial_number=hexagon_server_process["device_adr"],
- rpc_info=rpc_info,
- hexagon_debug=hexagon_debug,
- sysmon_profile=sysmon_profile,
- clear_logcat=clear_logcat,
- )
- yield launcher
- finally:
- if launcher is not None:
- if android_serial_num == [HEXAGON_SIMULATOR_NAME]:
- launcher.stop_server()
- elif not hexagon_debug:
- launcher.cleanup_directory()
-
-
-@pytest.fixture
-def hexagon_session(hexagon_launcher: HexagonLauncherRPC) -> Session:
- if hexagon_launcher is None:
- yield None
- else:
- with hexagon_launcher.create_session() as session:
- yield session
-
-
-# If the execution aborts while an RPC server is running, the python
-# code that is supposed to shut it down will never execute. This will
-# keep pytest from terminating (indefinitely), so add a cleanup
-# fixture to terminate any still-running servers.
-@pytest.fixture(scope="session", autouse=True)
-def terminate_rpc_servers():
- # Since this is a fixture that runs regardless of whether the
- # execution happens on simulator or on target, make sure the
- # yield happens every time.
- serial = os.environ.get(ANDROID_SERIAL_NUMBER)
- yield []
- if serial == [HEXAGON_SIMULATOR_NAME]:
- os.system("ps ax | grep tvm_rpc_x86 | awk '{print $1}' | xargs kill")
-
-
-aot_host_target = tvm.testing.parameter(HEXAGON_AOT_LLVM_TARGET)
-
-
-@tvm.testing.fixture
-def aot_target(aot_host_target):
- if aot_host_target == "c":
- yield tvm.target.Target({"kind": "hexagon", "mtriple": "hexagon", "mcpu": "hexagonv68"})
- elif isinstance(aot_host_target, dict) and aot_host_target.get("kind") == "llvm":
- yield aot_host_target
- elif isinstance(aot_host_target, str) and aot_host_target.startswith("llvm"):
- yield aot_host_target
- else:
- assert False, "Incorrect AoT host target: {aot_host_target}. Options are [c, llvm]."
-
-
-@pytest.fixture(scope="session")
-def skip_rpc(request) -> bool:
- return request.config.getoption("--skip-rpc")
-
-
-@pytest.fixture(scope="session")
-def hexagon_debug(request) -> bool:
- return request.config.getoption("--hexagon-debug")
-
-
-@pytest.fixture(scope="session")
-def sysmon_profile(request) -> bool:
- return request.config.getoption("--sysmon-profile")
-
-
-@pytest.fixture(scope="session")
-def clear_logcat(request) -> bool:
- return request.config.getoption("--clear-logcat")
-
-
-def pytest_addoption(parser):
- """Add pytest options."""
-
- parser.addoption(
- "--skip-rpc",
- action="store_true",
- default=False,
- help="If set true, the RPC server initialization on Android would be skipped",
- )
- parser.addoption(
- "--hexagon-debug",
- action="store_true",
- default=False,
- help="If set true, it will keep the hexagon test directories on the target. "
- + "Additionally logcat logs will be copied from device and cdsp errors printed out.",
- )
- parser.addoption(
- "--sysmon-profile",
- action="store_true",
- default=False,
- help="If set true, it will run sysmon profiler during the tests.",
- )
- parser.addoption(
- "--clear-logcat",
- action="store_true",
- default=False,
- help="If set true, it will clear logcat before execution.",
- )
diff --git a/python/tvm/contrib/hexagon/session.py b/python/tvm/contrib/hexagon/session.py
deleted file mode 100644
index 8bdaf33da0..0000000000
--- a/python/tvm/contrib/hexagon/session.py
+++ /dev/null
@@ -1,287 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-# pylint: disable=consider-using-from-import
-
-"""Defines a Session class for Hexagon devices."""
-
-import os
-import pathlib
-import tempfile
-
-import tvm
-import tvm.contrib.hexagon as hexagon
-from tvm import rpc as _rpc
-from tvm import runtime
-from tvm.support import utils
-
-from .tools import HEXAGON_SIMULATOR_NAME, export_module
-
-
-class Session:
- """Hexagon Device Session
-
- Parameters
- ----------
- remote_workspace : Union[str, pathlib.Path]
- Remote workspace path
-
- rpc_tracker : tuple(str, int)
- RPC tracker host and port number.
-
- rpc_server_key : str
- RPC server key on remote device.
-
- serial_number : str
- Device serial number. `simulator` used for hexagon simulator.
-
- session_name : str
- Hexagon RPC session name.
-
- remote_stack_size_bytes : int
- The stack size of the remote device, to be passed to
- tvm.contrib.hexagon.create_hexagon_session.
-
- rpc_receive_buffer_size_bytes : int
- RPC receive buffer size in bytes.
- """
-
- def __init__(
- self,
- remote_workspace: str | pathlib.Path,
- rpc_tracker: tuple,
- rpc_server_key: str,
- serial_number: str,
- session_name: str = "hexagon-rpc",
- remote_stack_size_bytes: int = 256 * 1024, # Min size for main thread in QuRT/sim
- rpc_receive_buffer_size_bytes: int = 256 * 1024 * 1024, # Size for passing hexagon tests
- ):
- self._workspace = str(remote_workspace)
- self._rpc_tracker = rpc_tracker
- self._rpc_server_key = rpc_server_key
- self._serial_number = serial_number
- self._session_name: str = session_name
- self._remote_stack_size_bytes: int = remote_stack_size_bytes
- self._rpc_receive_buffer_size_bytes: int = rpc_receive_buffer_size_bytes
- self._rpc = None
- self._requires_cpu_device = False
- self._device = None
-
- def __enter__(self):
- if self._rpc:
- # Already initialized
- return self
-
- tracker = _rpc.connect_tracker(self._rpc_tracker[0], self._rpc_tracker[1])
- try:
- self._rpc = tracker.request(
- self._rpc_server_key,
- priority=0,
- session_timeout=0,
- session_constructor_args=[
- "tvm.contrib.hexagon.create_hexagon_session",
- self._session_name,
- self._remote_stack_size_bytes,
- os.environ.get("HEXAGON_SIM_ARGS", ""),
- self._rpc_receive_buffer_size_bytes,
- ],
- )
- func = self._rpc.get_function("device_api.hexagon.acquire_resources")
- func()
- return self
-
- except RuntimeError as exception:
- raise exception
-
- def __exit__(self, exc_type, exc_value, exc_traceback):
- try:
- func = self._rpc.get_function("device_api.hexagon.release_resources")
- func()
- except RuntimeError as exception:
- print(
- "Exception occurred while calling release_resources() during Session __exit__: ",
- exception,
- )
- finally:
- # close session to the tracker
- shutdown_func = self._rpc._sess.get_function("CloseRPCConnection")
- shutdown_func()
- del self._rpc
-
- @property
- def device(self):
- """Session device."""
-
- if self._device is not None:
- return self._device
-
- if self._requires_cpu_device:
- self._device = self._rpc.cpu(0)
- else:
- self._device = self._rpc.hexagon(0)
-
- return self._device
-
- def is_simulator(self):
- return self._serial_number == HEXAGON_SIMULATOR_NAME
-
- def get_function(self, name):
- return self._rpc.get_function(name)
-
- def upload(self, local_path: str | pathlib.Path, remote_filename: str) -> pathlib.Path:
- """Upload a local file to the remote workspace.
-
- Parameters
- ----------
- local_path : str or pathlib.Path
- Path to the local file to be copied.
- remote_filename : str
- Name of the file in the remote workspace.
-
- Returns
- -------
- pathlib.Path :
- Uploaded file remote path.
- """
- upload_func = self._rpc.get_function("tvm.rpc.server.upload")
- remote_path = f"{self._workspace}/{remote_filename}"
- with open(local_path, mode="rb") as src_f:
- data = bytearray(src_f.read())
- upload_func(remote_path, data)
- return remote_path
-
- def load_module(self, module: str | pathlib.Path | tvm.runtime.Module):
- """Load TVM module.
-
- The session must be established (via __enter__) prior to
- calling this function.
-
- Parameters
- ----------
- module : Union[str, pathlib.Path, tvm.runtime.Module]
-
- The module to load. If `module` is a
- `tvm.runtime.Module`, it will be uploaded to the remote
- session and loaded.
-
- If the object passed is a string or pathlib.Path, it must
- be a full path in the remote system.
-
- Returns
- -------
- TVMModule :
- TVM module object.
- """
-
- assert self._rpc is not None, "Hexagon session must be started using __enter__ prior to use"
-
- if isinstance(module, tvm.runtime.Module):
- with tempfile.TemporaryDirectory() as temp_dir:
- binary_name = "test_binary.so"
- binary_path = export_module(module, temp_dir, binary_name)
- remote_file_path = self.upload(binary_path, binary_name)
- else:
- remote_file_path = module
-
- assert isinstance(remote_file_path, str | pathlib.Path), "Invalid path type:" + str(
- type(remote_file_path)
- )
- return self._rpc.get_function("tvm.hexagon.load_module")(str(remote_file_path))
-
- def get_executor_from_factory(
- self, module: runtime.Executable | str, hexagon_arch: str = "v68"
- ):
- """Create a local GraphModule which consumes a remote libmod.
-
- Parameters
- ----------
-
- module : Union[runtime.Executable, str]
-
- The module to upload to the remote
- session and load.
- hexagon_arch : str
- The hexagon arch to be used
- """
- if isinstance(module, runtime.Executable | str):
- return self._vm_executable_executor(module, hexagon_arch=hexagon_arch)
-
- raise TypeError(f"Unsupported executor type: {type(module)}")
-
- def _set_device_type(self, module: str | pathlib.Path):
- """Set session device type(hexagon, cpu) based on target in module.
-
- Parameters
- ----------
-
- module: TVMModule
- TVM module object.
- """
- # for cases when module is a single schedule without target attribute.
- if not hasattr(module, "target"):
- self._requires_cpu_device = False
- else:
- assert len(module.target) == 1
- for target in module.target:
- target_type = str(target).split()[0]
-
- if target_type == "llvm":
- self._requires_cpu_device = True
- else:
- self._requires_cpu_device = False
-
- def _vm_executable_executor(self, executable: runtime.Executable | str, hexagon_arch: str):
- """Create a local TVM module which consumes a remote vm executable.
-
- Parameters
- ----------
-
- executable : runtime.Executable
- The Executable to upload to the remote and load. This will typically be the
- output of `tvm.compile` or the path to an already built and exported shared library
- hexagon_arch : str
- The hexagon arch to be used
-
- Returns
- -------
- TVMModule :
- TVM module object
- """
- assert self._rpc is not None, "Hexagon session must be started using __enter__ prior to use"
-
- if isinstance(executable, runtime.Executable):
- temp_dir = utils.tempdir()
- path_exec = temp_dir.relpath("exec.so")
-
- executable.export_library(
- path_exec,
- fcompile=hexagon.create_aot_shared,
- hexagon_arch=hexagon_arch,
- )
-
- path = self.upload(path_exec, "exec.so")
- elif isinstance(executable, str):
- path_exec = executable
- else:
- raise TypeError(f"Unsupported executor type: {type(executable)}")
-
- path = self.upload(path_exec, "exec.so")
- return self._rpc.get_function("tvm.hexagon.load_module")(str(path))
-
- def get_profile_output(self, mode: str, path: str):
- assert isinstance(mode, str), f"Invalid mode type, {type(mode)} != str"
- assert isinstance(path, str), f"Invalid path type, {type(path)} != str"
- return self._rpc.get_function("tvm.hexagon.get_profile_output")(mode, path)
diff --git a/src/backend/hexagon/runtime/profiler/README.md b/src/backend/hexagon/runtime/profiler/README.md
deleted file mode 100644
index fdcc94f692..0000000000
--- a/src/backend/hexagon/runtime/profiler/README.md
+++ /dev/null
@@ -1,85 +0,0 @@
-<!--- Licensed to the Apache Software Foundation (ASF) under one -->
-<!--- or more contributor license agreements. See the NOTICE file -->
-<!--- distributed with this work for additional information -->
-<!--- regarding copyright ownership. The ASF licenses this file -->
-<!--- to you under the Apache License, Version 2.0 (the -->
-<!--- "License"); you may not use this file except in compliance -->
-<!--- with the License. You may obtain a copy of the License at -->
-
-<!--- http://www.apache.org/licenses/LICENSE-2.0 -->
-
-<!--- Unless required by applicable law or agreed to in writing, -->
-<!--- software distributed under the License is distributed on an -->
-<!--- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -->
-<!--- KIND, either express or implied. See the License for the -->
-<!--- specific language governing permissions and limitations -->
-<!--- under the License. -->
-
-# Hexagon lightweight instrumentation based profiling (LWP)
-
-For Hexagon, LWP can be used to get function and loop level processor cycle count.
-This is done by instrumenting the code with profiling builtin calls using a TIR pass.
-During codegen, these builtin calls are replaced with the calls to a hexagon specific
-handler which records the runtime information into a buffer.
-This buffer is written into a JSON file ('lwp.json') which is processed to construct
-function and loop level profiling information as a csv file.
-
-**Note:** During codegen, the profiling builtin calls are ignored for other targets.
-
-The TIR pass offers several config flags to control the level of instrumentation
-as mentioned below:
-
-1) `lwp_disable_func_prof`: To disable function level profiling. By default, it is
-set to 'False', i.e., the function level profiling is enabled.
-
-2) `instr_siblings`: When enabled, only loops with siblings are instrumented and rest are
-ignored. The inner-most loops are always excluded from instrumentation unless overwritten
-using `lwp_min_height`. This is done to minimize the adverse effect of instrumentation on
-actual performance. By default, it is set to 'True'.
-
-3) `lwp_max_depth`: To instrument loops up to a certain depth. This flag is effective
-only when `instr_siblings` is disabled. By default, it is set to 0.
-
-4) `lwp_min_height`: To exclude inner loops up to a certain height from instrumentation.
-By default, it is set to 1.
-
-For additional usage information on various config flags, please refer to the tests in
-`tests/python/tir-transform/test_tir_transform_profiling_instr.py`
-
-
-## How to use lightweight profiling with RPC Launcher:
-
-`tests/python/contrib/test_hexagon/test_launcher.py` contains two tests, `test_lwp` and
-`test_lwp_multiple_conv2d`, to demonstrate lightweight profiling usage.
-
-The steps involved are as follows:
-
-1) While building a model, set `tir.instrument_lwp` to `True`.
- By default, the builtin calls will only be inserted for the loops with siblings. But it
- can be altered using LWP config options as described above.
-2) Create `HexagonProfiler` object
-
-4) Run the model and get the profiling data as a CSV file. It is done by post-processing
- 'lwp.json' file generated during runtime.
-
-```
- graph_mod.run(**inputs)
-
- # Get lightweight profiling output as a CSV file
- profiler.get_profile_output(hexagon_launcher, hexagon_session, hexagon_server_process)
-```
-**Note:**
-
-- For on-device runs, 'lwp.json' is copied into a temp directory along with the test .so and the processed
- CSV file
-- For the simulator runs, the file is generated in the simulator test output directory. Test .so
- will still be in a separate temp directory. lwp CSV file will also be in the same directory.
-
-**Helpful Hints:**
-
-- To prevent the test directories on the Hexagon device as well as temporary test directory on x86
-from being deleted for profiling related runs, pass `--hexagon-debug` to pytest.
-
-```
-python -m pytest --hexagon-debug tests/python/contrib/test_hexagon/test_launcher.py::test_lwp
-```
diff --git a/src/backend/hexagon/runtime/rpc/android_bash.sh.template b/src/backend/hexagon/runtime/rpc/android_bash.sh.template
deleted file mode 100644
index c45b03818f..0000000000
--- a/src/backend/hexagon/runtime/rpc/android_bash.sh.template
+++ /dev/null
@@ -1,31 +0,0 @@
-#!/bin/sh
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-export LD_LIBRARY_PATH=.
-
-# Enable FARF-based logging for Hexagon code invoked by 'tvm_rpc_android_server'.
-export ADSP_LIBRARY_PATH=`pwd`
-
-echo <FARF_CONFIG> > tvm_rpc_android.farf
-
-./tvm_rpc_android server --port=<RPC_SERVER_PORT> --tracker=<RPC_TRACKER_HOST>:<RPC_TRACKER_PORT> --key=<HEXAGON_REMOTE_DEVICE_KEY> >${PWD}/tvm_rpc_android.log 2>&1 &
-
-rpc_pid=$!
-
-rm -f rpc_pid.txt
-echo $rpc_pid >> rpc_pid.txt
diff --git a/tests/python/contrib/test_hexagon/README.md b/tests/python/contrib/test_hexagon/README.md
deleted file mode 100644
index 7c79626f9e..0000000000
--- a/tests/python/contrib/test_hexagon/README.md
+++ /dev/null
@@ -1,130 +0,0 @@
-<!--- Licensed to the Apache Software Foundation (ASF) under one -->
-<!--- or more contributor license agreements. See the NOTICE file -->
-<!--- distributed with this work for additional information -->
-<!--- regarding copyright ownership. The ASF licenses this file -->
-<!--- to you under the Apache License, Version 2.0 (the -->
-<!--- "License"); you may not use this file except in compliance -->
-<!--- with the License. You may obtain a copy of the License at -->
-
-<!--- http://www.apache.org/licenses/LICENSE-2.0 -->
-
-<!--- Unless required by applicable law or agreed to in writing, -->
-<!--- software distributed under the License is distributed on an -->
-<!--- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -->
-<!--- KIND, either express or implied. See the License for the -->
-<!--- specific language governing permissions and limitations -->
-<!--- under the License. -->
-
-# Test TVM on Hexagon
-This document explains various pieces that are involved in testing TVM on an Android device which includes Hexagon DSP or Hexagon simulator.
-
-## What is HexagonLauncherRPC?
-HexagonLauncherRPC is a class to handle interactions with an Android phone which includes Hexagon DSP or Hexagon simulator to run a TVMModule(function/operation/graph) on Hexagon. HexagonLauncherRPC reuses [minRPC](https://github.com/apache/tvm/tree/main/src/runtime/minrpc) implementation to set up an RPC connection from host (your local machine) to Hexagon target, and it is passed through Android RPC server.
-
-## Build Required Tools/Libraries
-To build TVM for Hexagon and run tests you need to run multiple steps which includes preparing required tools, setting up environment variables and building various versions of TVM. Alternatively, you can skip these instructions and use docker image which has pre-installed required tools. We highly recommend to use docker, especially if this is your first time working with Hexagon. For instructions on using docker image follow ["use hexagon docker image"](#use-hexagon-docker-image).
-
-- Build TVMRuntime library and C++ RPC server for Android.
-- Build minRPC server along with FastRPC for Hexagon.
-- Build TVM library with Hexagon support for host machine.
-- Build TVMRuntime library and RPC server for host machine.
-
-First, ensure to export Clang libraries to `LD_LIBRARY_PATH` and Hexagon toolchain to `HEXAGON_TOOLCHAIN`:
-```bash
-export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"Path to `llvm-clang/lib` sub-directory. Currently we use LLVM-13 in TVM CI."
-
-export HEXAGON_TOOLCHAIN="Path to Hexagon toolchain. It can be the Hexagon toolchain included in the SDK, for example `HEXAGON_SDK_ROOT/tools/HEXAGON_Tools/x.y.z/Tools`. The `x.y.z` in the path is the toolchain version number, which is specific to the version of the SDK."
-```
-
-You can find more information about downloading [Hexagon SDK](https://developer.qualcomm.com/software/hexagon-dsp-sdk).
-
-First build Hexagon API application under `apps/hexagon_api`. This step will generate `tvm_rpc_android` and `libtvm_runtime.so` to run on Android. Also, it generates `libtvm_runtime.a` `libtvm_runtime.so`, `libhexagon_rpc_skel.so` and `libhexagon_rpc_sim.so` to run on Hexagon device or Hexagon simulator.
-
-```bash
-cd apps/hexagon_api
-mkdir build
-cd build
-cmake -DANDROID_ABI=arm64-v8a \
- -DANDROID_PLATFORM=android-28 \
- -DUSE_ANDROID_TOOLCHAIN="path to `android-ndk/build/cmake/android.toolchain.cmake` file" \
- -DUSE_HEXAGON_ARCH=v65|v66|v68|v69|v73|v75 \
- -DUSE_HEXAGON_SDK="path to Hexagon SDK" \
- -DUSE_HEXAGON_TOOLCHAIN="path to Hexagon toolchain `Tools` sub-directory which explained above" \
- -DUSE_OUTPUT_BINARY_DIR="path to `build/hexagon_api_output` which is a sub-directory of `tvm`" ..
-
-make -j2
-```
-
-Next, we need to build TVM on host with RPC and Hexagon dependencies. To do that follow these commands.
-
-```bash
-cd tvm
-mkdir build
-cd build
-cmake -DUSE_LLVM="path to `llvm/bin/llvm-config`" \
- -DUSE_RPC=ON \
- -DCMAKE_CXX_COMPILER="path to `clang++` executable" \
- -DUSE_HEXAGON_SDK="path to Hexagon SDK" \
- -DUSE_HEXAGON=ON ..
-
-make -j2
-```
-
-## Use Hexagon Docker Image
-To use hexagon docker image, install TVM and Hexagon API follow these steps from your TVM home directory:
-
-```bash
-# Log in to docker image
-./docker/bash.sh ci_hexagon
-
-# Build TVM
-rm -rf build
-mkdir build && cd build
-cmake -DUSE_LLVM=ON \
- -DUSE_RPC=ON \
- -DUSE_HEXAGON_SDK=$HEXAGON_SDK_PATH \
- -DUSE_HEXAGON=ON ..
-make -j2
-```
-
-Now that you have built required tools, you can jump to [run test examples](#run-tests).
-
-## Run Tests
-You have the options of running Hexagon test on real hardware or on Hexagon simulator. Also, depending on whether you decided to use Hexagon docker image or not we will explain both cases here.
-
-### Only follow these steps if running tests outside of docker
-```bash
-export LD_LIBRARY_PATH=$LD_LIBRARY_PATH:"path to `llvm-clang/lib` sub-directory"
-
-export HEXAGON_TOOLCHAIN="Path to Hexagon toolchain. It can be the Hexagon toolchain included in the HexagonSDK, for example `HEXAGON_SDK_ROOT/tools/HEXAGON_Tools/x.y.z/Tools`. The `x.y.z` in the path is the toolchain version number, which is specific to the version of the SDK."
-
-export PYTHONPATH=$PYTHONPATH:"path to `tvm/python`"
-```
-
-### Now, follow these steps
-**Note:** If you are using Hexagon docker image, first step is to log into the Hexagon docker image. Following these commands you will log in to the most recent version of Hexagon docker image on your TVM local branch. Since we have already built TVM for hexagon, we can just log in and use it. From your TVM home directory:
-
-```bash
-./docker/bash.sh ci_hexagon
-```
-
-Now, you need to export few environment variables and execute following commands:
-
-```bash
-# Run RPC Tracker in the background
-export TVM_TRACKER_HOST="Your host IP address or 0.0.0.0"
-export TVM_TRACKER_PORT="Port number of your choice."
-python -m tvm.exec.rpc_tracker --host $TVM_TRACKER_HOST --port $TVM_TRACKER_PORT&
-
-# Only For real hardware testing
-export ANDROID_SERIAL_NUMBER="You can get this number by running 'adb devices' command"
-
-# Only For simulator testing
-export HEXAGON_SHARED_LINK_FLAGS="-Lbuild/hexagon_api_output -lhexagon_rpc_sim"
-export ANDROID_SERIAL_NUMBER="simulator"
-```
-
-Finally, to run a Hexagon Launcher tests you can run:
-```bash
-pytest tests/python/contrib/test_hexagon/test_launcher.py
-```
diff --git a/tests/python/contrib/test_hexagon/README_RPC.md b/tests/python/contrib/test_hexagon/README_RPC.md
deleted file mode 100644
index 2bbedc9599..0000000000
--- a/tests/python/contrib/test_hexagon/README_RPC.md
+++ /dev/null
@@ -1,371 +0,0 @@
-<!--- Licensed to the Apache Software Foundation (ASF) under one -->
-<!--- or more contributor license agreements. See the NOTICE file -->
-<!--- distributed with this work for additional information -->
-<!--- regarding copyright ownership. The ASF licenses this file -->
-<!--- to you under the Apache License, Version 2.0 (the -->
-<!--- "License"); you may not use this file except in compliance -->
-<!--- with the License. You may obtain a copy of the License at -->
-
-<!--- http://www.apache.org/licenses/LICENSE-2.0 -->
-
-<!--- Unless required by applicable law or agreed to in writing, -->
-<!--- software distributed under the License is distributed on an -->
-<!--- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -->
-<!--- KIND, either express or implied. See the License for the -->
-<!--- specific language governing permissions and limitations -->
-<!--- under the License. -->
-
-
-# A life of a Hexagon API call
-
-The goal is to understand what exactly is happening during `A_data.copyfrom(np.array([2, 3]))`, where `A_data` lives in Hexagon.
-
-## Overview
-The diagram below describes the sequence of calls and components involved when memcpy over the Hexagon device is invoked.
-
-
-
-The communication between x86 and Android is done via the standard TVM RPC protocol implemented mostly in `src/runtime/rpc/rpc_endpoint.cc`.
-
-A packet between Android and Hexagon is proxy-ed by the Hexagon FastRPC mechanism. FastRPC depends on the auto-generated implementations of client- and server- side API. During the build time, the Android side API (”stub”) and the Hexagon side API (”skel”) is generated from `src/runtime/hexagon/rpc/hexagon_rpc.idl` (see `cmake/modules/Hexagon.cmake`).
-
-When TVM’s RPC server on Android, `tvm_rpc_android_server`, invokes `hexagon_rpc_send(...)`, it actually calls into the same-name function defined in the stub with the exact same arguments (which includes the URI for the `*skel.so` library to use on Hexagon, which in our case is `libhexagon_rpc_skel.so`). Similarly, on the Hexagon side, `hexagon_rpc_send(...)` call is first intercepted by the “skel” API, which in turn calls the actual implementation defined in `src/runtime/hexagon/rpc/he [...]
-
-## Initialization: Setting up Android and establishing connection between x86 host and Android
-
-What’s happening during the launcher initialization at [https://github.com/apache/tvm/blob/7cfaa88e6c18edc0a41e1a984d3cb9d8659a1c2c/tests/python/contrib/test_hexagon/test_launcher.py#L71-L73](https://github.com/apache/tvm/blob/7cfaa88e6c18edc0a41e1a984d3cb9d8659a1c2c/tests/python/contrib/test_hexagon/test_launcher.py#L71-L73) ?
-
-```python
-launcher = HexagonLauncher(serial_number=android_serial_number, rpc_info=rpc_info)
-launcher.upload(dso_binary_path, dso_binary)
-launcher.start_server()
-```
-
-Here, we send various files over Android via `adb`, and initialize a RPC server via `tvm_rpc_android` binary (built from [https://github.com/apache/tvm/tree/main/apps/cpp_rpc](https://github.com/apache/tvm/tree/main/apps/cpp_rpc)):
-
-[https://github.com/apache/tvm/blob/0c0245ae2230fa07d3e4b8be490fc9c88965730c/python/tvm/contrib/hexagon/build.py#L373-L378](https://github.com/apache/tvm/blob/0c0245ae2230fa07d3e4b8be490fc9c88965730c/python/tvm/contrib/hexagon/build.py#L373-L378)
-
-```python
-subprocess.Popen(
- self._adb_device_sub_cmd + ["shell", f"cd {self._workspace} && ./android_bash.sh"],
- stdout=subprocess.PIPE,
- stdin=subprocess.PIPE,
- stderr=subprocess.PIPE,
-)
-```
-
-[https://github.com/apache/tvm/blob/cd2fa69677516048e165e84a88c774dfb0ee65d1/src/runtime/hexagon/rpc/android_bash.sh.template#L20](https://github.com/apache/tvm/blob/cd2fa69677516048e165e84a88c774dfb0ee65d1/src/runtime/hexagon/rpc/android_bash.sh.template#L20)
-
-```
-./tvm_rpc_android server --port=<RPC_SERVER_PORT> --tracker=<RPC_TRACKER_HOST>:<RPC_TRACKER_PORT> --key=<HEXAGON_REMOTE_DEVICE_KEY>&
-```
-
-When we do `launcher.create_session()` , a remote RPC session between x86 and Android is established via this line:
-
-[https://github.com/apache/tvm/blob/0c0245ae2230fa07d3e4b8be490fc9c88965730c/python/tvm/contrib/hexagon/session.py#L57-L67](https://github.com/apache/tvm/blob/0c0245ae2230fa07d3e4b8be490fc9c88965730c/python/tvm/contrib/hexagon/session.py#L57-L67)
-
-```python
-self._rpc = tracker.request(
- ...
- session_constructor_args=[
- "tvm.contrib.hexagon.create_hexagon_session",
- self._session_name,
- self._remote_stack_size_bytes,
- ],
-)
-```
-
-Which eventually jumps to the following line in C++, which creates a RPC client session on an x86 host and run a server initialization function `tvm.contrib.hexagon.create_hexagon_session` on Android:
-
-[https://github.com/apache/tvm/blob/2cca934aad1635e3a83b712958ea83ff65704316/src/runtime/rpc/rpc_socket_impl.cc#L123-L129](https://github.com/apache/tvm/blob/2cca934aad1635e3a83b712958ea83ff65704316/src/runtime/rpc/rpc_socket_impl.cc#L123-L129)
-
-```cpp
-TVM_FFI_STATIC_INIT_BLOCK() {
- namespace refl = tvm::ffi::reflection;
- refl::GlobalDef().def_packed("rpc.Connect", [](ffi::PackedArgs args, ffi::Any* rv) {
- auto url = args[0].cast<std::string>();
- int port = args[1].cast<int>();
- auto key = args[2].cast<std::string>();
- *rv = RPCClientConnect(url, port, key,
- ffi::PackedArgs(args.values + 3, args.type_codes + 3, args.size() - 3));
- });
-}
-```
-
-`tvm.contrib.hexagon.create_hexagon_session` is defined here. It establishes a link between Android and Hexagon, this code runs on Android.
-
-[https://github.com/apache/tvm/blob/cd2fa69677516048e165e84a88c774dfb0ee65d1/src/runtime/hexagon/rpc/android/session.cc#L106](https://github.com/apache/tvm/blob/cd2fa69677516048e165e84a88c774dfb0ee65d1/src/runtime/hexagon/rpc/android/session.cc#L106)
-
-```cpp
-
-TVM_FFI_STATIC_INIT_BLOCK() {
- namespace refl = tvm::ffi::reflection;
- refl::GlobalDef().def_packed(
- "tvm.contrib.hexagon.create_hexagon_session", [](ffi::PackedArgs args, ffi::Any* rv) {
- auto session_name = args[0].cast<std::string>();
- int remote_stack_size_bytes = args[1].cast<int>();
- HexagonTransportChannel* hexagon_channel =
- new HexagonTransportChannel(hexagon_rpc_URI CDSP_DOMAIN, remote_stack_size_bytes);
- std::unique_ptr<RPCChannel> channel(hexagon_channel);
- auto ep = RPCEndpoint::Create(std::move(channel), session_name, "", NULL);
- auto sess = CreateClientSession(ep);
- *rv = CreateRPCSessionModule(sess);
- });
-}
-```
-
-`HexagonTransportChannel` is the one that actually knows how to talk to Hexagon. It uses functions such as `hexagon_rpc_send`, `hexagon_rpc_receive` defined in
-
-[https://github.com/apache/tvm/blob/cd2fa69677516048e165e84a88c774dfb0ee65d1/src/runtime/hexagon/rpc/hexagon/rpc_server.cc](https://github.com/apache/tvm/blob/cd2fa69677516048e165e84a88c774dfb0ee65d1/src/runtime/hexagon/rpc/hexagon/rpc_server.cc)
-
-## x86 host → Android
-
-`A_data.copyfrom(np.array([2, 3]))` reaches this line. This is the boundary between Python and C++ land in TVM FFI:
-
-[https://github.com/apache/tvm/blob/b2757817af7ba3aefe16ea3ccb6d4982dd7fd531/python/tvm/runtime/ndarray.py#L183](https://github.com/apache/tvm/blob/b2757817af7ba3aefe16ea3ccb6d4982dd7fd531/python/tvm/runtime/ndarray.py#L183)
-
-```python
-check_call(_LIB.TVMTensorCopyFromBytes(self.handle, data, nbytes))
-```
-
-[https://github.com/apache/tvm/blob/37cd9837ff302e4490696ca57a9fbba6404c7046/src/runtime/tensor.cc#L322](https://github.com/apache/tvm/blob/37cd9837ff302e4490696ca57a9fbba6404c7046/src/runtime/tensor.cc#L322)
-
-```cpp
-int TVMTensorCopyFromBytes(TVMArrayHandle handle, void* data, size_t nbytes) {
- API_BEGIN();
- TensorCopyFromBytes(handle, data, nbytes);
- API_END();
-}
-```
-
-Now we come to `TensorCopyFromBytes` function. The first non-obvious question is, which `DeviceAPI` is selected by `DeviceAPI::Get(handle->device)`?
-
-```cpp
-void TensorCopyFromBytes(DLTensor* handle, const void* data, size_t nbytes) {
- ...
- DLTensor from;
- ...
- DeviceAPI::Get(handle->device)->CopyDataFromTo(&from, handle, nullptr);
- // Synchronize in case data become unavailable later.
- DeviceAPI::Get(handle->device)->StreamSync(handle->device, nullptr);
-}
-```
-
-The answer: `RPCDeviceAPI` defined below, not `HexagonDeviceAPI`.
-
-[https://github.com/apache/tvm/blob/899bc064e1bf8df915bcadc979a6f37210cdce33/src/runtime/rpc/rpc_device_api.cc#L34](https://github.com/apache/tvm/blob/899bc064e1bf8df915bcadc979a6f37210cdce33/src/runtime/rpc/rpc_device_api.cc#L34)
-
-```cpp
-class RPCDeviceAPI final : public DeviceAPI {
- ...
-```
-
-This is due to the fact that `sess.device`, used in `test_launcher.py` below, encodes two pieces of information: (1) The device is RPC and (2) it wraps the underlying “real” device Hexagon.
-
-[https://github.com/apache/tvm/blob/2b35cfd6ddb73afecd3f550f33881e1fdc7c3267/tests/python/contrib/test_hexagon/rpc/test_launcher.py#L112](https://github.com/apache/tvm/blob/2b35cfd6ddb73afecd3f550f33881e1fdc7c3267/tests/python/contrib/test_hexagon/rpc/test_launcher.py#L112)
-
-See below for how `sess.device` is created during `HexagonLauncher` initialization.
-
- `self.device = self._rpc.hexagon(0)`.
-
-[https://github.com/apache/tvm/blob/cd2fa69677516048e165e84a88c774dfb0ee65d1/python/tvm/contrib/hexagon/session.py#L64](https://github.com/apache/tvm/blob/cd2fa69677516048e165e84a88c774dfb0ee65d1/python/tvm/contrib/hexagon/session.py#L64)
-
-`RPCDeviceAPI::CopyDataFromTo` is defined in [https://github.com/apache/tvm/blob/899bc064e1bf8df915bcadc979a6f37210cdce33/src/runtime/rpc/rpc_device_api.cc#L80](https://github.com/apache/tvm/blob/899bc064e1bf8df915bcadc979a6f37210cdce33/src/runtime/rpc/rpc_device_api.cc#L80)
-
-Here, we meet another `GetAPI` call:
-
-```cpp
-GetSess(dev_from)->GetDeviceAPI(remote_dev)->CopyDataFromTo(&from_tensor, &to_tensor, stream);
-```
-
-[https://github.com/apache/tvm/blob/899bc064e1bf8df915bcadc979a6f37210cdce33/src/runtime/rpc/rpc_device_api.cc#L94](https://github.com/apache/tvm/blob/899bc064e1bf8df915bcadc979a6f37210cdce33/src/runtime/rpc/rpc_device_api.cc#L94)
-
-At first, it is not obvious where this `CopyDataFromTo` jumps to (initially I thought it would jump to `HexagonDeviceAPI`). Since `GetSess(dev_from)` returns the client RPC connection between x86 and Android, created during initialization in
-
-[https://github.com/apache/tvm/blob/2cca934aad1635e3a83b712958ea83ff65704316/src/runtime/rpc/rpc_socket_impl.cc#L107](https://github.com/apache/tvm/blob/2cca934aad1635e3a83b712958ea83ff65704316/src/runtime/rpc/rpc_socket_impl.cc#L107)
-
-```cpp
-Module RPCClientConnect(std::string url, int port, std::string key, ffi::PackedArgs init_seq) {
- auto endpt = RPCConnect(url, port, "client:" + key, init_seq);
- return CreateRPCSessionModule(CreateClientSession(endpt));
-}
-```
-
-, this jumps to `RPCClientSession` class defined in [https://github.com/apache/tvm/blob/899bc064e1bf8df915bcadc979a6f37210cdce33/src/runtime/rpc/rpc_endpoint.cc#L994](https://github.com/apache/tvm/blob/899bc064e1bf8df915bcadc979a6f37210cdce33/src/runtime/rpc/rpc_endpoint.cc#L994)
-
-```cpp
-class RPCClientSession : public RPCSession, public DeviceAPI {
- ...
-```
-
-`rpc_endpoint.cc` is a very important file. It contains the core RPC protocol logic. `CopyDataFromTo` in `rpc_device_api.cc` jumps to
-
-[https://github.com/apache/tvm/blob/899bc064e1bf8df915bcadc979a6f37210cdce33/src/runtime/rpc/rpc_endpoint.cc#L1060-L1062](https://github.com/apache/tvm/blob/899bc064e1bf8df915bcadc979a6f37210cdce33/src/runtime/rpc/rpc_endpoint.cc#L1060-L1062)
-
-```cpp
-void CopyDataFromTo(DLTensor* from, DLTensor* to, TVMStreamHandle stream) final {
- endpoint_->SysCallRemote(RPCCode::kCopyAmongRemote, from, to, stream);
-}
-```
-
-from which things transfer to the Android side.
-
-Here is where `RPCCode::kCopyAmongRemote` is handled:
-
-[https://github.com/apache/tvm/blob/899bc064e1bf8df915bcadc979a6f37210cdce33/src/runtime/rpc/rpc_endpoint.cc#L979-L981](https://github.com/apache/tvm/blob/899bc064e1bf8df915bcadc979a6f37210cdce33/src/runtime/rpc/rpc_endpoint.cc#L979-L981)
-
-```cpp
-case RPCCode::kCopyAmongRemote:
- SysCallHandler(RPCCopyAmongRemote);
- break;
-```
-
-The handler is represented by `serving_session_`, which is initialized during server initialization at
-
-[https://github.com/apache/tvm/blob/899bc064e1bf8df915bcadc979a6f37210cdce33/src/runtime/rpc/rpc_endpoint.cc#L541](https://github.com/apache/tvm/blob/899bc064e1bf8df915bcadc979a6f37210cdce33/src/runtime/rpc/rpc_endpoint.cc#L541)
-
-```cpp
-serving_session_ = RPCModuleGetSession(mod);
-```
-
-which corresponds to the Hexagon session created before in [https://github.com/apache/tvm/blob/cd2fa69677516048e165e84a88c774dfb0ee65d1/src/runtime/hexagon/rpc/android/session.cc#L106](https://github.com/apache/tvm/blob/cd2fa69677516048e165e84a88c774dfb0ee65d1/src/runtime/hexagon/rpc/android/session.cc#L106).
-
-The handler is passed to the following function
-
-[https://github.com/apache/tvm/blob/899bc064e1bf8df915bcadc979a6f37210cdce33/src/runtime/rpc/rpc_endpoint.cc#L909-L922](https://github.com/apache/tvm/blob/899bc064e1bf8df915bcadc979a6f37210cdce33/src/runtime/rpc/rpc_endpoint.cc#L909-L922)
-
-```cpp
-void RPCCopyAmongRemote(RPCSession* handler, ffi::PackedArgs args, ffi::Any* rv) {
- auto from = args[0].cast<DLTensor*>();
- auto to = args[1].cast<DLTensor*>();
- ...
- handler->GetDeviceAPI(dev)->CopyDataFromTo(from, to, stream);
-}
-```
-
-This is an interesting function. Here, `handler` is again `RPCClientSession` due to the line in
-
-[https://github.com/apache/tvm/blob/cd2fa69677516048e165e84a88c774dfb0ee65d1/src/runtime/hexagon/rpc/android/session.cc#L114](https://github.com/apache/tvm/blob/cd2fa69677516048e165e84a88c774dfb0ee65d1/src/runtime/hexagon/rpc/android/session.cc#L114)
-
-```cpp
-auto sess = CreateClientSession(ep);
-```
-
-so apparently, things might look like it is looping back to `RPCClientSession::CopyDataFromTo`:
-
-```cpp
-void CopyDataFromTo(DLTensor* from, DLTensor* to, TVMStreamHandle stream) final {
- endpoint_->SysCallRemote(RPCCode::kCopyAmongRemote, from, to, stream);
- }
-```
-
-But this time, `endpoint_` is different. Previously, this `endpoint_` represented the connection between x86 and Android (created in [https://github.com/apache/tvm/blob/2cca934aad1635e3a83b712958ea83ff65704316/src/runtime/rpc/rpc_socket_impl.cc#L99-L100](https://github.com/apache/tvm/blob/2cca934aad1635e3a83b712958ea83ff65704316/src/runtime/rpc/rpc_socket_impl.cc#L99-L100)), but this `endpoint_` belongs to the Hexagon session created in [https://github.com/apache/tvm/blob/cd2fa6967751604 [...]
-
-## Android → Hexagon
-
-Recall that the `endpoint_` owned by the Hexagon session is created via `tvm.contrib.hexagon.create_hexagon_session` when the Android RPC server is being initialized. The `endpoint_` is represented by the following class:
-
-[https://github.com/apache/tvm/blob/c20cbc55c03f9f048b151a1221469b9888123608/src/runtime/hexagon/rpc/android/session.cc#L46](https://github.com/apache/tvm/blob/c20cbc55c03f9f048b151a1221469b9888123608/src/runtime/hexagon/rpc/android/session.cc#L46)
-
-```cpp
-class HexagonTransportChannel : public RPCChannel {
- public:
- explicit HexagonTransportChannel(const std::string& uri, int remote_stack_size_bytes) {
- ...
- hexagon_rpc_open(uri.c_str(), &_handle);
- ...
- }
-
- size_t Send(const void* data, size_t size) override {
- hexagon_rpc_send(_handle, static_cast<const unsigned char*>(data), static_cast<int>(size));
- ...
- }
-```
-
-On construction, `hexagon_rpc_open` is called, which will initialize the TVM MinRPC server on Hexagon and overwrites `device_api.hexagon` registry to point to the call to `HexagonDeviceAPI`. [https://github.com/apache/tvm/blob/c20cbc55c03f9f048b151a1221469b9888123608/src/runtime/hexagon/rpc/hexagon/rpc_server.cc#L210-L213](https://github.com/apache/tvm/blob/c20cbc55c03f9f048b151a1221469b9888123608/src/runtime/hexagon/rpc/hexagon/rpc_server.cc#L210-L213)
-
-The endpoint routes each RPC packet by `Send` function, which in turn calls `hexagon_rpc_send(...)` defined in:
-
-[https://github.com/apache/tvm/blob/c20cbc55c03f9f048b151a1221469b9888123608/src/runtime/hexagon/rpc/hexagon/rpc_server.cc#L243](https://github.com/apache/tvm/blob/c20cbc55c03f9f048b151a1221469b9888123608/src/runtime/hexagon/rpc/hexagon/rpc_server.cc#L243)
-
-```cpp
-AEEResult hexagon_rpc_send(remote_handle64 _handle, const unsigned char* data,
- int dataLen) {
- get_hexagon_rpc_server()->Write(reinterpret_cast<const uint8_t*>(data),
- static_cast<size_t>(dataLen));
- ...
-}
-```
-
-This is where FastRPC comes into play and things get very confusing. The endpoint lives in Android, so `hexagon_rpc_send` call (also `hexagon_rpc_open`) happens at Android. But the implementations of these functions in `rpc_server.cc` describe the behavior on the Hexagon side... What’s happening is that FastRPC “stub” and “skel” (see the overview at the top) API intercept those calls and play some magic behind the scene to make RPC call look transparent from the client (Android) perspective.
-
-So when the control comes to the point of definition of `hexagon_rpc_send` in `rpc_server.cc`, FastRPC has already finished its job and so we are really on the Hexagon side now. We come to `HexagonRPCServer::Write(...)` function, which in turn calls into TVM MinRPC server instance `rpc_server_` to process the incoming packet:
-
-[https://github.com/apache/tvm/blob/c20cbc55c03f9f048b151a1221469b9888123608/src/runtime/hexagon/rpc/hexagon/rpc_server.cc#L167](https://github.com/apache/tvm/blob/c20cbc55c03f9f048b151a1221469b9888123608/src/runtime/hexagon/rpc/hexagon/rpc_server.cc#L167)
-
-```cpp
-int64_t Write(const uint8_t* data, size_t data_size_bytes) {
- if (io_.SetReadBuffer(data, data_size_bytes) != AEE_SUCCESS) {
- return -1;
- }
- rpc_server_.ProcessOnePacket();
- return (int64_t)data_size_bytes;
-}
-```
-
-`MinRPCServer::ProcessOnePacket()` function dispatches to `HandleCopyFromRemote()` upon receiving `kCopyFromRemote` request:
-
-[https://github.com/apache/tvm/blob/8c125ca6090a29f38a66d26138b056b7de27cb0b/src/runtime/minrpc/minrpc_server.h#L87](https://github.com/apache/tvm/blob/8c125ca6090a29f38a66d26138b056b7de27cb0b/src/runtime/minrpc/minrpc_server.h#L87)
-
-```cpp
-bool ProcessOnePacket() {
- ...
-
- if (...) {
- ...
- } else {
- switch (code) {
- ...
- case RPCCode::kCopyFromRemote: {
- HandleCopyFromRemote();
- break;
- }
- ...
-```
-
-[https://github.com/apache/tvm/blob/8c125ca6090a29f38a66d26138b056b7de27cb0b/src/runtime/minrpc/minrpc_server.h#L178](https://github.com/apache/tvm/blob/8c125ca6090a29f38a66d26138b056b7de27cb0b/src/runtime/minrpc/minrpc_server.h#L178)
-
-```cpp
-void HandleCopyFromRemote() {
- DLTensor* arr = this->ArenaAlloc<DLTensor>(1);
- uint64_t data_handle;
- this->Read(&data_handle);
- arr->data = reinterpret_cast<void*>(data_handle);
- ...
- this->ReadArray(arr->shape, arr->ndim);
-
- if (...) {
- ...
- } else {
- data_ptr = this->ArenaAlloc<uint8_t>(num_bytes);
- DLTensor temp;
- ...
- call_ecode = TVMDeviceCopyDataFromTo(arr, &temp, nullptr);
- // need sync to make sure that the copy is completed.
- if (call_ecode == 0) {
- call_ecode = TVMSynchronize(arr->device.device_type, arr->device.device_id, nullptr);
- }
- }
-```
-
-And finally we see a call to `DeviceAPIManager::Get(dev)->CopyDataFromTo` which translates to `HexagonDeviceAPI::CopyDataFromTo` .
-
-[https://github.com/apache/tvm/blob/f929b0fc8e7a600978c9ac0418469bd70d046446/src/runtime/c_runtime_api.cc#L623-L630](https://github.com/apache/tvm/blob/f929b0fc8e7a600978c9ac0418469bd70d046446/src/runtime/c_runtime_api.cc#L623-L630)
-
-```cpp
-int TVMDeviceCopyDataFromTo(DLTensor* from, DLTensor* to, TVMStreamHandle stream) {
- ...
- DeviceAPIManager::Get(dev)->CopyDataFromTo(from, to, stream);
- ...
-}
-```
diff --git a/tests/python/contrib/test_hexagon/__init__.py b/tests/python/contrib/test_hexagon/__init__.py
deleted file mode 100644
index 07fb45c52d..0000000000
--- a/tests/python/contrib/test_hexagon/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# isort: skip_file
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Testing infrastructure for Hexagon"""
diff --git a/tests/python/contrib/test_hexagon/benchmark_util.py b/tests/python/contrib/test_hexagon/benchmark_util.py
deleted file mode 100644
index c9be25efd3..0000000000
--- a/tests/python/contrib/test_hexagon/benchmark_util.py
+++ /dev/null
@@ -1,277 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-# ruff: noqa: RUF012
-
-"""Utility functions used for benchmarks"""
-
-import csv
-import os
-import tempfile
-
-import pytest
-
-from tvm.contrib.hexagon.tools import HEXAGON_SIMULATOR_NAME
-
-
-def skip_benchmarks_flag_and_reason():
- """
- Returns one of these tuples:
- (False, '') or
- (True, (a string describing why the test should be skipped))
-
- NOTE: This function is a temporary measure to prevent the TVM CI system
- running benchmark scripts every time the CI pre-commit hook executes.
- This should go away when a better system is in place to govern when various
- tests / benchmarks are executed.
- """
- asn = os.environ.get("ANDROID_SERIAL_NUMBER")
-
- if asn == HEXAGON_SIMULATOR_NAME:
- return (True, "Skipping benchmarks when ANDROID_SERIAL_NUMBER='simluator'")
-
- return (False, "")
-
-
-class UnsupportedException(Exception):
- """
- Indicates that the specified benchmarking configuration is known to
- currently be unsupported. The Exception message may provide more detail.
- """
-
-
-class NumericalAccuracyException(Exception):
- """
- Indicates that the benchmarking configuration appeared to run successfully,
- but the output data didn't have the expected accuracy.
- """
-
-
-class BenchmarksTable:
- """
- Stores/reports the result of benchmark runs.
-
- Each line item has a status: success, fail, or skip.
-
- Each 'success' line item must include benchmark data,
- in the form provided by TVM's `time_evaluator` mechanism.
-
- Each line item may also specify values for any subset of
- the columns provided to the table's construstor.
- """
-
- BUILTIN_COLUMN_NAMES = set(
- [
- "row_status",
- "timings_min_usecs",
- "timings_max_usecs",
- "timings_median_usecs",
- "timings_mean_usecs",
- "timings_stddev_usecs",
- ]
- )
-
- def __init__(self):
- self._line_items = []
-
- def validate_user_supplied_kwargs(self, kwarg_dict):
- name_conflicts = set(kwarg_dict).intersection(self.BUILTIN_COLUMN_NAMES)
-
- if name_conflicts:
- name_list = ", ".join(name_conflicts)
- raise Exception(f"Attempting to supply values for built-in column names: {name_list}")
-
- def record_success(self, timings, **kwargs):
- """
- `timings` : Assumed to have the structure and meaning of
- the timing results provided by TVM's `time_evaluator`
- mechanism.
-
- `kwargs` : Optional values for any of the other columns
- defined for this benchmark table.
- """
- self.validate_user_supplied_kwargs(kwargs)
- line_item = kwargs
-
- line_item["row_status"] = "SUCCESS"
-
- line_item["timings_min_usecs"] = timings.min * 1000000
- line_item["timings_max_usecs"] = timings.max * 1000000
- line_item["timings_median_usecs"] = timings.median * 1000000
- line_item["timings_stddev_usecs"] = timings.std * 1000000
- line_item["timings_mean_usecs"] = timings.mean * 1000000
-
- self._line_items.append(line_item)
-
- def record_skip(self, **kwargs):
- self.validate_user_supplied_kwargs(kwargs)
-
- line_item = dict(kwargs)
- line_item["row_status"] = "SKIP"
- self._line_items.append(line_item)
-
- def record_fail(self, **kwargs):
- self.validate_user_supplied_kwargs(kwargs)
-
- line_item = dict(kwargs)
- line_item["row_status"] = "FAIL"
- self._line_items.append(line_item)
-
- def has_fail(self):
- """
- Returns True if the table contains at least one 'fail' line item,
- otherwise returns False.
- """
- return any(item["row_status"] == "FAIL" for item in self._line_items)
-
- def print_csv(self, f, column_name_order, timing_decimal_places=3):
- """
- Print the benchmark results as a csv.
-
- `f` : The output stream.
-
- `column_name_order`: an iterable sequence of column names, indicating the
- left-to-right ordering of columns in the CSV output.
-
- The CSV output will contain only those columns that are mentioned in
- this list.
-
- `timing_decimal_places`: for the numeric timing values, this is the
- number of decimal places to provide in the printed output.
- For example, a value of 3 is equivalent to the Python formatting string
- `'{:.3f}'`
- """
- writer = csv.DictWriter(
- f, column_name_order, dialect="excel-tab", restval="", extrasaction="ignore"
- )
-
- writer.writeheader()
-
- for line_item_dict in self._line_items:
- # Use a copy of the line-item dictionary, because we might do some modifications
- # for the sake of rendering...
- csv_line_dict = dict(line_item_dict)
-
- for col_name in [
- "timings_min_usecs",
- "timings_max_usecs",
- "timings_median_usecs",
- "timings_stddev_usecs",
- "timings_mean_usecs",
- ]:
- if col_name in csv_line_dict:
- old_value = csv_line_dict[col_name]
- assert isinstance(old_value, float), (
- f"Formatting code assumes that column {col_name} is"
- f" some col_nameind of float, but its actual type is {type(old_value)}"
- )
- str_value = f"{old_value:>0.{timing_decimal_places}f}"
- csv_line_dict[col_name] = str_value
-
- writer.writerow(csv_line_dict)
-
-
-def get_benchmark_id(keys_dict):
- """
- Given a dictionary with the distinguishing characteristics of a particular benchmark
- line item, compute a string that uniquely identifies the benchmark.
-
- The returned string:
- - is a valid directory name on the host's file systems, and
- - should be easy for humans to parse
-
- Note that the insertion order for `keys_dict` affects the computed name.
- """
- # Creat a copy, because we might be modifying it.
- keys_dict_copy = dict(keys_dict)
-
- # Sniff for shape-like lists, because we want them in a form that's both
- # readable and filesystem-friendly...
- for k, v in keys_dict_copy.items():
- if isinstance(v, list | tuple):
- v_str = "_".join([str(x) for x in v])
- keys_dict_copy[k] = v_str
-
- return "-".join([f"{k}:{v}" for k, v in keys_dict_copy.items()])
-
-
-def get_benchmark_decription(keys_dict):
- """
- Similar to `get_benchmark_id`, but the focus is on human-readability.
-
- The returned string contains no line-breaks, but may contain spaces and
- other characters that make it unsuitable for use as a filename.
- """
- return " ".join([f"{k}={v}" for k, v in keys_dict.items()])
-
-
-@pytest.fixture(scope="class")
-def benchmark_group(request):
- """This fixture provides some initialization / finalization logic for groups of related
- benchmark runs.
- See the fixture implementation below for details.
-
- The fixture's mechanics are described here: https://stackoverflow.com/a/63047695
-
- TODO: There may be cleaner ways to let each class that uses this fixture provide its
- own value for `csv_column_order`.
-
- TODO: In the future we may wish to break this fixture up in to several smaller ones.
-
- The overall contract for a class (e.g. `MyTest`) using this fixture is as follows:
-
- https://stackoverflow.com/a/63047695
-
- @pytest.mark.usefixtures("benchmark_group")
- class MyTest:
-
- # The fixture requires that this class variable is defined before
- # the fixture's finalizer-logic executes.
- #
- # This is used as an argument to BenchmarkTable.print_csv(...) after
- # all of MyTest's unit tests have executed.
- csv_column_order = [
- ...
- ]
-
- # Before the MyTest's first unit test executes, the fixture will populate the
- # following class variables:
- MyTest.working_dir : str
- MyTest.benchmark_table : BenchmarkTable"""
- working_dir = tempfile.mkdtemp()
- table = BenchmarksTable()
-
- request.cls.working_dir = working_dir
- request.cls.benchmark_table = table
-
- yield
-
- tabular_output_filename = os.path.join(working_dir, "benchmark-results.csv")
-
- if not hasattr(request.cls, "csv_column_order"):
- raise Exception('Classes using this fixture must have a member named "csv_column_order"')
-
- with open(tabular_output_filename, "w", encoding="UTF-8") as csv_file:
- table.print_csv(csv_file, request.cls.csv_column_order)
-
- print()
- print("*" * 80)
- print(f"BENCHMARK RESULTS FILE: {tabular_output_filename}")
- print("*" * 80)
- print()
-
- if table.has_fail() > 0:
- pytest.fail("At least one benchmark configuration failed", pytrace=False)
diff --git a/tests/python/contrib/test_hexagon/conftest.py b/tests/python/contrib/test_hexagon/conftest.py
deleted file mode 100644
index f187cc1c39..0000000000
--- a/tests/python/contrib/test_hexagon/conftest.py
+++ /dev/null
@@ -1,27 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Hexagon testing fixtures used to deduce testing argument
-values from testing parameters"""
-
-# pytest 9 no longer supports declaring ``pytest_plugins`` in a
-# non-rootdir conftest, so re-export the plugin's hooks and fixtures
-# here instead. The explicit import covers the underscore-prefixed
-# fixture that the wildcard import skips.
-# ruff: noqa: F401, F403
-from tvm.contrib.hexagon.pytest_plugin import *
-from tvm.contrib.hexagon.pytest_plugin import _tracker_info
diff --git a/tests/python/contrib/test_hexagon/conv2d/README.md b/tests/python/contrib/test_hexagon/conv2d/README.md
deleted file mode 100644
index d29d8b9c86..0000000000
--- a/tests/python/contrib/test_hexagon/conv2d/README.md
+++ /dev/null
@@ -1,37 +0,0 @@
-<!--- Licensed to the Apache Software Foundation (ASF) under one -->
-<!--- or more contributor license agreements. See the NOTICE file -->
-<!--- distributed with this work for additional information -->
-<!--- regarding copyright ownership. The ASF licenses this file -->
-<!--- to you under the Apache License, Version 2.0 (the -->
-<!--- "License"); you may not use this file except in compliance -->
-<!--- with the License. You may obtain a copy of the License at -->
-
-<!--- http://www.apache.org/licenses/LICENSE-2.0 -->
-
-<!--- Unless required by applicable law or agreed to in writing, -->
-<!--- software distributed under the License is distributed on an -->
-<!--- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -->
-<!--- KIND, either express or implied. See the License for the -->
-<!--- specific language governing permissions and limitations -->
-<!--- under the License. -->
-
-Documents manual TE schedule to illustrate Hexagon operator slicing.
-
-High Level Notes:
-* Using float32 (for now) so that tests will pass on CPU
-* Using global storage scope (for now) which means "cache" reads and writes from global, to global
-* TIR is pending changes from the work-in-progress layout RFC
- (https://github.com/apache/tvm-rfcs/pull/39)
-* TIR has been hand-edited for context and clarity
- * Added C-style comments
- * Changed variable names
- * Added spacing and line breaks
-* Naming conventions
- * Using input (instead of activation)
- * Using filter (instead of weight, kernel)
- * Using `k` to denote channel-out and `c` or `rc` (reduction channel) to denote channel-in
- * Using `rh` and `rw` (reduction height / width) to denote filter height and width
-
-[Conv2d](test_conv2d_blocked.md)
-
-[Conv2d -> Conv2d](test_conv2d_conv2d.md)
diff --git a/tests/python/contrib/test_hexagon/conv2d/__init__.py b/tests/python/contrib/test_hexagon/conv2d/__init__.py
deleted file mode 100644
index b17055a624..0000000000
--- a/tests/python/contrib/test_hexagon/conv2d/__init__.py
+++ /dev/null
@@ -1,19 +0,0 @@
-# isort: skip_file
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Testing infrastructure for Hexagon/TOPI/Conv2d"""
diff --git a/tests/python/contrib/test_hexagon/conv2d/test_conv2d_blocked.md b/tests/python/contrib/test_hexagon/conv2d/test_conv2d_blocked.md
deleted file mode 100644
index 417ce0b123..0000000000
--- a/tests/python/contrib/test_hexagon/conv2d/test_conv2d_blocked.md
+++ /dev/null
@@ -1,494 +0,0 @@
-<!--- Licensed to the Apache Software Foundation (ASF) under one -->
-<!--- or more contributor license agreements. See the NOTICE file -->
-<!--- distributed with this work for additional information -->
-<!--- regarding copyright ownership. The ASF licenses this file -->
-<!--- to you under the Apache License, Version 2.0 (the -->
-<!--- "License"); you may not use this file except in compliance -->
-<!--- with the License. You may obtain a copy of the License at -->
-
-<!--- http://www.apache.org/licenses/LICENSE-2.0 -->
-
-<!--- Unless required by applicable law or agreed to in writing, -->
-<!--- software distributed under the License is distributed on an -->
-<!--- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -->
-<!--- KIND, either express or implied. See the License for the -->
-<!--- specific language governing permissions and limitations -->
-<!--- under the License. -->
-
-Hexagon conv2d schedules
-
-# Baseline conv2d
-
-This is a baseline 1x1 conv2d schedule for Hexagon.
-
-## Command
-
-pytest -sv "tests/python/contrib/test_hexagon/test_conv2d_blocked.py::TestConv2dPackedFilter::test_conv2d[1-64-64-0-1-1-128-1-1-float32-llvm]"
-
-## Parameters
-
-| Parameter | Value |
-| --------- | ----- |
-| Batch | 1 |
-| Spatial | 64x64 |
-| Input Ch | 64 |
-| Padding | 0 |
-| Stride | 1 |
-| Filter | 1x1 |
-| Output Ch | 128 |
-
-## Assumptions
-
-* n/a
-
-## To Do
-
-* n/a
-
-## Annotated TIR
-
-```
-primfn(input_handle: handle, filter_handle: handle, output_handle: handle) -> ()
- attr = {"from_legacy_te_schedule": True, "global_symbol": "default_function", "tir.noalias": True, "target": meta[Target][0]}
- buffers = {output_buffer: Buffer(output_pointer: Pointer(float32), float32, [1, 8, 8, 4, 8, 8, 32], []), // NHWC8h8w32c
- filter_buffer: Buffer(filter_pointer: Pointer(float32), float32, [4, 2, 1, 1, 8, 32, 4], []), // OIHW8i32o4i
- input_buffer: Buffer(input_pointer: Pointer(float32), float32, [1, 64, 64, 64], [])} // NHWC (pending RFC)
- buffer_map = {input_handle: input_buffer, filter_handle: filter_buffer, output_handle: output_buffer} {
- allocate(input.cache: Pointer(global float32), float32, [32768]), storage_scope = global;
- allocate(filter.cache: Pointer(global float32), float32, [2048]), storage_scope = global;
- allocate(output.cache: Pointer(global float32), float32, [16384]), storage_scope = global;
-
- for (ko.outer: int32, 0, 4) {
- for (ho.outer: int32, 0, 8) {
-
- // input cache read
- // NHWC -> NHWC8h8w32c (pending RFC)
- for (wo: int32, 0, 8) {
- for (co: int32, 0, 2) {
- for (hi: int32, 0, 8) {
- for (wi: int32, 0, 8) {
- for (ci: int32, 0, 32) {
- input.cache[(((((wo*4096) + (co*2048)) + (hi*256)) + (wi*32)) + ci)] =
- (float32*)input_pointer[((((((ho.outer*32768) + (hi*4096)) + (wo*512)) + (wi*64)) + (co*32)) + ci)]
- }
- }
- }
- }
- }
-
- // filter cache read
- for (co: int32, 0, 2) {
- for (ci8: int32, 0, 8) {
- for (ki: int32, 0, 32) {
- for (ci4: int32, 0, 4) {
- filter.cache[((((co*1024) + (ci8*128)) + (ki*4)) + ci4)] =
- (float32*)filter_pointer[(((((ko.outer*2048) + (co*1024)) + (ci8*128)) + (ki*4)) + ci4)]
- }
- }
- }
- }
-
- // compute
- for (wo.c: int32, 0, 8) {
-
- // init output cache
- for (hi.c.init: int32, 0, 8) {
- for (wi.c.init: int32, 0, 8) {
- for (ki.c.init: int32, 0, 32) {
- output.cache[((((wo.c*2048) + (hi.c.init*256)) + (wi.c.init*32)) + ki.c.init)] = 0f32
- }
- }
- }
-
- // convolution
- for (rc.outer: int32, 0, 2) {
- for (hi.c: int32, 0, 8) {
- for (wi.c: int32, 0, 8) {
- for (ki.c: int32, 0, 32) {
- for (rc.inner: int32, 0, 32) {
- output.cache[((((wo.c*2048) + (hi.c*256)) + (wi.c*32)) + ki.c)] =
- (
- (float32*)output.cache[((((wo.c*2048) + (hi.c*256)) + (wi.c*32)) + ki.c)] +
- (
- (float32*)input.cache[(((((wo.c*4096) + (rc.outer*2048)) + (hi.c*256)) + (wi.c*32)) + rc.inner)] *
- (float32*)filter.cache[((((rc.outer*1024) + (floordiv(rc.inner, 4)*128)) + (ki.c*4)) + floormod(rc.inner, 4))]
- )
- )
- }
- }
- }
- }
- }
- } // end wo.c
-
- // cache write
- for (wo: int32, 0, 8) {
- for (hi: int32, 0, 8) {
- for (wi: int32, 0, 8) {
- for (ki: int32, 0, 32) {
- output_pointer[((((((ho.outer*65536) + (wo*8192)) + (ko.outer*2048)) + (hi*256)) + (wi*32)) + ki)] =
- (float32*)output.cache[((((wo*2048) + (hi*256)) + (wi*32)) + ki)]
- }
- }
- }
- }
- } // end ho.outer
- } // end ko.outer
-}
-```
-
-# Split on Channel Out and Height - "Full Output Slice"
-
-Adds new parameters `k_split` and `h_split` which creates a loop split on the outer channel out `ko` and height `ho` loops creating `outer` and `inner` loops for each split. The cache reads and writes are computed at `ho.outer` which means that cache allocation grow in proportion to `k_split` and `h_split` factors.
-
-The key changes in TIR versus the above are...
-
-1) Increased cache allocations:
-
-```
- // input cache grows by factor of h_split = 2
- allocate(input.cache: Pointer(global float32), float32, [65536]), storage_scope = global;
-
- // filter cache grows by factor of k_split = 2
- allocate(filter.cache: Pointer(global float32), float32, [4096]), storage_scope = global;
-
- // output cache grows by factor of h_split * k_split = 4
- allocate(output.cache: Pointer(global float32), float32, [65536]), storage_scope = global;
-```
-
-2) Outer loop splits using k_split and h_split factors
-
-```
- // ko.outer = outer loop split on ko using k_split factor
- for (ko.outer: int32, 0, 2) {
- // ho.outer = outer loop split on ho using h_split factor
- for (ho.outer: int32, 0, 4) {
-```
-
-3) Inner loop splits in both cache read / write and compute schedules. This is taken from the compute schedule e.g.
-```
- for (ko.c.inner: int32, 0, 2) {
- for (ho.c.inner: int32, 0, 2) {
-```
-
-## Command
-
-pytest -sv "tests/python/contrib/test_hexagon/test_conv2d_blocked.py::TestConv2dPackedFilter::test_conv2d[1-64-64-0-1-1-128-2-2-float32-llvm]"
-
-## Parameters
-
-| Parameter | Value |
-| --------- | ----- |
-| Batch | 1 |
-| Spatial | 64x64 |
-| Input Ch | 64 |
-| Padding | 0 |
-| Stride | 1 |
-| Filter | 1x1 |
-| Output Ch | 128 |
-| k_split | 2 |
-| h_split | 2 |
-
-## Assumptions
-
-* n/a
-
-## To Do
-
-* n/a
-
-## Annotated TIR
-
-```
-primfn(input_handle: handle, filter_handle: handle, output_handle: handle) -> ()
- attr = {"from_legacy_te_schedule": True, "global_symbol": "default_function", "tir.noalias": True, "target": meta[Target][0]}
- buffers = {output_buffer: Buffer(output_pointer: Pointer(float32), float32, [1, 8, 8, 4, 8, 8, 32], []), // NHWC8h8w32c
- filter_buffer: Buffer(filter_pointer: Pointer(float32), float32, [4, 2, 1, 1, 8, 32, 4], []), // OIHW8i32o4i
- input_buffer: Buffer(input_pointer: Pointer(float32), float32, [1, 64, 64, 64], [])} // NHWC (pending RFC)
- buffer_map = {input_handle: input_buffer, filter_handle: filter_buffer, output_handle: output_buffer} {
-
- // input cache grows by factor of h_split = 2
- allocate(input.cache: Pointer(global float32), float32, [65536]), storage_scope = global;
-
- // filter cache grows by factor of k_split = 2
- allocate(filter.cache: Pointer(global float32), float32, [4096]), storage_scope = global;
-
- // output cache grows by factor of h_split * k_split = 4
- allocate(output.cache: Pointer(global float32), float32, [65536]), storage_scope = global;
-
- // ko.outer = outer loop split on ko using k_split factor
- for (ko.outer: int32, 0, 2) {
- // ho.outer = outer loop split on ho using h_split factor
- for (ho.outer: int32, 0, 4) {
-
- // input cache read
- // NHWC -> NHWC8h8w32c (pending RFC)
- for (ho.inner: int32, 0, 2) {
- for (wo: int32, 0, 8) {
- for (co: int32, 0, 2) {
- for (hi: int32, 0, 8) {
- for (wi: int32, 0, 8) {
- for (ci: int32, 0, 32) {
- input.cache[((((((ho.inner*32768) + (wo*4096)) + (co*2048)) + (hi*256)) + (wi*32)) + ci)] =
- (float32*)input_pointer[(((((((ho.outer*65536) + (ho.inner*32768)) + (hi*4096)) + (wo*512)) + (wi*64)) + (co*32)) + ci)]
- }
- }
- }
- }
- }
- } // end ho.inner
-
- // filter cache read
- for (ko.inner: int32, 0, 2) {
- for (co: int32, 0, 2) {
- for (ci8: int32, 0, 8) {
- for (ki: int32, 0, 32) {
- for (ci4: int32, 0, 4) {
- filter.cache[(((((ko.inner*2048) + (co*1024)) + (ci8*128)) + (ki*4)) + ci4)] =
- (float32*)filter_pointer[((((((ko.outer*4096) + (ko.inner*2048)) + (co*1024)) + (ci8*128)) + (ki*4)) + ci4)]
- }
- }
- }
- }
- } // end ko.inner
-
- // compute
- for (ko.c.inner: int32, 0, 2) {
- for (ho.c.inner: int32, 0, 2) {
- for (wo.c: int32, 0, 8) {
-
- // init output cache
- for (hi.c.init: int32, 0, 8) {
- for (wi.c.init: int32, 0, 8) {
- for (ki.c.init: int32, 0, 32) {
- output.cache[((((((ho.c.inner*32768) + (wo.c*4096)) + (ko.c.inner*2048)) + (hi.c.init*256)) + (wi.c.init*32)) + ki.c.init)] = 0f32
- }
- }
- }
-
- // convolution
- for (rc.outer: int32, 0, 2) {
- for (hi.c: int32, 0, 8) {
- for (wi.c: int32, 0, 8) {
- for (ki.c: int32, 0, 32) {
- for (rc.inner: int32, 0, 32) {
- output.cache[((((((ho.c.inner*32768) + (wo.c*4096)) + (ko.c.inner*2048)) + (hi.c*256)) + (wi.c*32)) + ki.c)] =
- (
- (float32*)output.cache[((((((ho.c.inner*32768) + (wo.c*4096)) + (ko.c.inner*2048)) + (hi.c*256)) + (wi.c*32)) + ki.c)] +
- (
- (float32*)input.cache[((((((ho.c.inner*32768) + (wo.c*4096)) + (rc.outer*2048)) + (hi.c*256)) + (wi.c*32)) + rc.inner)] *
- (float32*)filter.cache[(((((ko.c.inner*2048) + (rc.outer*1024)) + (floordiv(rc.inner, 4)*128)) + (ki.c*4)) + floormod(rc.inner, 4))]
- )
- )
- }
- }
- }
- }
- }
- } // end wo.c
- } // end ho.c.inner
- } // end ko.c.inner
-
- // cache write
- for (ko.inner: int32, 0, 2) {
- for (ho.inner: int32, 0, 2) {
- for (wo: int32, 0, 8) {
- for (hi: int32, 0, 8) {
- for (wi: int32, 0, 8) {
- for (ki: int32, 0, 32) {
- output_pointer[((((((((ho.outer*131072) + (ho.inner*65536)) + (wo*8192)) + (ko.outer*4096)) + (ko.inner*2048)) + (hi*256)) + (wi*32)) + ki)] =
- (float32*)output.cache[((((((ho.inner*32768) + (wo*4096)) + (ko.inner*2048)) + (hi*256)) + (wi*32)) + ki)]
- }
- }
- }
- }
- } // end ho.inner
- } // end ko.inner
- } // end ho.outer
- } // end ko.outer
-}
-```
-
-# 3x3 conv2d (no padding)
-
-Change from a 1x1 filter to a 3x3 filter. The implication of this change is that `h_split + 1` rather than just `h_split` "full width" slices of the input are required to compute the output. This is due to the fact that the 3x3 filter will "fall off the bottom" of the input and thus the vertically adjacent "full width" slice must be prefetched into the input cache.
-
-The key changes in TIR versus the above are...
-
-1) Increased input cache size to hold the vertically adjacent slice
-
-```
- // input cache grows to hold vertically adjacent slice
- allocate(input.cache: Pointer(global float32), float32, [98304]), storage_scope = global;
-```
-
-2) Loop over `ho.inner` upper bound increased from `h_split` = 2 to `h_split + 1` = 3
-
-```
- for (ho.outer: int32, 0, 4) {
- for (ho.inner: int32, 0, 3) {
- if (((ho.outer*2) + ho.inner) < 8) {
-```
-
-The `if` statement above indicates NOT to prefetch the vertically adjacent slice at the "bottom" of the input since it does not exist.
-
-
-3) Increased filter cache size to hold 3x3 filter
-
-```
- // filter cache grows to hold larger 3x3 filter
- allocate(filter.cache: Pointer(global float32), float32, [36864]), storage_scope = global;
-```
-
-4) Loops over `rh` and `rw` the kernel spatial dimensions:
-```
- for (rh: int32, 0, 3) {
- for (rw: int32, 0, 3) {
-```
-
-## Command
-
-pytest -sv "tests/python/contrib/test_hexagon/test_conv2d_blocked.py::TestConv2dPackedFilter::test_conv2d[1-64-64-0-1-3-128-2-2-float32-llvm]"
-
-## Parameters
-
-| Parameter | Value |
-| --------- | ----- |
-| Batch | 1 |
-| Spatial | 64x64 |
-| Input Ch | 64 |
-| Padding | 0 |
-| Stride | 1 |
-| Filter | 1x1 |
-| Output Ch | 128 |
-| k_split | 2 |
-| h_split | 2 |
-
-## Assumptions
-
-* n/a
-
-## To Do
-
-There may be some opportunity to optimize cache reuse in this case. Consider the loops over `ho.outer` and `ho.inner` and the index calculation `ho.outer * 64k + ho.inner * 32k` into the input pointer:
-
-| ho.outer | ho.inner | ho.outer * 64k + ho.inner * 32k |
-| -------- | -------- | ------------------------------------- |
-| 0 | 0 | 0 |
-| 0 | 1 | 32k |
-| 0 | 2 | 64k (vertical adjacent slice loop 0) |
-| 1 | 0 | 64k |
-| 1 | 1 | 96k |
-| 1 | 2 | 128k (vertical adjacent slice loop 1) |
-| 2 | 0 | 128k |
-| 2 | 1 | 160k |
-| 2 | 2 | 192k (vertical adjacent slice loop 2) |
-| 3 | 0 | 192k |
-| 3 | 1 | 224k |
-| 3 | 2 | (No vertical adjacent slice loop 3) |
-
-Noe that the vertically adjacent slice in loop N (i.e. the loop where `ho.outer` = N) is reused in loop N + 1.
-
-## Annotated TIR
-
-```
-primfn(input_handle: handle, filter_handle: handle, output_handle: handle) -> ()
- attr = {"from_legacy_te_schedule": True, "global_symbol": "default_function", "tir.noalias": True, "target": meta[Target][0]}
- buffers = {output_buffer: Buffer(output_pointer: Pointer(float32), float32, [1, 8, 8, 4, 8, 8, 32], []), // NHWC8h8w32c
- filter_buffer: Buffer(filter_pointer: Pointer(float32), float32, [4, 2, 3, 3, 8, 32, 4], []), // OIHW8i32o4i
- input_buffer: Buffer(input_pointer: Pointer(float32), float32, [1, 64, 64, 64], [])} // NHWC (pending RFC)
- buffer_map = {input_handle: input_buffer, filter_handle: filter_buffer, output_handle: output_buffer} {
- // input cache grows to hold vertically adjacent slice
- allocate(input.cache: Pointer(global float32), float32, [98304]), storage_scope = global;
- // filter cache grows to hold larger 3x3 filter
- allocate(filter.cache: Pointer(global float32), float32, [36864]), storage_scope = global;
- allocate(output.cache: Pointer(global float32), float32, [65536]), storage_scope = global;
- for (ko.outer: int32, 0, 2) {
- for (ho.outer: int32, 0, 4) {
- // input cache read
- // NHWC -> NHWC8h8w32c (pending RFC)
- for (ho.inner: int32, 0, 3) {
- if (((ho.outer*2) + ho.inner) < 8) {
- for (wo: int32, 0, 8) {
- for (co: int32, 0, 2) {
- for (hi: int32, 0, 8) {
- for (wi: int32, 0, 8) {
- for (ci: int32, 0, 32) {
- input.cache[((((((ho.inner*32768) + (wo*4096)) + (co*2048)) + (hi*256)) + (wi*32)) + ci)] =
- (float32*)input_pointer[(((((((ho.outer*65536) + (ho.inner*32768)) + (hi*4096)) + (wo*512)) + (wi*64)) + (co*32)) + ci)]
- }
- }
- }
- }
- }
- }
- }
- // filter cache read
- for (ko.inner: int32, 0, 2) {
- for (co: int32, 0, 2) {
- for (rh: int32, 0, 3) {
- for (rw: int32, 0, 3) {
- for (ci8: int32, 0, 8) {
- for (ki: int32, 0, 32) {
- for (ci4: int32, 0, 4) {
- filter.cache[(((((((ko.inner*18432) + (co*9216)) + (rh*3072)) + (rw*1024)) + (ci8*128)) + (ki*4)) + ci4)] =
- (float32*)filter_pointer[((((((((ko.outer*36864) + (ko.inner*18432)) + (co*9216)) + (rh*3072)) + (rw*1024)) + (ci8*128)) + (ki*4)) + ci4)]
- }
- }
- }
- } // end rw
- } // end rh
- }
- }
- for (ko.c.inner: int32, 0, 2) {
- for (ho.c.inner: int32, 0, 2) {
- for (wo.c: int32, 0, 8) {
- for (hi.c.init: int32, 0, 8) {
- for (wi.c.init: int32, 0, 8) {
- for (ki.c.init: int32, 0, 32) {
- output.cache[((((((ho.c.inner*32768) + (wo.c*4096)) + (ko.c.inner*2048)) + (hi.c.init*256)) + (wi.c.init*32)) + ki.c.init)] = 0f32
- }
- }
- }
- for (rc.outer: int32, 0, 2) {
- for (hi.c: int32, 0, 8) {
- for (wi.c: int32, 0, 8) {
- for (rh: int32, 0, 3) {
- for (rw: int32, 0, 3) {
- for (ki.c: int32, 0, 32) {
- for (rc.inner: int32, 0, 32) {
- output.cache[((((((ho.c.inner*32768) + (wo.c*4096)) + (ko.c.inner*2048)) + (hi.c*256)) + (wi.c*32)) + ki.c)] =
- (
- (float32*)output.cache[((((((ho.c.inner*32768) + (wo.c*4096)) + (ko.c.inner*2048)) + (hi.c*256)) + (wi.c*32)) + ki.c)] +
- (
- (float32*)input.cache[((((((((floordiv((hi.c + rh), 8)*32768) + (ho.c.inner*32768)) + (floordiv((wi.c + rw), 8)*4096)) + (wo.c*4096)) + (rc.outer*2048)) + (floormod((hi.c + rh), 8)*256)) + (floormod((wi.c + rw), 8)*32)) + rc.inner)] *
- (float32*)filter.cache[(((((((ko.c.inner*18432) + (rc.outer*9216)) + (rh*3072)) + (rw*1024)) + (floordiv(rc.inner, 4)*128)) + (ki.c*4)) + floormod(rc.inner, 4))]
- )
- )
- }
- }
- } // end rw
- } // end rh
- }
- }
- }
- } // end wo.c
- } // end ho.c.inner
- } // end ko.c.inner
- for (ko.inner: int32, 0, 2) {
- for (ho.inner: int32, 0, 2) {
- for (wo: int32, 0, 8) {
- for (hi: int32, 0, 8) {
- for (wi: int32, 0, 8) {
- for (ki: int32, 0, 32) {
- output_pointer[((((((((ho.outer*131072) + (ho.inner*65536)) + (wo*8192)) + (ko.outer*4096)) + (ko.inner*2048)) + (hi*256)) + (wi*32)) + ki)] =
- (float32*)output.cache[((((((ho.inner*32768) + (wo*4096)) + (ko.inner*2048)) + (hi*256)) + (wi*32)) + ki)]
- }
- }
- }
- }
- } // end ho.inner
- } // end ko.inner
- } // end ho.outer
- } // end ko.outer
-}```
diff --git a/tests/python/contrib/test_hexagon/conv2d/test_conv2d_conv2d.md b/tests/python/contrib/test_hexagon/conv2d/test_conv2d_conv2d.md
deleted file mode 100644
index 3671d90c24..0000000000
--- a/tests/python/contrib/test_hexagon/conv2d/test_conv2d_conv2d.md
+++ /dev/null
@@ -1,986 +0,0 @@
-<!--- Licensed to the Apache Software Foundation (ASF) under one -->
-<!--- or more contributor license agreements. See the NOTICE file -->
-<!--- distributed with this work for additional information -->
-<!--- regarding copyright ownership. The ASF licenses this file -->
-<!--- to you under the Apache License, Version 2.0 (the -->
-<!--- "License"); you may not use this file except in compliance -->
-<!--- with the License. You may obtain a copy of the License at -->
-
-<!--- http://www.apache.org/licenses/LICENSE-2.0 -->
-
-<!--- Unless required by applicable law or agreed to in writing, -->
-<!--- software distributed under the License is distributed on an -->
-<!--- "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY -->
-<!--- KIND, either express or implied. See the License for the -->
-<!--- specific language governing permissions and limitations -->
-<!--- under the License. -->
-
-Hexagon conv2d -> conv2d schedules
-
-# Baseline conv2d -> conv2d
-
-This is a baseline 1x1 conv2d -> 1x1 conv2d schedule for Hexagon.
-
-## Command
-
-pytest -sv "tests/python/contrib/test_hexagon/test_conv2d_conv2d.py::TestConv2dConv2dPackedFilter::test_conv2d[1-64-128-0-1-1-128-1-1-128-1-1-float32-llvm]"
-
-## Parameters
-
-| Parameter | Value |
-| ------------------------ | ----- |
-| Batch | 1 |
-| Input Size | 64x64 |
-| Input Channel | 128 |
-| Conv2d #1 Pad | 0 |
-| Conv2d #1 Stride | 1 |
-| Conv2d #1 Kernel Size | 1 |
-| Conv2d #1 Output Channel | 128 |
-| Conv2d #2 Stride | 1 |
-| Conv2d #2 Kernel Size | 1 |
-| Conv2d #2 Output Channel | 128 |
-| k_split | 1 |
-| h_split | 1 |
-
-## Constants
-
-| Constant | Value |
-| ------------------ | ----- |
-| Conv2d #2 Pad | 0 |
-| Conv2d #1 Dilation | 1 |
-| Conv2d #2 Dilation | 1 |
-
-## Shapes and Layouts
-
-The input is provided and padded in logical layout and then packed into its physical layout prior to compute. Logical layout / shape information is provided as a reference for physical tensors.
-
-| Tensor | Type | Layout | Shape | Logical Layout | Logical Shape |
-| ------------ | -------- | ----------- | ---------------------- | -------------- | ---------------- |
-| Input | Logical | NHWC | [1, 64, 64, 128] | | |
-| Padded Input | Logical | NHWC | [1, 64, 64, 128] | | |
-| Packed Input | Physical | NHWC8h8w32c | [1, 8, 8, 4, 8, 8, 32] | NHWC | [1, 64, 64, 128] |
-| Filter 1 | Physical | OIHW8i32o4i | [4, 4, 1, 1, 8, 32, 4] | OIHW | [128, 128, 1, 1] |
-| Temp Output | Physical | NHWC8h8w32c | [1, 8, 8, 4, 8, 8, 32] | NHWC | [1, 64, 64, 128] |
-| Filter 2 | Physical | OIHW8i32o4i | [4, 4, 1, 1, 8, 32, 4] | OIHW | [128, 128, 1, 1] |
-| Output | Physical | NHWC8h8w32c | [1, 8, 8, 4, 8, 8, 32] | NHWC | [1, 64, 64, 128] |
-
-## Schedule
-
-This is the conv2d compute schedule:
-
-```
- for (ko.outer: int32, 0, 4) {
- for (ho.outer: int32, 0, 8) {
-
- // input cache read
-
- for (ko.outer_1: int32, 0, 4) {
-
- // filter #1 cache read
-
- // conv2d #1
- for (wo: int32, 0, 8) {
- for (rc.outer: int32, 0, 4) {
- for (hi: int32, 0, 8) {
- for (wi: int32, 0, 8) {
- for (ki: int32, 0, 32) {
- for (rc.inner: int32, 0, 32) {
- } // end ko.outer_1
-
- // filter #2 cache read
-
- // conv2d #2
- for (wo.c: int32, 0, 8) {
- for (rc.outer_1: int32, 0, 4) {
- for (hi.c: int32, 0, 8) {
- for (wi.c: int32, 0, 8) {
- for (ki.c: int32, 0, 32) {
- for (rc.inner_1: int32, 0, 32) {
-
- // write back output cache
-
- } // end ho.outer
- } // end ko.outer
-```
-
-Note that conv2d #1 has an independent loop over the channel out `ko.outer_1` dimension. This is because the output channels of conv2d #1 are the input channels to conv2d #2 and we compute over all input channels for each conv2d so we must compute over all output channels of conv2d #1 before we compute conv2d #2.
-
-```
- for (ko.outer_1: int32, 0, 2) {
-```
-
-## Cache Usage
-
-*Input Cache*
-
-We compute over the WC8h8w32c portion of the input so we need 8 * 4 * 8 * 8 * 32 = 64kb for the input cache.
-
-```
- allocate(packed_input.global: Pointer(global float32), float32, [65536]), storage_scope = global;
-```
-
-*Filter Cache*
-
-We compute over the IHW8i32o4i portion of each filter so we need 4 * 1 * 1 * 8 * 32 * 4 = 4kb filter cache.
-
-```
- allocate(packed_filter.global: Pointer(global float32), float32, [4096]), storage_scope = global;
-```
-
-Note that there is just one cache which is reused for conv2d / filter #1 and conv2d / filter #2.
-
-*Output Cache*
-
-We compute over the WK8h832k portion of the output where `k` denotes the output channel. The output cache is computed for each `ko.outer` which means it should be W * 8h * 8w * 32k = 8 * 8 * 8 * 32 = 16kb. And, in fact, this is the case for a single conv2d case. But, as already noted, for this conv2d -> conv2d case "the output channels of conv2d #1 are the input channels to conv2d #2 and we compute over all input channels for each conv2d so we must compute over all output channels of [...]
-
-```
- allocate(temp_output: Pointer(global float32), float32, [65536]), storage_scope = global;
-```
-
-Note that the input cache is reused to store the results of conv2d #2.
-
-## Assumptions
-
-* n/a
-
-## To Do
-
-* Reuse of the input cache to store the results of conv2d #2 could be problematic for async copy. e.g.
-
-```
-slice 0: global -> load -> cache0 -> conv2d_0 -> cache1 -> conv2d_1 -> cache0 -> store -> global
-slice 1: global -> load -> cache0 -> conv2d_0 -> cache1 -> conv2d_1 -> cache0 -> store -> global
-```
-
-In this case the store from slice 0: cache0 -> store -> global
-can potentially block the load in slice 1: global -> load -> cache0
-
-StorageRewrite is responsible for planning these caches, we'll need to understand how to avoid this for the async case.
-
-## Annotated TIR
-
-```
-primfn(placeholder_3: handle, placeholder_4: handle, placeholder_5: handle, output_1: handle) -> ()
- attr = {"from_legacy_te_schedule": True, "global_symbol": "main", "tir.noalias": True}
- buffers = {output: Buffer(output_2: Pointer(float32), float32, [1, 8, 8, 4, 8, 8, 32], []), // nhw8h8w32c
- placeholder_2: Buffer(placeholder_6: Pointer(float32), float32, [4, 4, 1, 1, 8, 32, 4], []), // oihw8i32o4i
- placeholder_1: Buffer(placeholder_7: Pointer(float32), float32, [4, 4, 1, 1, 8, 32, 4], []), // oihw8i32o4i
- placeholder: Buffer(placeholder_8: Pointer(float32), float32, [1, 64, 64, 128], [])} // nhwc
- buffer_map = {placeholder_3: placeholder, placeholder_4: placeholder_1, placeholder_5: placeholder_2, output_1: output} {
- allocate(packed_input.global: Pointer(global float32), float32, [65536]), storage_scope = global;
- allocate(temp_output: Pointer(global float32), float32, [65536]), storage_scope = global;
- allocate(packed_filter.global: Pointer(global float32), float32, [4096]), storage_scope = global;
- for (ko.outer: int32, 0, 4) {
- for (ho.outer: int32, 0, 8) {
-
- // input cache read
- for (wo: int32, 0, 8) {
- for (co: int32, 0, 4) {
- for (hi: int32, 0, 8) {
- for (wi: int32, 0, 8) {
- for (ci: int32, 0, 32) {
- packed_input.global[(((((wo*8192) + (co*2048)) + (hi*256)) + (wi*32)) + ci)] =
- (float32*)placeholder_8[((((((ho.outer*65536) + (hi*8192)) + (wo*1024)) + (wi*128)) + (co*32)) + ci)]
- }
- }
- }
- }
- }
-
- // NOTE: compute over all output channels of conv2d #1 before computing conv2d #2
- for (ko.outer_1: int32, 0, 4) {
-
- // filter #1 cache read
- for (co: int32, 0, 4) {
- for (cio: int32, 0, 8) {
- for (ki: int32, 0, 32) {
- for (cii: int32, 0, 4) {
- packed_filter.global[((((co*1024) + (cio*128)) + (ki*4)) + cii)] =
- (float32*)placeholder_7[(((((ko.outer_1*4096) + (co*1024)) + (cio*128)) + (ki*4)) + cii)]
- }
- }
- }
- }
-
- // conv2d #1
- for (wo: int32, 0, 8) {
-
- // init temp output to zero
- for (hi.init: int32, 0, 8) {
- for (wi.init: int32, 0, 8) {
- for (ki.init: int32, 0, 32) {
- temp_output[(((((wo*8192) + (ko.outer_1*2048)) + (hi.init*256)) + (wi.init*32)) + ki.init)] = 0f32
- }
- }
- }
-
- // compute
- for (rc.outer: int32, 0, 4) {
- for (hi: int32, 0, 8) {
- for (wi: int32, 0, 8) {
- for (ki: int32, 0, 32) {
- for (rc.inner: int32, 0, 32) {
- temp_output[(((((wo*8192) + (ko.outer_1*2048)) + (hi*256)) + (wi*32)) + ki)] =
- (
- (float32*)temp_output[(((((wo*8192) + (ko.outer_1*2048)) + (hi*256)) + (wi*32)) + ki)] +
- (
- (float32*)packed_input.global[(((((wo*8192) + (rc.outer*2048)) + (hi*256)) + (wi*32)) + rc.inner)] *
- (float32*)packed_filter.global[((((rc.outer*1024) + (floordiv(rc.inner, 4)*128)) + (ki*4)) + floormod(rc.inner, 4))]
- )
- )
- }
- }
- }
- }
- }
- }
- }
-
- // filter #2 cache read
- // NOTE: reusing same filter cache
- for (co: int32, 0, 4) {
- for (cio: int32, 0, 8) {
- for (ki: int32, 0, 32) {
- for (cii: int32, 0, 4) {
- packed_filter.global[((((co*1024) + (cio*128)) + (ki*4)) + cii)] =
- (float32*)placeholder_6[(((((ko.outer*4096) + (co*1024)) + (cio*128)) + (ki*4)) + cii)]
- }
- }
- }
- }
-
- // conv2d #2
- for (wo.c: int32, 0, 8) {
-
- // init output cache to zero
- // NOTE: reusing the input cache as the output cache
- for (hi.c.init: int32, 0, 8) {
- for (wi.c.init: int32, 0, 8) {
- for (ki.c.init: int32, 0, 32) {
- packed_input.global[((((wo.c*2048) + (hi.c.init*256)) + (wi.c.init*32)) + ki.c.init)] = 0f32
- }
- }
- }
-
- // compute
- for (rc.outer_1: int32, 0, 4) {
- for (hi.c: int32, 0, 8) {
- for (wi.c: int32, 0, 8) {
- for (ki.c: int32, 0, 32) {
- for (rc.inner_1: int32, 0, 32) {
- packed_input.global[((((wo.c*2048) + (hi.c*256)) + (wi.c*32)) + ki.c)] =
- (
- (float32*)packed_input.global[((((wo.c*2048) + (hi.c*256)) + (wi.c*32)) + ki.c)] +
- (
- (float32*)temp_output[(((((wo.c*8192) + (rc.outer_1*2048)) + (hi.c*256)) + (wi.c*32)) + rc.inner_1)] *
- (float32*)packed_filter.global[((((rc.outer_1*1024) + (floordiv(rc.inner_1, 4)*128)) + (ki.c*4)) + floormod(rc.inner_1, 4))]
- )
- )
- }
- }
- }
- }
- }
- }
-
- // write back output cache
- for (wo_1: int32, 0, 8) {
- for (hi_1: int32, 0, 8) {
- for (wi_1: int32, 0, 8) {
- for (ki_1: int32, 0, 32) {
- output_2[((((((ho.outer*65536) + (wo_1*8192)) + (ko.outer*2048)) + (hi_1*256)) + (wi_1*32)) + ki_1)] =
- (float32*)packed_input.global[((((wo_1*2048) + (hi_1*256)) + (wi_1*32)) + ki_1)]
- }
- }
- }
- }
- }
- }
-}
-```
-
-# Split on Channel Out and Height
-
-Uses parameters `k_split` and `h_split` which creates a loop split on the outer channel out `ko` and height `ho` loops creating `outer` and `inner` loops for each split. The cache reads and writes are computed at `ho.outer` which means that cache allocation grow in proportion to `k_split` and `h_split` factors.
-
-## Command
-
-pytest -sv "tests/python/contrib/test_hexagon/test_conv2d_conv2d.py::TestConv2dConv2dPackedFilter::test_conv2d[1-64-128-0-1-1-128-1-1-128-2-2-float32-llvm]"
-
-## Parameters
-
-| Parameter | Value |
-| ------------------------ | ----- |
-| Batch | 1 |
-| Input Size | 64x64 |
-| Input Channel | 128 |
-| Conv2d #1 Pad | 0 |
-| Conv2d #1 Stride | 1 |
-| Conv2d #1 Kernel Size | 1 |
-| Conv2d #1 Output Channel | 128 |
-| Conv2d #2 Stride | 1 |
-| Conv2d #2 Kernel Size | 1 |
-| Conv2d #2 Output Channel | 128 |
-| k_split | 2 ^ |
-| h_split | 2 ^ |
-
-^ Changes from above
-
-## Constants
-
-| Constant | Value |
-| ------------------ | ----- |
-| Conv2d #2 Pad | 0 |
-| Conv2d #1 Dilation | 1 |
-| Conv2d #2 Dilation | 1 |
-
-## Shapes and Layouts
-
-The input is provided and padded in logical layout and then packed into its physical layout prior to compute. Logical layout / shape information is provided as a reference for physical tensors.
-
-| Tensor | Type | Layout | Shape | Logical Layout | Logical Shape |
-| ------------ | -------- | ----------- | ---------------------- | -------------- | ---------------- |
-| Input | Logical | NHWC | [1, 64, 64, 128] | | |
-| Padded Input | Logical | NHWC | [1, 64, 64, 128] | | |
-| Packed Input | Physical | NHWC8h8w32c | [1, 8, 8, 4, 8, 8, 32] | NHWC | [1, 64, 64, 128] |
-| Filter 1 | Physical | OIHW8i32o4i | [4, 4, 1, 1, 8, 32, 4] | OIHW | [128, 128, 1, 1] |
-| Temp Output | Physical | NHWC8h8w32c | [1, 8, 8, 4, 8, 8, 32] | NHWC | [1, 64, 64, 128] |
-| Filter 2 | Physical | OIHW8i32o4i | [4, 4, 1, 1, 8, 32, 4] | OIHW | [128, 128, 1, 1] |
-| Output | Physical | NHWC8h8w32c | [1, 8, 8, 4, 8, 8, 32] | NHWC | [1, 64, 64, 128] |
-
-## Schedule
-
-This is the conv2d compute schedule:
-
-```
- for (ko.outer: int32, 0, 2) {
- for (ho.outer: int32, 0, 4) {
-
- // input cache read
- for (ho.inner: int32, 0, 2) {
- ...
- }
-
- for (ko.outer_1: int32, 0, 2) {
-
- // filter #1 cache read
- for (ko.inner: int32, 0, 2) {
- ...
- }
-
- // conv2d #1
- for (ko.inner: int32, 0, 2) {
- for (ho.inner: int32, 0, 2) {
- for (wo: int32, 0, 8) {
- for (rc.outer: int32, 0, 4) {
- for (hi: int32, 0, 8) {
- for (wi: int32, 0, 8) {
- for (ki: int32, 0, 32) {
- for (rc.inner: int32, 0, 32) {
- } // end ko.outer_1
-
- // filter #2 cache read
- for (ko.inner: int32, 0, 2) {
- ...
- }
-
- // conv2d #2
- for (ko.c.inner: int32, 0, 2) {
- for (ho.c.inner: int32, 0, 2) {
- for (wo.c: int32, 0, 8) {
- for (rc.outer_1: int32, 0, 4) {
- for (hi.c: int32, 0, 8) {
- for (wi.c: int32, 0, 8) {
- for (ki.c: int32, 0, 32) {
- for (rc.inner_1: int32, 0, 32) {
-
- // write back output cache
-
- } // end ho.outer
- } // end ko.outer
-```
-
-The major change here versus above is the presence of `inner` loops for both channel out `ko` and height `ho` dimensions created from the `k_split` and `h_split` schedule parameters respectively, for example:
-
-
-```
- for (ko.c.inner: int32, 0, 2) {
- for (ho.c.inner: int32, 0, 2) {
-```
-
-The effect of this change is increased cache usage given where the caches are computed in the schedule. Specifically, the input cache is now computed over `ho.inner` and the filter caches are computed over `ko.inner` which will grow the size of the cache. Details below.
-
-(Same as above) Note that conv2d #1 has an independent loop over the channel out `ko.outer_1` dimension. This is because the output channels of conv2d #1 are the input channels to conv2d #2 and we compute over all input channels for each conv2d so we must compute over all output channels of conv2d #1 before we compute conv2d #2.
-
-```
- for (ko.outer_1: int32, 0, 2) {
-```
-
-## Cache Usage
-
-*Input Cache*
-
-The input cache grows by a factor of `h_split = 2` compared with above:
-
-```
- allocate(packed_input.global: Pointer(global float32), float32, [131072]), storage_scope = global;
-```
-
-*Filter Cache*
-
-The filter cache grows by a factor of `k_split = 2` compared with above:
-
-```
- allocate(packed_filter.global: Pointer(global float32), float32, [8192]), storage_scope = global;
-```
-
-(Same as above) Note that there is just one cache which is reused for conv2d / filter #1 and conv2d / filter #2.
-
-*Output Cache*
-
-The output cache grows by a factor of `k_split = 2` compared with above:
-
-```
- allocate(temp_output: Pointer(global float32), float32, [131072]), storage_scope = global;
-```
-
-(Same as above) Note that the input cache is reused to store the results of conv2d #2.
-
-## Assumptions
-
-* n/a
-
-## To Do
-
-* n/a
-
-## Annotated TIR
-
-```
-primfn(placeholder_3: handle, placeholder_4: handle, placeholder_5: handle, output_1: handle) -> ()
- attr = {"from_legacy_te_schedule": True, "global_symbol": "main", "tir.noalias": True}
- buffers = {output: Buffer(output_2: Pointer(float32), float32, [1, 8, 8, 4, 8, 8, 32], []), // nhw8h8w32c
- placeholder_2: Buffer(placeholder_6: Pointer(float32), float32, [4, 4, 1, 1, 8, 32, 4], []), // oihw8i32o4i
- placeholder_1: Buffer(placeholder_7: Pointer(float32), float32, [4, 4, 1, 1, 8, 32, 4], []), // oihw8i32o4i
- placeholder: Buffer(placeholder_8: Pointer(float32), float32, [1, 64, 64, 128], [])} // nhwc
- buffer_map = {placeholder_3: placeholder, placeholder_4: placeholder_1, placeholder_5: placeholder_2, output_1: output} {
- allocate(packed_input.global: Pointer(global float32), float32, [131072]), storage_scope = global;
- allocate(temp_output: Pointer(global float32), float32, [131072]), storage_scope = global;
- allocate(packed_filter.global: Pointer(global float32), float32, [8192]), storage_scope = global;
- for (ko.outer: int32, 0, 2) {
- for (ho.outer: int32, 0, 4) {
-
- // input cache read
- for (ho.inner: int32, 0, 2) {
- for (wo: int32, 0, 8) {
- for (co: int32, 0, 4) {
- for (hi: int32, 0, 8) {
- for (wi: int32, 0, 8) {
- for (ci: int32, 0, 32) {
- packed_input.global[((((((ho.inner*65536) + (wo*8192)) + (co*2048)) + (hi*256)) + (wi*32)) + ci)] =
- (float32*)placeholder_8[(((((((ho.outer*131072) + (ho.inner*65536)) + (hi*8192)) + (wo*1024)) + (wi*128)) + (co*32)) + ci)]
- }
- }
- }
- }
- }
- }
-
- // NOTE: compute over all output channels of conv2d #1 before computing conv2d #2
- for (ko.outer_1: int32, 0, 2) {
-
- // filter #1 cache read
- for (ko.inner: int32, 0, 2) {
- for (co: int32, 0, 4) {
- for (cio: int32, 0, 8) {
- for (ki: int32, 0, 32) {
- for (cii: int32, 0, 4) {
- packed_filter.global[(((((ko.inner*4096) + (co*1024)) + (cio*128)) + (ki*4)) + cii)] =
- (float32*)placeholder_7[((((((ko.outer_1*8192) + (ko.inner*4096)) + (co*1024)) + (cio*128)) + (ki*4)) + cii)]
- }
- }
- }
- }
- }
-
- // conv2d #1
- for (ko.inner: int32, 0, 2) {
- for (ho.inner: int32, 0, 2) {
- for (wo: int32, 0, 8) {
-
- // init temp output to zero
- for (hi.init: int32, 0, 8) {
- for (wi.init: int32, 0, 8) {
- for (ki.init: int32, 0, 32) {
- temp_output[(((((((ho.inner*65536) + (wo*8192)) + (ko.outer_1*4096)) + (ko.inner*2048)) + (hi.init*256)) + (wi.init*32)) + ki.init)] = 0f32
- }
- }
- }
-
- // compute
- for (rc.outer: int32, 0, 4) {
- for (hi: int32, 0, 8) {
- for (wi: int32, 0, 8) {
- for (ki: int32, 0, 32) {
- for (rc.inner: int32, 0, 32) {
- temp_output[(((((((ho.inner*65536) + (wo*8192)) + (ko.outer_1*4096)) + (ko.inner*2048)) + (hi*256)) + (wi*32)) + ki)] =
- (
- (float32*)temp_output[(((((((ho.inner*65536) + (wo*8192)) + (ko.outer_1*4096)) + (ko.inner*2048)) + (hi*256)) + (wi*32)) + ki)] +
- (
- (float32*)packed_input.global[((((((ho.inner*65536) + (wo*8192)) + (rc.outer*2048)) + (hi*256)) + (wi*32)) + rc.inner)] *
- (float32*)packed_filter.global[(((((ko.inner*4096) + (rc.outer*1024)) + (floordiv(rc.inner, 4)*128)) + (ki*4)) + floormod(rc.inner, 4))]
- )
- )
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- // filter #2 cache read
- // NOTE: reusing same filter cache
- for (ko.inner: int32, 0, 2) {
- for (co: int32, 0, 4) {
- for (cio: int32, 0, 8) {
- for (ki: int32, 0, 32) {
- for (cii: int32, 0, 4) {
- packed_filter.global[(((((ko.inner*4096) + (co*1024)) + (cio*128)) + (ki*4)) + cii)] =
- (float32*)placeholder_6[((((((ko.outer*8192) + (ko.inner*4096)) + (co*1024)) + (cio*128)) + (ki*4)) + cii)]
- }
- }
- }
- }
- }
-
- // conv2d #2
- for (ko.c.inner: int32, 0, 2) {
- for (ho.c.inner: int32, 0, 2) {
- for (wo.c: int32, 0, 8) {
-
- // init output cache to zero
- // NOTE: reusing the input cache as the output cache
- for (hi.c.init: int32, 0, 8) {
- for (wi.c.init: int32, 0, 8) {
- for (ki.c.init: int32, 0, 32) {
- packed_input.global[((((((ho.c.inner*32768) + (wo.c*4096)) + (ko.c.inner*2048)) + (hi.c.init*256)) + (wi.c.init*32)) + ki.c.init)] = 0f32
- }
- }
- }
-
- // compute
- for (rc.outer_1: int32, 0, 4) {
- for (hi.c: int32, 0, 8) {
- for (wi.c: int32, 0, 8) {
- for (ki.c: int32, 0, 32) {
- for (rc.inner_1: int32, 0, 32) {
- packed_input.global[((((((ho.c.inner*32768) + (wo.c*4096)) + (ko.c.inner*2048)) + (hi.c*256)) + (wi.c*32)) + ki.c)] =
- (
- (float32*)packed_input.global[((((((ho.c.inner*32768) + (wo.c*4096)) + (ko.c.inner*2048)) + (hi.c*256)) + (wi.c*32)) + ki.c)] +
- (
- (float32*)temp_output[((((((ho.c.inner*65536) + (wo.c*8192)) + (rc.outer_1*2048)) + (hi.c*256)) + (wi.c*32)) + rc.inner_1)] *
- (float32*)packed_filter.global[(((((ko.c.inner*4096) + (rc.outer_1*1024)) + (floordiv(rc.inner_1, 4)*128)) + (ki.c*4)) + floormod(rc.inner_1, 4))]
- )
- )
- }
- }
- }
- }
- }
- }
- }
- }
-
- // write back output cache
- for (ko.inner_1: int32, 0, 2) {
- for (ho.inner_1: int32, 0, 2) {
- for (wo_1: int32, 0, 8) {
- for (hi_1: int32, 0, 8) {
- for (wi_1: int32, 0, 8) {
- for (ki_1: int32, 0, 32) {
- output_2[((((((((ho.outer*131072) + (ho.inner_1*65536)) + (wo_1*8192)) + (ko.outer*4096)) + (ko.inner_1*2048)) + (hi_1*256)) + (wi_1*32)) + ki_1)] =
- (float32*)packed_input.global[((((((ho.inner_1*32768) + (wo_1*4096)) + (ko.inner_1*2048)) + (hi_1*256)) + (wi_1*32)) + ki_1)]
- }
- }
- }
- }
- }
- }
- }
- }
-}
-```
-
-# 3x3 conv2d -> conv2d (no padding)
-
-Change from a 1x1 filter to a 3x3 filter.
-
-## Command
-
-pytest -sv "tests/python/contrib/test_hexagon/test_conv2d_conv2d.py::TestConv2dConv2dPackedFilter::test_conv2d[1-64-128-0-1-3-128-1-3-128-2-2-float32-llvm]"
-
-## Parameters
-
-| Parameter | Value |
-| ------------------------ | ----- |
-| Batch | 1 |
-| Input Size | 64x64 |
-| Input Channel | 128 |
-| Conv2d #1 Pad | 0 |
-| Conv2d #1 Stride | 1 |
-| Conv2d #1 Kernel Size | 3 ^ |
-| Conv2d #1 Output Channel | 128 |
-| Conv2d #2 Stride | 1 |
-| Conv2d #2 Kernel Size | 3 ^ |
-| Conv2d #2 Output Channel | 128 |
-| k_split | 2 |
-| h_split | 2 |
-
-^ Changes from above
-
-## Constants
-
-| Constant | Value |
-| ------------------ | ----- |
-| Conv2d #2 Pad | 0 |
-| Conv2d #1 Dilation | 1 |
-| Conv2d #2 Dilation | 1 |
-
-## Shapes and Layouts
-
-The input is provided and padded in logical layout and then packed into its physical layout prior to compute. Logical layout / shape information is provided as a reference for physical tensors.
-
-| Tensor | Type | Layout | Shape | Logical Layout | Logical Shape |
-| ------------ | -------- | ----------- | ---------------------- | -------------- | ---------------- |
-| Input | Logical | NHWC | [1, 64, 64, 128] | | |
-| Padded Input | Logical | NHWC | [1, 64, 64, 128] | | |
-| Packed Input | Physical | NHWC8h8w32c | [1, 8, 8, 4, 8, 8, 32] | NHWC | [1, 64, 64, 128] |
-| Filter 1 | Physical | OIHW8i32o4i | [4, 4, 3, 3, 8, 32, 4] | OIHW | [128, 128, 3, 3] |
-| Temp Output | Physical | NHWC8h8w32c | [1, 8, 8, 4, 8, 8, 32] | NHWC | [1, 62, 62, 128] |
-| Filter 2 | Physical | OIHW8i32o4i | [4, 4, 3, 3, 8, 32, 4] | OIHW | [128, 128, 3, 3] |
-| Output | Physical | NHWC8h8w32c | [1, 8, 8, 4, 8, 8, 32] | NHWC | [1, 60, 60, 128] |
-
-## Schedule
-
-This is the conv2d compute schedule:
-
-```
- for (ko.outer: int32, 0, 2) {
- for (ho.outer: int32, 0, 4) {
-
- for (ko.outer_1: int32, 0, 2) {
- for (ho.outer_1: int32, 0, 2) {
-
- // input cache read
- for (ho.inner: int32, 0, 3) {
- if ((((ho.outer_1*2) + (ho.outer*2)) + ho.inner) < 8) {
- ...
- }
- }
-
- // filter #1 cache read
- for (ko.inner: int32, 0, 2) {
- ...
- }
-
- // conv2d #1
- for (ko.inner: int32, 0, 2) {
- for (ho.inner: int32, 0, 2) {
- for (wo: int32, 0, 8) {
- if (((ho.outer_1*2) + ho.inner) < 3) {
- if ((((ho.outer_1*2) + (ho.outer*2)) + ho.inner) < 8) {
- for (rc.outer: int32, 0, 4) {
- for (hi: int32, 0, 8) {
- for (wi: int32, 0, 8) {
- for (rh: int32, 0, 3) {
- for (rw: int32, 0, 3) {
- for (ki: int32, 0, 32) {
- for (rc.inner: int32, 0, 32) {
- } // end ho.outer_1
- } // end ko.outer_1
-
- // filter #2 cache read
- for (ko.inner: int32, 0, 2) {
- ...
- }
-
- // conv2d #2
- for (ko.c.inner: int32, 0, 2) {
- for (ho.c.inner: int32, 0, 2) {
- for (wo.c: int32, 0, 8) {
- for (rc.outer_1: int32, 0, 4) {
- for (hi.c: int32, 0, 8) {
- for (wi.c: int32, 0, 8) {
- for (rh_1: int32, 0, 3) {
- for (rw_1: int32, 0, 3) {
- for (ki.c: int32, 0, 32) {
- for (rc.inner_1: int32, 0, 32) {
-
- // write back output cache
-
- } // end ho.outer
- } // end ko.outer
-```
-
-There are two major changes here:
-
-1) The first change is the farily obvious presence of the kernel height `rh` and width `rw` iterators, for example:
-
-```
- for (rh_1: int32, 0, 3) {
- for (rw_1: int32, 0, 3) {
-```
-
-The effect of this change is to grow the filter cache by the size of the kernel. Details below.
-
-2) The second change is a bit more tricky. Remember that we want to produce `h_split` (2) "full width" and "full channel depth" slices from each conv2d. Given the 3x3 kernel size there are several changes to the schedule regarding the handling of the height dimension.
-
-First, notice that in order to produce `h_split` (2) "full width" and "full channel depth" slices for conv2d #1 we will need `h_split + 1` (3) "full width" and "full channel depth" slices of the input. This is because a 3x3 kernel (as opposed to a 1x1 kernel) creates a many-to-one relationship between the spatial coordinates of the input relative to the output. To illustrate, the 3x3 kernel will "fall off the bottom" of the 2nd input slice requiring values from the vertically adjacent [...]
-
-```
- for (ho.inner: int32, 0, 3) {
- if ((((ho.outer_1*2) + (ho.outer*2)) + ho.inner) < 8) {
-```
-
-The `if` statement above indicates NOT to prefetch the vertically adjacent slice at the "bottom" of the input since it does not exist.
-
-Second, notice that conv2d #1 must produce sufficient output in the height dimension before conv2d #2 can proceed. This is similar to the requirement that conv2d #1 in regard to the channel out dimension, but also different because we do not require *all* output in the height dimenson only *sufficient* output in the height dimension. How much output in the height dimension is required? The intuitive guess might be `h_split + 1` (3) slices but that is wrong and the reason is that the o [...]
-
-```
- for (ho.outer_1: int32, 0, 2) {
-```
-
-There are similar `if` statements in the conv2d compute schedule to prevent computing off the "bottom" of the input and output.
-
-(Same as above) Note that conv2d #1 has an independent loop over the channel out `ko.outer_1` dimension. This is because the output channels of conv2d #1 are the input channels to conv2d #2 and we compute over all input channels for each conv2d so we must compute over all output channels of conv2d #1 before we compute conv2d #2.
-
-```
- for (ko.outer_1: int32, 0, 2) {
-```
-
-## Cache Usage
-
-*Input Cache*
-
-The input cache grows to hold the vertically adjacent slice:
-
-```
- allocate(packed_input.global: Pointer(global float32), float32, [196608]), storage_scope = global;
-```
-
-*Filter Cache*
-
-The filter cache grows to hold the 3x3 filter:
-
-```
- allocate(packed_filter.global: Pointer(global float32), float32, [73728]), storage_scope = global;
-```
-
-(Same as above) Note that there is just one cache which is reused for conv2d / filter #1 and conv2d / filter #2.
-
-*Output Cache*
-
-The output cache scales with the input cache:
-
-```
- allocate(temp_output: Pointer(global float32), float32, [196608]), storage_scope = global;
-```
-
-(Same as above) Note that the input cache is reused to store the results of conv2d #2.
-
-## Assumptions
-
-* n/a
-
-## To Do
-
-* There may be some opportunity to optimized cache reuse in this case as the vertically adjacent input slice from a previous input cache read will be reloaded as in a subsequent input cache read
-
-## Annotated TIR
-
-```
-primfn(placeholder_3: handle, placeholder_4: handle, placeholder_5: handle, output_1: handle) -> ()
- attr = {"from_legacy_te_schedule": True, "global_symbol": "main", "tir.noalias": True}
- buffers = {output: Buffer(output_2: Pointer(float32), float32, [1, 8, 8, 4, 8, 8, 32], []), // nhw8h8w32c
- placeholder_2: Buffer(placeholder_6: Pointer(float32), float32, [4, 4, 3, 3, 8, 32, 4], []), // oihw8i32o4i
- placeholder_1: Buffer(placeholder_7: Pointer(float32), float32, [4, 4, 3, 3, 8, 32, 4], []), // oihw8i32o4i
- placeholder: Buffer(placeholder_8: Pointer(float32), float32, [1, 64, 64, 128], [])} // nhwc
- buffer_map = {placeholder_3: placeholder, placeholder_4: placeholder_1, placeholder_5: placeholder_2, output_1: output} {
- allocate(packed_input.global: Pointer(global float32), float32, [196608]), storage_scope = global;
- allocate(temp_output: Pointer(global float32), float32, [196608]), storage_scope = global;
- allocate(packed_filter.global: Pointer(global float32), float32, [73728]), storage_scope = global;
- for (ko.outer: int32, 0, 2) {
- for (ho.outer: int32, 0, 4) {
- // NOTE: compute over all output channels of conv2d #1 before computing conv2d #2
- for (ko.outer_1: int32, 0, 2) {
- // NOTE: compute enough height of conv2d #1 before computing conv2d #2
- for (ho.outer_1: int32, 0, 2) {
-
- // input cache read
- for (ho.inner: int32, 0, 3) {
- if ((((ho.outer_1*2) + (ho.outer*2)) + ho.inner) < 8) {
- for (wo: int32, 0, 8) {
- for (co: int32, 0, 4) {
- for (hi: int32, 0, 8) {
- for (wi: int32, 0, 8) {
- for (ci: int32, 0, 32) {
- packed_input.global[((((((ho.inner*65536) + (wo*8192)) + (co*2048)) + (hi*256)) + (wi*32)) + ci)] =
- (float32*)placeholder_8[((((((((ho.outer_1*131072) + (ho.outer*131072)) + (ho.inner*65536)) + (hi*8192)) + (wo*1024)) + (wi*128)) + (co*32)) + ci)]
- }
- }
- }
- }
- }
- }
- }
-
- // filter #1 cache read
- for (ko.inner: int32, 0, 2) {
- for (co: int32, 0, 4) {
- for (rh: int32, 0, 3) {
- for (rw: int32, 0, 3) {
- for (cio: int32, 0, 8) {
- for (ki: int32, 0, 32) {
- for (cii: int32, 0, 4) {
- packed_filter.global[(((((((ko.inner*36864) + (co*9216)) + (rh*3072)) + (rw*1024)) + (cio*128)) + (ki*4)) + cii)] =
- (float32*)placeholder_7[((((((((ko.outer_1*73728) + (ko.inner*36864)) + (co*9216)) + (rh*3072)) + (rw*1024)) + (cio*128)) + (ki*4)) + cii)]
- }
- }
- }
- }
- }
- }
- }
-
- // conv2d #1
- for (ko.inner: int32, 0, 2) {
- for (ho.inner: int32, 0, 2) {
- for (wo: int32, 0, 8) {
-
- // init temp output to zero
- if (((ho.outer_1*2) + ho.inner) < 3) {
- for (hi.init: int32, 0, 8) {
- for (wi.init: int32, 0, 8) {
- for (ki.init: int32, 0, 32) {
- temp_output[((((((((ho.outer_1*131072) + (ho.inner*65536)) + (wo*8192)) + (ko.outer_1*4096)) + (ko.inner*2048)) + (hi.init*256)) + (wi.init*32)) + ki.init)] = 0f32
- }
- }
- }
- }
-
- // compute
- if (((ho.outer_1*2) + ho.inner) < 3) {
- if ((((ho.outer_1*2) + (ho.outer*2)) + ho.inner) < 8) {
- for (rc.outer: int32, 0, 4) {
- for (hi: int32, 0, 8) {
- for (wi: int32, 0, 8) {
- for (rh: int32, 0, 3) {
- for (rw: int32, 0, 3) {
- for (ki: int32, 0, 32) {
- for (rc.inner: int32, 0, 32) {
- temp_output[((((((((ho.outer_1*131072) + (ho.inner*65536)) + (wo*8192)) + (ko.outer_1*4096)) + (ko.inner*2048)) + (hi*256)) + (wi*32)) + ki)] =
- (
- (float32*)temp_output[((((((((ho.outer_1*131072) + (ho.inner*65536)) + (wo*8192)) + (ko.outer_1*4096)) + (ko.inner*2048)) + (hi*256)) + (wi*32)) + ki)] +
- (
- (float32*)packed_input.global[((((((((floordiv((hi + rh), 8)*65536) + (ho.inner*65536)) + (floordiv((wi + rw), 8)*8192)) + (wo*8192)) + (rc.outer*2048)) + (floormod((hi + rh), 8)*256)) + (floormod((wi + rw), 8)*32)) + rc.inner)] *
- (float32*)packed_filter.global[(((((((ko.inner*36864) + (rc.outer*9216)) + (rh*3072)) + (rw*1024)) + (floordiv(rc.inner, 4)*128)) + (ki*4)) + floormod(rc.inner, 4))]
- )
- )
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- // filter #2 cache read
- // NOTE: reusing same filter cache
- for (ko.inner: int32, 0, 2) {
- for (co: int32, 0, 4) {
- for (rh: int32, 0, 3) {
- for (rw: int32, 0, 3) {
- for (cio: int32, 0, 8) {
- for (ki: int32, 0, 32) {
- for (cii: int32, 0, 4) {
- packed_filter.global[(((((((ko.inner*36864) + (co*9216)) + (rh*3072)) + (rw*1024)) + (cio*128)) + (ki*4)) + cii)] =
- (float32*)placeholder_6[((((((((ko.outer*73728) + (ko.inner*36864)) + (co*9216)) + (rh*3072)) + (rw*1024)) + (cio*128)) + (ki*4)) + cii)]
- }
- }
- }
- }
- }
- }
- }
-
- // conv2d #2
- for (ko.c.inner: int32, 0, 2) {
- for (ho.c.inner: int32, 0, 2) {
- for (wo.c: int32, 0, 8) {
-
- // init output cache to zero
- // NOTE: reusing the input cache as the output cache
- for (hi.c.init: int32, 0, 8) {
- for (wi.c.init: int32, 0, 8) {
- for (ki.c.init: int32, 0, 32) {
- packed_input.global[((((((ho.c.inner*32768) + (wo.c*4096)) + (ko.c.inner*2048)) + (hi.c.init*256)) + (wi.c.init*32)) + ki.c.init)] = 0f32
- }
- }
- }
-
- // compute
- for (rc.outer_1: int32, 0, 4) {
- for (hi.c: int32, 0, 8) {
- for (wi.c: int32, 0, 8) {
- for (rh_1: int32, 0, 3) {
- for (rw_1: int32, 0, 3) {
- for (ki.c: int32, 0, 32) {
- for (rc.inner_1: int32, 0, 32) {
- packed_input.global[((((((ho.c.inner*32768) + (wo.c*4096)) + (ko.c.inner*2048)) + (hi.c*256)) + (wi.c*32)) + ki.c)] =
- (
- (float32*)packed_input.global[((((((ho.c.inner*32768) + (wo.c*4096)) + (ko.c.inner*2048)) + (hi.c*256)) + (wi.c*32)) + ki.c)] +
- (
- (float32*)temp_output[((((((((floordiv((hi.c + rh_1), 8)*65536) + (ho.c.inner*65536)) + (floordiv((wi.c + rw_1), 8)*8192)) + (wo.c*8192)) + (rc.outer_1*2048)) + (floormod((hi.c + rh_1), 8)*256)) + (floormod((wi.c + rw_1), 8)*32)) + rc.inner_1)] *
- (float32*)packed_filter.global[(((((((ko.c.inner*36864) + (rc.outer_1*9216)) + (rh_1*3072)) + (rw_1*1024)) + (floordiv(rc.inner_1, 4)*128)) + (ki.c*4)) + floormod(rc.inner_1, 4))]
- )
- )
- }
- }
- }
- }
- }
- }
- }
- }
- }
- }
-
- // write back output cache
- for (ko.inner_1: int32, 0, 2) {
- for (ho.inner_1: int32, 0, 2) {
- for (wo_1: int32, 0, 8) {
- for (hi_1: int32, 0, 8) {
- for (wi_1: int32, 0, 8) {
- for (ki_1: int32, 0, 32) {
- output_2[((((((((ho.outer*131072) + (ho.inner_1*65536)) + (wo_1*8192)) + (ko.outer*4096)) + (ko.inner_1*2048)) + (hi_1*256)) + (wi_1*32)) + ki_1)] =
- (float32*)packed_input.global[((((((ho.inner_1*32768) + (wo_1*4096)) + (ko.inner_1*2048)) + (hi_1*256)) + (wi_1*32)) + ki_1)]
- }
- }
- }
- }
- }
- }
- }
- }
-}
-```
diff --git a/tests/python/contrib/test_hexagon/infrastructure.py b/tests/python/contrib/test_hexagon/infrastructure.py
deleted file mode 100644
index 57332eb9f1..0000000000
--- a/tests/python/contrib/test_hexagon/infrastructure.py
+++ /dev/null
@@ -1,376 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-# pylint: disable=invalid-name
-
-"""Hexagon testing infrastructure"""
-
-import numpy
-
-import tvm
-from tvm import te
-
-
-def ceildiv(o, d):
- assert o >= 0
- assert d >= 0
- return tvm.tirx.floordiv(o + d - 1, d)
-
-
-# defines inner block shape: 8h8w32c
-def get_sblock_shape():
- return 8, 8, 32
-
-
-# defines inner filter block shape: 8i32o41
-def get_filter_block_shape():
- return 8, 32, 4
-
-
-# input: locgical shape in nhwc layout
-# output: physical packed shape in nhw8h8w32c layout
-def get_packed_shape(logical_shape_nhwc):
- assert len(logical_shape_nhwc) == 4
- physical_shape_nhwc8h8w32c = [logical_shape_nhwc[0]]
- block_shape = get_sblock_shape()
- off_h, off_w, off_c = block_shape
- physical_shape_nhwc8h8w32c.append(ceildiv(logical_shape_nhwc[1], off_h))
- physical_shape_nhwc8h8w32c.append(ceildiv(logical_shape_nhwc[2], off_w))
- physical_shape_nhwc8h8w32c.append(ceildiv(logical_shape_nhwc[3], off_c))
- physical_shape_nhwc8h8w32c.extend(block_shape)
- return physical_shape_nhwc8h8w32c
-
-
-# input: physical packed shape in nhw8h8w32c layout
-# output: logical shape in nhwc layout
-def get_logical_shape(physical_shape_nhwc8h8w32c):
- assert len(physical_shape_nhwc8h8w32c) == 7
- logical_shape_nhwc = [physical_shape_nhwc8h8w32c[0]]
- logical_shape_nhwc.append(physical_shape_nhwc8h8w32c[1] * physical_shape_nhwc8h8w32c[4])
- logical_shape_nhwc.append(physical_shape_nhwc8h8w32c[2] * physical_shape_nhwc8h8w32c[5])
- logical_shape_nhwc.append(physical_shape_nhwc8h8w32c[3] * physical_shape_nhwc8h8w32c[6])
- return logical_shape_nhwc
-
-
-def get_packed_filter_shape(logical_shape_oihw):
- """return packed filter shape
-
- Parameters
- ----------
- logical_shape_oihw :
- logical shape in oihw layout
-
- Returns
- -------
- physical_shape_oihw8i32o4i :
- physical packed shape in oihw8i3204i layout
- """
- assert len(logical_shape_oihw) == 4
- filter_block_shape = get_filter_block_shape()
- filter_Cio, filter_Ki, filter_Cii = filter_block_shape
- filter_Ci = filter_Cio * filter_Cii
- physical_shape_oihw8i32o4i = []
- physical_shape_oihw8i32o4i.append(int(ceildiv(logical_shape_oihw[0], filter_Ki)))
- physical_shape_oihw8i32o4i.append(int(ceildiv(logical_shape_oihw[1], filter_Ci)))
- physical_shape_oihw8i32o4i.append(logical_shape_oihw[2])
- physical_shape_oihw8i32o4i.append(logical_shape_oihw[3])
- physical_shape_oihw8i32o4i.extend(filter_block_shape)
- return physical_shape_oihw8i32o4i
-
-
-def build_and_run(inputs, func, target: str, target_host: str, *args, **kwargs):
- """build and run the function func"""
- schedule, placeholders, binds = func(*args, **kwargs)
-
- func = tvm.compile(
- schedule, placeholders, target=tvm.target.Target(target, host=target_host), binds=binds
- )
- dev = tvm.device(target)
- tensors = []
- for tensor in inputs:
- tensors.append(tvm.runtime.tensor(tensor, dev))
- tensors.append(
- tvm.runtime.tensor(
- numpy.zeros([i.value for i in placeholders[-1].shape], dtype=placeholders[-1].dtype),
- dev,
- )
- )
- func(*tensors)
-
- return tensors[-1].numpy()
-
-
-def run_module(mod, inputs):
- """invokes run function of specified module with inputs provided"""
- mod.set_input(**inputs)
- mod.run()
- output = mod.get_output(0).numpy()
- return output
-
-
-def get_conv2d_nhwc_shape(shape_nhwc, kernel_size, strides, padding, dilation, out_channels):
- assert len(shape_nhwc) == 4
- kernel = []
- kernel.append((kernel_size[0] - 1) * dilation[0] + 1)
- kernel.append((kernel_size[1] - 1) * dilation[1] + 1)
- return (
- shape_nhwc[0],
- (shape_nhwc[1] - kernel[0] + padding[0] + padding[1]) // strides[0] + 1,
- (shape_nhwc[2] - kernel[1] + padding[2] + padding[3]) // strides[1] + 1,
- out_channels,
- )
-
-
-def conv2d_verify(output, ref_output, dtype):
- """transpose and reshape output and compare with ref_output"""
- # nhwc8h8w32c -> nhwc
- logical_output_shape = get_logical_shape(output.shape)
- output = output.transpose(0, 1, 4, 2, 5, 3, 6).reshape(logical_output_shape)
-
- # slice output to match ref_output shape
- # e.g. 8x8 spatial 3x3 filter = 6x6 ref output
- # but still 8x8 output given the blocked layout
- output = output[
- 0 : ref_output.shape[0] : 1,
- 0 : ref_output.shape[1] : 1,
- 0 : ref_output.shape[2] : 1,
- 0 : ref_output.shape[3] : 1,
- ]
-
- if "int" in dtype:
- tol = {"atol": 0, "rtol": 0}
- elif dtype == "float32":
- tol = {"rtol": 1e-4, "atol": 2e-4}
- tvm.testing.assert_allclose(output, ref_output, **tol)
-
-
-def conv2d_compute(X, filt, pad, stride, dilation):
- """Define conv2d compute"""
- block_shape = get_sblock_shape()
- block_H, block_W, block_C = block_shape
- filter_c_io, _, filter_c_ii = get_filter_block_shape()
- filter_c_i = filter_c_io * filter_c_ii
-
- shape_filter = filt.shape
- kernel_size = tuple(shape_filter[2:4])
- out_channels = shape_filter[0] * shape_filter[5]
-
- logical_input_shape = get_logical_shape(X.shape)
- logical_output_shape = get_conv2d_nhwc_shape(
- logical_input_shape,
- kernel_size,
- stride,
- pad,
- dilation,
- out_channels,
- )
-
- output_shape = get_packed_shape(logical_output_shape)
- rh = te.reduce_axis((0, kernel_size[0]), name="rh")
- rw = te.reduce_axis((0, kernel_size[1]), name="rw")
- rc = te.reduce_axis((0, logical_input_shape[3]), name="rc")
-
- def compute(n, ho, wo, ko, hi, wi, ki):
- h = ho * block_H + hi
- h_contig = h * stride[0] + rh
- h_block_id = h_contig // block_H
- h_block_offset = h_contig % block_H
-
- w = wo * block_W + wi
- w_contig = w * stride[1] + rw
- w_block_id = w_contig // block_W
- w_block_offset = w_contig % block_W
-
- c_block_id = rc // block_C
- c_block_offset = rc % block_C
-
- rco = rc // filter_c_i
- rcio = (rc % filter_c_i) // filter_c_ii
- rcii = rc % filter_c_ii
-
- return te.sum(
- X[
- n,
- h_block_id,
- w_block_id,
- c_block_id,
- h_block_offset,
- w_block_offset,
- c_block_offset,
- ]
- * filt[ko, rco, rh, rw, rcio, ki, rcii],
- axis=[rh, rw, rc],
- )
-
- return output_shape, compute
-
-
-def transform_numpy(arr_np, current_layout: str, new_layout: str):
- """Reshape and transpose numpy array according to the specified layout"""
- if current_layout == "nhwc":
- if new_layout == "nhwc":
- return arr_np
- if new_layout in ["nhwc-8h2w32c2w-2d", "nhwc-8h2w32c2w-1d"]:
- n, h, w, c = arr_np.shape
- return arr_np.reshape([n, h // 8, 8, w // 4, 2, 2, c // 32, 32]).transpose(
- 0, 1, 3, 6, 2, 4, 7, 5
- )
- if new_layout in ["nhwc-4h2w32c2w-2d"]:
- n, h, w, c = arr_np.shape
- return arr_np.reshape([n, h // 4, 4, w // 4, 2, 2, c // 32, 32]).transpose(
- 0, 1, 3, 6, 2, 4, 7, 5
- )
- if new_layout in ["n11c-1024c-2d", "n11c-1024c-1d"]:
- n, h, w, c = arr_np.shape
- assert h == 1 and w == 1, "The size of h and w must be 1"
- return arr_np.reshape([n, 1, 1, c // 1024, 1024])
- if new_layout == "nc-1024-2d":
- n, c = arr_np.shape
- return arr_np.reshape([n, c // 1024, 1024])
- if new_layout == "nhwc-1024c-2d":
- N, H, W, C = arr_np.shape
- return arr_np.reshape([N, H, W, C // 1024, 1024])
- if new_layout == "nc-2048-2d":
- N, C = arr_np.shape
- return arr_np.reshape([N, C // 2048, 2048])
- if new_layout == "nhwc-2048c-2d":
- N, H, W, C = arr_np.shape
- return arr_np.reshape([N, H, W, C // 2048, 2048])
- if new_layout == "nhwc-8h8w32c-2d":
- n, h, w, c = arr_np.shape
- return arr_np.reshape([n, h // 8, 8, w // 8, 8, c // 32, 32]).transpose(
- 0, 1, 3, 5, 2, 4, 6
- )
- if new_layout == "n11c-2048c-2d":
- n, h, w, c = arr_np.shape
- assert h == 1 and w == 1, "The size of h and w must be 1"
- return arr_np.reshape([n, h, w, c // 2048, 2048])
- raise RuntimeError(f"Unexpected new_layout '{new_layout}'")
-
- if current_layout == "nc":
- n, c = arr_np.shape
- if new_layout in ["nc-2048c-1d"]:
- return arr_np.reshape([n, c // 2048, 2048])
- if new_layout in ["nc-2048c-2d"]:
- return arr_np.reshape([n, c // 2048, 2048])
- if new_layout in ["nc-1024c-2d"]:
- return arr_np.reshape([n, c // 1024, 1024])
- if new_layout in ["nc-1024c-1d"]:
- return arr_np.reshape([n, c // 1024, 1024])
- if new_layout in ["nc-512c-2d"]:
- return arr_np.reshape([n, c // 512, 512])
- if new_layout in ["nc-2048c-2d"]:
- return arr_np.reshape([n, c // 2048, 2048])
- raise RuntimeError(f"Unexpected new_layout '{new_layout}'")
-
- if current_layout == "nhw":
- if new_layout in ["nhw-32h16w-2d"]:
- n, h, w = arr_np.shape
- return arr_np.reshape([n, h // 32, 32, w // 16, 16]).transpose(0, 1, 3, 2, 4)
-
- raise RuntimeError(f"Unexpected new_layout '{new_layout}'")
-
- if current_layout == "ncw":
- if new_layout == "ncw":
- return arr_np
- if new_layout in ["ncw-32c64w-2d"]:
- n, c, w = arr_np.shape
- return arr_np.reshape([n, c // 32, 32, w // 64, 64]).transpose(0, 1, 3, 2, 4)
-
- raise RuntimeError(f"Unexpected new_layout '{new_layout}'")
-
- if current_layout == "nchw":
- if new_layout in ["nchw-32c8h8w-2d", "nchw-32c8h8w-1d"]:
- n, c, h, w = arr_np.shape
- return arr_np.reshape([n, c // 32, 32, h // 8, 8, w // 8, 8]).transpose(
- 0, 1, 3, 5, 2, 4, 6
- )
- if new_layout in ["nchw-32c8h4w-2d", "nchw-32c8h4w-1d"]:
- n, c, h, w = arr_np.shape
- return arr_np.reshape([n, c // 32, 32, h // 8, 8, w // 4, 4]).transpose(
- 0, 1, 3, 5, 2, 4, 6
- )
- raise RuntimeError(f"Unexpected new_layout '{new_layout}'")
-
- raise RuntimeError(f"Unexpected current_layout '{current_layout}'")
-
-
-def quantize_np(arr_np: numpy.ndarray, dtype: str):
- """
- Returns quantized array along with scale and zero-point
-
- Parameters
- ----------
- arr_np: numpy.ndarray
- Input numpy array to be quantized
- dtype: str
- dtype of the quantized array: "uint8", "int8", etc
-
- Returns
- -------
- quant_np: numpy.ndarray
- Quantized numpy array
- scale: float
- Scale
- zero_point: int
- Value corresponding to float 0
-
- """
- if dtype == "uint8":
- qmax = 255
- qmin = 0
- elif dtype == "int8":
- qmax = 127
- qmin = -128
- else:
- raise RuntimeError(f"Unsupported quantized data type '{dtype}'")
- fmin = numpy.amin(arr_np)
- fmax = numpy.amax(arr_np)
-
- # Include floating-point zero in the range
- if fmax < 0:
- fmax = 0.0
- elif fmin > 0:
- fmin = 0.0
-
- scale = (fmax - fmin) / (qmax - qmin)
- zero_point = numpy.rint((fmax * qmin - fmin * qmax) / (fmax - fmin)).astype("int32")
- quant_np = numpy.clip(((arr_np / scale).round() + zero_point), qmin, qmax).astype(dtype)
- return quant_np, scale, zero_point
-
-
-def get_hexagon_target(cpu_ver: str, **kwargs) -> tvm.target.Target:
- """Creates a Hexagon target from a registered tag.
-
- Parameters
- ----------
- cpu_ver : str
- Hexagon CPU version, e.g. "v68", "v69".
- **kwargs :
- Optional target attribute overrides (e.g. vtcm_capacity=1024).
- """
- tag = "qcom/hexagon-" + cpu_ver
- if kwargs:
- config = {"tag": tag}
- if "vtcm_capacity" in kwargs:
- config["vtcm-capacity"] = kwargs.pop("vtcm_capacity")
- if "num_cores" in kwargs:
- config["num-cores"] = kwargs.pop("num_cores")
- config.update(kwargs)
- target = tvm.target.Target(config)
- else:
- target = tvm.target.Target(tag)
- return tvm.target.Target(target, host=target)
diff --git a/tests/python/contrib/test_hexagon/pytest_util.py b/tests/python/contrib/test_hexagon/pytest_util.py
deleted file mode 100644
index 1c7b92a529..0000000000
--- a/tests/python/contrib/test_hexagon/pytest_util.py
+++ /dev/null
@@ -1,176 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Hexagon pytest utility functions"""
-
-import collections
-
-import numpy as np
-
-
-def get_test_id(*test_params, test_param_descs: list[str | None] | None = None) -> str:
- """
- An opinionated alternative to pytest's default algorithm for generating a
- test's ID string. Intended to make it easier for human readers to
- interpret the test IDs.
-
- 'test_params': The sequence of pytest parameter values supplied to some unit
- test.
-
- 'test_param_descs': An (optional) means to provide additional text for some/all of the
- paramuments in 'test_params'.
-
- If provided, then len(test_params) must equal len(test_param_descs).
- Each element test_param_descs that is a non-empty string will be used
- in some sensible way in this function's returned string.
- """
-
- assert len(test_params) > 0
-
- if test_param_descs is None:
- test_param_descs = [None] * len(test_params)
- else:
- assert len(test_param_descs) == len(test_params)
-
- def get_single_param_chunk(param_val, param_desc: str | None):
- if isinstance(param_val, list):
- # Like str(list), but avoid the whitespace padding.
- val_str = "[" + ",".join(str(x) for x in param_val) + "]"
- need_prefix_separator = False
-
- elif isinstance(param_val, bool):
- if param_val:
- val_str = "T"
- else:
- val_str = "F"
- need_prefix_separator = True
-
- elif isinstance(param_val, TensorContentConstant):
- val_str = f"const[{param_val.elem_value}]"
- need_prefix_separator = True
-
- elif isinstance(param_val, TensorContentDtypeMin):
- val_str = "min"
- need_prefix_separator = True
-
- elif isinstance(param_val, TensorContentDtypeMax):
- val_str = "max"
- need_prefix_separator = True
-
- elif isinstance(param_val, TensorContentRandom):
- val_str = "random"
- need_prefix_separator = True
-
- elif isinstance(param_val, TensorContentSequentialCOrder):
- val_str = f"seqC[start:{param_val.start_value},inc:{param_val.increment}]"
- need_prefix_separator = True
-
- else:
- val_str = str(param_val)
- need_prefix_separator = True
-
- if param_desc and need_prefix_separator:
- return f"{param_desc}:{val_str}"
- elif param_desc and not need_prefix_separator:
- return f"{param_desc}{val_str}"
- else:
- return val_str
-
- chunks = [
- get_single_param_chunk(param_val, param_desc)
- for param_val, param_desc in zip(test_params, test_param_descs)
- ]
- return "-".join(chunks)
-
-
-def get_multitest_ids(
- multitest_params_list: list[list], param_descs: list[str | None] | None
-) -> list[str]:
- """
- A convenience function for classes that use both 'tvm.testing.parameters' and 'get_test_id'.
-
- This function provides a workaround for a specific quirk in Python, where list-comprehension
- can't necessarily access the value of another class-variable, discused here:
- https://stackoverflow.com/q/13905741
- """
- return [
- get_test_id(*single_test_param_list, test_param_descs=param_descs)
- for single_test_param_list in multitest_params_list
- ]
-
-
-def get_numpy_dtype_info(dtype) -> np.finfo | np.iinfo:
- """
- Return an appropriate 'np.iinfo' or 'np.finfo' object corresponding to
- the specified Numpy dtype.
-
- 'dtype' must be a value that 'numpy.dtype(...)' can handle.
- """
- np_dtype = np.dtype(dtype)
- kind = np_dtype.kind
-
- if kind == "f":
- return np.finfo(np_dtype)
- elif kind == "i":
- return np.iinfo(np_dtype)
- else:
- raise TypeError(f"dtype ({dtype}) must indicate some floating-point or integral data type")
-
-
-TensorContentConstant = collections.namedtuple("TensorContentConstant", ["elem_value"])
-TensorContentSequentialCOrder = collections.namedtuple(
- "TensorContentSequentialCOrder", ["start_value", "increment"]
-)
-TensorContentRandom = collections.namedtuple("TensorContentRandom", [])
-TensorContentDtypeMin = collections.namedtuple("TensorContentDtypeMin", [])
-TensorContentDtypeMax = collections.namedtuple("TensorContentDtypeMax", [])
-
-
-def create_populated_numpy_tensor(
- input_shape: list | tuple, dtype: str, input_tensor_populator
-) -> np.ndarray:
- """
- Create a numpy tensor with the specified shape, dtype, and content.
- """
- itp = input_tensor_populator # just for brevity
-
- if isinstance(itp, TensorContentConstant):
- return np.full(tuple(input_shape), itp.elem_value, dtype=dtype)
-
- elif isinstance(itp, TensorContentDtypeMin):
- info = get_numpy_dtype_info(dtype)
- return np.full(tuple(input_shape), info.min, dtype=dtype)
-
- elif isinstance(itp, TensorContentDtypeMax):
- info = get_numpy_dtype_info(dtype)
- return np.full(tuple(input_shape), info.max, dtype=dtype)
-
- elif isinstance(itp, TensorContentRandom):
- return np.random.random(input_shape).astype(dtype)
-
- elif isinstance(itp, TensorContentSequentialCOrder):
- a = np.empty(tuple(input_shape), dtype)
-
- with np.nditer(a, op_flags=["writeonly"], order="C") as iterator:
- next_elem_val = itp.start_value
- for elem in iterator:
- elem[...] = next_elem_val
- next_elem_val += itp.increment
- return a
-
- else:
- raise ValueError(f"Unexpected input_tensor_populator type: {type(itp)}")
diff --git a/tests/python/contrib/test_hexagon/test_async_dma_pipeline.py b/tests/python/contrib/test_hexagon/test_async_dma_pipeline.py
deleted file mode 100644
index fba8ccb47d..0000000000
--- a/tests/python/contrib/test_hexagon/test_async_dma_pipeline.py
+++ /dev/null
@@ -1,889 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-# ruff: noqa: F841
-
-"""Test different strategies for loading data into vtcm before running HVX workloads."""
-
-import numpy as np
-import pytest
-
-import tvm
-from tvm.script import tirx as T
-from tvm.testing import env
-
-VRMPY_SIZE_B = 128
-VRMPY_SIZE_INT32 = 32
-
-
-# pylint: disable=invalid-name
-@T.prim_func(s_tir=True)
-def conv2d_async_non_contig(
- p0: T.Buffer((T.int64(1), T.int64(1), T.int64(56), T.int64(56), T.int64(4)), "uint8"),
- fused_constant_1: T.Buffer(
- (T.int64(1), T.int64(1), T.int64(3), T.int64(3), T.int64(1), T.int64(32), T.int64(4)),
- "uint8",
- ),
- conv2d_NCHWc_int8: T.Buffer(
- (T.int64(1), T.int64(1), T.int64(54), T.int64(54), T.int64(32)), "int32"
- ),
-):
- """Non contiguous memory access is used in this conv2d taken from MS."""
- # pylint: disable=no-self-argument
- # function attr dict
- T.func_attr({"tirx.noalias": True, "global_symbol": "main"})
- # body
- # with T.sblock("root")
- p0_global_vtcm = T.sblock_alloc_buffer(
- [T.int64(1), T.int64(1), T.int64(56), T.int64(56), T.int64(4)],
- dtype="uint8",
- scope="global.vtcm",
- )
- fused_constant_global_vtcm = T.sblock_alloc_buffer(
- [T.int64(1), T.int64(1), T.int64(3), T.int64(3), T.int64(1), T.int64(32), T.int64(4)],
- dtype="uint8",
- scope="global.vtcm",
- )
- for oh_0 in T.serial(T.int64(3)):
- for ow_0 in T.serial(
- T.int64(3),
- annotations={
- "software_pipeline_async_stages": [0],
- "software_pipeline_order": [0, 1, 2],
- "software_pipeline_stage": [0, 0, 1],
- },
- ):
- for ax0_ax1_ax2_ax3_ax4_fused in T.serial(T.int64(1600)):
- with T.sblock("p0_global.vtcm"):
- v0 = T.axis.spatial(T.int64(1), T.int64(0))
- v1 = T.axis.spatial(T.int64(1), T.int64(0))
- v2 = T.axis.spatial(
- T.int64(56), oh_0 * T.int64(18) + ax0_ax1_ax2_ax3_ax4_fused // T.int64(80)
- )
- v3 = T.axis.spatial(
- T.int64(56),
- ow_0 * T.int64(18) + ax0_ax1_ax2_ax3_ax4_fused % T.int64(80) // T.int64(4),
- )
- v4 = T.axis.spatial(T.int64(4), ax0_ax1_ax2_ax3_ax4_fused % T.int64(4))
- T.reads(p0[v0, v1, v2, v3, v4])
- T.writes(p0_global_vtcm[v0, v1, v2, v3, v4])
- p0_global_vtcm[v0, v1, v2, v3, v4] = p0[v0, v1, v2, v3, v4]
- for ax0_ax1_ax2_ax3_ax4_ax5_ax6_fused in T.serial(T.int64(1152)):
- with T.sblock("fused_constant_global.vtcm"):
- v0 = T.axis.spatial(T.int64(1), T.int64(0))
- v1 = T.axis.spatial(T.int64(1), T.int64(0))
- v2 = T.axis.spatial(
- T.int64(3), ax0_ax1_ax2_ax3_ax4_ax5_ax6_fused // T.int64(384)
- )
- v3 = T.axis.spatial(
- T.int64(3), ax0_ax1_ax2_ax3_ax4_ax5_ax6_fused % T.int64(384) // T.int64(128)
- )
- v4 = T.axis.spatial(T.int64(1), T.int64(0))
- v5 = T.axis.spatial(
- T.int64(32), ax0_ax1_ax2_ax3_ax4_ax5_ax6_fused % T.int64(128) // T.int64(4)
- )
- v6 = T.axis.spatial(T.int64(4), ax0_ax1_ax2_ax3_ax4_ax5_ax6_fused % T.int64(4))
- T.reads(fused_constant_1[v0, v1, v2, v3, v4, v5, v6])
- T.writes(fused_constant_global_vtcm[v0, v1, v2, v3, v4, v5, v6])
- fused_constant_global_vtcm[v0, v1, v2, v3, v4, v5, v6] = fused_constant_1[
- v0, v1, v2, v3, v4, v5, v6
- ]
- for oh_1, ow_1 in T.grid(T.int64(3), T.int64(6)):
- for oh_2_init, ow_2_init in T.grid(T.int64(6), T.int64(3)):
- with T.sblock("conv2d_NCHWc_int8_o_init"):
- v_n = T.axis.spatial(T.int64(1), T.int64(0))
- v_oc_chunk = T.axis.spatial(T.int64(1), T.int64(0))
- v_oh = T.axis.spatial(
- T.int64(54), oh_0 * T.int64(18) + oh_1 * T.int64(6) + oh_2_init
- )
- v_ow = T.axis.spatial(
- T.int64(54), ow_0 * T.int64(18) + ow_1 * T.int64(3) + ow_2_init
- )
- T.reads()
- T.writes(
- conv2d_NCHWc_int8[v_n, v_oc_chunk, v_oh, v_ow, T.int64(0) : T.int64(32)]
- )
- for oc_block_1 in T.vectorized(T.int64(32)):
- with T.sblock("conv2d_NCHWc_int8_init"):
- v_oc_block_i_init = T.axis.spatial(T.int64(32), oc_block_1)
- T.reads()
- T.writes(
- conv2d_NCHWc_int8[
- v_n, v_oc_chunk, v_oh, v_ow, v_oc_block_i_init
- ]
- )
- conv2d_NCHWc_int8[
- v_n, v_oc_chunk, v_oh, v_ow, v_oc_block_i_init
- ] = 0
- for kh_1, kw_1, oh_2, ow_2 in T.grid(
- T.int64(3), T.int64(3), T.int64(6), T.int64(3)
- ):
- with T.sblock("conv2d_NCHWc_int8_o_update"):
- v_n = T.axis.spatial(T.int64(1), T.int64(0))
- v_oc_chunk = T.axis.spatial(T.int64(1), T.int64(0))
- v_oh = T.axis.spatial(
- T.int64(54), oh_0 * T.int64(18) + oh_1 * T.int64(6) + oh_2
- )
- v_ow = T.axis.spatial(
- T.int64(54), ow_0 * T.int64(18) + ow_1 * T.int64(3) + ow_2
- )
- v_kh, v_kw = T.axis.remap("RR", [kh_1, kw_1])
- v_ic_outer = T.axis.reduce(T.int64(1), T.int64(0))
- v_ic_f_inner = T.axis.reduce(T.int64(1), T.int64(0))
- T.reads(
- conv2d_NCHWc_int8[
- v_n, v_oc_chunk, v_oh, v_ow, T.int64(0) : T.int64(32)
- ],
- p0_global_vtcm[
- v_n,
- v_ic_outer,
- v_oh + v_kh,
- v_ow + v_kw,
- v_ic_f_inner * T.int64(4) : v_ic_f_inner * T.int64(4) + T.int64(4),
- ],
- fused_constant_global_vtcm[
- v_oc_chunk,
- v_ic_outer,
- v_kh,
- v_kw,
- v_ic_f_inner,
- T.int64(0) : T.int64(32),
- T.int64(0) : T.int64(4),
- ],
- )
- T.writes(
- conv2d_NCHWc_int8[v_n, v_oc_chunk, v_oh, v_ow, T.int64(0) : T.int64(32)]
- )
- A = T.match_buffer(
- p0_global_vtcm[
- v_n,
- v_ic_outer,
- v_oh + v_kh,
- v_ow + v_kw,
- v_ic_f_inner * T.int64(4) : v_ic_f_inner * T.int64(4) + T.int64(4),
- ],
- [T.int64(4)],
- dtype="uint8",
- scope="global.vtcm",
- offset_factor=1,
- )
- B = T.match_buffer(
- fused_constant_global_vtcm[
- v_oc_chunk,
- v_ic_outer,
- v_kh,
- v_kw,
- v_ic_f_inner,
- T.int64(0) : T.int64(32),
- T.int64(0) : T.int64(4),
- ],
- [T.int64(32), T.int64(4)],
- dtype="uint8",
- scope="global.vtcm",
- offset_factor=1,
- )
- C = T.match_buffer(
- conv2d_NCHWc_int8[
- v_n, v_oc_chunk, v_oh, v_ow, T.int64(0) : T.int64(32)
- ],
- [T.int64(32)],
- dtype="int32",
- offset_factor=1,
- )
- A_u8x4: T.uint8x4 = A[T.int64(0) : T.int64(4)]
- A_i32: T.int32 = T.reinterpret(A_u8x4, dtype="int32")
- B_i8x128 = B[T.int64(0), T.int64(0) : T.int64(128)]
- B_i32x32: T.int32x32 = T.reinterpret(B_i8x128, dtype="int32x32")
- C[0:32] = T.call_llvm_pure_intrin(
- T.llvm_lookup_intrinsic_id("llvm.hexagon.V6.vrmpyubv.acc.128B"),
- C[0:32],
- B_i32x32,
- A_i32,
- dtype="int32x32",
- )
-
-
-def conv_approximation(size_a, size_w):
- """Conv approximation."""
- a_shape = (size_a, VRMPY_SIZE_B)
- w_shape = (size_w, VRMPY_SIZE_B)
- out_shape = (size_a, VRMPY_SIZE_INT32)
-
- @T.prim_func(s_tir=True)
- def operator(a_input: T.handle, b_input: T.handle, c_output: T.handle) -> None:
- T.func_attr({"global_symbol": "main", "tirx.noalias": True})
- a_buffer = T.match_buffer(a_input, a_shape, dtype="uint8")
- w_buffer = T.match_buffer(b_input, w_shape, dtype="uint8")
- c_buffer = T.match_buffer(c_output, out_shape, dtype="int32")
- for n, index_0 in T.grid(size_a, size_w):
- with T.sblock("c_buffer"):
- vn_index, vi_index = T.axis.remap("SR", [n, index_0])
- T.reads(
- a_buffer[vn_index, 0:VRMPY_SIZE_B],
- w_buffer[vi_index, 0:VRMPY_SIZE_B],
- c_buffer[vn_index, 0:VRMPY_SIZE_INT32],
- )
- T.writes(c_buffer[vn_index, 0:VRMPY_SIZE_INT32])
- with T.init():
- for x in T.serial(VRMPY_SIZE_INT32):
- c_buffer[vn_index, x] = 0
- c_buffer[vn_index, T.ramp(0, 1, 32)] = T.call_llvm_intrin(
- T.llvm_lookup_intrinsic_id("llvm.hexagon.V6.vrmpyubv.acc.128B"),
- c_buffer[vn_index, T.ramp(0, 1, 32)],
- T.reinterpret(a_buffer[vn_index, T.ramp(0, 1, 128)], dtype="int32x32"),
- T.reinterpret(w_buffer[vi_index, T.ramp(0, 1, 128)], dtype="int32x32"),
- dtype="int32x32",
- )
- # Currently async DMA lowering does not add any wait to the end of schedules so
- # for timing purposes we are manually adding a wait to ensure that all copies
- # are complete when the schedule exits.
- T.evaluate(
- T.tvm_call_packed(
- "device_api.hexagon.dma_wait",
- 0, # QueueId
- 0, # Wait for 0 in flight
- dtype="int32",
- )
- )
-
- return tvm.s_tir.Schedule(operator)
-
-
-def evaluate(
- hexagon_session,
- sch,
- a_data,
- b_data,
- c_data,
- expected_output=None,
- use_async_copy=0,
-):
- """Evaluate function."""
- target_hexagon = tvm.target.Target("qcom/hexagon-v68")
- with tvm.transform.PassContext(
- config={
- "tirx.use_async_copy": use_async_copy,
- "tirx.experimental_dma_bypass_cache": 1,
- }
- ):
- func_tir = tvm.compile(
- sch.mod["main"], target=tvm.target.Target(target_hexagon, host=target_hexagon)
- )
- module = hexagon_session.load_module(func_tir)
-
- a_hexagon = tvm.runtime.tensor(a_data, device=hexagon_session.device)
- b_hexagon = tvm.runtime.tensor(b_data, device=hexagon_session.device)
- c_hexagon = tvm.runtime.tensor(c_data, device=hexagon_session.device)
-
- if tvm.testing.utils.IS_IN_CI:
- # Run with reduced number and repeat for CI
- timer = module.time_evaluator("main", hexagon_session.device, number=1, repeat=1)
- else:
- timer = module.time_evaluator("main", hexagon_session.device, number=10, repeat=10)
-
- time = timer(a_hexagon, b_hexagon, c_hexagon)
- if expected_output is not None:
- tvm.testing.assert_allclose(c_hexagon.numpy(), expected_output)
- return round(time.mean * 1000, 4)
-
-
-def get_fake_conv_vtcm_schedule(size_a, size_w, blocks=2):
- """Generate fake conv schedule with VTCM."""
- sch = conv_approximation(size_a, size_w)
-
- compute_block = sch.get_sblock("c_buffer")
- sch.cache_read(compute_block, 1, "global.vtcm")
-
- n = sch.get_loops(compute_block)[0]
- n_outer, _ = sch.split(n, [blocks, None])
-
- cache_read_block_a = sch.cache_read(compute_block, 0, "global.vtcm")
- sch.compute_at(cache_read_block_a, n_outer)
- sch.fuse(*sch.get_loops(cache_read_block_a)[1:])
-
- cache_write_block_c = sch.cache_write(compute_block, 0, "global.vtcm")
- sch.reverse_compute_at(cache_write_block_c, n_outer)
- sch.fuse(*sch.get_loops(cache_write_block_c)[1:])
-
- return sch
-
-
-def get_multi_input_fake_conv_vtcm_schedule(size_a, size_w, blocks=2):
- """Generate multi input fake Conv using VTCM."""
- sch = conv_approximation(size_a, size_w)
-
- compute_block = sch.get_sblock("c_buffer")
-
- n = sch.get_loops(compute_block)[0]
- n_outer, _ = sch.split(n, [blocks, None])
-
- cache_read_block_a = sch.cache_read(compute_block, 0, "global.vtcm")
- sch.compute_at(cache_read_block_a, n_outer)
- sch.fuse(*sch.get_loops(cache_read_block_a)[1:])
-
- cache_read_block_b = sch.cache_read(compute_block, 1, "global.vtcm")
- sch.compute_at(cache_read_block_b, n_outer)
- sch.fuse(*sch.get_loops(cache_read_block_b)[1:])
-
- cache_write_block_c = sch.cache_write(compute_block, 0, "global.vtcm")
- sch.reverse_compute_at(cache_write_block_c, n_outer)
- sch.fuse(*sch.get_loops(cache_write_block_c)[1:])
-
- return sch
-
-
-def print_results(test_key, runtimes):
- print(test_key)
- for runtime in runtimes.items():
- print(f"-{runtime[0]} took {runtime[1]} ms")
- print()
-
-
-class TestAsyncDMAPipeline:
- """Async DMA pipeline test class."""
-
- # Removed most of these to speedup CI.
- size_a = tvm.testing.parameter(
- 1024,
- 64 * 64,
- # 128 * 64, # Only works on 8Gen1 HDK's
- )
-
- size_w = tvm.testing.parameter(
- 1 * 1,
- 3 * 3,
- 9 * 9,
- )
-
- @tvm.testing.fixture
- def input_a(self, size_a):
- return np.random.randint(0, 8, (size_a, VRMPY_SIZE_B), dtype="uint8")
-
- @tvm.testing.fixture
- def input_w(self, size_w):
- return np.random.randint(0, 8, (size_w, VRMPY_SIZE_B), dtype="uint8")
-
- @tvm.testing.fixture
- def expected_output(self, size_a, size_w, input_a, input_w):
- """Generate expected output."""
- if tvm.testing.utils.IS_IN_CI and (size_a > 1024 or size_w > 1):
- pytest.skip("Skipping test since it takes too long in CI.")
- expected_result = np.zeros((size_a, VRMPY_SIZE_INT32), dtype="int32")
- for n in range(size_a):
- for x in range(size_w):
- for index_0 in range(VRMPY_SIZE_INT32):
- for r_index in range(4):
- expected_result[n, index_0] += np.uint32(
- input_a[n, index_0 * 4 + r_index]
- ) * np.uint32(input_w[x, index_0 * 4 + r_index])
- return expected_result
-
- @pytest.mark.skipif(not env.has_hexagon(), reason="need hexagon")
- def test_loading_vtcm_for_vrmpy(
- self,
- hexagon_session,
- size_a,
- size_w,
- input_a,
- input_w,
- expected_output,
- ):
- """VTCM for VRMPY test."""
-
- if tvm.testing.utils.IS_IN_CI and (size_a > 1024 or size_w > 1):
- pytest.skip("Skipping test since it takes too long in CI.")
-
- sch = conv_approximation(size_a, size_w)
- base_runtime = evaluate(
- hexagon_session,
- sch,
- input_a,
- input_w,
- np.zeros(expected_output.shape, "int32"),
- expected_output,
- )
-
- sch = get_fake_conv_vtcm_schedule(size_a, size_w)
- base_vtcm_runtime = evaluate(
- hexagon_session,
- sch,
- input_a,
- input_w,
- np.zeros(expected_output.shape, "int32"),
- expected_output,
- use_async_copy=1,
- )
-
- sch = get_fake_conv_vtcm_schedule(size_a, size_w)
- n = sch.get_loops(sch.get_sblock("c_buffer"))[0]
- sch.annotate(n, "software_pipeline_stage", [0, 1, 2])
- sch.annotate(n, "software_pipeline_order", [0, 1, 2])
- sch.annotate(n, "software_pipeline_async_stages", [0])
- async_input_runtime = evaluate(
- hexagon_session,
- sch,
- input_a,
- input_w,
- np.zeros(expected_output.shape, "int32"),
- expected_output,
- use_async_copy=1,
- )
-
- sch = get_fake_conv_vtcm_schedule(size_a, size_w)
- n = sch.get_loops(sch.get_sblock("c_buffer"))[0]
- sch.annotate(n, "software_pipeline_stage", [0, 1, 2])
- sch.annotate(n, "software_pipeline_order", [0, 1, 2])
- sch.annotate(n, "software_pipeline_async_stages", [0, 2])
- async_input_output = evaluate(
- hexagon_session,
- sch,
- input_a,
- input_w,
- np.zeros(expected_output.shape, "int32"),
- expected_output,
- use_async_copy=1,
- )
-
- sch = get_fake_conv_vtcm_schedule(size_a, size_w)
- n = sch.get_loops(sch.get_sblock("c_buffer"))[0]
- sch.annotate(n, "software_pipeline_stage", [0, 3, 6])
- sch.annotate(n, "software_pipeline_order", [0, 1, 2])
- sch.annotate(n, "software_pipeline_async_stages", [0, 6])
- async_larger_buffers = evaluate(
- hexagon_session,
- sch,
- input_a,
- input_w,
- np.zeros(expected_output.shape, "int32"),
- expected_output,
- use_async_copy=1,
- )
-
- sch = get_multi_input_fake_conv_vtcm_schedule(size_a, size_w)
- n = sch.get_loops(sch.get_sblock("c_buffer"))[0]
- sch.annotate(n, "software_pipeline_stage", [0, 0, 1, 2])
- sch.annotate(n, "software_pipeline_order", [0, 1, 2, 3])
- sch.annotate(n, "software_pipeline_async_stages", [0, 2])
- async_multi_input_output = evaluate(
- hexagon_session,
- sch,
- input_a,
- input_w,
- np.zeros(expected_output.shape, "int32"),
- expected_output,
- use_async_copy=1,
- )
-
- sch = get_fake_conv_vtcm_schedule(size_a, size_w)
- n = sch.get_loops(sch.get_sblock("c_buffer"))[0]
- sch.annotate(n, "software_pipeline_stage", [0, 1, 2])
- sch.annotate(n, "software_pipeline_order", [0, 1, 2])
- sch.annotate(n, "software_pipeline_async_stages", [2])
- async_output_runtime = evaluate(
- hexagon_session,
- sch,
- input_a,
- input_w,
- np.zeros(expected_output.shape, "int32"),
- expected_output,
- use_async_copy=1,
- )
-
- # Total transfer size is equal to the size of
- # a_buffer + w_buffer + c_buffer which is equal to 2 * size_a * 128 + size_w * 128
- transfer_mb = round((2 * size_a * VRMPY_SIZE_B + size_w * VRMPY_SIZE_B) / 1e6, 2)
-
- # Total number of operations can be calculated given
- # the total number of vrmpy calls (size_a * size_w) * operations
- # per vrmpy accumulate (128 multiplies + 3 adds for reduction
- # per lane + 1 add for accumulate per lane)
- complexity = round(size_a * size_w * (VRMPY_SIZE_B * 4) / 1e9, 3)
- print_results(
- (
- f"Test with a_buffer.size: {size_a * VRMPY_SIZE_B}, w_buffer.size:"
- f" {size_w * VRMPY_SIZE_B}, computational complexity of {complexity} GOPs"
- f", and total memory transfer of {transfer_mb} MB..."
- ),
- {
- "without_vtcm": base_runtime,
- "base_vtcm": base_vtcm_runtime,
- "async_dma_input": async_input_runtime,
- "async_dma_output": async_output_runtime,
- "async_dma_input_output": async_input_output,
- "async_dma_multi_input_output": async_multi_input_output,
- "async_input_output_runtime_larger_buffers": async_larger_buffers,
- },
- )
-
-
-# from tvm.script import tirx as T
-@tvm.script.ir_module
-class ModulePipelined:
- """Pipelined module class."""
-
- # pylint: disable=no-self-argument
- @T.prim_func(s_tir=True)
- def main(
- p0_buffer: T.Buffer((1, 1, 230, 230, 4), "uint8"),
- p1_buffer: T.Buffer((2, 1, 7, 7, 1, 32, 4), "int8"),
- t_cast: T.Buffer((1, 2, 112, 112, 32), "int32"),
- ) -> None:
- # pylint: disable=missing-function-docstring
- # function attr dict
- T.func_attr({"tirx.noalias": True, "global_symbol": "main"})
- # body
- # with T.sblock("root")
- conv2d_nchwc_int8 = T.sblock_alloc_buffer(
- [1, 2, 112, 112, 32], dtype="int32", scope="global.vtcm"
- )
- p0_global_vtcm = T.sblock_alloc_buffer(
- [1, 1, 230, 230, 4], dtype="uint8", scope="global.vtcm"
- )
- p1_global_vtcm = T.sblock_alloc_buffer(
- [2, 1, 7, 7, 1, 32, 4], dtype="int8", scope="global.vtcm"
- )
- for ax0, ax1, ax2, ax3, ax4, ax5, ax6 in T.grid(2, 1, 7, 7, 1, 32, 4):
- with T.sblock("p1_global.vtcm"):
- v0_ind, v1_ind, v2_ind, v3_ind, v4_ind, v5_ind, v6_ind = T.axis.remap(
- "SSSSSSS", [ax0, ax1, ax2, ax3, ax4, ax5, ax6]
- )
- T.reads(p1_buffer[v0_ind, v1_ind, v2_ind, v3_ind, v4_ind, v5_ind, v6_ind])
- T.writes(p1_global_vtcm[v0_ind, v1_ind, v2_ind, v3_ind, v4_ind, v5_ind, v6_ind])
- p1_global_vtcm[v0_ind, v1_ind, v2_ind, v3_ind, v4_ind, v5_ind, v6_ind] = p1_buffer[
- v0_ind, v1_ind, v2_ind, v3_ind, v4_ind, v5_ind, v6_ind
- ]
- for p_outer in T.serial(4):
- for index_0 in T.serial(55876):
- with T.sblock("p0_global.vtcm"):
- v0_ind = T.axis.spatial(1, 0)
- v1_ind = T.axis.spatial(1, 0)
- v2_ind = T.axis.spatial(230, p_outer * 56 + index_0 // 916)
- v3_ind = T.axis.spatial(230, index_0 % 916 // 4)
- v4_ind = T.axis.spatial(4, index_0 % 4)
- T.reads(p0_buffer[v0_ind, v1_ind, v2_ind, v3_ind, v4_ind])
- T.writes(p0_global_vtcm[v0_ind, v1_ind, v2_ind, v3_ind, v4_ind])
- p0_global_vtcm[v0_ind, v1_ind, v2_ind, v3_ind, v4_ind] = p0_buffer[
- v0_ind, v1_ind, v2_ind, v3_ind, v4_ind
- ]
- for index_0 in T.parallel(28):
- for index_1, index_2, index_3 in T.grid(2, 14, 8):
- with T.sblock("conv2d_NCHWc_int8_o_init"):
- n = T.axis.spatial(1, 0)
- oc_chunk = T.axis.spatial(2, index_1)
- o_height = T.axis.spatial(
- 112, (p_outer * 28 + index_0) // 14 * 14 + index_2
- )
- o_width = T.axis.spatial(112, (p_outer * 28 + index_0) % 14 * 8 + index_3)
- oc_block_o = T.axis.spatial(1, 0) # pylint: disable=unused-variable
- T.reads()
- T.writes(conv2d_nchwc_int8[n, oc_chunk, o_height, o_width, 0:32])
- for i4_1 in T.vectorized(32):
- with T.sblock("conv2d_NCHWc_int8_init"):
- oc_block_i_init = T.axis.spatial(32, i4_1)
- T.reads()
- T.writes(
- conv2d_nchwc_int8[
- n, oc_chunk, o_height, o_width, oc_block_i_init
- ]
- )
- conv2d_nchwc_int8[
- n, oc_chunk, o_height, o_width, oc_block_i_init
- ] = 0
- for i1_1, i5_1, i6_1, i2_2, i3_2 in T.grid(2, 7, 7, 14, 8):
- with T.sblock("conv2d_NCHWc_int8_o_update"):
- n = T.axis.spatial(1, 0)
- oc_chunk = T.axis.spatial(2, i1_1)
- o_height = T.axis.spatial(112, (p_outer * 28 + index_0) // 14 * 14 + i2_2)
- o_width = T.axis.spatial(112, (p_outer * 28 + index_0) % 14 * 8 + i3_2)
- oc_block_o = T.axis.spatial(1, 0) # pylint: disable=unused-variable
- k_height = T.axis.reduce(7, i5_1)
- k_width = T.axis.reduce(7, i6_1)
- ic_outer = T.axis.reduce(1, 0)
- ic_f_inner = T.axis.reduce(1, 0)
- ic_s_inner_o = T.axis.reduce(1, 0) # pylint: disable=unused-variable
- T.reads(
- conv2d_nchwc_int8[n, oc_chunk, o_height, o_width, 0:32],
- p0_global_vtcm[
- n,
- ic_outer,
- o_height * 2 + k_height,
- o_width * 2 + k_width,
- ic_f_inner * 4 : ic_f_inner * 4 + 4,
- ],
- p1_global_vtcm[
- oc_chunk, ic_outer, k_height, k_width, ic_f_inner, 0:32, 0:4
- ],
- )
- T.writes(conv2d_nchwc_int8[n, oc_chunk, o_height, o_width, 0:32])
- a_buffer = T.match_buffer(
- p0_global_vtcm[
- n,
- ic_outer,
- o_height * 2 + k_height,
- o_width * 2 + k_width,
- ic_f_inner * 4 : ic_f_inner * 4 + 4,
- ],
- [4],
- dtype="uint8",
- offset_factor=1,
- scope="global.vtcm",
- )
- b_buffer = T.match_buffer(
- p1_global_vtcm[
- oc_chunk, ic_outer, k_height, k_width, ic_f_inner, 0:32, 0:4
- ],
- [32, 4],
- dtype="int8",
- offset_factor=1,
- scope="global.vtcm",
- )
- c_buffer = T.match_buffer(
- conv2d_nchwc_int8[n, oc_chunk, o_height, o_width, 0:32],
- [32],
- dtype="int32",
- offset_factor=1,
- scope="global.vtcm",
- )
- a_u8x4: T.uint8x4 = a_buffer[0:4]
- a_i32: T.int32 = T.reinterpret(a_u8x4, dtype="int32")
- b_i8x128 = b_buffer[0, 0:128]
- b_i32x32: T.int32x32 = T.reinterpret(b_i8x128, dtype="int32x32")
- c_buffer[0:32] = T.call_llvm_pure_intrin(
- T.llvm_lookup_intrinsic_id("llvm.hexagon.V6.vrmpyubv.acc.128B"),
- c_buffer[0:32],
- T.broadcast(a_i32, 32),
- b_i32x32,
- dtype="int32x32",
- )
- for index_0 in T.serial(200704):
- with T.sblock("conv2d_nchwc_int8.vtcm"):
- ax0_1 = T.axis.spatial(1, 0)
- ax1_1 = T.axis.spatial(2, index_0 % 7168 // 3584)
- ax2_1 = T.axis.spatial(
- 112, (p_outer * 28 + index_0 // 7168) // 14 * 14 + index_0 % 3584 // 256
- )
- ax3_1 = T.axis.spatial(
- 112, (p_outer * 28 + index_0 // 7168) % 14 * 8 + index_0 % 256 // 32
- )
- ax4 = T.axis.spatial(32, index_0 % 32)
- T.reads(conv2d_nchwc_int8[ax0_1, ax1_1, ax2_1, ax3_1, ax4])
- T.writes(t_cast[ax0_1, ax1_1, ax2_1, ax3_1, ax4])
- t_cast[ax0_1, ax1_1, ax2_1, ax3_1, ax4] = conv2d_nchwc_int8[
- ax0_1, ax1_1, ax2_1, ax3_1, ax4
- ]
-
-
-# from tvm.script import tirx as T
-@tvm.script.ir_module
-class ModuleBase:
- """Base module test class."""
-
- # pylint: disable=no-self-argument
- @T.prim_func(s_tir=True)
- def main(
- p0_buffer: T.Buffer((1, 1, 230, 230, 4), "uint8"),
- p1_buffer: T.Buffer((2, 1, 7, 7, 1, 32, 4), "int8"),
- t_cast: T.Buffer((1, 2, 112, 112, 32), "int32"),
- ) -> None:
- # pylint: disable=missing-function-docstring
- # function attr dict
- T.func_attr({"tirx.noalias": True, "global_symbol": "main"})
- # buffer definition
- # body
- # with T.sblock("root")
- conv2d_nchwc_int8 = T.sblock_alloc_buffer([1, 2, 112, 112, 32], dtype="int32")
- for i0_0_i1_0_i2_0_i3_0_fused in T.parallel(
- 112, annotations={"pragma_auto_unroll_max_step": 64, "pragma_unroll_explicit": 1}
- ):
- for i4_0_0 in T.serial(1): # pylint: disable=unused-variable
- for i1_1_init, i2_1_init, i3_1_init, i1_2_init, i2_2_init, i3_2_init in T.grid(
- 2, 1, 1, 1, 14, 8
- ):
- with T.sblock("conv2d_NCHWc_int8_o_init"):
- n = T.axis.spatial(1, 0)
- oc_chunk = T.axis.spatial(2, i1_1_init + i1_2_init)
- o_height = T.axis.spatial(
- 112, i0_0_i1_0_i2_0_i3_0_fused // 14 * 14 + i2_1_init * 14 + i2_2_init
- )
- o_width = T.axis.spatial(
- 112, i0_0_i1_0_i2_0_i3_0_fused % 14 * 8 + i3_1_init * 8 + i3_2_init
- )
- oc_block_o = T.axis.spatial(1, 0) # pylint: disable=unused-variable
- T.reads()
- T.writes(conv2d_nchwc_int8[n, oc_chunk, o_height, o_width, 0:32])
- for i4_1 in T.vectorized(32):
- with T.sblock("conv2d_NCHWc_int8_init"):
- oc_block_i_init = T.axis.spatial(32, i4_1)
- T.reads()
- T.writes(
- conv2d_nchwc_int8[
- n, oc_chunk, o_height, o_width, oc_block_i_init
- ]
- )
- conv2d_nchwc_int8[
- n, oc_chunk, o_height, o_width, oc_block_i_init
- ] = 0
- for i5_0, i6_0, i7_0, i8_0, i9_0_0 in T.grid( # pylint: disable=unused-variable
- 1, 1, 1, 1, 1
- ): # pylint: disable=unused-variable
- for (
- i0_1, # pylint: disable=unused-variable
- i1_1,
- i2_1,
- i3_1,
- i4_0_1, # pylint: disable=unused-variable
- i5_1,
- i6_1,
- i7_1, # pylint: disable=unused-variable
- i8_1, # pylint: disable=unused-variable
- i9_0_1, # pylint: disable=unused-variable
- i0_2, # pylint: disable=unused-variable
- i1_2,
- i2_2,
- i3_2,
- i4_0_2, # pylint: disable=unused-variable
- ) in T.grid(1, 2, 1, 1, 1, 7, 7, 1, 1, 1, 1, 1, 14, 8, 1):
- with T.sblock("conv2d_NCHWc_int8_o_update"):
- n = T.axis.spatial(1, 0)
- oc_chunk = T.axis.spatial(2, i1_1 + i1_2)
- o_height = T.axis.spatial(
- 112, i0_0_i1_0_i2_0_i3_0_fused // 14 * 14 + i2_1 * 14 + i2_2
- )
- o_width = T.axis.spatial(
- 112, i0_0_i1_0_i2_0_i3_0_fused % 14 * 8 + i3_1 * 8 + i3_2
- )
- oc_block_o = T.axis.spatial(1, 0) # pylint: disable=unused-variable
- k_height = T.axis.reduce(7, i5_0 * 7 + i5_1)
- k_width = T.axis.reduce(7, i6_0 * 7 + i6_1)
- ic_outer = T.axis.reduce(1, 0)
- ic_f_inner = T.axis.reduce(1, 0)
- ic_s_inner_o = T.axis.reduce(1, 0) # pylint: disable=unused-variable
- T.reads(
- conv2d_nchwc_int8[n, oc_chunk, o_height, o_width, 0:32],
- p0_buffer[
- n,
- ic_outer,
- o_height * 2 + k_height,
- o_width * 2 + k_width,
- ic_f_inner * 4 : ic_f_inner * 4 + 4,
- ],
- p1_buffer[
- oc_chunk, ic_outer, k_height, k_width, ic_f_inner, 0:32, 0:4
- ],
- )
- T.writes(conv2d_nchwc_int8[n, oc_chunk, o_height, o_width, 0:32])
- a_buffer = T.match_buffer(
- p0_buffer[
- n,
- ic_outer,
- o_height * 2 + k_height,
- o_width * 2 + k_width,
- ic_f_inner * 4 : ic_f_inner * 4 + 4,
- ],
- [4],
- dtype="uint8",
- offset_factor=1,
- )
- b_buffer = T.match_buffer(
- p1_buffer[
- oc_chunk, ic_outer, k_height, k_width, ic_f_inner, 0:32, 0:4
- ],
- [32, 4],
- dtype="int8",
- offset_factor=1,
- )
- c_buffer = T.match_buffer(
- conv2d_nchwc_int8[n, oc_chunk, o_height, o_width, 0:32],
- [32],
- dtype="int32",
- offset_factor=1,
- )
- a_u8x4: T.uint8x4 = a_buffer[0:4]
- a_i32: T.int32 = T.reinterpret(a_u8x4, dtype="int32")
- b_i8x128 = b_buffer[0, 0:128]
- b_i32x32: T.int32x32 = T.reinterpret(b_i8x128, dtype="int32x32")
- c_buffer[0:32] = T.call_llvm_pure_intrin(
- T.llvm_lookup_intrinsic_id("llvm.hexagon.V6.vrmpyubv.acc.128B"),
- c_buffer[0:32],
- T.broadcast(a_i32, 32),
- b_i32x32,
- dtype="int32x32",
- )
- for ax0, ax1, ax2, ax3 in T.grid(1, 2, 14, 8):
- for ax4_fused in T.vectorized(32):
- with T.sblock("T_cast_2"):
- ax0_1, ax1_1 = T.axis.remap("SS", [ax0, ax1])
- ax2_1 = T.axis.spatial(
- 112, i0_0_i1_0_i2_0_i3_0_fused // 14 * 14 + ax2
- )
- ax3_1 = T.axis.spatial(
- 112, i0_0_i1_0_i2_0_i3_0_fused % 14 * 8 + ax3
- )
- ax4 = T.axis.spatial(32, ax4_fused)
- T.reads(conv2d_nchwc_int8[ax0_1, ax1_1, ax2_1, ax3_1, ax4])
- T.writes(t_cast[ax0_1, ax1_1, ax2_1, ax3_1, ax4])
- t_cast[ax0_1, ax1_1, ax2_1, ax3_1, ax4] = conv2d_nchwc_int8[
- ax0_1, ax1_1, ax2_1, ax3_1, ax4
- ]
-
-
-@pytest.mark.skipif(not env.has_hexagon(), reason="need hexagon")
-def test_meta(hexagon_session):
- """Test meta."""
- if tvm.testing.utils.IS_IN_CI:
- pytest.skip("Skipping test since it takes too long in CI.")
-
- a_data = np.random.randint(1, 8, (1, 1, 230, 230, 4), dtype="uint8")
- w_data = np.random.randint(1, 8, (2, 1, 7, 7, 1, 32, 4), dtype="int8")
- c_data = np.zeros((1, 2, 112, 112, 32), dtype="int32")
-
- sch = tvm.s_tir.Schedule(ModuleBase)
- base_runtime = evaluate(hexagon_session, sch, a_data, w_data, c_data)
-
- sch = tvm.s_tir.Schedule(ModulePipelined)
- compute_block = sch.get_sblock("conv2d_NCHWc_int8_o_update")
- outer = sch.get_loops(compute_block)[0]
-
- unscheduled_vtcm_runtime = evaluate(
- hexagon_session, sch, a_data, w_data, c_data, use_async_copy=1
- )
-
- sch = tvm.s_tir.Schedule(ModulePipelined)
- compute_block = sch.get_sblock("conv2d_NCHWc_int8_o_update")
- outer = sch.get_loops(compute_block)[0]
-
- sch.annotate(outer, "software_pipeline_stage", [0, 1, 2])
- sch.annotate(outer, "software_pipeline_order", [0, 1, 2])
- sch.annotate(outer, "software_pipeline_async_stages", [0, 2])
-
- pipeline_runtime = evaluate(hexagon_session, sch, a_data, w_data, c_data, use_async_copy=1)
-
- transfer_mb = round((a_data.size + w_data.size + c_data.size) / 1e6, 2)
- print_results(
- (
- f"Test with a_buffer.size: {a_data.size}, w_buffer.size: {w_data.size}"
- f", and total memory transfer of {transfer_mb} MB..."
- ),
- {
- "without_vtcm": base_runtime,
- "unscheduled_vtcm_runtime": unscheduled_vtcm_runtime,
- "pipeline_runtime": pipeline_runtime,
- },
- )
-
-
-if __name__ == "__main__":
- tvm.testing.main()
diff --git a/tests/python/contrib/test_hexagon/test_benchmark_elemwise_add.py b/tests/python/contrib/test_hexagon/test_benchmark_elemwise_add.py
deleted file mode 100644
index 39b4eef555..0000000000
--- a/tests/python/contrib/test_hexagon/test_benchmark_elemwise_add.py
+++ /dev/null
@@ -1,425 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-"""benchmark_elemwise_add"""
-
-import os
-import os.path
-import sys
-import tempfile
-
-import numpy as np
-import pytest
-
-import tvm.script
-import tvm.testing
-from tvm.contrib.hexagon.session import Session
-from tvm.script import tirx as T
-from tvm.testing import env
-
-from . import benchmark_util as bu
-from .infrastructure import get_hexagon_target
-
-_SHOULD_SKIP_BENCHMARKS, _SKIP_BENCHMARKS_REASON = bu.skip_benchmarks_flag_and_reason()
-
-# This is a fixed detail of the v68 architecture.
-HVX_VECTOR_BYTES = 128
-
-# NOTE on server ports:
-# These tests use different port numbers for the RPC server (7070 + ...).
-# The reason is that an RPC session cannot be gracefully closed without
-# triggering TIME_WAIT state on the server socket. This prevents another
-# server to bind to the same port until the wait time elapses.
-
-_BT = bu.BenchmarksTable()
-
-_CSV_COLUMN_ORDER = [
- # Identifies which TE-compute / TIRScript is used as the basis for the
- # benchmarked primfunc. Only needs to be meaningful to humans.
- "basic_kernel",
- # The tensors' element type
- "dtype",
- # When applicable, indicates the particular variation of schedules
- # apply by the Python code. Decoding this may require looking at this
- # script's source code.
- "sched_type",
- # The memory location of the tensors used during the execution of
- # the primfunc. We currently assume just one location.
- # This will likely need to be generalized as we add more sophisticated
- # primfuncs.
- "mem_scope",
- # For primfuncs that treat tensor buffers as collections of 1D vectors,
- # this is the number of vectors in each tensor.
- # This will likely need to be generalized as we add more sophisticated
- # primfuncs.
- "num_vectors_per_tensor",
- # Reserved columns defined by the BenchmarksTable class.
- "row_status",
- "timings_min_usecs",
- "timings_max_usecs",
- "timings_median_usecs",
- "timings_mean_usecs",
- "timings_stddev_usecs",
- # For benchmarks that produce files on the host file system, this indicates
- # their location. Useful for post-mortem investigation of benchmark results.
- "host_files_dir_path",
- # Miscellaneous comments about the benchmark.
- "comments",
-]
-
-_HOST_OUTPUT_DIR = tempfile.mkdtemp()
-
-_PRIMFUNC_NAME = "elemwise_add"
-
-print("-" * 80)
-print(f"OUTPUT DIRECTORY: {_HOST_OUTPUT_DIR}")
-print("-" * 80)
-print()
-
-
-def _get_irmod_elemwise_add(shape: list, dtype: str, mem_scope: str) -> tvm.ir.module.IRModule:
- """
- Return an IRModule containing a single primfunc, expressed as NS-TIR.
-
- The primfunc implements elementwise-add. Its signature is (A,B,C), where
- A and B are the input tensors, and C is the output tensor.
- All three tensors have the specfied shape, dtype, and mem_scope.
-
- If the specified primfunc is known to be unsupported, raise an UnsupportedExcetion.
- """
- assert len(shape) == 2
-
- # TVMScript can reference simple Python variables, but it doesn't
- # curently support more complex Python expressions...
- (
- dim0_size,
- dim1_size,
- ) = shape
-
- if mem_scope == "global.vtcm":
- raise bu.UnsupportedException("This benchmark kernel does not yet support VTCM buffers.")
-
- # This check is currently elided by the one above, but it should become relevant as soon
- # as we add VTCM support to this kernel generator.
- #
- # Also: The VTCM budget is a very rough estimate, based only on experience.
- # Assuming that it's even reasonable to use a hard-coded estimate AT ALL, this number
- # may need tweaking.
-
- # The below code is commented is commented to avoid unreachable error
- # with pylint. Please enable this once the kernel starts supporting
- # VTCM buffers
-
- # Code starts below:
- # ---- ------ -----
- # estimated_vtcm_budget_bytes = HVX_VECTOR_BYTES * 1024
-
- # dtype_bits = tvm.runtime.DataType(dtype).bits
- # assert dtype_bits % 8 == 0
- # dtype_bytes = dtype_bits // 8
-
- # num_vtcm_tensors = 3
- # estimated_vtcm_needed_bytes = shape[0] * shape[1] * dtype_bytes * num_vtcm_tensors
-
- # if estimated_vtcm_needed_bytes > estimated_vtcm_budget_bytes:
- # raise bu.UnsupportedException("Expect to exceed VTCM budget.")
-
- @tvm.script.ir_module
- class BenchmarkModule:
- """Elementwise STIR module for benchmarking"""
-
- # pylint: disable=no-self-argument,invalid-name,missing-function-docstring
- @T.prim_func(s_tir=True)
- def main(a: T.handle, b: T.handle, c: T.handle):
- # We exchange data between function by handles, which are similar to pointer.
- T.func_attr({"global_symbol": "main", "tirx.noalias": True})
-
- A = T.match_buffer(a, shape, dtype=dtype)
- B = T.match_buffer(b, shape, dtype=dtype)
- C = T.match_buffer(c, shape, dtype=dtype)
-
- for i in range(dim0_size):
- for j in range(dim1_size):
- C[i, j] = A[i, j] + B[i, j]
-
- # pylint: enable=no-self-argument,invalid-name,missing-function-docstring
-
- return BenchmarkModule
-
-
-def _benchmark_hexagon_elementwise_add_kernel(
- hexagon_session: Session, shape: list, dtype: str, mem_scope: str
-):
- """
- Generate and benchmark a single elementwise-add kernel for Hexagon.
-
- Produce these outputs:
- - Printed status updates / results to stdout and/or stderr.
-
- - Create a new subdirectory under _HOST_OUTPUT_DIR, and populate it with
- various logs and intermediate files.
-
- - Add to _BT a row describing this benchmark run.
- """
- # Represent the benchmark details in a form required by the benchmark table
- # and for other logging...
- keys_dict = {
- "basic_kernel": "ewise-add",
- "dtype": dtype,
- "shape": shape,
- "mem_scope": mem_scope,
- }
-
- desc = bu.get_benchmark_decription(keys_dict)
-
- # Create the host-side directory for this benchmark run's files / logs...
- host_files_dir_name = bu.get_benchmark_id(keys_dict)
- host_files_dir_path = os.path.join(_HOST_OUTPUT_DIR, host_files_dir_name)
- os.mkdir(host_files_dir_path)
-
- keys_dict["host_files_dir_path"] = host_files_dir_path
-
- log_file_path = os.path.join(host_files_dir_path, "out.txt")
- with open(log_file_path, "w", encoding="UTF-8") as log_file:
- print(f"CONFIGURATION: {desc}")
- log_file.write(f"CONFIGURATION: {desc}\n")
-
- try:
- ns_tir_module = _get_irmod_elemwise_add(shape, dtype, mem_scope)
-
- # Lower the primfunc's IRModule to Hexagon object code...
- input1 = tvm.te.placeholder(shape, dtype=dtype)
- input2 = tvm.te.placeholder(shape, dtype=dtype)
- output = tvm.te.placeholder(shape, dtype=dtype)
-
- built_module: tvm.driver.build_module.OperatorModule = tvm.compile(
- ns_tir_module,
- [
- input1,
- input2,
- output,
- ],
- get_hexagon_target("v69"),
- name=_PRIMFUNC_NAME,
- )
-
- # Create an actual Hexagon-native shared object file, initially stored on the
- # host's file system...
- host_dso_binary_path = os.path.join(host_files_dir_path, "test_binary.so")
- built_module.write_to_file(host_dso_binary_path)
- print(f"SAVED BINARY TO HOST PATH: {host_dso_binary_path}")
-
- # Upload the .so to the Android device's file system (or wherever is appropriate
- # when using the Hexagon simulator)...
- target_dso_binary_filename = "test_binary.so"
- target_dso_binary_pathname = hexagon_session.upload(
- host_dso_binary_path, target_dso_binary_filename
- )
-
- # Generate our testing / validation data...
- (
- host_numpy_input1_data,
- host_numpy_input2_data,
- host_numpy_output_data_expected,
- ) = _get_elemwise_add_reference_value_tensors(shape, dtype)
-
- # On the target device / simulator, make our Hexagon-native shared object
- # available for use...
- loaded_hexagon_module: tvm.runtime.module.Module = hexagon_session.load_module(
- target_dso_binary_pathname
- )
-
- # Create the target-side tensors to hold the primfunc's inputs and outputs...
- input1_data = tvm.runtime.empty(shape, dtype, hexagon_session.device, mem_scope)
- input2_data = tvm.runtime.empty(shape, dtype, hexagon_session.device, mem_scope)
- output_data = tvm.runtime.empty(shape, dtype, hexagon_session.device, mem_scope)
-
- # Populate the primfunc's input tensors...
- input1_data.copyfrom(host_numpy_input1_data)
- input2_data.copyfrom(host_numpy_input2_data)
-
- # Actually benchmark the primfunc...
- timer = loaded_hexagon_module.time_evaluator(
- "main", hexagon_session.device, number=10, repeat=1
- )
- timing_result = timer(input1_data, input2_data, output_data)
-
- print(f"TIMING RESULT: {timing_result}")
- log_file.write(f"TIMING RESULT: {timing_result}\n")
-
- # Verify that the computation actually happened, and produced the correct result.
- result = output_data.numpy()
-
- if dtype == "float16":
- # These are the closest tolerance we currently expect / require for these
- # kernels. They may be changed in the future.
- rel_tolerance = 0.005
- abs_tolerance = 2.0
- elif dtype == "int8":
- rel_tolerance = 0
- abs_tolerance = 0
- else:
- raise Exception(f"Unexpected dtype: {dtype}")
-
- # TODO: We're assuming that *any* assertion thrown by 'assert_allclose' is because
- # the numerical differences were too large. But ideally this code would
- # differentiate between (a) numerical difference errors, which should simply be
- # recorded as a failed benchmark run, vs. (b) more serious errors that should
- # kill the overall script.
- try:
- tvm.testing.assert_allclose(
- result, host_numpy_output_data_expected, rel_tolerance, abs_tolerance
- )
- except AssertionError as err:
- raise bu.NumericalAccuracyException(str(err))
-
- _BT.record_success(timing_result, **keys_dict)
-
- except bu.NumericalAccuracyException as err:
- print()
- print("FAIL: Numerical accuracy error. See log file.")
-
- log_file.write("\n")
- log_file.write(f"FAIL: {err}\n")
-
- _BT.record_fail(**keys_dict, comments="Numerical accuracy error. See log file.")
-
- except bu.UnsupportedException as err:
- print()
- print(f"SKIP: {err}")
-
- log_file.write("\n")
- log_file.write(f"SKIP: {err}\n")
-
- _BT.record_skip(**keys_dict, comments=f"Unsupported configuration: {err}")
-
-
-def _get_elemwise_add_reference_value_tensors(shape: list, dtype: str):
- """
- Return [A:np.array, B:np.array, C:np.array]
-
- `A`, `B`, and `C` are reference data used to exercise and validate
- an elementwise-add kernel: C = A+B.
-
- NOTE: These data are primarily meant for performance testing.
- The values may be helpful in detecting correctness issues, but that's
- a secondary consideration here.
- """
- assert len(shape) == 2
-
- input1 = np.ndarray(shape, dtype=dtype)
- input2 = np.ndarray(shape, dtype=dtype)
-
- np_dtype = input1.dtype
-
- if np_dtype.kind in ["i", "u"]:
- # We allow overflow for integer types because it tends to be well-behaved
- # and well-understood...
- min_value = np.iinfo(np_dtype).min
- max_value = np.iinfo(np_dtype).max
-
- next_value = min_value
-
- for i in range(shape[0]):
- for j in range(shape[1]):
- input1[i, j] = next_value
- input2[i, j] = next_value * 2
- next_value += 1
-
- elif np_dtype.kind == "f":
- # NOTE: For simplicity, we avoid test data that require
- # well-defined behavior on floating-point overflow.
- # But it may be reasonable to test that in the future.
- min_value = np.finfo(np_dtype).min
- max_value = np.finfo(np_dtype).max
-
- min_input_value = min_value / 2.0 + 1
- max_input_value = max_value / 2.0 - 2
- delta = (max_input_value - min_input_value) / (shape[0] * shape[1])
-
- next_value = min_input_value
-
- for i in range(shape[0]):
- for j in range(shape[1]):
- input1[i, j] = next_value
- input2[i, j] = next_value + 1
- next_value += delta
-
- else:
- assert False, f"Unexpected data type: {np_dtype}"
-
- output = input1 + input2
- return [
- input1,
- input2,
- output,
- ]
-
-
-@pytest.mark.skipif(_SHOULD_SKIP_BENCHMARKS, reason=_SKIP_BENCHMARKS_REASON)
-@pytest.mark.skipif(not env.has_hexagon(), reason="need hexagon")
-def test_elemwise_add(hexagon_session: Session):
- """Main elementwise add test function"""
- for dtype in [
- "int8",
- "float16",
- ]:
- for mem_scope in [
- "global",
- "global.vtcm",
- ]:
- # These numbers are fairly arbitrary, but they're meant to stress memory/caches to
- # various extents.
- for num_vectors_per_tensor in [
- 1,
- 16,
- 64,
- 512,
- 2048,
- ]:
- dtype_bits = tvm.runtime.DataType(dtype).bits
- assert dtype_bits % 8 == 0
- dtype_bytes = dtype_bits // 8
-
- elem_per_hvx_vector = HVX_VECTOR_BYTES // dtype_bytes
-
- shape = [
- num_vectors_per_tensor,
- elem_per_hvx_vector,
- ]
-
- print()
- _benchmark_hexagon_elementwise_add_kernel(hexagon_session, shape, dtype, mem_scope)
-
- print("-" * 80)
- print(f"OUTPUT DIRECTORY: {_HOST_OUTPUT_DIR}")
- print("-" * 80)
- print()
-
- tabular_output_filename = os.path.join(_HOST_OUTPUT_DIR, "benchmark-results.csv")
- with open(tabular_output_filename, "w", encoding="UTF-8") as csv_file:
- _BT.print_csv(csv_file, _CSV_COLUMN_ORDER)
-
- print(f"BENCHMARK RESULTS FILE: {tabular_output_filename}")
-
- _BT.print_csv(sys.stdout, _CSV_COLUMN_ORDER)
-
- if _BT.has_fail() > 0:
- pytest.fail("At least one benchmark configuration failed", pytrace=False)
-
-
-if __name__ == "__main__":
- tvm.testing.main()
diff --git a/tests/python/contrib/test_hexagon/test_benchmark_maxpool2d.py b/tests/python/contrib/test_hexagon/test_benchmark_maxpool2d.py
deleted file mode 100644
index 91830e5d1a..0000000000
--- a/tests/python/contrib/test_hexagon/test_benchmark_maxpool2d.py
+++ /dev/null
@@ -1,358 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-# ruff: noqa: F401, RUF012
-
-"""
-This module serves two purposes:
- (1) Demonstrates how to write Python code that exercises various
- Hexagon-related algorithms / features.
-
- (2) Benchmark the resulting primfuncs.
-
-Current limitations:
- - Input shapes are limited to NHWC --> NHWC_8h8w32c.
-
- - Testing parameters (input shapes, dtypes, etc.) currently
- support only one value for each parameter.
-
- - height, width, channel must be integer multiples of 8, 8, and 32,
- respectively. I.e., partial blocks aren't currently
- supported by this script.
-
- - Requires that I/O tensors reside in "global.VTCM" memory,
- rather than "global" memory.
- This prevents benchmarking with I/O tensors that are too
- large to fit into availble VTCM.
-
- - The script only develops one primfunc.
- Future revisions to this script are expected to add more
- primfuncs and demonstrate more coding strategies.
-"""
-
-import copy
-import os
-
-import numpy as np
-import pytest
-
-pytest.importorskip("scipy") # tvm.topi.testing imports scipy
-
-import tvm.testing
-from tvm import te, tirx, topi
-from tvm.contrib.hexagon import allocate_hexagon_array
-from tvm.contrib.hexagon.session import Session
-from tvm.testing import env
-from tvm.topi import testing
-
-from . import benchmark_util as bu
-from .infrastructure import get_hexagon_target
-
-# Pytest seems to require that fixture names exist in the current module.
-# E.g., it doesn't allow: @pytest.mark.usefixtures("bu.benchmark_group")
-BENCHMARK_GROUP = bu.benchmark_group
-
-_SHOULD_SKIP_BENCHMARKS, _SKIP_BENCHMARKS_REASON = bu.skip_benchmarks_flag_and_reason()
-
-
-def _ceil_div(numerator, denominator):
- return (numerator + (denominator - 1)) // denominator
-
-
-def _int8_nhwc_8h8w32c_map(n_batch, height, width, channel):
- return [
- n_batch,
- height // 8,
- width // 8,
- channel // 32,
- te.AXIS_SEPARATOR,
- height % 8,
- width % 8,
- channel % 32,
- ]
-
-
-def _int8_nhwc_8h8w32c_shape(n_batch, height, width, channel) -> list[int]:
- return [
- n_batch,
- _ceil_div(height, 8),
- _ceil_div(width, 8),
- _ceil_div(channel, 32),
- 8,
- 8,
- 32,
- ]
-
-
-def _int8_nhwc_8h8w32c_xform_immediate(arr_in: np.ndarray) -> np.ndarray:
- """
- Return a deep copy of 'arr_in', transformed from a NWHC to
- NHWC-8h8wc32 shape. Any newly created array elements have value 0.
- """
- stage1 = copy.copy(arr_in)
-
- (
- n_batch,
- height,
- width,
- channel,
- ) = stage1.shape
-
- (
- h_minor,
- w_minor,
- c_minor,
- ) = [8, 8, 32]
-
- h_major = _ceil_div(height, h_minor)
- w_major = _ceil_div(width, w_minor)
- c_major = _ceil_div(channel, c_minor)
-
- # This handles cases where the dimensions of arr_in are not cleanly divided
- # by the minor block size, i.e. [8, 8, 32].
- #
- # Any additional array elements that this creates will ahve value 0.
- # We shouldn't actually care what value is used for those elements, because they
- # shouldn't be treated as meaningful by any of our algorithms.
- if (height % h_minor) or (width % w_minor) or (channel % c_minor):
- stage1.resize(
- (n_batch, h_major * h_minor, w_major * w_minor, c_major * c_minor), refcheck=False
- )
-
- stage2 = stage1.reshape(n_batch, h_major, h_minor, w_major, w_minor, c_major, c_minor)
- stage3 = stage2.transpose(0, 1, 3, 5, 2, 4, 6)
- return stage3
-
-
-def _create_test_input(shape, dtype: str) -> np.ndarray:
- np_dtype = np.dtype(dtype)
- min_value = np.iinfo(np_dtype).min
- max_value = np.iinfo(np_dtype).max
- return np.random.randint(low=min_value, high=max_value, size=tuple(shape), dtype=np.int8)
-
-
-@pytest.mark.usefixtures("BENCHMARK_GROUP")
-class TestMaxPool2D:
- """maxpool2D base test class"""
-
- csv_column_order = [
- # Identifies which TE-compute / TIRScript is used as the basis for the
- # benchmarked primfunc. Only needs to be meaningful to humans.
- "basic_kernel",
- # When applicable, indicates the particular variation of schedules
- # apply by the Python code. Decoding this may require looking at this
- # script's source code.
- "sched_type",
- # Values directly based on test parameters...
- "input_shape_4d",
- "block_shape",
- "dtype",
- "kernel",
- "stride",
- "dilation",
- "padding",
- "io_tensor_mem_scope",
- # Reserved columns defined by the BenchmarksTable class.
- "row_status",
- "timings_min_usecs",
- "timings_max_usecs",
- "timings_median_usecs",
- "timings_mean_usecs",
- "timings_stddev_usecs",
- # For benchmarks that produce files on the host file system, this indicates
- # their location. Useful for post-mortem investigation of benchmark results.
- "host_files_dir_path",
- # Miscellaneous comments about the benchmark.
- "comments",
- ]
-
- dtype = tvm.testing.parameter("int8")
-
- # FIXME(cconvey): The script currently fails when height, width, or channel is not an
- # integer multiple of 8, 8, or 32, respectively.
- n_batch = tvm.testing.parameter(1)
- height = tvm.testing.parameter(*[x * 8 for x in [1, 4, 16]])
- width = tvm.testing.parameter(*[x * 8 for x in [1, 4, 16]])
- channel = tvm.testing.parameter(*[x * 32 for x in [1, 2]])
-
- kernel = tvm.testing.parameter((1, 1), (3, 3))
- stride = tvm.testing.parameter((1, 1))
- dilation = tvm.testing.parameter((1, 1))
- padding = tvm.testing.parameter((0, 0, 0, 0))
- io_tensor_mem_scope = tvm.testing.parameter("global.vtcm")
-
- @pytest.mark.skipif(_SHOULD_SKIP_BENCHMARKS, reason=_SKIP_BENCHMARKS_REASON)
- @pytest.mark.skipif(not env.has_hexagon(), reason="need hexagon")
- def test_maxpool2d_nhwc(
- self,
- n_batch,
- height,
- width,
- channel,
- dtype,
- kernel,
- stride,
- dilation,
- padding,
- io_tensor_mem_scope,
- hexagon_session: Session,
- ):
- """Test maxpool2d NHWC"""
-
- keys_dict = {
- "basic_kernel": "max_pool2d",
- "sched_type": 1,
- "input_shape_4d": [n_batch, height, width, channel],
- "block_shape": [8, 8, 32],
- "dtype": dtype,
- "kernel": kernel,
- "stride": stride,
- "dilation": dilation,
- "padding": padding,
- "io_tensor_mem_scope": io_tensor_mem_scope,
- }
-
- desc = bu.get_benchmark_decription(keys_dict)
-
- # Create the host-side directory for this benchmark run's files / logs...
- host_files_dir_name = bu.get_benchmark_id(keys_dict)
- host_files_dir_path = os.path.join(self.working_dir, host_files_dir_name)
- os.mkdir(host_files_dir_path)
-
- keys_dict["host_files_dir_path"] = host_files_dir_path
-
- log_file_path = os.path.join(host_files_dir_path, "out.txt")
- with open(log_file_path, "w") as log_file:
- print(f"CONFIGURATION: {desc}")
- log_file.write(f"CONFIGURATION: {desc}\n")
-
- try:
- input_tensor_shape_4d = [n_batch, height, width, channel]
- input_tensor_shape_7d = _int8_nhwc_8h8w32c_shape(n_batch, height, width, channel)
-
- data = te.placeholder(tuple(input_tensor_shape_4d), dtype=dtype)
-
- output = topi.nn.pool2d(
- data, kernel, stride, dilation, padding, "max", layout="NHWC"
- )
- primfunc = te.create_prim_func([data, output])
-
- sch = tvm.s_tir.Schedule(primfunc, debug_mask="all")
-
- sch.transform_layout(
- block="tensor", buffer="placeholder", index_map=_int8_nhwc_8h8w32c_map
- )
-
- built_module = tvm.compile(
- sch.mod,
- target=get_hexagon_target("v69"),
- )
-
- # Save a local copy of the Hexagon object code (in the form of a .so file)
- # to allow post-mortem inspection.
- host_dso_binary_path = os.path.join(host_files_dir_path, "test_binary.so")
- built_module.write_to_file(host_dso_binary_path)
- print(f"SAVED BINARY TO HOST PATH: {host_dso_binary_path}")
-
- hexagon_mod = hexagon_session.load_module(built_module)
-
- # Generate the input tensor's data.
- # Note that we'll eventually need it in two different layouts:
- # (1) NHWC as an argument to testing.poolnd_python.
- # (2) NHWC_8h8w32c for as an argument to our Hexagon primfunc.
- # a_numpy_4d = np.random.randint(low=-128, high=127,
- # size=input_tensor_shape_4d, dtype=np.int8)
- a_numpy_4d = _create_test_input(input_tensor_shape_4d, dtype)
-
- ref_output_4d = testing.poolnd_python(
- a_numpy_4d.astype("int32"),
- kernel,
- stride,
- dilation,
- padding[0:2],
- padding[2:],
- pool_type="max",
- dtype="int32",
- layout="NHWC",
- ).astype(dtype)
-
- output_tensor_shape_4d = ref_output_4d.shape
-
- a_numpy_7d = _int8_nhwc_8h8w32c_xform_immediate(a_numpy_4d)
-
- a_hexagon_7d = allocate_hexagon_array(
- hexagon_session.device,
- tensor_shape=input_tensor_shape_7d,
- axis_separators=[4],
- dtype=dtype,
- mem_scope=io_tensor_mem_scope,
- )
-
- c_hexagon_4d = allocate_hexagon_array(
- hexagon_session.device,
- tensor_shape=output_tensor_shape_4d,
- axis_separators=[],
- dtype=dtype,
- mem_scope=io_tensor_mem_scope,
- )
-
- a_hexagon_7d.copyfrom(a_numpy_7d)
-
- if dtype == "int8":
- rel_tolerance = 0
- abs_tolerance = 0
- else:
- assert False, f"TODO: decide acceptable tolerances for dtype {dtype}"
-
- timer = hexagon_mod.time_evaluator(
- "main", hexagon_session.device, number=10, repeat=1
- )
- timing_result = timer(a_hexagon_7d, c_hexagon_4d)
-
- try:
- tvm.testing.assert_allclose(
- ref_output_4d, c_hexagon_4d.numpy(), rtol=rel_tolerance, atol=abs_tolerance
- )
- except AssertionError as exception:
- raise bu.NumericalAccuracyException(str(exception))
-
- except bu.NumericalAccuracyException as exception:
- print()
- print("FAIL: Numerical accuracy error. See log file.")
-
- log_file.write("\n")
- log_file.write(f"FAIL: {exception}\n")
-
- self.benchmark_table.record_fail(
- **keys_dict, comments="Numerical accuracy error. See log file."
- )
-
- except bu.UnsupportedException as exception:
- print()
- print(f"SKIP: {exception}")
-
- log_file.write("\n")
- log_file.write(f"SKIP: {exception}\n")
-
- self.benchmark_table.record_skip(
- **keys_dict, comments=f"Unsupported configuration: {exception}"
- )
-
- self.benchmark_table.record_success(timing_result, **keys_dict)
-
-
-if __name__ == "__main__":
- tvm.testing.main()
diff --git a/tests/python/contrib/test_hexagon/test_dma_builtin.py b/tests/python/contrib/test_hexagon/test_dma_builtin.py
deleted file mode 100644
index 961d3bb5d6..0000000000
--- a/tests/python/contrib/test_hexagon/test_dma_builtin.py
+++ /dev/null
@@ -1,190 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""
-Test relax vm builtin to enable DMA copy and wait operations.
-"""
-
-import numpy as np
-import pytest
-
-import tvm
-import tvm.contrib.hexagon
-import tvm.script
-import tvm.testing
-from tvm import relax
-from tvm.script.parser import ir as I
-from tvm.script.parser import relax as R
-from tvm.script.parser import tirx as T
-from tvm.testing import env
-
-# pylint: disable=invalid-name, missing-class-docstring, missing-function-docstring, no-self-argument
-
-data_type = "int32"
-
-
-@I.ir_module(s_tir=True)
-class Module_1D:
- @T.prim_func(s_tir=True)
- def compute_add_in_vtcm(a: T.handle, b: T.handle, c: T.handle) -> None:
- m = T.int32()
- A = T.match_buffer(a, (m,), data_type, scope="global.vtcm")
- B = T.match_buffer(b, (m,), data_type, scope="global.vtcm")
- C = T.match_buffer(c, (m,), data_type, scope="global.vtcm")
- for ax0 in T.grid(m):
- with T.sblock("T_add"):
- v_ax0 = T.axis.remap("S", [ax0])
- T.reads(A[v_ax0], B[v_ax0])
- T.writes(C[v_ax0])
- C[v_ax0] = A[v_ax0] + B[v_ax0]
-
- @R.function(pure=False)
- def main(
- x: R.Tensor((12800,), data_type),
- y: R.Tensor((12800,), data_type),
- ) -> R.Tensor((12800,), data_type):
- cls = Module_1D
- vtcm_obj: R.Object = R.vm.alloc_storage(
- R.shape(
- [
- 3 * 12800, # 3 = 2 inputs + 1 output
- ]
- ),
- runtime_device_index=0,
- dtype=data_type,
- storage_scope="global.vtcm",
- )
- a: R.Tensor(
- [
- 12800,
- ],
- dtype=data_type,
- ) = R.vm.alloc_tensor(
- vtcm_obj,
- offset=0,
- shape=R.shape(
- [
- 12800,
- ]
- ),
- dtype=data_type,
- )
- __: R.Tuple = R.call_builtin_with_ctx(
- "vm.builtin.hexagon.dma_copy",
- [x, a, 0, True],
- sinfo_args=[],
- )
- b: R.Tensor(
- [
- 12800,
- ],
- dtype=data_type,
- ) = R.vm.alloc_tensor(
- vtcm_obj,
- offset=12800 * 4,
- shape=R.shape(
- [
- 12800,
- ]
- ),
- dtype=data_type,
- )
- __: R.Tuple = R.call_builtin_with_ctx(
- "vm.builtin.hexagon.dma_copy",
- [y, b, 1, True],
- sinfo_args=[],
- )
- c: R.Tensor(
- [
- 12800,
- ],
- dtype=data_type,
- ) = R.vm.alloc_tensor(
- vtcm_obj,
- offset=2 * 12800 * 4,
- shape=R.shape(
- [
- 12800,
- ]
- ),
- dtype=data_type,
- )
- __: R.Tuple = R.call_builtin_with_ctx(
- "vm.builtin.hexagon.dma_wait",
- [0, 2, True, x, a],
- sinfo_args=[],
- )
- __: R.Tuple = R.call_builtin_with_ctx(
- "vm.builtin.hexagon.dma_wait",
- [1, 1, True, y, b],
- sinfo_args=[],
- )
- ___: R.Tuple = cls.compute_add_in_vtcm(a, b, c)
- ret_val: R.Tensor((12800,), dtype=data_type) = R.builtin.alloc_tensor(
- R.shape(
- [
- 12800,
- ]
- ),
- R.dtype(data_type),
- R.prim_value(0),
- )
- __: R.Tuple = R.call_builtin_with_ctx(
- "vm.builtin.hexagon.dma_copy",
- [c, ret_val, 0, True],
- sinfo_args=[],
- )
- __: R.Tuple = R.call_builtin_with_ctx(
- "vm.builtin.hexagon.dma_wait",
- [0, 1, True, c, ret_val],
- sinfo_args=[],
- )
- _t3: R.Tuple = R.vm.kill_object(vtcm_obj)
- _t6: R.Tuple = R.vm.kill_object(a)
- _t7: R.Tuple = R.vm.kill_object(b)
- _t8: R.Tuple = R.vm.kill_object(c)
- lv: R.Tensor((12800,), dtype=data_type) = ret_val
- return lv
-
-
-class TestDMACopyWait:
- """Tests for Copy and wait"""
-
- mode = tvm.testing.parameter("bytecode", "compiled")
- module = tvm.testing.parameter(Module_1D)
-
- @pytest.mark.skipif(not env.has_hexagon(), reason="need hexagon")
- def test_vtcm_alloc_compute(self, hexagon_launcher, mode, module):
- target_hexagon = tvm.target.Target("qcom/hexagon-v69")
- target = tvm.target.Target(target_hexagon, host=target_hexagon)
- with tvm.transform.PassContext(opt_level=3, config=[]):
- ex = tvm.compile(mod=module, target=target, exec_mode=mode)
- with hexagon_launcher.create_session() as session:
- dev = session.device
- input_arg0_data = np.random.randint(0, 9, size=(12800,), dtype=data_type)
- input_arg1_data = np.random.randint(0, 9, size=(12800,), dtype=data_type)
- output_data = np.add(input_arg0_data, input_arg1_data)
- vm_mod = session.get_executor_from_factory(ex)
- vm_rt = relax.VirtualMachine(
- vm_mod, dev, "naive"
- ) # Use naive allocator to exercise VTCM allocation in relax
- data0 = tvm.runtime.tensor(input_arg0_data, dev)
- data1 = tvm.runtime.tensor(input_arg1_data, dev)
- vm_rt.set_input("main", data0, data1)
- vm_rt.invoke_stateful("main")
- hexagon_output = vm_rt.get_outputs("main").numpy()
- tvm.testing.assert_allclose(output_data, hexagon_output)
diff --git a/tests/python/contrib/test_hexagon/test_memory_alloc.py b/tests/python/contrib/test_hexagon/test_memory_alloc.py
deleted file mode 100644
index 3030f9a6cb..0000000000
--- a/tests/python/contrib/test_hexagon/test_memory_alloc.py
+++ /dev/null
@@ -1,84 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-"""Test memory allocation."""
-
-import numpy as np
-
-import tvm
-from tvm.contrib.hexagon import allocate_hexagon_array
-from tvm.script import tirx as T
-
-from .infrastructure import get_hexagon_target
-
-
-def generated_func(shape: tuple, dtype: str, axis_separators: list):
- """Generate element wise function."""
- dim0, dim1 = shape
-
- @T.prim_func(s_tir=True)
- def elwise(a: T.handle, b: T.handle):
- a_buffer = T.match_buffer(a, shape, dtype=dtype, axis_separators=axis_separators)
- b_buffer = T.match_buffer(b, shape, dtype=dtype, axis_separators=axis_separators)
-
- for i, j in T.grid(dim0, dim1):
- with T.sblock("compute"):
- b_buffer[i, j] = a_buffer[i, j] * T.cast(2, dtype=dtype)
-
- return elwise
-
-
-class TestMemoryAlloc:
- """Memory allocation test."""
-
- dtype = tvm.testing.parameter("int8")
- shape = tvm.testing.parameter((128, 128))
-
- (
- scope,
- axis_separators,
- ) = tvm.testing.parameters(
- ("global", []),
- ("global.vtcm", []),
- ("global.vtcm", [1]),
- ("global.ddr", []),
- ("global.ddr", [1]),
- )
-
- def test_global_axis_separator(self, hexagon_session, shape, dtype, scope, axis_separators):
- """Test with global axis separator."""
- mod1 = tvm.compile(
- generated_func(shape, dtype, axis_separators),
- target=get_hexagon_target("v69"),
- )
- mod2 = hexagon_session.load_module(mod1)
-
- a_np = np.ones(shape=shape, dtype=dtype)
- a = allocate_hexagon_array(
- hexagon_session.device, data=a_np, mem_scope=scope, axis_separators=axis_separators
- )
-
- b_np = np.zeros(shape=shape, dtype=dtype)
- b = allocate_hexagon_array(
- hexagon_session.device, data=b_np, mem_scope=scope, axis_separators=axis_separators
- )
-
- mod2(a, b)
- tvm.testing.assert_allclose(a.numpy() * 2, b.numpy(), atol=1e-4, rtol=1e-4)
-
-
-if __name__ == "__main__":
- tvm.testing.main()
diff --git a/tests/python/contrib/test_hexagon/test_meta_schedule.py b/tests/python/contrib/test_hexagon/test_meta_schedule.py
deleted file mode 100644
index 4c9f7d2ee3..0000000000
--- a/tests/python/contrib/test_hexagon/test_meta_schedule.py
+++ /dev/null
@@ -1,370 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Test rpc based launcher for hexagon"""
-
-import tempfile
-
-import numpy as np
-import pytest
-
-pytest.importorskip("scipy") # tvm.topi.testing imports scipy
-
-import tvm.testing
-import tvm.topi.testing
-from tvm import te
-from tvm.contrib.hexagon.meta_schedule import (
- get_hexagon_local_builder,
- get_hexagon_rpc_runner,
-)
-from tvm.s_tir import meta_schedule as ms
-from tvm.s_tir.meta_schedule import postproc, schedule_rule
-from tvm.s_tir.meta_schedule.arg_info import TensorInfo
-from tvm.s_tir.meta_schedule.builder import BuilderInput
-from tvm.s_tir.meta_schedule.runner import RunnerInput
-from tvm.s_tir.tensor_intrin.hexagon import VRMPY_u8u8i32_INTRIN
-from tvm.script import tirx as T
-from tvm.testing import env
-from tvm.tirx import FloatImm
-
-from .infrastructure import get_hexagon_target
-
-MATMUL_N = 16
-MATMUL_M = 32
-
-
-@tvm.script.ir_module
-class MatmulModule:
- """Matmultest class"""
-
- # pylint: disable=no-self-argument
- @T.prim_func(s_tir=True)
- def main(a: T.handle, b: T.handle, c: T.handle) -> None: # type: ignore
- # pylint: disable=missing-function-docstring
- T.func_attr({"global_symbol": "main", "tirx.noalias": True})
- a_buffer = T.match_buffer(a, (16, 16), "float32")
- b_buffer = T.match_buffer(b, (16, 16), "float32")
- c_buffer = T.match_buffer(c, (16, 16), "float32")
- for i, j, k in T.grid(16, 16, 16):
- with T.sblock("matmul"):
- vi_axis, vj_axis, vk_axis = T.axis.remap("SSR", [i, j, k])
- with T.init():
- c_buffer[vi_axis, vj_axis] = 0.0 # type: ignore
- c_buffer[vi_axis, vj_axis] = (
- c_buffer[vi_axis, vj_axis]
- + a_buffer[vi_axis, vk_axis] * b_buffer[vk_axis, vj_axis]
- )
-
-
-@pytest.mark.skipif(not env.has_hexagon(), reason="need hexagon")
-def test_builder_runner(hexagon_launcher):
- """Test builder and runner."""
- if hexagon_launcher.is_simulator():
- pytest.skip("Tuning on simulator not supported.")
-
- mod = MatmulModule
-
- max_workers = 4
- builder = get_hexagon_local_builder(max_workers=max_workers)
- runner = get_hexagon_rpc_runner(
- hexagon_launcher, number=1, repeat=1, min_repeat_ms=0, max_workers=max_workers
- )
-
- (builder_result,) = builder.build([BuilderInput(mod, get_hexagon_target("v68"))])
- assert builder_result.artifact_path is not None
- assert builder_result.error_msg is None
-
- runner_input = RunnerInput(
- builder_result.artifact_path,
- "llvm",
- [
- TensorInfo("float32", (MATMUL_N, MATMUL_N)),
- TensorInfo("float32", (MATMUL_N, MATMUL_N)),
- TensorInfo("float32", (MATMUL_N, MATMUL_N)),
- ],
- )
-
- # Run the module
- (runner_future,) = runner.run([runner_input])
- runner_result = runner_future.result()
-
- assert runner_result.error_msg is None
- for result in runner_result.run_secs:
- if isinstance(result, FloatImm):
- result = result.value
- assert isinstance(result, float)
- assert result >= 0.0
-
-
-def dense_compute(m, n, k):
- """dense compute"""
- X = te.placeholder((m, k), name="X", dtype="uint8")
- packed_width = te.placeholder((n // 32, k // 4, 32, 4), name="packed_width", dtype="uint8")
-
- axis_k = te.reduce_axis((0, k), name="k")
- out = te.compute(
- (m, n),
- lambda i, j: te.sum(
- X[i, axis_k].astype("int32")
- * packed_width[
- tvm.tirx.indexdiv(j, 32), tvm.tirx.indexdiv(axis_k, 4), j % 32, axis_k % 4
- ].astype("int32"),
- axis=axis_k,
- ),
- name="compute",
- )
- return [X, packed_width, out]
-
-
-def schedule_dense(sch, block, m_size, do_tune):
- """dense schedule"""
- a_y, a_x, _ = sch.get_loops(block)[-3:]
-
- if do_tune:
- y_factors = sch.sample_perfect_tile(a_y, n=2, max_innermost_factor=128)
- a_yo, a_yi = sch.split(a_y, factors=y_factors)
- else:
- a_yo, a_yi = sch.split(a_y, factors=[None, min(m_size, 32)])
-
- a_xo, a_xi = sch.split(a_x, factors=[None, 32])
- sch.reorder(a_yo, a_xo, a_yi, a_xi)
-
- a_xi, a_k = sch.get_loops(block)[-2:]
- a_ko, a_ki = sch.split(a_k, factors=[None, 4])
- sch.reorder(a_ko, a_xi, a_ki)
-
- fused = sch.fuse(a_yo, a_xo)
-
- sch.parallel(fused)
-
- dec = sch.decompose_reduction(block, a_ko)
-
- init_loop = sch.get_loops(dec)[-1]
- sch.vectorize(init_loop)
-
- sch.tensorize(a_xi, VRMPY_u8u8i32_INTRIN)
-
-
-def verify_dense(sch, target, m_size, n_size, k_size, hexagon_session):
- """Verify dense operator."""
- f = tvm.compile(sch.mod["main"], target=target)
- mod = hexagon_session.load_module(f)
- dev = hexagon_session.device
-
- a_np = np.random.uniform(1, 10, size=(m_size, k_size)).astype("uint8")
- b_np = np.random.uniform(1, 10, size=(n_size, k_size)).astype("uint8")
- c_np = np.dot(a_np.astype("int32"), b_np.transpose().astype("int32"))
-
- pack_width = np.random.uniform(1, 10, size=(n_size // 32, (k_size // 4), 32, 4)).astype("uint8")
-
- for r_idx in range(n_size // 32):
- for k_output in range(k_size // 4):
- for s_idx in range(32):
- for t_idx in range(4):
- pack_width[r_idx][k_output][s_idx][t_idx] = b_np[r_idx * 32 + s_idx][
- k_output * 4 + t_idx
- ]
-
- a = tvm.runtime.tensor(a_np, dev)
- b = tvm.runtime.tensor(pack_width, dev)
- c = tvm.runtime.tensor(np.zeros((m_size, n_size), dtype="int32"), dev)
-
- mod(a, b, c)
- np.testing.assert_equal(c.numpy(), c_np)
-
- evaluator = mod.time_evaluator(mod.entry_name, dev, number=10)
- gflops = (n_size * m_size * k_size) * 2 / 1e9
- time_ms = evaluator(a, b, c).mean * 1e3
- print(f"{time_ms:f} ms, {gflops / (time_ms / 1e3):f} GOPS")
-
-
-@pytest.mark.skipif(not env.has_hexagon(), reason="need hexagon")
-def test_vrmpy_dense(hexagon_launcher):
- """Test vector reduce muliply dense."""
- if hexagon_launcher.is_simulator():
- pytest.skip("Tuning on simulator not supported.")
-
- do_tune = True
-
- m_size, n_size, k_size = 128, 768, 768
- workload = te.create_prim_func(dense_compute(m_size, n_size, k_size))
-
- if not do_tune:
- ir_module = tvm.IRModule({"main": workload})
- sch = tvm.s_tir.Schedule(ir_module)
- block = sch.get_sblock("compute")
- schedule_dense(sch, block, m_size, do_tune)
- else:
- with tempfile.TemporaryDirectory() as work_dir:
-
- def schedule_dense_for_tune(sch):
- block = sch.get_sblock("compute")
- return schedule_dense(sch, block, None, True)
-
- target = get_hexagon_target("v69")
- database = ms.tir_integration.tune_tir(
- mod=workload,
- target=target,
- work_dir=work_dir,
- max_trials_global=8,
- space=ms.space_generator.ScheduleFn(
- schedule_dense_for_tune,
- sch_rules=[],
- postprocs=[],
- mutator_probs={},
- ),
- strategy="replay-trace",
- builder=get_hexagon_local_builder(),
- runner=get_hexagon_rpc_runner(hexagon_launcher, number=10),
- )
- sch = ms.tir_integration.compile_tir(database, workload, target)
-
- with hexagon_launcher.create_session() as session:
- verify_dense(sch, get_hexagon_target("v68"), m_size, n_size, k_size, session)
-
-
-# This is an example of a schedule found by vrmpy auto tensorization.
-# It gets 440 GFLOPS on SD888.
-@tvm.script.ir_module
-class ModuleVRMPYAutoTensorize:
- """Vector Reduce Multimply auto tensorize test class."""
-
- # pylint: disable=no-self-argument
- @T.prim_func(s_tir=True)
- def main( # type: ignore
- X: T.Buffer((128, 768), "uint8"), # type: ignore
- packed_width: T.Buffer((24, 192, 32, 4), "uint8"), # type: ignore
- compute: T.Buffer((128, 768), "int32"), # type: ignore
- ) -> None:
- # pylint: disable=missing-function-docstring
- T.func_attr({"global_symbol": "main", "tirx.noalias": True})
- for i0_0_i1_0_0_fused in T.parallel(
- 512, annotations={"pragma_auto_unroll_max_step": 64, "pragma_unroll_explicit": 1}
- ):
- for i0_1_init, i1_0_1_init, i0_2_init, i1_0_2_init in T.grid(2, 3, 1, 1):
- with T.sblock("compute_o_init"):
- i = T.axis.spatial(128, i0_0_i1_0_0_fused // 8 * 2 + i0_1_init + i0_2_init)
- j_o = T.axis.spatial(24, i1_0_2_init + i0_0_i1_0_0_fused % 8 * 3 + i1_0_1_init)
- T.reads()
- T.writes(compute[i, j_o * 32 : j_o * 32 + 32]) # type: ignore
- for i1_1 in T.vectorized(32):
- with T.sblock("compute_init"):
- j_i_init = T.axis.spatial(32, i1_1)
- T.reads()
- T.writes(compute[i, j_o * 32 + j_i_init])
- compute[i, j_o * 32 + j_i_init] = 0 # type: ignore
- for i2_0_0, i0_1, i1_0_1, i2_0_1, i0_2, i1_0_2 in T.grid(32, 2, 3, 6, 1, 1):
- with T.sblock("compute_o_update"):
- i = T.axis.spatial(128, i0_0_i1_0_0_fused // 8 * 2 + i0_1 + i0_2)
- j_o = T.axis.spatial(24, i1_0_2 + i0_0_i1_0_0_fused % 8 * 3 + i1_0_1)
- k_o = T.axis.reduce(192, i2_0_0 * 6 + i2_0_1)
- T.reads(
- compute[i, j_o * 32 : j_o * 32 + 32], # type: ignore
- X[i, k_o * 4 : k_o * 4 + 4], # type: ignore
- packed_width[j_o, k_o, 0:32, 0:4], # type: ignore
- )
- T.writes(compute[i, j_o * 32 : j_o * 32 + 32]) # type: ignore
- a_buffer = T.match_buffer(
- X[i, k_o * 4 : k_o * 4 + 4],
- [4],
- dtype="uint8",
- offset_factor=1, # type: ignore
- )
- b_buffer = T.match_buffer(
- packed_width[j_o, k_o, 0:32, 0:4], [32, 4], dtype="uint8", offset_factor=1
- )
- c_buffer = T.match_buffer(
- compute[i, j_o * 32 : j_o * 32 + 32],
- [32],
- dtype="int32",
- offset_factor=1, # type: ignore
- )
- a_u8x4: T.uint8x4 = a_buffer[0:4] # type: ignore
- a_i32: T.int32 = T.reinterpret(a_u8x4, dtype="int32") # type: ignore
- b_i32x32: T.int32x32 = T.reinterpret(b_buffer[0, 0:128], dtype="int32x32") # type: ignore
- c_buffer[0:32] = T.call_llvm_pure_intrin( # type: ignore
- 4390, c_buffer[0:32], b_i32x32, a_i32, dtype="int32x32"
- )
-
-
-@pytest.mark.skipif(not env.has_hexagon(), reason="need hexagon")
-def test_vrmpy_dense_auto_tensorize(hexagon_launcher):
- """Test VRMPY dense operator."""
- if hexagon_launcher.is_simulator():
- pytest.skip("Tuning on simulator not supported.")
-
- m_size, n_size, k_size = 128, 768, 768
- workload = te.create_prim_func(dense_compute(m_size, n_size, k_size))
-
- sch_rules = [
- schedule_rule.MultiLevelTilingWithIntrin(
- VRMPY_u8u8i32_INTRIN,
- structure="SRSRS",
- tile_binds=None,
- max_innermost_factor=64,
- vector_load_lens=None,
- reuse_read=None,
- reuse_write=schedule_rule.ReuseType(
- req="may",
- levels=[1, 2],
- scope="global",
- ),
- ),
- schedule_rule.ParallelizeVectorizeUnroll(
- max_jobs_per_core=16,
- max_vectorize_extent=128,
- unroll_max_steps=[0, 16, 64, 512],
- unroll_explicit=True,
- ),
- ]
-
- postprocs = [
- postproc.RewriteParallelVectorizeUnroll(),
- postproc.RewriteReductionBlock(),
- postproc.RewriteTensorize(vectorize_init_loop=True),
- ]
-
- # Make this to False to compile and run the best tuned schedule
- run_tuning = True
- if run_tuning:
- with tempfile.TemporaryDirectory() as work_dir:
- target = get_hexagon_target("v68")
- database = ms.tir_integration.tune_tir(
- mod=workload,
- target=target,
- max_trials_global=8,
- num_trials_per_iter=8,
- work_dir=work_dir,
- space=ms.space_generator.PostOrderApply(
- f_block_filter=None,
- sch_rules=sch_rules,
- postprocs=postprocs,
- mutator_probs={},
- ),
- builder=get_hexagon_local_builder(),
- runner=get_hexagon_rpc_runner(hexagon_launcher, number=10),
- )
- sch = ms.tir_integration.compile_tir(database, workload, target)
- else:
- sch = tvm.s_tir.Schedule(ModuleVRMPYAutoTensorize, debug_mask="all")
-
- with hexagon_launcher.create_session() as session:
- verify_dense(sch, get_hexagon_target("v68"), m_size, n_size, k_size, session)
-
-
-if __name__ == "__main__":
- tvm.testing.main()
diff --git a/tests/python/contrib/test_hexagon/test_parallel_hvx.py b/tests/python/contrib/test_hexagon/test_parallel_hvx.py
deleted file mode 100644
index 4f8747c4e0..0000000000
--- a/tests/python/contrib/test_hexagon/test_parallel_hvx.py
+++ /dev/null
@@ -1,240 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""
-Test parallelizing HVX workloads and compare them to single thread examples.
-"""
-
-import numpy as np
-import pytest
-
-import tvm
-from tvm.script import tirx as T
-from tvm.testing import env
-
-from .infrastructure import get_hexagon_target
-
-TEST_OUTPUT_TEMPLATE = (
- "Test {} with {} operations... \n"
- " -Single Thread: {} ms \n"
- " -Parallel: {} ms\n -Speedup: {}x\n"
-)
-
-
-def get_vrmpy_shape_dtypes(operations):
- return ((operations, 128), "uint8", (operations, 128), "uint8", (operations, 32), "int32")
-
-
-def get_vmpy_vadd_shape_dtype(operations):
- return ((operations, 128), "uint8", (operations, 128), "uint8", (operations, 128), "int16")
-
-
-def vmpy_expected_producer(shape, a, b):
- expected = np.zeros(shape, dtype="int16")
- for n in range(shape[0]):
- for i in range(0, 128, 2):
- expected[n, i // 2] = np.int16(a[n, i]) * np.int16(b[n, i])
- for i in range(1, 128, 2):
- expected[n, i // 2 + 64] = np.int16(a[n, i]) * np.int16(b[n, i])
- return expected
-
-
-def vadd_expected_producer(shape, a, b):
- expected = np.zeros(shape, dtype="int16")
- for n in range(shape[0]):
- for i in range(0, 128, 2):
- expected[n, i // 2] = np.int16(a[n, i]) + np.int16(b[n, i])
- for i in range(1, 128, 2):
- expected[n, i // 2 + 64] = np.int16(a[n, i]) + np.int16(b[n, i])
- return expected
-
-
-def vrmpy_expected_producer(shape, a, b):
- expected = np.zeros(shape, dtype="int32")
- for n in range(shape[0]):
- for i in range(32):
- for r_ind in range(4):
- expected[n, i] = expected[n, i] + np.uint32(a[n, i * 4 + r_ind]) * np.uint32(
- b[n, i * 4 + r_ind]
- )
- return expected
-
-
-def get_vmpy_operator(operations):
- """Generate vector multiply operator"""
-
- @T.prim_func(s_tir=True)
- def operator(a: T.handle, b: T.handle, c: T.handle) -> None:
- T.func_attr({"global_symbol": "main", "tirx.noalias": True})
- a_buffer = T.match_buffer(a, [operations, 128], dtype="uint8")
- b_buffer = T.match_buffer(b, [operations, 128], dtype="uint8")
- c_buffer = T.match_buffer(c, [operations, 128], dtype="int16")
- for n in T.grid(operations):
- with T.sblock("c_buffer"):
- vn_ind = T.axis.remap("S", [n])
- c_buffer[vn_ind, T.ramp(0, 1, 128)] = T.call_llvm_intrin(
- T.llvm_lookup_intrinsic_id("llvm.hexagon.V6.vmpybusv.128B"),
- T.reinterpret(a_buffer[vn_ind, T.ramp(0, 1, 128)], dtype="int32x32"),
- T.reinterpret(b_buffer[vn_ind, T.ramp(0, 1, 128)], dtype="int32x32"),
- dtype="int16x128",
- )
-
- return operator
-
-
-def get_vadd_operator(operations):
- """Generate vadd operator."""
-
- @T.prim_func(s_tir=True)
- def operator(a: T.handle, b: T.handle, c: T.handle) -> None:
- T.func_attr({"global_symbol": "main", "tirx.noalias": True})
- a_buffer = T.match_buffer(a, [operations, 128], dtype="uint8")
- b_buffer = T.match_buffer(b, [operations, 128], dtype="uint8")
- c_buffer = T.match_buffer(c, [operations, 128], dtype="int16")
- for n in T.grid(operations):
- with T.sblock("c_buffer"):
- vn_ind = T.axis.remap("S", [n])
- c_buffer[vn_ind, T.ramp(0, 1, 128)] = T.call_llvm_intrin(
- T.llvm_lookup_intrinsic_id("llvm.hexagon.V6.vaddubh.128B"),
- T.reinterpret(a_buffer[vn_ind, T.ramp(0, 1, 128)], dtype="int32x32"),
- T.reinterpret(b_buffer[vn_ind, T.ramp(0, 1, 128)], dtype="int32x32"),
- dtype="int16x128",
- )
-
- return operator
-
-
-def get_vrmpy_operator(operations):
- """Generate vrmpy operator."""
-
- @T.prim_func(s_tir=True)
- def operator(a: T.handle, b: T.handle, c: T.handle) -> None:
- T.func_attr({"global_symbol": "main", "tirx.noalias": True})
- a_buffer = T.match_buffer(a, [operations, 128], dtype="uint8")
- b_buffer = T.match_buffer(b, [operations, 128], dtype="uint8")
- c_buffer = T.match_buffer(c, [operations, 32], dtype="int32")
- for n in T.grid(operations):
- with T.sblock("c_buffer"):
- vn_ind = T.axis.remap("S", [n])
- c_buffer[vn_ind, T.ramp(0, 1, 32)] = T.call_llvm_intrin(
- T.llvm_lookup_intrinsic_id("llvm.hexagon.V6.vrmpyubv.128B"),
- T.reinterpret(a_buffer[vn_ind, T.ramp(0, 1, 128)], dtype="int32x32"),
- T.reinterpret(b_buffer[vn_ind, T.ramp(0, 1, 128)], dtype="int32x32"),
- dtype="int32x32",
- )
-
- return operator
-
-
-def evaluate(hexagon_session, shape_dtypes, expected_output_producer, sch):
- """Evaluate schedule."""
- a_shape, a_dtype, b_shape, b_dtype, c_shape, c_dtype = shape_dtypes
-
- func_tir = tvm.compile(sch.mod["main"], target=get_hexagon_target("v68"))
- module = hexagon_session.load_module(func_tir)
-
- a = np.random.randint(0, 16, a_shape, dtype=a_dtype)
- b = np.random.randint(0, 16, b_shape, dtype=b_dtype)
- c = np.zeros(c_shape, dtype=c_dtype)
-
- a_hexagon = tvm.runtime.tensor(a, device=hexagon_session.device)
- b_hexagon = tvm.runtime.tensor(b, device=hexagon_session.device)
- c_hexagon = tvm.runtime.tensor(c, device=hexagon_session.device)
-
- # These are reduced for CI but number=100 and repeat=10 does a good job of removing noise.
- number = 1
- repeat = 1
-
- timer = module.time_evaluator("main", hexagon_session.device, number=number, repeat=repeat)
- runtime = timer(a_hexagon, b_hexagon, c_hexagon)
- tvm.testing.assert_allclose(c_hexagon.numpy(), expected_output_producer(c_shape, a, b))
-
- return round(runtime.mean * 1000, 6)
-
-
-class TestMatMulVec:
- """MatMul test class."""
-
- (
- operation_name,
- operator_producer,
- shape_dtypes_producer,
- expected_output_producer,
- ) = tvm.testing.parameters(
- ("vrmpy", get_vrmpy_operator, get_vrmpy_shape_dtypes, vrmpy_expected_producer),
- ("vmpy", get_vmpy_operator, get_vmpy_vadd_shape_dtype, vmpy_expected_producer),
- ("vadd", get_vadd_operator, get_vmpy_vadd_shape_dtype, vadd_expected_producer),
- )
-
- # Experimentally best split factor but all multiples of 4 perform pretty well.
- # This is because there are 4 HVX untis available on the device and pipelining
- # works best with parallels of the number of available HVX.
- split_factor = tvm.testing.parameter(4)
-
- # Removed most of these to speedup CI.
- operation_count = tvm.testing.parameter(
- 128,
- # 256,
- # 512,
- # Single thread runs faster since L2 cache can handle the entire request quickly
- # 1024,
- # 2048,
- # Significant performance degredation once the inputs and outputs cannot all fit in L2
- # 4096,
- # 8192,
- # 16384,
- )
-
- @pytest.mark.skipif(not env.has_hexagon(), reason="need hexagon")
- def test(
- self,
- hexagon_session,
- operation_count,
- operation_name,
- operator_producer,
- shape_dtypes_producer,
- expected_output_producer,
- split_factor,
- ):
- """Test function handler."""
-
- sch = tvm.s_tir.Schedule(operator_producer(operation_count))
- single_thread_runtime = evaluate(
- hexagon_session, shape_dtypes_producer(operation_count), expected_output_producer, sch
- )
-
- sch = tvm.s_tir.Schedule(operator_producer(operation_count))
- block = sch.get_sblock("c_buffer")
- b = sch.get_loops(block)
- b_output, _ = sch.split(b[0], factors=[split_factor, None])
- sch.parallel(b_output)
-
- parallel_runtime = evaluate(
- hexagon_session, shape_dtypes_producer(operation_count), expected_output_producer, sch
- )
-
- speedup = round(single_thread_runtime / parallel_runtime, 2)
-
- print(
- TEST_OUTPUT_TEMPLATE.format(
- operation_name, operation_count, single_thread_runtime, parallel_runtime, speedup
- )
- )
-
-
-if __name__ == "__main__":
- tvm.testing.main()
diff --git a/tests/python/contrib/test_hexagon/test_parallel_hvx_load_vtcm.py b/tests/python/contrib/test_hexagon/test_parallel_hvx_load_vtcm.py
deleted file mode 100644
index 39755e2811..0000000000
--- a/tests/python/contrib/test_hexagon/test_parallel_hvx_load_vtcm.py
+++ /dev/null
@@ -1,562 +0,0 @@
-# Licensed to the Apache Software Foundation (ASF) under one
-# or more contributor license agreements. See the NOTICE file
-# distributed with this work for additional information
-# regarding copyright ownership. The ASF licenses this file
-# to you under the Apache License, Version 2.0 (the
-# "License"); you may not use this file except in compliance
-# with the License. You may obtain a copy of the License at
-#
-# http://www.apache.org/licenses/LICENSE-2.0
-#
-# Unless required by applicable law or agreed to in writing,
-# software distributed under the License is distributed on an
-# "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
-# KIND, either express or implied. See the License for the
-# specific language governing permissions and limitations
-# under the License.
-
-"""Test different strategies for loading data into vtcm before running HVX workloads."""
-
-import numpy as np
-import pytest
-
-import tvm
-from tvm.script import tirx as T
-from tvm.testing import env
-
-from .infrastructure import get_hexagon_target
-
-TEST_OUTPUT_TEMPLATE = (
- "Test with {} MB of data to load... \n"
- " -No VTCM: {} Gops \n -Basic VTCM: {} Gops \n"
- " -Vectorized: {} Gops\n -Vectorized and"
- " Parallelized: {} Gops\n -Preallocated and Vectorized: {} Gops\n"
- " -Preallocated, Vectorized, and Parallelized: {} Gops\n"
- " -Single DMA: {} Gops\n -Preloaded: {} Gops\n"
-)
-
-
-def apply_parallel_unroll_vectorize(sch, blocks, outer_split, unroll_split, vector_split):
- """Apply parallel unroll vectorized."""
- for block in blocks:
- vb_index, vi_index = sch.get_loops(block)
- v = sch.fuse(vb_index, vi_index)
- vbo, vbi, vio, vii = sch.split( # pylint: disable=unused-variable
- v, factors=[outer_split, None, unroll_split, vector_split]
- ) # pylint: disable=unused-variable
- sch.vectorize(vii)
- sch.unroll(vio)
... 2274 lines suppressed ...