LiteRT Next API 采用 C++ 编写,与 Kotlin API 相比,可让 Android 开发者更好地控制内存分配和低级开发。
如需查看 C++ 中的 LiteRT Next 应用示例,请参阅使用 C++ 进行异步分段演示。
开始使用
请按以下步骤将 LiteRT Next 添加到您的 Android 应用。
更新 build 配置
使用 Bazel 使用 LiteRT 构建适用于 GPU、NPU 和 CPU 加速的 C++ 应用需要定义 cc_binary
规则,以确保编译、关联和打包所有必要的组件。通过以下示例设置,您的应用可以动态选择或利用 GPU、NPU 和 CPU 加速器。
以下是 Bazel 构建配置中的关键组件:
cc_binary
规则:这是用于定义 C++ 可执行目标(例如name = "your_application_name"
)。srcs
属性:列出应用的 C++ 源文件(例如main.cc
和其他.cc
或.h
文件)。data
属性(运行时依赖项):对于打包应用在运行时加载的共享库和资源至关重要。- LiteRT 核心运行时:主要的 LiteRT C API 共享库(例如
//litert/c:litert_runtime_c_api_shared_lib
)。 - 调度库:LiteRT 用于与硬件驱动程序(例如
//litert/vendors/qualcomm/dispatch:dispatch_api_so
)。 - GPU 后端库:用于 GPU 加速的共享库(例如
"@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so
)。 - NPU 后端库:用于 NPU 加速的特定共享库,例如 Qualcomm 的 QNN HTP 库(例如
@qairt//:lib/aarch64-android/libQnnHtp.so
、@qairt//:lib/hexagon-v79/unsigned/libQnnHtpV79Skel.so
)。 - 模型文件和资源:经过训练的模型文件、测试图片、着色器或运行时所需的任何其他数据(例如
:model_files
、:shader_files
)。
- LiteRT 核心运行时:主要的 LiteRT C API 共享库(例如
deps
属性(编译时依赖项):此属性会列出您的代码需要针对哪些库进行编译。- LiteRT API 和实用程序:适用于 LiteRT 组件(例如张量缓冲区)的头文件和静态库
//litert/cc:litert_tensor_buffer
)。 - 图形库(适用于 GPU):与图形 API 相关的依赖项(如果 GPU 加速器使用它们),例如
gles_deps()
)。
- LiteRT API 和实用程序:适用于 LiteRT 组件(例如张量缓冲区)的头文件和静态库
linkopts
属性:指定传递给链接器的选项,其中可能包括与系统库(例如-landroid
(适用于 Android build)或 GLES 库(使用gles_linkopts()
)。
以下是 cc_binary
规则的示例:
cc_binary(
name = "your_application",
srcs = [
"main.cc",
],
data = [
...
# litert c api shared library
"//litert/c:litert_runtime_c_api_shared_lib",
# GPU accelerator shared library
"@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so",
# NPU accelerator shared library
"//litert/vendors/qualcomm/dispatch:dispatch_api_so",
],
linkopts = select({
"@org_tensorflow//tensorflow:android": ["-landroid"],
"//conditions:default": [],
}) + gles_linkopts(), # gles link options
deps = [
...
"//litert/cc:litert_tensor_buffer", # litert cc library
...
] + gles_deps(), # gles dependencies
)
加载模型
获取 LiteRT 模型或将模型转换为 .tflite
格式后,通过创建 Model
对象加载模型。
LITERT_ASSIGN_OR_RETURN(auto model, Model::CreateFromFile("mymodel.tflite"));
创建环境
Environment
对象提供一个运行时环境,其中包含编译器插件和 GPU 上下文等组件。创建 CompiledModel
和 TensorBuffer
时,必须提供 Environment
。以下代码会创建一个 Environment
,用于在 CPU 和 GPU 上执行,不带任何选项:
LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
创建已编译的模型
使用 CompiledModel
API,使用新创建的 Model
对象初始化运行时。您可以在此时指定硬件加速(kLiteRtHwAcceleratorCpu
或 kLiteRtHwAcceleratorGpu
):
LITERT_ASSIGN_OR_RETURN(auto compiled_model,
CompiledModel::Create(env, model, kLiteRtHwAcceleratorCpu));
创建输入和输出缓冲区
创建必要的数据结构(缓冲区),以存储您要馈送给模型进行推理的输入数据,以及模型在运行推理后生成的输出数据。
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());
如果您使用的是 CPU 内存,请直接将数据写入第一个输入缓冲区以填充输入。
input_buffers[0].Write<float>(absl::MakeConstSpan(input_data, input_size));
调用模型
提供输入和输出缓冲区,使用上一步中指定的模型和硬件加速运行已编译的模型。
compiled_model.Run(input_buffers, output_buffers);
检索输出
通过直接从内存中读取模型输出来检索输出。
std::vector<float> data(output_data_size);
output_buffers[0].Read<float>(absl::MakeSpan(data));
// ... process output data
主要概念和组件
如需了解 LiteRT Next API 的主要概念和组件,请参阅以下部分。
错误处理
LiteRT 使用 litert::Expected
返回值或传播错误的方式与 absl::StatusOr
或 std::expected
类似。您可以自行手动检查是否存在错误。
为方便起见,LiteRT 提供了以下宏:
如果
expr
未产生错误,LITERT_ASSIGN_OR_RETURN(lhs, expr)
会将其结果分配给lhs
;否则,会返回错误。它会展开为类似以下代码段的内容。
auto maybe_model = Model::CreateFromFile("mymodel.tflite"); if (!maybe_model) { return maybe_model.Error(); } auto model = std::move(maybe_model.Value());
LITERT_ASSIGN_OR_ABORT(lhs, expr)
的用途与LITERT_ASSIGN_OR_RETURN
相同,但在出现错误时会终止程序。如果
LITERT_RETURN_IF_ERROR(expr)
的评估产生错误,则返回expr
。LITERT_ABORT_IF_ERROR(expr)
的用途与LITERT_RETURN_IF_ERROR
相同,但在出现错误时会中止程序。
如需详细了解 LiteRT 宏,请参阅 litert_macros.h
。
已编译的模型 (CompiledModel)
编译型模型 API (CompiledModel
) 负责加载模型、应用硬件加速、实例化运行时、创建输入和输出缓冲区以及运行推理。
以下简化版代码段演示了 Compiled Model API 如何获取 LiteRT 模型 (.tflite
) 和目标硬件加速器 (GPU),并创建一个已准备好运行推理的已编译模型。
// Load model and initialize runtime
LITERT_ASSIGN_OR_RETURN(auto model, Model::CreateFromFile("mymodel.tflite"));
LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
LITERT_ASSIGN_OR_RETURN(auto compiled_model,
CompiledModel::Create(env, model, kLiteRtHwAcceleratorCpu));
以下简化版代码段演示了 Compiled Model API 如何接受输入和输出缓冲区,以及如何使用已编译的模型运行推理。
// Preallocate input/output buffers
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());
// Fill the first input
float input_values[] = { /* your data */ };
LITERT_RETURN_IF_ERROR(
input_buffers[0].Write<float>(absl::MakeConstSpan(input_values, /*size*/)));
// Invoke
LITERT_RETURN_IF_ERROR(compiled_model.Run(input_buffers, output_buffers));
// Read the output
std::vector<float> data(output_data_size);
LITERT_RETURN_IF_ERROR(
output_buffers[0].Read<float>(absl::MakeSpan(data)));
如需更全面地了解 CompiledModel
API 的实现方式,请参阅 litert_compiled_model.h 的源代码。
张量缓冲区 (TensorBuffer)
LiteRT Next 内置了对 I/O 缓冲区互操作性的支持,使用 Tensor Buffer API (TensorBuffer
) 来处理数据在已编译模型中的流动。Tensor Buffer API 提供写入 (Write<T>()
) 和读取 (Read<T>()
) 功能,以及锁定 CPU 内存的功能。
如需更全面地了解 TensorBuffer
API 的实现方式,请参阅 litert_tensor_buffer.h 的源代码。
查询模型输入/输出要求
分配张量缓冲区 (TensorBuffer
) 的要求通常由硬件加速器指定。输入和输出的缓冲区可能对对齐、缓冲区步长和内存类型有要求。您可以使用 CreateInputBuffers
等辅助函数自动处理这些要求。
以下简化版代码段演示了如何检索输入数据的缓冲区要求:
LITERT_ASSIGN_OR_RETURN(auto reqs, compiled_model.GetInputBufferRequirements(signature_index, input_index));
如需更全面地了解 TensorBufferRequirements
API 的实现方式,请参阅 litert_tensor_buffer_requirements.h 的源代码。
创建托管式张量缓冲区 (TensorBuffers)
以下简化版代码段演示了如何创建托管的 Tensor 缓冲区,其中 TensorBuffer
API 会分配相应的缓冲区:
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_cpu,
TensorBuffer::CreateManaged(env, /*buffer_type=*/kLiteRtTensorBufferTypeHostMemory,
ranked_tensor_type, buffer_size));
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_gl, TensorBuffer::CreateManaged(env,
/*buffer_type=*/kLiteRtTensorBufferTypeGlBuffer, ranked_tensor_type, buffer_size));
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_ahwb, TensorBuffer::CreateManaged(env,
/*buffer_type=*/kLiteRtTensorBufferTypeAhwb, ranked_tensor_type, buffer_size));
使用零拷贝创建 Tensor 缓冲区
如需将现有缓冲区封装为张量缓冲区(零拷贝),请使用以下代码段:
// Create a TensorBuffer from host memory
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_from_host,
TensorBuffer::CreateFromHostMemory(env, ranked_tensor_type,
ptr_to_host_memory, buffer_size));
// Create a TensorBuffer from GlBuffer
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_from_gl,
TensorBuffer::CreateFromGlBuffer(env, ranked_tensor_type, gl_target, gl_id,
size_bytes, offset));
// Create a TensorBuffer from AHardware Buffer
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_from_ahwb,
TensorBuffer::CreateFromAhwb(env, ranked_tensor_type, ahardware_buffer, offset));
从 Tensor 缓冲区读取和写入
以下代码段演示了如何从输入缓冲区读取并写入输出缓冲区:
// Example of reading to input buffer:
std::vector<float> input_tensor_data = {1,2};
LITERT_ASSIGN_OR_RETURN(auto write_success,
input_tensor_buffer.Write<float>(absl::MakeConstSpan(input_tensor_data)));
if(write_success){
/* Continue after successful write... */
}
// Example of writing to output buffer:
std::vector<float> data(total_elements);
LITERT_ASSIGN_OR_RETURN(auto read_success,
output_tensor_buffer.Read<float>(absl::MakeSpan(data)));
if(read_success){
/* Continue after successful read */
}
高级:专用硬件缓冲区类型的零拷贝缓冲区互操作
某些缓冲区类型(例如 AHardwareBuffer
)支持与其他缓冲区类型进行互操作。例如,可以使用零拷贝从 AHardwareBuffer
创建 OpenGL 缓冲区。以下代码段展示了一个示例:
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_ahwb,
TensorBuffer::CreateManaged(env, kLiteRtTensorBufferTypeAhwb,
ranked_tensor_type, buffer_size));
// Buffer interop: Get OpenGL buffer from AHWB,
// internally creating an OpenGL buffer backed by AHWB memory.
LITERT_ASSIGN_OR_RETURN(auto gl_buffer, tensor_buffer_ahwb.GetGlBuffer());
您还可以通过 AHardwareBuffer
创建 OpenCL 缓冲区:
LITERT_ASSIGN_OR_RETURN(auto cl_buffer, tensor_buffer_ahwb.GetOpenClMemory());
在支持 OpenCL 和 OpenGL 之间互操作的移动设备上,可以通过 GL 缓冲区创建 CL 缓冲区:
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_from_gl,
TensorBuffer::CreateFromGlBuffer(env, ranked_tensor_type, gl_target, gl_id,
size_bytes, offset));
// Creates an OpenCL buffer from the OpenGL buffer, zero-copy.
LITERT_ASSIGN_OR_RETURN(auto cl_buffer, tensor_buffer_from_gl.GetOpenClMemory());
实现示例
请参阅以下 C++ 中的 LiteRT Next 实现。
基本推理(CPU)
以下是开始使用部分中代码段的浓缩版本。这是使用 LiteRT Next 进行推理的最简单实现。
// Load model and initialize runtime
LITERT_ASSIGN_OR_RETURN(auto model, Model::CreateFromFile("mymodel.tflite"));
LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
LITERT_ASSIGN_OR_RETURN(auto compiled_model, CompiledModel::Create(env, model,
kLiteRtHwAcceleratorCpu));
// Preallocate input/output buffers
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());
// Fill the first input
float input_values[] = { /* your data */ };
input_buffers[0].Write<float>(absl::MakeConstSpan(input_values, /*size*/));
// Invoke
compiled_model.Run(input_buffers, output_buffers);
// Read the output
std::vector<float> data(output_data_size);
output_buffers[0].Read<float>(absl::MakeSpan(data));
使用主机内存实现零拷贝
LiteRT Next 编译型模型 API 可减少推理流水线的摩擦,尤其是在处理多个硬件后端和零拷贝流时。以下代码段在创建输入缓冲区时使用 CreateFromHostMemory
方法,该方法使用主机内存进行零拷贝。
// Define an LiteRT environment to use existing EGL display and context.
const std::vector<Environment::Option> environment_options = {
{OptionTag::EglDisplay, user_egl_display},
{OptionTag::EglContext, user_egl_context}};
LITERT_ASSIGN_OR_RETURN(auto env,
Environment::Create(absl::MakeConstSpan(environment_options)));
// Load model1 and initialize runtime.
LITERT_ASSIGN_OR_RETURN(auto model1, Model::CreateFromFile("model1.tflite"));
LITERT_ASSIGN_OR_RETURN(auto compiled_model1, CompiledModel::Create(env, model1, kLiteRtHwAcceleratorGpu));
// Prepare I/O buffers. opengl_buffer is given outside from the producer.
LITERT_ASSIGN_OR_RETURN(auto tensor_type, model.GetInputTensorType("input_name0"));
// Create an input TensorBuffer based on tensor_type that wraps the given OpenGL Buffer.
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_from_opengl,
litert::TensorBuffer::CreateFromGlBuffer(env, tensor_type, opengl_buffer));
// Create an input event and attach it to the input buffer. Internally, it creates
// and inserts a fence sync object into the current EGL command queue.
LITERT_ASSIGN_OR_RETURN(auto input_event, Event::CreateManaged(env, LiteRtEventTypeEglSyncFence));
tensor_buffer_from_opengl.SetEvent(std::move(input_event));
std::vector<TensorBuffer> input_buffers;
input_buffers.push_back(std::move(tensor_buffer_from_opengl));
// Create an output TensorBuffer of the model1. It's also used as an input of the model2.
LITERT_ASSIGN_OR_RETURN(auto intermedidate_buffers, compiled_model1.CreateOutputBuffers());
// Load model2 and initialize runtime.
LITERT_ASSIGN_OR_RETURN(auto model2, Model::CreateFromFile("model2.tflite"));
LITERT_ASSIGN_OR_RETURN(auto compiled_model2, CompiledModel::Create(env, model2, kLiteRtHwAcceleratorGpu));
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model2.CreateOutputBuffers());
compiled_model1.RunAsync(input_buffers, intermedidate_buffers);
compiled_model2.RunAsync(intermedidate_buffers, output_buffers);