LiteRT CompiledModel C++ API

LiteRT CompiledModel API 以 C++ 提供,可让开发者对内存分配和低阶开发进行精细控制。如需查看示例,请参阅图像分割 C++ 应用

以下指南展示了 CompiledModel Kotlin API 的基本 CPU 推理。如需了解高级加速功能,请参阅有关 GPU 加速NPU 加速的指南。

添加 build 依赖项

选择适合您项目的路径:

  • 使用预构建库 (Android):使用 LiteRT Maven 软件包中的预构建库进行即时设置。了解如何从 LiteRT Maven 使用预构建的 C++ 库

  • 从源代码构建(跨平台):从源代码构建,以实现完全控制和多平台支持(Android、iOS、macOS、Linux、Windows)。请参阅以下说明。

使用 Bazel 从源代码构建

使用 Bazel 构建支持 GPU、NPU 和 CPU 加速的 LiteRT C++ 应用涉及定义 cc_binary 规则,以确保所有必需的组件都经过编译、链接和打包。以下示例设置允许您的应用动态选择或利用 GPU、NPU 和 CPU 加速器。

以下是 Bazel 构建配置中的关键组件:

  • cc_binary 规则:这是用于定义 C++ 可执行目标的基本 Bazel 规则(例如,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)。
  • deps 属性(编译时依赖项):此属性列出了代码需要针对哪些库进行编译。
    • LiteRT 和实用程序:用于 LiteRT 组件(例如张量缓冲区 [例如,//litert/cc:litert_tensor_buffer)。
    • 图形库(适用于 GPU):如果 GPU 加速器使用图形 API,则为与图形 API 相关的依赖项(例如,gles_deps())。
  • linkopts 属性:指定传递给链接器的选项,这些选项可以包括与系统库(例如 -landroid 的 Android build,或带有 gles_linkopts() 的 GLES 库)。

以下是 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 上下文等组件。创建 CompiledModelTensorBuffer 时必须提供 Environment。以下代码创建了一个 Environment,用于在 CPU 和 GPU 上执行,不带任何选项:

LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));

创建 CompiledModel

使用 CompiledModel API,通过新创建的 Model 对象初始化运行时。您可以在此点指定硬件加速(kLiteRtHwAcceleratorCpukLiteRtHwAcceleratorGpu):

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 CompiledModel API 的主要概念和组件,请参阅以下部分。

错误处理

LiteRT 使用 litert::Expected 返回值或传播错误,方式与 absl::StatusOrstd::expected 类似。您可以自行手动检查是否存在此错误。

为方便起见,LiteRT 提供了以下宏:

  • 如果 expr 未产生错误,LITERT_ASSIGN_OR_RETURN(lhs, expr) 会将 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

Tensor 缓冲区 (TensorBuffer)

LiteRT 使用 Tensor Buffer API (TensorBuffer) 处理编译后模型的数据流入和流出,为 I/O 缓冲区互操作性提供内置支持。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 的源代码。

创建受管理的 Tensor 缓冲区 (TensorBuffers)

以下简化代码段演示了如何创建受管理的张量缓冲区,其中 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 实现。

基本推理 (CPU)

以下是开始使用部分中的代码段的精简版本。这是使用 LiteRT 进行推理的最简单实现。

// 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 CompiledModel 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);