Skip to content

Commit ce66eee

Browse files
committed
[Llava] Add support to cross compile llava_runner for Android
Tested on M1 (non Android) and S23 (Android), Debug and Release build Known issues: - Dummy image hack should go - ET_LOG is broken and doesn't print anything on adb shell
1 parent e73dce2 commit ce66eee

File tree

3 files changed

+139
-38
lines changed

3 files changed

+139
-38
lines changed

.ci/scripts/test_llava.sh

Lines changed: 108 additions & 36 deletions
Original file line numberDiff line numberDiff line change
@@ -9,47 +9,96 @@ set -exu
99
# shellcheck source=/dev/null
1010

1111
BUILD_TYPE=${1:-Debug}
12+
TARGET_OS=${2:-Native}
13+
BUILD_DIR=${3:-cmake-out}
1214

13-
echo "Building with BUILD_TYPE: $BUILD_TYPE"
15+
echo "Building with BUILD_TYPE: $BUILD_TYPE, TARGET_OS: $TARGET_OS, BUILD_DIR: $BUILD_DIR"
1416

1517
if [[ -z "${PYTHON_EXECUTABLE:-}" ]]; then
16-
PYTHON_EXECUTABLE=python3
18+
PYTHON_EXECUTABLE=python3
1719
fi
1820

21+
TARGET_OS_lower="$(echo "${TARGET_OS}" | awk '{print tolower($0)}')"
22+
if [[ "${TARGET_OS_lower}" == "android" ]]; then
23+
if [[ -z "${ANDROID_NDK}" ]]; then
24+
echo "Set ANDROID_NDK environment variable to build for Android."
25+
exit 1
26+
fi
27+
fi
28+
29+
# Number of processes for a parallel build
30+
NPROC=8
31+
if hash nproc &> /dev/null; then NPROC=$(nproc); fi
32+
33+
EXECUTORCH_COMMON_CMAKE_ARGS=" \
34+
-DCMAKE_INSTALL_PREFIX=${BUILD_DIR} \
35+
-DCMAKE_BUILD_TYPE=${BUILD_TYPE} \
36+
-DEXECUTORCH_BUILD_EXTENSION_MODULE=ON \
37+
-DEXECUTORCH_BUILD_EXTENSION_DATA_LOADER=ON \
38+
-DEXECUTORCH_BUILD_KERNELS_CUSTOM=ON \
39+
-DEXECUTORCH_BUILD_KERNELS_OPTIMIZED=ON \
40+
-DEXECUTORCH_BUILD_KERNELS_QUANTIZED=ON \
41+
-DEXECUTORCH_BUILD_XNNPACK=ON \
42+
-DEXECUTORCH_DO_NOT_USE_CXX11_ABI=ON \
43+
-DEXECUTORCH_XNNPACK_SHARED_WORKSPACE=ON"
44+
1945
cmake_install_executorch_libraries() {
20-
cmake \
21-
-DCMAKE_INSTALL_PREFIX=cmake-out \
22-
-DCMAKE_BUILD_TYPE=${BUILD_TYPE} \
23-
-DEXECUTORCH_BUILD_EXTENSION_MODULE=ON \
24-
-DEXECUTORCH_BUILD_EXTENSION_DATA_LOADER=ON \
25-
-DEXECUTORCH_BUILD_KERNELS_CUSTOM=ON \
26-
-DEXECUTORCH_BUILD_KERNELS_OPTIMIZED=ON \
27-
-DEXECUTORCH_BUILD_KERNELS_QUANTIZED=ON \
28-
-DEXECUTORCH_BUILD_XNNPACK=ON \
29-
-DEXECUTORCH_DO_NOT_USE_CXX11_ABI=ON \
30-
-DEXECUTORCH_XNNPACK_SHARED_WORKSPACE=ON \
31-
-Bcmake-out .
32-
33-
34-
cmake --build cmake-out -j9 --target install --config ${BUILD_TYPE}
46+
cmake \
47+
${EXECUTORCH_COMMON_CMAKE_ARGS} \
48+
-B${BUILD_DIR} .
49+
50+
cmake --build ${BUILD_DIR} -j${NPROC} --target install --config ${BUILD_TYPE}
51+
}
52+
53+
cmake_install_executorch_libraries_for_android() {
54+
cmake \
55+
-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
56+
-DANDROID_ABI=arm64-v8a \
57+
-DANDROID_PLATFORM=android-23 \
58+
${EXECUTORCH_COMMON_CMAKE_ARGS} \
59+
-B${BUILD_DIR} .
60+
61+
cmake --build ${BUILD_DIR} -j${NPROC} --target install --config ${BUILD_TYPE}
3562
}
3663

