Skip to content

Commit

Permalink
[hannk] Add support for building/running for wasm (#6361)
Browse files Browse the repository at this point in the history
* [hannk] Allow disabling TFLite+Delegate build in CMake

Preparatory work for allowing building of hannk with Emscripten; TFLite (and its dependees) problematic to build in that environment, but this will allow us to build a tflite-parser-only environment.

(Note that more work is needed to get this working for wasm, as crosscompiling in CMake is still pretty painful; this work was split out to make subsequent reviews simpler)

* [hannk] Add support for building/running for wasm

* HANNK_BUILD_TFLITE_DELEGATE -> HANNK_BUILD_TFLITE

* Use explicit host build strategy for cross compiling HANNK (#6365)

* Ignore local emsdk clone

* Fix usage of CMAKE_BUILD_TYPE

* Only print the Halide target info once per CMake run

* Fix Halide "cmake" target detection for Emscripten

* Prefer target_link_options to _link_libraries when applicable

* Validate, rather than find, NODE_JS_EXECUTABLE (set by emsdk)

* Emscripten already wraps tests with node.

* Add dependency on Android logging library.

* For cross-compiling, find host tools instead of recursive call.

Rather than shelling out via execute_process and potentially
guessing the toolchain options wrong, expect to find our host
tools (i.e. generators) in a package called "hannk_tools".

The package is created by the host build via the CMake export()
command. Importing this package in the cross build creates
IMPORTED targets with the same names as our generators. We then
use these generators rather than creating generators for the
target build.

* Rework cross-compiling script.

* Respond to (easy) reviewer comments.

* Add HANNK_AOT_HOST_ONLY option. Use in script.

* [hannk] tests should only process .tflite files (#6368)

currently, random dotfiles (e.g. .DS_Store on OSX) can creep in, causing bogus failures

* Add comment about node wrapping.

* Rename hannk_tools to hannk-halide_generators

* Add comment about exporting targets.

* Bump version to Halide 14.0.0 (#6369)

Co-authored-by: Steven Johnson <srj@google.com>

Co-authored-by: Alex Reinking <alex_reinking@berkeley.edu>
  • Loading branch information
steven-johnson and alexreinking committed Nov 1, 2021
1 parent 69d8ef0 commit 1a1c97f
Show file tree
Hide file tree
Showing 8 changed files with 291 additions and 81 deletions.
1 change: 1 addition & 0 deletions apps/hannk/.gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
build*/
bin/
emsdk
68 changes: 56 additions & 12 deletions apps/hannk/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,14 @@ set(CMAKE_POLICY_DEFAULT_CMP0077 NEW)

enable_testing()

# ----------------------------

option(HANNK_AOT_HOST_ONLY "Only build AOT host tools for cross-compiling" OFF)

option(HANNK_BUILD_TFLITE "Build TFLite+Delegate for HANNK" ON)
if (HANNK_BUILD_TFLITE AND (Halide_TARGET MATCHES "wasm"))
message(FATAL_ERROR "HANNK_BUILD_TFLITE must be OFF when targeting wasm")
endif ()
message(STATUS "HANNK_BUILD_TFLITE is ${HANNK_BUILD_TFLITE}")

# -fPIC is necessary for .so builds (at least on Linux); not necessary for the non-delegate
Expand All @@ -18,35 +25,43 @@ set(CMAKE_CXX_STANDARD 17)
set(CMAKE_CXX_STANDARD_REQUIRED YES)
set(CMAKE_CXX_EXTENSIONS NO)

# Find Halide
find_package(Halide REQUIRED)

# Set up the version of TFLite we expect
# (We need to do this even if HANNK_BUILD_TFLITE is off,
# so that the .tflite file parser can get the right schema)
set(TFLITE_VERSION_MAJOR "2" CACHE STRING "Major version of TFLite to assume")
set(TFLITE_VERSION_MINOR "6" CACHE STRING "Minor version of TFLite to assume")
set(TFLITE_VERSION_PATCH "0" CACHE STRING "Patch version of TFLite to assume")
set(TFLITE_VERSION "${TFLITE_VERSION_MAJOR}.${TFLITE_VERSION_MINOR}.${TFLITE_VERSION_PATCH}")

# ----------------------------

add_compile_definitions(TFLITE_VERSION_MAJOR=${TFLITE_VERSION_MAJOR})
add_compile_definitions(TFLITE_VERSION_MINOR=${TFLITE_VERSION_MINOR})
add_compile_definitions(TFLITE_VERSION_PATCH=${TFLITE_VERSION_PATCH})
if (HANNK_BUILD_TFLITE)
add_compile_definitions(HANNK_BUILD_TFLITE=1)
else ()
add_compile_definitions(HANNK_BUILD_TFLITE=0)
endif ()
add_compile_definitions(HANNK_BUILD_TFLITE=$<BOOL:${HANNK_BUILD_TFLITE}>)

set(TFLITE_VERSION "${TFLITE_VERSION_MAJOR}.${TFLITE_VERSION_MINOR}.${TFLITE_VERSION_PATCH}")
# ----------------------------

# Find HalideHelpers -- this is just the Runtime headers and CMake functions, but no libraries
find_package(HalideHelpers REQUIRED)

# ----------------------------

add_subdirectory(halide)
if (HANNK_AOT_HOST_ONLY)
# Don't add anything else to the build... everything for AOT is in the halide subdirectory
return()
endif ()

add_subdirectory(interpreter)
add_subdirectory(tflite)
add_subdirectory(util)
if (HANNK_BUILD_TFLITE)
add_subdirectory(delegate)
endif ()

# ----------------------------

# Benchmarking executable
add_executable(benchmark benchmark.cpp)
target_link_libraries(benchmark PRIVATE
Expand All @@ -66,12 +81,41 @@ target_link_libraries(compare_vs_tflite PRIVATE
target_include_directories(compare_vs_tflite
PUBLIC $<BUILD_INTERFACE:${hannk_SOURCE_DIR}>)

# TODO: Surely there's a better way to set Emscripten flags.
if (Halide_TARGET MATCHES "wasm")
foreach (t IN ITEMS benchmark compare_vs_tflite)
# Note: "SHELL:" prevents de-duplication of the -s flag.
target_link_options(
${t} PRIVATE
"SHELL:-s ALLOW_MEMORY_GROWTH=1"
"SHELL:-s ENVIRONMENT=node"
"SHELL:-s NODERAWFS"
"SHELL:$<$<CONFIG:Debug>:-s ASSERTIONS=1>"
)
endforeach ()
endif ()

if (Halide_TARGET MATCHES "wasm" AND NODE_JS_EXECUTABLE)
execute_process(COMMAND "${NODE_JS_EXECUTABLE}" --version
OUTPUT_VARIABLE NODE_JS_VERSION_RAW
OUTPUT_STRIP_TRAILING_WHITESPACE)
string(REPLACE "v" "" NODE_JS_VERSION ${NODE_JS_VERSION_RAW})

if (NODE_JS_VERSION VERSION_LESS "16.13")
message(FATAL_ERROR "Halide requires Node v16.13 or later, but found ${NODE_JS_VERSION_RAW} at ${NODE_JS_EXECUTABLE}. Please set NODE_JS_EXECUTABLE on the CMake command line.")
endif ()
endif ()

# Tests
file(GLOB TEST_FILES CONFIGURE_DEPENDS "test/*/*.tflite")
foreach (t IN LISTS TEST_FILES)
file(RELATIVE_PATH test_name ${hannk_SOURCE_DIR} ${t})
add_test(NAME ${test_name} COMMAND compare_vs_tflite ${t} --benchmark 0)

# Emscripten sets CMAKE_CROSSCOMPILING_TOOLCHAIN to NODE_JS_EXECUTABLE,
# which ensures these tests will run in Node.
add_test(NAME ${test_name}
COMMAND compare_vs_tflite ${t} --benchmark 0)

set_tests_properties(${test_name} PROPERTIES
LABELS hannk_tests)
endforeach()

endforeach ()
43 changes: 43 additions & 0 deletions apps/hannk/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,46 @@ Usage:

compare_vs_tflite a.tflite [b.tflite ...]

### WebAssembly

There is limited support for building and running hannk under WebAssembly.

#### Requirements:

- You must use CMake to build (Make isn't supported).
- You must have Emscripten v2.0.32 (or later) installed and activated.
- You must have Node.js v16.13 (or later) installed for testing.
Note that (as of this writing), EMSDK includes an older version of Node that *will not* work.

#### Building:

The simplest way is:

```
$ HL_TARGET=wasm-32-wasmrt-wasm_simd128 NODE_JS_EXECUTABLE=/path/to/good/version/of/node ./configure_cmake.sh
...output...
$ ninja
```

Note that `wasm_simd128` is optional, but highly recommended.


#### Running:

If you've built as described above, you can just run `ctest` to run the basic self-tests.

If you want to run `benchmark` or `compare_vs_tflite` manually, you'll need to launch it under `node`
manually; as noted above, when EMSDK is activated, `node` will likely refer to a version of Node.js
that won't work, so you will need to provide a path to a suitable version:

```
$ cd build
$ /path/to/good/version/of/node benchmark ../test/*/*.tflite
$ /path/to/good/version/of/node compare_vs_tflite ../test/*/*.tflite
```

Note that compare_vs_tflite doesn't actually build or use tflite when compiling under WebAssembly!
The only mode it supports is directly parsing the .tflite files, which is pretty close to the same as
the `benchmark` tool.

115 changes: 86 additions & 29 deletions apps/hannk/configure_cmake.sh
Original file line number Diff line number Diff line change
Expand Up @@ -2,57 +2,114 @@

set -e

HANNK_DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" >/dev/null 2>&1 && pwd )"
BUILD_DIR="${HANNK_DIR}/build"
HANNK_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" >/dev/null 2>&1 && pwd)"

mkdir -p "${BUILD_DIR}"
cd "${BUILD_DIR}"
if [ -z "${BUILD_DIR}" ]; then
BUILD_DIR="${HANNK_DIR}/build"
fi

if [ -z ${HALIDE_INSTALL_PATH} ]; then
HALIDE_INSTALL_PATH=${HOME}/halide-14-install/
if [ -z "${HALIDE_INSTALL_PATH}" ]; then
HALIDE_INSTALL_PATH="${HOME}/halide-14-install/"
fi
echo Using HalideInstall=${HALIDE_INSTALL_PATH}

if [ -z ${HL_TARGET} ]; then
HL_TARGET=host
if [ -z "${HL_TARGET}" ]; then
HL_TARGET=host
fi
echo Using HL_TARGET=${HL_TARGET}

if [ -z "${CMAKE_GENERATOR}" ]; then
CMAKE_GENERATOR=Ninja
CMAKE_GENERATOR=Ninja
fi
echo Using build tool=${CMAKE_GENERATOR}

if [ -z "${CMAKE_BUILD_TYPE}" ]; then
CMAKE_BUILD_TYPE=Release
CMAKE_BUILD_TYPE=Release
fi

if [ -z "${ANDROID_PLATFORM}" ]; then
ANDROID_PLATFORM=21
fi
echo Using CMAKE_BUILD_TYPE=${CMAKE_BUILD_TYPE}

if [ -z "${HANNK_BUILD_TFLITE}" ]; then
HANNK_BUILD_TFLITE=ON
HANNK_BUILD_TFLITE=ON
else
HANNK_BUILD_TFLITE=OFF
HANNK_BUILD_TFLITE=OFF
fi
echo Using HANNK_BUILD_TFLITE=${HANNK_BUILD_TFLITE}

EXTRAS=
# TODO: this doesn't work (yet); crosscompiling in CMake is painful.
## In a cross-compiling scenario, use a separate host and build dir
# TODO: figure out if there's a way to generalize "is this crosscompiling or not", and just make this a single if-else

if [[ "${HL_TARGET}" =~ ^arm-64-android.* ]]; then
echo Configuring for Android arm64-v8a build...
echo Using ANDROID_NDK_ROOT=${ANDROID_NDK_ROOT}
EXTRAS="-DCMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake -DANDROID_ABI=arm64-v8a"
HOST_BUILD_DIR="${BUILD_DIR}/_host"
HOST_BUILD_TARGET=(--target hannk-halide_generators)
HOST_HL_TARGET=host
HOST_CMAKE_DEFS=(-DHANNK_AOT_HOST_ONLY=ON)
elif [[ "${HL_TARGET}" =~ ^wasm-32-wasmrt.* ]]; then
HOST_BUILD_DIR="${BUILD_DIR}/_host"
HOST_BUILD_TARGET=(--target hannk-halide_generators)
HOST_HL_TARGET=host
HOST_CMAKE_DEFS=(-DHANNK_AOT_HOST_ONLY=ON)
else
echo Assuming host build...
HOST_BUILD_DIR="${BUILD_DIR}"
HOST_BUILD_TARGET=()
HOST_HL_TARGET="${HL_TARGET}"
HOST_CMAKE_DEFS=()
fi

## Build HANNK for the host no matter what

echo "Configuring HANNK for ${HOST_HL_TARGET}"
cmake \
${EXTRAS} \
-G "${CMAKE_GENERATOR}" \
-S "${HANNK_DIR}" \
-B "${HOST_BUILD_DIR}" \
"${HOST_CMAKE_DEFS[@]}" \
-DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" \
-DHalide_DIR="${HALIDE_INSTALL_PATH}" \
-DCMAKE_PREFIX_PATH="${HALIDE_INSTALL_PATH}" \
-DHalide_TARGET=${HL_TARGET} \
-DHANNK_BUILD_TFLITE=${HANNK_BUILD_TFLITE} \
-DHalide_DIR="${HALIDE_INSTALL_PATH}/lib/cmake/Halide" \
-DHalideHelpers_DIR="${HALIDE_INSTALL_PATH}/lib/cmake/HalideHelpers" \
-DHalide_TARGET="${HOST_HL_TARGET}" \
-DHANNK_BUILD_TFLITE=${HANNK_BUILD_TFLITE}

if [ -z "${HOST_BUILD_TARGET[*]}" ]; then
echo "Building HANNK for ${HOST_HL_TARGET}"
else
echo "Building HANNK host generator executables"
fi
cmake --build "${HOST_BUILD_DIR}" "${HOST_BUILD_TARGET[@]}"

## Now if we're cross-compiling for Android or WASM, set up the build
## for that, using the platform-provided CMake toolchain files.

if [[ "${HL_TARGET}" =~ ^arm-64-android.* ]]; then
echo "Using ANDROID_NDK_ROOT=${ANDROID_NDK_ROOT}"
echo "Using CMAKE_TOOLCHAIN_FILE=${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake"
CROSS_CMAKE_DEFS=(
-DCMAKE_TOOLCHAIN_FILE="${ANDROID_NDK_ROOT}/build/cmake/android.toolchain.cmake"
-DANDROID_ABI=arm64-v8a
"-DANDROID_PLATFORM=${ANDROID_PLATFORM}"
# Required because TFLite's internal Eigen tries to compile an unnecessary BLAS with the system Fortran compiler.
"-DCMAKE_Fortran_COMPILER=NO"
)
elif [[ "${HL_TARGET}" =~ ^wasm-32-wasmrt.* ]]; then
echo "Using NODE_JS_EXECUTABLE=${NODE_JS_EXECUTABLE}"
CROSS_CMAKE_DEFS=(
-DCMAKE_TOOLCHAIN_FILE="${EMSDK}/upstream/emscripten/cmake/Modules/Platform/Emscripten.cmake"
-DNODE_JS_EXECUTABLE="${NODE_JS_EXECUTABLE}"
)
else
# Not cross-compiling, so we're done.
exit
fi

echo "Configuring cross-build HANNK for ${HL_TARGET}"
cmake \
-G "${CMAKE_GENERATOR}" \
-S "${HANNK_DIR}" \
-B "${BUILD_DIR}"
-B "${BUILD_DIR}" \
"${CROSS_CMAKE_DEFS[@]}" \
-DCMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}" \
-DHANNK_BUILD_TFLITE=OFF \
-DHalide_TARGET="${HL_TARGET}" \
-DHalideHelpers_DIR="${HALIDE_INSTALL_PATH}/lib/cmake/HalideHelpers" \
-Dhannk-halide_generators_ROOT="${HOST_BUILD_DIR}"

echo "Building cross-build HANNK for ${HL_TARGET}"
cmake --build "${BUILD_DIR}"
Loading

0 comments on commit 1a1c97f

Please sign in to comment.