Las APIs de LiteRT Compiled Model están disponibles en C++, lo que les brinda a los desarrolladores de Android un control detallado sobre la asignación de memoria y el desarrollo de bajo nivel.
Si deseas ver un ejemplo de una aplicación de LiteRT en C++, consulta la demostración de segmentación asíncrona con C++.
Comenzar
Sigue estos pasos para agregar la API de LiteRT Compiled Model a tu aplicación para Android.
Actualiza la configuración de compilación
Compilar una aplicación en C++ con LiteRT para la aceleración de GPU, NPU y CPU con Bazel implica definir una regla cc_binary para garantizar que todos los componentes necesarios se compilen, vinculen y empaqueten. La siguiente configuración de ejemplo permite que tu aplicación elija o utilice de forma dinámica aceleradores de GPU, NPU y CPU.
Estos son los componentes clave de la configuración de compilación de Bazel:
- Regla
cc_binary: Esta es la regla fundamental de Bazel que se usa para definir tu destino ejecutable de C++ (p.ej.,name = "your_application_name"). - Atributo
srcs: Enumera los archivos fuente de C++ de tu aplicación (p.ej.,main.ccy otros archivos.cco.h). - Atributo
data(dependencias de tiempo de ejecución): Es fundamental para empaquetar bibliotecas compartidas y recursos que tu aplicación carga en el tiempo de ejecución.- Tiempo de ejecución principal de LiteRT: Es la biblioteca compartida de la API de C principal de LiteRT (p.ej.,
//litert/c:litert_runtime_c_api_shared_lib). - Bibliotecas de envío: Son bibliotecas compartidas específicas del proveedor que LiteRT usa para comunicarse con los controladores de hardware (p.ej.,
//litert/vendors/qualcomm/dispatch:dispatch_api_so). - Bibliotecas de backend de GPU: Son las bibliotecas compartidas para la aceleración por GPU (p.ej.,
"@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so). - Bibliotecas de backend de la NPU: Son las bibliotecas compartidas específicas para la aceleración de la NPU, como las bibliotecas HTP de QNN de Qualcomm (p.ej.,
@qairt//:lib/aarch64-android/libQnnHtp.so,@qairt//:lib/hexagon-v79/unsigned/libQnnHtpV79Skel.so). - Archivos y recursos del modelo: Son los archivos del modelo entrenado, las imágenes de prueba, los sombreadores o cualquier otro dato necesario en el tiempo de ejecución (p.ej.,
:model_files,:shader_files).
- Tiempo de ejecución principal de LiteRT: Es la biblioteca compartida de la API de C principal de LiteRT (p.ej.,
- Atributo
deps(dependencias en tiempo de compilación): En esta sección, se enumeran las bibliotecas con las que tu código debe compilarse.- APIs y utilidades de LiteRT: Encabezados y bibliotecas estáticas para componentes de LiteRT, como búferes de tensores (p.ej.,
//litert/cc:litert_tensor_buffer). - Bibliotecas de gráficos (para GPU): Dependencias relacionadas con las APIs de gráficos si el acelerador de GPU las usa (p.ej.,
gles_deps()).
- APIs y utilidades de LiteRT: Encabezados y bibliotecas estáticas para componentes de LiteRT, como búferes de tensores (p.ej.,
- Atributo
linkopts: Especifica las opciones que se pasan al vinculador, que pueden incluir la vinculación con bibliotecas del sistema (p.ej.,-landroidpara compilaciones de Android o bibliotecas de GLES congles_linkopts()).
A continuación, se muestra un ejemplo de una regla de 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
)
Carga el modelo
Después de obtener un modelo de LiteRT o convertir un modelo al formato .tflite, carga el modelo creando un objeto Model.
LITERT_ASSIGN_OR_RETURN(auto model, Model::CreateFromFile("mymodel.tflite"));
Crea el entorno
El objeto Environment proporciona un entorno de ejecución que incluye componentes como la ruta de acceso del complemento del compilador y los contextos de la GPU. El campo Environment es obligatorio cuando se crean CompiledModel y TensorBuffer. El siguiente código crea un Environment para la ejecución de CPU y GPU sin ninguna opción:
LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
Crea el modelo compilado
Con la API de CompiledModel, inicializa el tiempo de ejecución con el objeto Model recién creado. En este punto, puedes especificar la aceleración de hardware (kLiteRtHwAcceleratorCpu o kLiteRtHwAcceleratorGpu):
LITERT_ASSIGN_OR_RETURN(auto compiled_model,
CompiledModel::Create(env, model, kLiteRtHwAcceleratorCpu));
Cómo crear búferes de entrada y salida
Crea las estructuras de datos (búferes) necesarias para contener los datos de entrada que ingresarás en el modelo para la inferencia y los datos de salida que el modelo produce después de ejecutar la inferencia.
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());
Si usas memoria de CPU, completa las entradas escribiendo datos directamente en el primer búfer de entrada.
input_buffers[0].Write<float>(absl::MakeConstSpan(input_data, input_size));
Invoca el modelo
Proporciona los búferes de entrada y salida, y ejecuta el modelo compilado con el modelo y la aceleración por hardware especificados en los pasos anteriores.
compiled_model.Run(input_buffers, output_buffers);
Recupera resultados
Recupera los resultados leyendo directamente el resultado del modelo de la memoria.
std::vector<float> data(output_data_size);
output_buffers[0].Read<float>(absl::MakeSpan(data));
// ... process output data
Componentes y conceptos clave
Consulta las siguientes secciones para obtener información sobre los conceptos y componentes clave de las APIs de modelos compilados de LiteRT.
Manejo de errores
LiteRT usa litert::Expected para devolver valores o propagar errores de manera similar a absl::StatusOr o std::expected. Puedes verificar manualmente el error.
Para mayor comodidad, LiteRT proporciona las siguientes macros:
LITERT_ASSIGN_OR_RETURN(lhs, expr)asigna el resultado deexpralhssi no produce un error y, de lo contrario, devuelve el error.Se expandirá a algo similar al siguiente fragmento.
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)hace lo mismo queLITERT_ASSIGN_OR_RETURN, pero anula el programa en caso de error.LITERT_RETURN_IF_ERROR(expr)devuelveexprsi su evaluación produce un error.LITERT_ABORT_IF_ERROR(expr)hace lo mismo queLITERT_RETURN_IF_ERROR, pero aborta el programa en caso de error.
Para obtener más información sobre las macros de LiteRT, consulta litert_macros.h.
Modelo compilado (CompiledModel)
La API de Compiled Model (CompiledModel) es responsable de cargar un modelo, aplicar la aceleración por hardware, crear una instancia del tiempo de ejecución, crear búferes de entrada y salida, y ejecutar la inferencia.
En el siguiente fragmento de código simplificado, se muestra cómo la API de Compiled Model toma un modelo de LiteRT (.tflite) y el acelerador de hardware de destino (GPU) y crea un modelo compilado listo para ejecutar la inferencia.
// 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));
En el siguiente fragmento de código simplificado, se muestra cómo la API de Compiled Model toma un búfer de entrada y salida, y ejecuta inferencias con el modelo compilado.
// 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)));
Para obtener una vista más completa de cómo se implementa la API de CompiledModel, consulta el código fuente de litert_compiled_model.h.
Búfer de tensor (TensorBuffer)
LiteRT proporciona compatibilidad integrada para la interoperabilidad del búfer de E/S, ya que usa la API de Tensor Buffer (TensorBuffer) para controlar el flujo de datos hacia el modelo compilado y desde él. La API de Tensor Buffer permite escribir (Write<T>()) y leer (Read<T>()), y bloquear la memoria de la CPU.
Para obtener una vista más completa de cómo se implementa la API de TensorBuffer, consulta el código fuente de litert_tensor_buffer.h.
Requisitos de entrada y salida del modelo de consulta
Los requisitos para asignar un búfer de tensor (TensorBuffer) suelen especificarse en el acelerador de hardware. Los búferes de entrada y salida pueden tener requisitos relacionados con la alineación, los avances de búfer y el tipo de memoria. Puedes usar funciones de ayuda, como CreateInputBuffers, para controlar automáticamente estos requisitos.
En el siguiente fragmento de código simplificado, se muestra cómo puedes recuperar los requisitos de búfer para los datos de entrada:
LITERT_ASSIGN_OR_RETURN(auto reqs, compiled_model.GetInputBufferRequirements(signature_index, input_index));
Para obtener una vista más completa de cómo se implementa la API de TensorBufferRequirements, consulta el código fuente de litert_tensor_buffer_requirements.h.
Crea búferes de tensores administrados (TensorBuffers)
En el siguiente fragmento de código simplificado, se muestra cómo crear búferes de tensores administrados, en los que la API de TensorBuffer asigna los búferes respectivos:
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));
Crea búferes de tensores con copia cero
Para encapsular un búfer existente como un Tensor Buffer (sin copia), usa el siguiente fragmento de código:
// 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));
Lectura y escritura desde Tensor Buffer
En el siguiente fragmento, se muestra cómo puedes leer desde un búfer de entrada y escribir en un búfer de salida:
// 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 */
}
Avanzado: Interoperabilidad de búfer de copia cero para tipos de búfer de hardware especializados
Ciertos tipos de búfer, como AHardwareBuffer, permiten la interoperabilidad con otros tipos de búfer. Por ejemplo, se puede crear un búfer de OpenGL a partir de un AHardwareBuffer con copia cero. En el siguiente fragmento de código, se muestra un ejemplo:
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());
También se pueden crear búferes de OpenCL a partir de AHardwareBuffer:
LITERT_ASSIGN_OR_RETURN(auto cl_buffer, tensor_buffer_ahwb.GetOpenClMemory());
En dispositivos móviles que admiten la interoperabilidad entre OpenCL y OpenGL, se pueden crear búferes de CL a partir de búferes de GL:
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());
Implementaciones de ejemplo
Consulta las siguientes implementaciones de LiteRT en C++.
Inferencia básica (CPU)
A continuación, se muestra una versión condensada de los fragmentos de código de la sección Primeros pasos. Es la implementación más simple de la inferencia con 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));
Copia cero con memoria del host
La API de LiteRT Compiled Model reduce la fricción de las canalizaciones de inferencia, en especial cuando se trabaja con varios backends de hardware y flujos de copia cero. En el siguiente fragmento de código, se usa el método CreateFromHostMemory cuando se crea el búfer de entrada, que usa la copia cero con la memoria del host.
// 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);