1. 准备工作
输入代码是锻炼肌肉记忆和加深对材料理解的好方法。虽然复制粘贴可以节省时间,但从长远来看,投资于这种实践可以提高效率并增强编码技能。
在此 Codelab 中,您将学习如何使用 Google 的高性能设备端运行时 LiteRT 构建直接在 Android 设备上运行的 C++ 图像分割二进制文件。此 Codelab 侧重于构建 C++ 二进制文件,而不是使用 Kotlin 或 Android Studio。您将使用 CMake 或 Bazel 对其进行交叉编译,并使用 ADB 进行部署。LiteRT C++ API 可在任何平台(Android、Linux、嵌入式)上运行,因此是性能关键型应用、机器人技术和边缘系统的实用基础。
您将了解整个流水线:
- 设置构建环境 (CMake + Android NDK 或 Bazel)。
- 关联 LiteRT C++ SDK - 从预构建版本或从源代码进行关联。
- 使用 OpenGL ES 计算着色器进行 GPU 加速的图像预处理和后处理。
- 使用 LiteRT C++ API 运行
selfie_multiclass分割模型。 - 在 CPU、GPU (OpenCL) 和 NPU (Qualcomm / MediaTek) 上加速推理。
- 将原始模型输出后处理为颜色混合的分割图片。
- 使用 adb 部署到 Android 设备并检索结果。
最后,您将生成与下图类似的内容:一张经过完整流水线处理的静态图片,其中 6 个分割类别中的每一个都以不同的颜色叠加显示:

前提条件
本 Codelab 专为熟悉 C++ 且希望获得在 C++ 层级上于 Android 设备上运行机器学习模型经验的开发者而设计。您应熟悉以下内容:
- C++ 基础知识(指针、向量、include)。
- 基本的 Android/ADB 概念(
adb push、adb shell)。 - 在 Linux 或 macOS 上使用终端和 shell 脚本。
学习内容
- 如何使用 CMake + NDK 或 Bazel 为 Android
arm64-v8a交叉编译 C++ 二进制文件。 - 如何使用 LiteRT C++ API(
Environment、CompiledModel、TensorBuffer)在设备上高效进行推理。 - OpenGL ES 3.1 计算着色器如何在 GPU 上完全加速预处理和后处理。
- 如何配置 LiteRT 以实现 CPU、GPU (OpenCL) 和 NPU(Qualcomm HTP、MediaTek APU、Google Tensor)加速。
- 同步 (
Run) 推理和异步 (RunAsync) 推理之间的区别。 - 如何使用 ADB 在 Android 上部署和运行 C++ 二进制文件。
所需条件
- Linux 或 macOS 机器(Windows 用户应使用 WSL2)。
- Android NDK r25c 或更高版本(下载)。
- 对于 CMake 路径:CMake ≥ 3.22 (
sudo apt-get install cmake)。 - 对于 Bazel 路径:已安装 Bazel,以及完整的 LiteRT 示例代码库。
PATH(Android 平台工具)中的 ADB。- Android 实体设备 - 最好在 Galaxy S24/S25 或 Pixel 上进行测试。
2. 图像分割
图像分割是一项计算机视觉任务,可为图像中的每个像素分配一个类别标签。与绘制边界框的对象检测不同,分割功能可以精确到像素级地了解每个对象的开始和结束位置。
此 Codelab 使用 selfie_multiclass_256x256 模型,该模型将每个像素分类为 6 个类别之一:
类索引 | Segment |
0 | 背景 |
1 | 美发 |
2 | 身体皮肤 |
3 | 面部皮肤 |
4 | 服饰 |
5 | 配饰(眼镜、珠宝首饰等) |
模型会输出形状为 [1, 256, 256, 6] 的浮点张量。对于每个 256×256 像素,都有 6 个置信度得分,每个类别一个。得分最高的类别赢得相应像素 (argmax)。
LiteRT:边缘性能
LiteRT 是 Google 的新一代高性能运行时,适用于 TFLite 模型。借助其 C++ API,您可以直接、低开销地访问硬件加速器,并且在所有这三者之间使用一致的接口:
- CPU - 普遍兼容;在中端设备上进行推理时,延迟时间约为 128 毫秒。
- GPU (OpenCL) - 推理时间约为 1 毫秒;端到端时间约为 17-43 毫秒,具体取决于缓冲区策略。
- NPU - 在 Qualcomm Snapdragon、MediaTek Dimensity 9400 和 Google Tensor 设备上,端到端延迟时间约为 9-28 毫秒,具体取决于 AOT 与。JIT 编译。
关键抽象是 CompiledModel:模型在加载时间预编译并针对目标硬件进行优化,从而将推理简化为对预分配缓冲区的 Run() 调用。
3. 进行设置
克隆存储库
git clone https://github.com/google-ai-edge/litert-samples.git
本 Codelab 的所有资源都位于以下目录中:
litert-samples/compiled_model_api/image_segmentation/c++_segmentation/
此目录包含两个子项目,每个子项目都是同一示例的完整 build:
目录 | 构建系统 | LiteRT 依赖项 |
| CMake + Android NDK | 预构建 |
| Bazel | 从源代码编译 LiteRT |
选择一条路径并按照该路径操作。这两个目录中的代码完全相同,只有构建系统和依赖项策略不同。如果您想以最快的速度完成设置,请选择 use_prebuilt_litert/。如果您需要修改 LiteRT 本身或在现有的 Bazel monorepo 中工作,请使用 build_from_source/。
关于文件路径的说明
本教程中的所有文件路径均采用 Linux/macOS 格式。Windows 用户应使用 WSL2。
目录概览
这两个子项目共享相同的源代码布局:
<variant>/
├── main_cpu.cc # CPU inference entry point
├── main_gpu.cc # GPU (OpenCL) inference entry point
├── main_npu.cc # NPU (Qualcomm / MediaTek) entry point
├── image_processor.h/.cc # OpenGL ES preprocessing and postprocessing
├── image_utils.h/.cc # STB-based image load / save utilities
├── timing_utils.h/.cc # Profiling helpers
├── shaders/ # GLSL ES 3.1 compute shaders
│ ├── preprocess_compute.glsl
│ ├── resize_compute.glsl
│ ├── mask_blend_compute.glsl
│ ├── deinterleave_masks.glsl
│ └── passthrough_shader.vert
├── models/
│ ├── selfie_multiclass_256x256.tflite (CPU / GPU / NPU JIT)
│ ├── selfie_multiclass_256x256_SM8650.tflite (Qualcomm S24 AOT)
│ └── selfie_multiclass_256x256_SM8750.tflite (Qualcomm S25 AOT)
└── test_images/
└── image.jpeg
此外:
use_prebuilt_litert/添加了CMakeLists.txt、build_prebuilt.sh、deploy_and_run_on_android.sh和third_party/stb/。build_from_source/添加了 BazelBUILD文件,并使用指向bazel-bin/的deploy_and_run_on_android.sh。

