API de C++ de LiteRT CompiledModel

La API de LiteRT CompiledModel está disponible en C++, lo que les brinda a los desarrolladores un control detallado sobre la asignación de memoria y el desarrollo de bajo nivel. Para ver un ejemplo, consulta la app de segmentación de imágenes en C++.

En la siguiente guía, se muestra la inferencia básica de la CPU de la API de CompiledModel de Kotlin. Consulta la guía sobre la aceleración por GPU y la aceleración por NPU para conocer las funciones de aceleración avanzadas.

Cómo agregar una dependencia de compilación

Elige la ruta que se adapte a tu proyecto:

  • Usar la biblioteca compilada previamente (multiplataforma): Usa la biblioteca compilada previamente de LiteRT para una configuración instantánea. Consulta cómo usar la biblioteca de C++ compilada previamente desde el paquete de LiteRT Maven en Android, o bien descarga o integra el archivo binario de C++ compilado previamente en Android, iOS, macOS, Linux y Windows.

  • Compilación desde la fuente (plataforma cruzada): Compila desde la fuente con CMake para tener control total y compatibilidad con múltiples plataformas (Android, iOS, macOS, Linux y Windows). Consulta los detalles en esta guía.

Inferencia básica

En esta sección, se muestra cómo se realiza la inferencia básica.

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 CompiledModel

Después de obtener un modelo de LiteRT o convertir un modelo al formato .tflite, inicializa el tiempo de ejecución con el archivo del modelo usando la API de CompiledModel. 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

Conceptos y componentes clave

Consulta las siguientes secciones para obtener información sobre los conceptos y componentes clave de la API de LiteRT CompiledModel.

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 de expr a lhs si no produce un error y, de lo contrario, devuelve el error.

    Se expandirá a algo similar al siguiente fragmento.

    auto maybe_model = CompiledModel::Create(env, "mymodel.tflite", HwAccelerators::kCpu);
    if (!maybe_model) {
      return maybe_model.Error();
    }
    auto model = std::move(maybe_model.Value());
    
  • LITERT_ASSIGN_OR_ABORT(lhs, expr) hace lo mismo que LITERT_ASSIGN_OR_RETURN, pero anula el programa en caso de error.

  • LITERT_RETURN_IF_ERROR(expr) devuelve expr si su evaluación produce un error.

  • LITERT_ABORT_IF_ERROR(expr) hace lo mismo que LITERT_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.

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 env, Environment::Create({}));
LITERT_ASSIGN_OR_RETURN(auto compiled_model, CompiledModel::Create(env, "mymodel.tflite",
  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 CompiledModel 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 compiled_model1, CompiledModel::Create(env, "model1.tflite", 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 compiled_model2, CompiledModel::Create(env, "model2.tflite", 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);