64+
65+
LLAVA_COMMON_CMAKE_ARGS=" \
66+
-DPYTHON_EXECUTABLE="$PYTHON_EXECUTABLE" \
67+
-DCMAKE_INSTALL_PREFIX=${BUILD_DIR} \
68+
-DCMAKE_BUILD_TYPE=${BUILD_TYPE} \
69+
-DEXECUTORCH_BUILD_KERNELS_CUSTOM=ON \
70+
-DEXECUTORCH_BUILD_KERNELS_OPTIMIZED=ON \
71+
-DEXECUTORCH_BUILD_XNNPACK=ON"
72+
3773
cmake_build_llava_runner() {
3874
dir=examples/models/llava
3975
python_lib=$($PYTHON_EXECUTABLE -c 'from distutils.sysconfig import get_python_lib; print(get_python_lib())')
4076

41-
cmake \
42-
-DCMAKE_INSTALL_PREFIX=cmake-out \
43-
-DCMAKE_BUILD_TYPE=${BUILD_TYPE} \
44-
-DEXECUTORCH_BUILD_KERNELS_CUSTOM=ON \
45-
-DEXECUTORCH_BUILD_KERNELS_OPTIMIZED=ON \
46-
-DEXECUTORCH_BUILD_XNNPACK=ON \
47-
-DCMAKE_PREFIX_PATH="$python_lib" \
48-
-Bcmake-out/${dir} \
77+
cmake \
78+
${LLAVA_COMMON_CMAKE_ARGS} \
79+
-DCMAKE_PREFIX_PATH="$python_lib" \
80+
-B${BUILD_DIR}/${dir} \
4981
${dir}
5082

83+
cmake --build ${BUILD_DIR}/${dir} -j${NPROC} --config ${BUILD_TYPE}
84+
}
85+
5186

52-
cmake --build cmake-out/${dir} -j9 --config ${BUILD_TYPE}
87+
cmake_build_llava_runner_for_android() {
88+
dir=examples/models/llava
89+
python_lib=$($PYTHON_EXECUTABLE -c 'from distutils.sysconfig import get_python_lib; print(get_python_lib())')
90+
91+
cmake \
92+
-DCMAKE_TOOLCHAIN_FILE=$ANDROID_NDK/build/cmake/android.toolchain.cmake \
93+
-DANDROID_ABI=arm64-v8a \
94+
-DANDROID_PLATFORM=android-23 \
95+
${LLAVA_COMMON_CMAKE_ARGS} \
96+
-DCMAKE_PREFIX_PATH="$python_lib" \
97+
-DLLAVA_RUNNER_NO_TORCH_DUMMY_IMAGE=ON \
98+
-B${BUILD_DIR}/${dir} \
99+
${dir}
100+
101+
cmake --build ${BUILD_DIR}/${dir} -j${NPROC} --config ${BUILD_TYPE}
53102
}
54103