4. 了解项目结构
三个入口点,一条流水线
main_cpu.cc、main_gpu.cc 和 main_npu.cc 各自包含一个 main() 函数,用于驱动整个分割流水线。这三者的流水线完全相同;只有 LiteRT 加速器配置和缓冲区策略有所不同:
文件 | 加速器 | 缓冲策略 |
|
| CPU 内存 |
|
| 使用 OpenCL 后端的 CPU 内存 |
|
| 具有 CPU 回退的 CPU 内存 |
这三者共享相同的 ImageProcessor(用于预处理和后处理的 OpenGL ES 计算着色器)和 ImageUtils(STB 图像 I/O)实用程序。
完整流水线
每个入口点都遵循相同的五阶段结构:
Load → GPU upload → Preprocess (shader) → Inference (LiteRT) → Postprocess (shader) → Save
- 加载 - 使用 STB 图片库将 JPEG 解码到 CPU 内存中。
ImageUtils::LoadImage() - 上传 -
processor.CreateOpenGLTexture()将原始像素上传到 GPU 纹理 (OpenGL RGBA8)。 - 预处理 -
processor.PreprocessInputForSegmentation()运行一个 GLSL 计算着色器,该着色器将纹理调整为 256x256,并将像素值从[0, 1]归一化为[-1, 1]。结果会存储在 GPU SSBO 中。 - 推理 - SSBO 数据写入 LiteRT
TensorBuffer和compiled_model.Run()(或RunAsync())执行模型。 - 后处理 - 模型的 6 通道浮点输出被解交织为 6 个单通道掩码 SSBO,然后将这些掩码重新进行颜色混合,以生成最终图像。
- 保存 -
ImageUtils::SaveImage()将最终 RGBA 图像写入为 PNG。
5. 核心 LiteRT C++ API
在构建之前,请先熟悉所有入口点中使用的三种关键 LiteRT C++ 类型。所有这些软件包都位于 litert:: 命名空间中。
litert::Environment
Environment 是所有 LiteRT 操作的根上下文。创建一次并将其传递给 CompiledModel::Create。对于 NPU 用法,请使用供应商插件库目录对其进行配置。
// For CPU or GPU - no extra options needed
LITERT_ASSIGN_OR_ABORT(auto env, litert::Environment::Create({}));
// For NPU: point at the vendor dispatch library directory on the device
std::vector<litert::Environment::Option> opts;
opts.push_back({litert::Environment::OptionTag::DispatchLibraryDir,
"/data/local/tmp/cpp_segmentation_android/npu/"});
LITERT_ASSIGN_OR_ABORT(auto env,
litert::Environment::Create(std::move(opts)));
litert::CompiledModel
CompiledModel 在构建时加载并预编译 TFLite 模型以用于所请求的硬件。然后,推理会简化为填充缓冲区和调用 Run()。
// CPU
LITERT_ASSIGN_OR_ABORT(auto model,
litert::CompiledModel::Create(env, model_path,
litert::HwAccelerators::kCpu));
// GPU (pass an Options object with GpuOptions configured)
LITERT_ASSIGN_OR_ABORT(auto model,
litert::CompiledModel::Create(env, model_path, gpu_options));
// NPU (pass an Options object with kNpu | kCpu and vendor options)
LITERT_ASSIGN_OR_ABORT(auto model,
litert::CompiledModel::Create(env, model_path, npu_options));
litert::TensorBuffer
张量缓冲区用于保存输入/输出数据。始终从 CompiledModel 创建它们,以便它们的大小和对齐方式适合目标硬件。
LITERT_ASSIGN_OR_ABORT(auto input_buffers,
compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_ABORT(auto output_buffers,
compiled_model.CreateOutputBuffers());
// Write preprocessed float data, run, read results
LITERT_ABORT_IF_ERROR(
input_buffers[0].Write(absl::MakeConstSpan(preprocessed_data)));
LITERT_ABORT_IF_ERROR(
compiled_model.Run(input_buffers, output_buffers));
LITERT_ABORT_IF_ERROR(
output_buffers[0].Read(absl::MakeSpan(output_data)));
错误处理宏
宏 | 行为 |
| 在失败时分配或调用 |
| 如果表达式返回错误,则调用 |
| 向调用方分配或传播错误 |
6. 构建 - 选项 A:预构建的 LiteRT C++ SDK (CMake)
如果您不需要修改 LiteRT 本身,建议采用此方法。构建脚本可处理以下任务:下载 SDK 标头、复制 .so、提取 STB,以及通过单个命令调用 CMake + NDK。
第 1 步 - 从 Maven 获取 libLiteRt.so
LiteRT 将其运行时作为共享库在 Google Maven 上的 Android AAR 中提供。下载该文件并提取 arm64-v8a .so:
# Download the AAR
wget -O litert.aar \
"https://dl.google.com/dl/android/maven2/com/google/ai/edge/litert/litert/2.1.3/litert-2.1.3.aar"
# Extract the runtime library
unzip litert.aar "jni/arm64-v8a/libLiteRt.so" -d extracted/
对于 GPU 支持,还要提取 OpenCL/GL 加速器:
unzip litert.aar "jni/arm64-v8a/libLiteRtClGlAccelerator.so" -d extracted/

第 2 步 - 运行 build_prebuilt.sh
cd litert-samples/compiled_model_api/image_segmentation/c++_segmentation/use_prebuilt_litert/
bash build_prebuilt.sh \
--litert_version=2.1.3 \
--ndk_path=/path/to/android-ndk \
--litert_so=extracted/jni/arm64-v8a/libLiteRt.so
该脚本将:
- 从 LiteRT GitHub 版本下载
litert_cc_sdk.zip(SDK 标头 + cmake 文件)- 如果已存在,则在后续运行中跳过。 - 将
libLiteRt.so复制到litert_cc_sdk/。 - 将 STB 映像头文件下载到
third_party/stb/中 - 如果存在则跳过。 - 使用 Android NDK 工具链为
arm64-v8a配置和构建android-26。
成功后,您将在 build/ 中看到三个二进制文件:
build/cpp_segmentation_cpu
build/cpp_segmentation_gpu
build/cpp_segmentation_npu

CMakeLists.txt 的作用
打开 CMakeLists.txt。它需要 C++20,通过 add_subdirectory 拉取 LiteRT SDK,链接 OpenGL ES 3 (GLESv3) 和 EGL,然后使用辅助宏从其 main_*.cc 来源创建每个二进制文件:
macro(add_segmentation_target target_name main_source)
add_executable(${target_name} ${main_source})
target_link_libraries(${target_name}
PRIVATE
image_processor image_utils timing_utils litert_cc_api
absl::log absl::check EGL GLESv3 android log
)
endmacro()
add_segmentation_target(cpp_segmentation_cpu main_cpu.cc)
add_segmentation_target(cpp_segmentation_gpu main_gpu.cc)
add_segmentation_target(cpp_segmentation_npu main_npu.cc)
7. 构建 - 选项 B:使用 Bazel(从源代码)构建
如果您偏好使用 Bazel 作为构建系统(该系统可从源代码编译 LiteRT 运行时),或者您需要在现有 Bazel 工作区中工作,请选择此路径。
前提条件
除了“准备工作”部分中列出的 NDK 和 ADB 之外,您还需要:
- Bazel 已安装,且位于您的
PATH中。 - LiteRT 示例源代码库的完整克隆。
第 1 步 - 配置 LiteRT 示例工作区
所有命令均从 LiteRT 示例代码库的根目录运行
cd /path/to/litert-samples
./configure
当系统提示时:
- 接受 Python 和 Python 库路径的默认值。
- 对 ROCm 和 CUDA 支持回答 N。
- 选择 clang(已使用 18.1.3 进行测试)作为编译器。
- 接受默认优化标志。
- 回答 Y 以配置 Android build 的工作区。
- 将最低 Android NDK 级别设置为至少 26。
- 提供 Android SDK 的路径。
- 将 Android SDK API 级别设置为默认值 (36),并将 build tools 设置为 36.0.0。

第 2 步 - 构建 CPU 和 GPU 目标
# CPU
bazel build \
//compiled_model_api/image_segmentation/c++_segmentation/build_from_source:cpp_segmentation_cpu \
--config=android_arm64
# GPU
bazel build \
//compiled_model_api/image_segmentation/c++_segmentation/build_from_source:cpp_segmentation_gpu \
--config=android_arm64
第 3 步 - 构建 NPU 目标
Qualcomm HTP
- 下载 QAIRT SDK v2.41 或更高版本并将其解压缩。
- 确保提取的 SDK 内容位于名为
latest/的子目录中:/path/to/qairt_sdk/ └── latest/ ├── include/ ├── lib/ └── ... - 构建,并传递以
/结尾的父级路径:bazel build \ //compiled_model_api/image_segmentation/c++_segmentation/build_from_source:cpp_segmentation_npu \ --config=android_arm64 \ --nocheck_visibility \ --action_env LITERT_QAIRT_SDK=/path/to/qairt_sdk/
由于某些上游 LiteRT 目标具有受限的默认公开范围,因此需要 --nocheck_visibility 标志。
MediaTek APU
无需额外的 SDK。NeuroPilot 运行时是 Dimensity 9400 设备上的一个系统库。
bazel build \
//compiled_model_api/image_segmentation/c++_segmentation/build_from_source:cpp_segmentation_npu_mtk \
--config=android_arm64 \
--nocheck_visibility

BUILD 文件
打开 build_from_source/BUILD。它定义了四个 cc_binary 目标(每个加速器一个,外加一个专用的 MediaTek NPU 目标),每个目标都依赖于共享的 image_processor、image_utils 和 timing_utils 库目标:
cc_binary(
name = "cpp_segmentation_cpu",
srcs = ["main_cpu.cc"],
deps = [
":image_processor",
":image_utils",
":timing_utils",
"@litert_archive//litert/cc:litert_api_with_dynamic_runtime",
"@com_google_absl//absl/time",
"@com_google_absl//absl/types:span",
] + gles_deps() + gl_native_deps(),
...
)
GPU 目标添加 libLiteRtClGlAccelerator.so 作为数据依赖项,以便 Bazel 将其包含在运行文件中。NPU 目标添加了供应商调度和编译器插件 .so 文件作为数据依赖项。
8. 使用计算着色器的 GPU 加速预处理
所有这三个入口点都使用相同的 OpenGL ES 计算着色器流水线进行预处理。了解这一点对于理解 GPU 路径为何比 CPU 路径快得多至关重要。
设置无头 EGL 上下文
ImageProcessor::InitializeGL() 会创建一个无头 EGL 上下文,即没有附加窗口或显示屏的 OpenGL 上下文。这是 Android 上离屏 GPU 计算的标准做法。然后,它会从磁盘编译五个 GLSL 计算着色器程序:
processor.InitializeGL(
"shaders/passthrough_shader.vert",
"shaders/mask_blend_compute.glsl",
"shaders/resize_compute.glsl",
"shaders/preprocess_compute.glsl",
"shaders/deinterleave_masks.glsl");
将输入图片上传到 GPU
JPEG 由 ImageUtils::LoadImage()(通过 STB 库)解码到 CPU 内存中,然后上传到 GPU 纹理:
auto img_data_cpu = ImageUtils::LoadImage(
input_file, width_orig, height_orig, channels_file, /*desired=*/3);
GLuint tex_id_orig = processor.CreateOpenGLTexture(
img_data_cpu, width_orig, height_orig, loaded_channels);
ImageUtils::FreeImageData(img_data_cpu); // CPU copy no longer needed
从这一刻起,原始图片将以 OpenGL 纹理的形式驻留在 GPU 内存中。
预处理计算着色器
shaders/preprocess_compute.glsl 在 256×256 的输出网格中调度 8×8 的线程组。每个线程处理一个输出像素:它使用双线性过滤(免费的硬件调整大小)对输入纹理进行采样,将 [0, 1] RGB 值转换为 [-1, 1],并写入输出 SSBO:
vec2 uv = vec2(float(pos.x) / float(out_width - 1),
float(pos.y) / float(out_height - 1));
vec4 color_0_1 = texture(inputTexture, uv);
vec3 color_neg1_1 = (color_0_1.rgb * 2.0) - 1.0;
int base = (pos.y * out_width + pos.x) * num_channels;
preprocessed_output.data[base + 0] = color_neg1_1.r;
preprocessed_output.data[base + 1] = color_neg1_1.g;
preprocessed_output.data[base + 2] = color_neg1_1.b;
对于标准(非零复制)路径,系统随后会将此 SSBO 读回 CPU 并写入 LiteRT 张量:
std::vector<float> preprocessed(256 * 256 * num_channels);
processor.ReadBufferData(preprocessed_buffer_id, 0,
preprocessed.size() * sizeof(float),
preprocessed.data());
LITERT_ABORT_IF_ERROR(
input_buffers[0].Write(absl::MakeConstSpan(preprocessed)));
9. CPU 推理
打开 main_cpu.cc。LiteRT 设置为三行:
// Create the root environment
LITERT_ASSIGN_OR_ABORT(auto env, litert::Environment::Create({}));
// Compile the model for the CPU
LITERT_ASSIGN_OR_ABORT(auto compiled_model,
litert::CompiledModel::Create(
env, model_path, litert::HwAccelerators::kCpu));
// Allocate input and output tensor buffers
LITERT_ASSIGN_OR_ABORT(auto input_buffers,
compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_ABORT(auto output_buffers,
compiled_model.CreateOutputBuffers());
预处理后,推理是单个同步调用:
LITERT_ABORT_IF_ERROR(compiled_model.Run(input_buffers, output_buffers));
Run() 会阻塞,直到推理完成。selfie_multiclass_256x256.tflite 浮点模型在 ARM Cortex 核心上运行,在中端设备上通常需要大约 116-128 毫秒。
二进制文件使用情况:
cpp_segmentation_cpu <model_path> <input_image> <output_image>
10. GPU 推断 (OpenCL)
打开 main_gpu.cc。GPU 路径引入了 CPU 路径中没有的两个概念:用于配置 GPU 加速器(使用 OpenCL 后端)的 litert::Options 和异步执行。
配置 GPU 选项
litert::Options CreateGpuOptions() {
LITERT_ASSIGN_OR_ABORT(litert::Options options, litert::Options::Create());
LITERT_ASSIGN_OR_ABORT(auto& gpu_options, options.GetGpuOptions());
LITERT_ABORT_IF_ERROR(
gpu_options.SetBackend(litert::GpuOptions::Backend::kOpenCl));
// Allow CPU fallback for any ops not supported by the GPU delegate
options.SetHardwareAccelerators(litert::HwAccelerators::kGpu |
litert::HwAccelerators::kCpu);
return options;
}
异步推理
GPU 路径使用 RunAsync() 而不是 Run()。此函数会将工作提交到 GPU 命令队列并立即返回。然后,在读取结果之前进行同步:
bool async = false;
LITERT_ABORT_IF_ERROR(
compiled_model.RunAsync(0, input_buffers, output_buffers, async));
if (output_buffers[0].HasEvent()) {
LITERT_ASSIGN_OR_ABORT(auto event, output_buffers[0].GetEvent());
event.Wait();
}
这种非阻塞设计可让您在实时流水线中将 CPU 工作与 GPU 执行重叠。
二进制文件使用情况:
cpp_segmentation_gpu <model_path> <input_image> <output_image>
11. 后处理 - 去交错和混合
在 Run() 或 RunAsync() 完成后,output_buffers[0] 会以交错顺序保存形状为 [256 × 256 × 6] 的扁平浮点数组。像素 (row, col) 的 6 个类得分位于索引 (row * 256 + col) * 6 到 (row * 256 + col) * 6 + 5 处。
解交织为 6 个掩码 SSBO
CPU 辅助程序将交错数组拆分为 6 个单通道浮点数组,并将每个数组上传到其自己的 GPU SSBO:
std::vector<float> data(256 * 256 * 6);
output_buffers[0].Read(absl::MakeSpan(data));
std::vector<GLuint> mask_ids(6);
for (int i = 0; i < 6; ++i)
mask_ids[i] = processor.CreateOpenGLBuffer(nullptr, 256 * 256 * sizeof(float));
processor.DeinterleaveMasksCpu(data.data(), 256, 256, mask_ids);
将颜色混合蒙版应用到原始图片
processor.ApplyColoredMasks() 运行 mask_blend_compute.glsl 着色器。对于每个输出像素,它会找到得分最高的类别(6 个掩码 SSBO 中的 argmax),并将相应的颜色与原始图片像素进行 alpha 合成。每个入口点中都定义了这六种颜色:
std::vector<RGBAColor> mask_colors = {
{1.0f, 0.0f, 0.0f, 0.1f}, // red - background
{0.0f, 1.0f, 0.0f, 0.1f}, // green - hair
{0.0f, 0.0f, 1.0f, 0.1f}, // blue - body skin
{1.0f, 1.0f, 0.0f, 0.1f}, // yellow - face skin
{1.0f, 0.0f, 1.0f, 0.1f}, // magenta - clothes
{0.0f, 1.0f, 1.0f, 0.1f}, // cyan - accessories
};
0.1f 的 Alpha 值可使色调保持柔和,以便原始图片保持可见。
保存输出
读回最终混合的 RGBA 浮点 SSBO,将其限制取值范围为 [0, 1],转换为 unsigned char,然后保存为 PNG:
for (size_t i = 0; i < float_data.size(); ++i)
uchar_data[i] = static_cast<unsigned char>(
std::max(0.0f, std::min(1.0f, float_data[i])) * 255.0f);
ImageUtils::SaveImage(output_file, width, height, 4, uchar_data.data());
12. 在设备上部署和运行
使用 USB 连接 Android 设备,并验证 ADB 连接:
adb devices

使用 deploy_and_run_on_android.sh
每个变体都有自己的部署脚本。CMake 变体指向 build/ 目录;Bazel 变体指向 bazel-bin/。两个脚本:
- 在设备上创建
/data/local/tmp/cpp_segmentation_android/。 - 推送二进制文件、GLSL 着色器、模型、测试图片和运行时
.so文件。 - 使用
adb shell运行推理。 - 将
output_segmented.png拉回到您的机器。
CMake 变体 (use_prebuilt_litert/)
# CPU
./deploy_and_run_on_android.sh --accelerator=cpu --phone=s25 build/
# GPU
./deploy_and_run_on_android.sh --accelerator=gpu --phone=s25 build/
Bazel 变体 (build_from_source/)
从 LiteRT 示例代码库根目录运行以下命令:
# CPU
./compiled_model_api/image_segmentation/c++_segmentation/build_from_source/deploy_and_run_on_android.sh \
--accelerator=cpu --phone=s25 bazel-bin/
# GPU
./compiled_model_api/image_segmentation/c++_segmentation/build_from_source/deploy_and_run_on_android.sh \
--accelerator=gpu --phone=s25 bazel-bin/
--phone 标志用于控制使用哪些特定于设备的型号和供应商库。支持的值:s24 (Snapdragon 8 Gen 3)、s25 (Snapdragon 8 Elite)、dim9400 (MediaTek Dimensity 9400)、pixel8 (Tensor G3)、pixel9 (Tensor G4)、pixel10 (Tensor G5) 和 pixel11 (Tensor G6)。
推理时间
推理结束后,PrintTiming() 会输出完整的分析细分:
Load image: X ms
Preprocess: X ms
Inference: X ms
Postprocess: X ms
E2E: X ms
Save image: X ms
Samsung S25 Ultra(Snapdragon 8 Elite)上的参考性能:
加速器 | 执行类型 | 推理 | E2E |
CPU | 同步 | ~116–128 毫秒 | 约 157 毫秒 |
GPU (OpenCL) | 异步 | 约 0.95 毫秒 | 约 35-43 毫秒 |
13. 高级(可选):NPU 推理
为了实现最佳性能,LiteRT 支持使用供应商专用插件库进行 NPU 加速。NPU 路径可实现低至 9 毫秒的端到端延迟时间。
支持的设备和模式
条状标签 | 设备示例 | 模式 | E2E |
高通 SM8650 | Galaxy S24 | AOT | 约 17 毫秒 |
高通 SM8750 | Galaxy S25 | AOT | 约 17 毫秒 |
Qualcomm(任意) | — | JIT | 约 28 毫秒 |
MediaTek Dimensity 9400 | — | JIT | 约 9 毫秒 |
Google Tensor G3-G6 | Pixel 8-11 | AOT/JIT | 不定 |
AOT(预先)使用设备专属的预编译模型(例如 selfie_multiclass_256x256_SM8650.tflite)。这是最快的选项,但仅适用于特定芯片。
JIT(即时)使用标准 selfie_multiclass_256x256.tflite,并在运行时编译到 NPU - 首次运行速度较慢,但与芯片无关。
额外前提条件
Qualcomm HTP:
- QAIRT SDK v2.41+(提供
libQnnHtp.so、桩或骨架.so文件)。 libLiteRtDispatch_Qualcomm.so来自 GitHub 上 LiteRT NPU 运行时库的版本。
MediaTek APU:
- LiteRT NPU 运行时库版本中的
libLiteRtDispatch_MediaTek.so。 - NeuroPilot 运行时(在 Dimensity 9400 设备上已是系统库 - 无需推送)。
Google Tensor:
- LiteRT NPU 运行时库版本中的
libLiteRtDispatch_GoogleTensor.so。
NPU 环境和选项
main_npu.cc 将 Environment 指向设备上的供应商调度库目录,然后设置特定于供应商的性能选项:
// Configure LiteRT to find the dispatch library
std::vector<litert::Environment::Option> env_opts;
env_opts.push_back({litert::Environment::OptionTag::DispatchLibraryDir,
kQualcommDispatchDir});
LITERT_ASSIGN_OR_ABORT(auto env,
litert::Environment::Create(std::move(env_opts)));
// Target NPU with CPU fallback
LITERT_ASSIGN_OR_ABORT(litert::Options options, litert::Options::Create());
options.SetHardwareAccelerators(litert::HwAccelerators::kNpu |
litert::HwAccelerators::kCpu);
// Qualcomm: burst performance mode
auto& qnn_opts = options.GetQualcommOptions();
qnn_opts.SetLogLevel(litert::qualcomm::QualcommOptions::LogLevel::kOff);
qnn_opts.SetHtpPerformanceMode(
litert::qualcomm::QualcommOptions::HtpPerformanceMode::kBurst);
LITERT_ASSIGN_OR_ABORT(auto model,
litert::CompiledModel::Create(env, model_path, options));
对于 MediaTek,请替换 GetQualcommOptions() 块:
// MediaTek: fast single-answer mode + low-latency hint
auto& mtk_opts = options.GetMediatekOptions();
mtk_opts.SetPerformanceMode(
kLiteRtMediatekNeuronAdapterPerformanceModeNeuronPreferFastSingleAnswer);
mtk_opts.SetOptimizationHint(
kLiteRtMediatekNeuronAdapterOptimizationHintLowLatency);
mtk_opts.SetNeronSDKVersionType(
kLiteRtMediatekOptionsNeronSDKVersionTypeVersion8);
针对 NPU 进行部署
CMake 变体 - Qualcomm S25 (AOT)
./deploy_and_run_on_android.sh \
--accelerator=npu --phone=s25 \
--host_npu_lib=/path/to/qairt/lib \
--host_npu_dispatch_lib=/path/to/dir/with/libLiteRtDispatch_Qualcomm.so \
build/
CMake 变体 - MediaTek Dimensity 9400 (JIT)
./deploy_and_run_on_android.sh \
--accelerator=npu --phone=dim9400 --jit \
--host_npu_dispatch_lib=/path/to/dir/with/libLiteRtDispatch_MediaTek.so \
build/
Bazel 变体 - Qualcomm S25 (AOT)
./compiled_model_api/image_segmentation/c++_segmentation/build_from_source/deploy_and_run_on_android.sh \
--accelerator=npu --phone=s25 bazel-bin/
Bazel 变体 - MediaTek Dimensity 9400 (JIT)
./compiled_model_api/image_segmentation/c++_segmentation/build_from_source/deploy_and_run_on_android.sh \
--accelerator=npu --phone=dim9400 --jit bazel-bin/
Bazel 变体 - Google Tensor Pixel 9 (JIT)
./compiled_model_api/image_segmentation/c++_segmentation/build_from_source/deploy_and_run_on_android.sh \
--accelerator=npu --phone=pixel9 --jit bazel-bin/
对于 Bazel 变体,如果在构建时设置了 LITERT_QAIRT_SDK,则会自动从 bazel-bin runfiles 树中提取 QAIRT SDK 库。CMake 变体需要使用 --host_npu_lib 标志指向您提取的 QAIRT SDK。
14. 恭喜!
您已使用 LiteRT 在 Android 上成功构建并运行了 C++ 图像分割流水线。您已了解如何:
- 使用 CMake + NDK 或 Bazel 为 Android
arm64-v8a交叉编译 C++ 二进制文件。 - 使用 LiteRT C++ API(
Environment、CompiledModel、TensorBuffer)可在设备上高效进行推理。 - 使用 OpenGL ES 3.1 计算着色器在 GPU 上预处理图像数据。
- 运行同步 CPU 推理和异步 GPU (OpenCL) 推理。
- 为 Qualcomm、MediaTek 和 Google Tensor 设备配置 NPU 加速。
- 使用 ADB 在 Android 上部署和运行 C++ 二进制文件。
后续步骤
- 换用其他 TFLite 模型(例如,深度估计或姿态检测)。
- 使用 JNI 将 C++ 流水线集成到 Android NDK 应用中。
- 使用 Android GPU 检查器分析内存用量,同时输出时间信息。
- 探索模型量化,以进一步缩短 NPU 推理延迟时间。