API C++ LiteRT CompiledModel

L'API LiteRT CompiledModel è disponibile in C++, offrendo agli sviluppatori un controllo granulare sull'allocazione della memoria e sullo sviluppo di basso livello. Per un esempio, vedi l'app C++ per la segmentazione delle immagini.

La seguente guida mostra l'inferenza della CPU di base dell'API CompiledModel Kotlin. Consulta la guida sull'accelerazione GPU e sull'accelerazione NPU per funzionalità di accelerazione avanzate.

Aggiungi dipendenza di build

Scegli il percorso più adatto al tuo progetto:

  • Utilizza la libreria predefinita (multipiattaforma): utilizza la libreria predefinita LiteRT per una configurazione immediata. Scopri come utilizzare la libreria C++ predefinita dal pacchetto Maven LiteRT su Android o scarica/integra il binario C++ predefinito su Android, iOS, macOS, Linux e Windows.

  • Compila dal codice sorgente (multipiattaforma): compila dal codice sorgente con CMake per un controllo completo e il supporto multipiattaforma (Android, iOS, macOS, Linux, Windows). Per maggiori dettagli, consulta questa guida.

Inferenza di base

Questa sezione mostra come viene eseguita l'inferenza di base.

Crea l'ambiente

L'oggetto Environment fornisce un ambiente di runtime che include componenti come il percorso del plug-in del compilatore e i contesti GPU. L'Environment è obbligatorio durante la creazione di CompiledModel e TensorBuffer. Il seguente codice crea un Environment per l'esecuzione di CPU e GPU senza opzioni:

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

Crea il CompiledModel

Dopo aver ottenuto un modello LiteRT o convertito un modello nel formato .tflite, inizializza il runtime con il file del modello utilizzando l'API CompiledModel. A questo punto puoi specificare l'accelerazione hardware (kLiteRtHwAcceleratorCpu o kLiteRtHwAcceleratorGpu):

LITERT_ASSIGN_OR_RETURN(auto compiled_model,
  CompiledModel::Create(env, model, kLiteRtHwAcceleratorCpu));

Creare buffer di input e output

Crea le strutture di dati (buffer) necessarie per contenere i dati di input che inserirai nel modello per l'inferenza e i dati di output che il modello produce dopo l'esecuzione dell'inferenza.

LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());

Se utilizzi la memoria della CPU, compila gli input scrivendo i dati direttamente nel primo buffer di input.

input_buffers[0].Write<float>(absl::MakeConstSpan(input_data, input_size));

Richiamare il modello

Fornisci i buffer di input e output, esegui il modello compilato con il modello e l'accelerazione hardware specificati nei passaggi precedenti.

compiled_model.Run(input_buffers, output_buffers);

Recuperare gli output

Recupera gli output leggendo direttamente l'output del modello dalla memoria.

std::vector<float> data(output_data_size);
output_buffers[0].Read<float>(absl::MakeSpan(data));
// ... process output data

Concetti e componenti chiave

Consulta le sezioni seguenti per informazioni sui concetti e sui componenti chiave dell'API LiteRT CompiledModel.

Gestione degli errori

LiteRT utilizza litert::Expected per restituire valori o propagare errori in modo simile a absl::StatusOr o std::expected. Puoi controllare manualmente l'errore.

Per comodità, LiteRT fornisce le seguenti macro:

  • LITERT_ASSIGN_OR_RETURN(lhs, expr) assegna il risultato di expr a lhs se non produce un errore, altrimenti restituisce l'errore.

    Si espanderà in modo da mostrare uno snippet simile al seguente.

    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) esegue la stessa operazione di LITERT_ASSIGN_OR_RETURN ma interrompe il programma in caso di errore.

  • LITERT_RETURN_IF_ERROR(expr) restituisce expr se la valutazione produce un errore.

  • LITERT_ABORT_IF_ERROR(expr) fa la stessa cosa di LITERT_RETURN_IF_ERROR, ma interrompe il programma in caso di errore.

Per ulteriori informazioni sulle macro LiteRT, consulta litert_macros.h.

Buffer tensore (TensorBuffer)

LiteRT fornisce il supporto integrato per l'interoperabilità dei buffer I/O, utilizzando l'API Tensor Buffer (TensorBuffer) per gestire il flusso di dati in entrata e in uscita dal modello compilato. L'API Tensor Buffer consente di scrivere (Write<T>()) e leggere (Read<T>()) e bloccare la memoria della CPU.

Per una visione più completa di come viene implementata l'API TensorBuffer, consulta il codice sorgente di litert_tensor_buffer.h.

Requisiti di input/output del modello di query

I requisiti per l'allocazione di un buffer tensore (TensorBuffer) sono in genere specificati dall'acceleratore hardware. I buffer per input e output possono avere requisiti relativi ad allineamento, passi del buffer e tipo di memoria. Puoi utilizzare funzioni di assistenza come CreateInputBuffers per gestire automaticamente questi requisiti.

Il seguente snippet di codice semplificato mostra come recuperare i requisiti del buffer per i dati di input:

LITERT_ASSIGN_OR_RETURN(auto reqs, compiled_model.GetInputBufferRequirements(signature_index, input_index));

Per una visione più completa di come viene implementata l'API TensorBufferRequirements, consulta il codice sorgente di litert_tensor_buffer_requirements.h.

Crea buffer di tensori gestiti (TensorBuffers)

Il seguente snippet di codice semplificato mostra come creare buffer Tensor gestiti, in cui l'API TensorBuffer alloca i buffer rispettivi:

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

Creare Tensor Buffer con zero-copy

Per eseguire il wrapping di un buffer esistente come buffer tensore (copia zero), utilizza il seguente snippet di codice:

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

Lettura e scrittura dal buffer tensore

Il seguente snippet mostra come leggere da un buffer di input e scrivere in un buffer di output:

// 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 */
}

Avanzato: interoperabilità del buffer zero-copy per tipi di buffer hardware specializzati

Alcuni tipi di buffer, come AHardwareBuffer, consentono l'interoperabilità con altri tipi di buffer. Ad esempio, è possibile creare un buffer OpenGL da un AHardwareBuffer con copia zero. Il seguente snippet di codice mostra un esempio:

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());

I buffer OpenCL possono essere creati anche da AHardwareBuffer:

LITERT_ASSIGN_OR_RETURN(auto cl_buffer, tensor_buffer_ahwb.GetOpenClMemory());

Sui dispositivi mobili che supportano l'interoperabilità tra OpenCL e OpenGL, è possibile creare buffer CL dai buffer 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());

Esempi di implementazioni

Fai riferimento alle seguenti implementazioni di LiteRT in C++.

Inferenza di base (CPU)

Di seguito è riportata una versione condensata degli snippet di codice della sezione Guida introduttiva. È l'implementazione più semplice dell'inferenza 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 zero con la memoria host

L'API LiteRT CompiledModel riduce l'attrito delle pipeline di inferenza, soprattutto quando si ha a che fare con più backend hardware e flussi di copia zero. Il seguente snippet di codice utilizza il metodo CreateFromHostMemory durante la creazione del buffer di input, che utilizza la copia zero con la memoria 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);