55104
# only export the one without custom op for now since it's
@@ -61,7 +110,7 @@ export_llava() {
61110
# Download a new image with different size, to test if the model can handle different image sizes
62111
prepare_image_tensor() {
63112
echo "Downloading image"
64-
curl -o basketball.jpg https://upload.wikimedia.org/wikipedia/commons/7/73/Chicago_Bulls_and_New_Jersey_Nets%2C_March_28%2C_1991.jpg
113+
curl -o basketball.jpg https://upload.wikimedia.org/wikipedia/commons/7/73/Chicago_Bulls_and_New_Jersey_Nets%2C_March_28%2C_1991.jpg
65114
$PYTHON_EXECUTABLE -m executorch.examples.models.llava.image_util --image-path basketball.jpg --output-path image.pt
66115
}
67116

@@ -80,13 +129,24 @@ run_and_verify() {
80129
echo "tokenizer.bin is missing."
81130
exit 1
82131
fi
83-
RUNTIME_ARGS="--model_path=llava.pte \
84-
--tokenizer_path=tokenizer.bin \
85-
--image_path=image.pt \
86-
--prompt=ASSISTANT: \
87-
--temperature=0 \
88-
--seq_len=650"
89-
cmake-out/examples/models/llava/llava_main ${RUNTIME_ARGS} > result.txt
132+
133+
134+
135+
RUNTIME_ARGS="--model_path=llava.pte \
136+
--tokenizer_path=tokenizer.bin \
137+
--image_path=image.pt \
138+
--prompt=ASSISTANT: \
139+
--temperature=0 \
140+
--seq_len=650"
141+
142+
if [[ "${TARGET_OS_lower}" == "android" ]]; then
143+
echo "Transfer relevant files to the phone via ADB and run llava_main with following args,"
144+
echo "$ llava_main ${RUNTIME_ARGS} "
145+
exit 0;
146+
fi
147+
148+
${BUILD_DIR}/examples/models/llava/llava_main ${RUNTIME_ARGS} > result.txt
149+
90150
# verify result.txt
91151
RESULT=$(cat result.txt)
92152
# set the expected prefix to be the same as prompt because there's a bug in sdpa_with_kv_cache that causes <unk> tokens.
@@ -109,8 +169,20 @@ run_and_verify() {
109169
fi
110170
}
111171

112-
cmake_install_executorch_libraries
113-
cmake_build_llava_runner
172+
# Step1. Build stuff
173+
if [[ "${TARGET_OS_lower}" == "android" ]]; then
174+
cmake_install_executorch_libraries_for_android
175+
cmake_build_llava_runner_for_android
176+
elif [[ "${TARGET_OS_lower}" == "native" ]]; then
177+
cmake_install_executorch_libraries
178+
cmake_build_llava_runner
179+
else
180+
echo "Invalid TARGET_OS ($2): ${TARGET_OS}"
181+
fi
182+
183+
# Step2. Generate the PTE
114184
export_llava
185+
186+
# Step3. Run
115187
prepare_image_tensor
116188
run_and_verify

examples/models/llava/CMakeLists.txt

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,9 @@ project(llava)
2121
# Duplicating options as root CMakeLists.txt
2222
option(EXECUTORCH_BUILD_KERNELS_OPTIMIZED "Build the optimized kernels" OFF)
2323

24+
# This is a temporary hack to get around Torch dep so we can test this on android
25+
option(LLAVA_RUNNER_NO_TORCH_DUMMY_IMAGE "Hack option to feed dummy image to remove torch.load dep" OFF)
26+
2427
include(CMakeDependentOption)
2528
#
2629
# pthreadpool: build pthreadpool library. Disable on unsupported platforms
@@ -70,7 +73,14 @@ set(_common_include_directories ${EXECUTORCH_ROOT}/..)
7073
set(gflags_DIR ${CMAKE_CURRENT_BINARY_DIR}/../../../third-party/gflags)
7174
find_package(gflags REQUIRED)
7275

73-
find_package(Torch CONFIG REQUIRED)
76+
# Avoid torch dep from torch.load()-ing the image.
77+
# This is a temporary hack.
78+
if(LLAVA_RUNNER_NO_TORCH_DUMMY_IMAGE)
79+
add_definitions(-DLLAVA_NO_TORCH_DUMMY_IMAGE=1)
80+
message("Buidling the runner without Torch, feeding a dummy image!")
81+
else()
82+
find_package(Torch CONFIG REQUIRED)
83+
endif()
7484
add_definitions(-D_GLIBCXX_USE_CXX11_ABI=0)
7585

7686
#
@@ -95,7 +105,11 @@ endif()
95105
# llava_runner library
96106
add_subdirectory(runner)
97107

98-
set(link_libraries gflags torch)
108+
set(LINK_LIBS gflags)
109+
if(NOT LLAVA_RUNNER_NO_TORCH_DUMMY_IMAGE)
110+
list(APPEND LINK_LIBS torch)
111+
endif()
112+
set(link_libraries ${LINK_LIBS})
99113
set(_srcs main.cpp)
100114

101115
if(EXECUTORCH_BUILD_KERNELS_OPTIMIZED)

examples/models/llava/main.cpp

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,11 @@
88

99
#include <executorch/examples/models/llava/runner/llava_runner.h>
1010
#include <gflags/gflags.h>
11+
#ifndef LLAVA_NO_TORCH_DUMMY_IMAGE
1112
#include <torch/torch.h>
13+
#else
14+
#include <algorithm> // std::fill
15+
#endif
1216

1317
#if defined(ET_USE_THREADPOOL)
1418
#include <executorch/extension/threadpool/cpuinfo_utils.h>
@@ -80,6 +84,15 @@ int32_t main(int32_t argc, char** argv) {
8084

8185
// read image and resize the longest edge to 336
8286
std::vector<uint8_t> image_data;
87+
88+
#ifdef LLAVA_NO_TORCH_DUMMY_IMAGE
89+
// Work without torch using a random data
90+
image_data.resize(3 * 240 * 336);
91+
std::fill(image_data.begin(), image_data.end(), 0); // black
92+
std::array<int32_t, 3> image_shape = {3, 240, 336};
93+
std::vector<torch::executor::Image> images = {
94+
{.data = image_data, .width = image_shape[2], .height = image_shape[1]}};
95+
#else // LLAVA_NO_TORCH_DUMMY_IMAGE
8396
// cv::Mat image = cv::imread(image_path, cv::IMREAD_COLOR);
8497
// int longest_edge = std::max(image.rows, image.cols);
8598
// float scale_factor = 336.0f / longest_edge;
@@ -102,6 +115,8 @@ int32_t main(int32_t argc, char** argv) {
102115
{.data = image_data,
103116
.width = static_cast<int32_t>(image_tensor.size(2)),
104117
.height = static_cast<int32_t>(image_tensor.size(1))}};
118+
#endif // LLAVA_NO_TORCH_DUMMY_IMAGE
119+
105120
// generate
106121
runner.generate(std::move(images), prompt, seq_len);
107122
return 0;

0 commit comments

Comments
 (0)