Le API LiteRT Compiled Model sono disponibili in C++, offrendo agli sviluppatori Android un controllo granulare sull'allocazione della memoria e sullo sviluppo di basso livello.
Per un esempio di applicazione LiteRT in C++, consulta la demo di segmentazione asincrona con C++.
Inizia
Segui questi passaggi per aggiungere l'API LiteRT Compiled Model alla tua applicazione Android.
Aggiorna la configurazione di compilazione
La creazione di un'applicazione C++ con LiteRT per l'accelerazione di GPU, NPU e CPU utilizzando
Bazel comporta la definizione di una regola cc_binary per garantire che tutti i
componenti necessari vengano compilati, collegati e pacchettizzati. La seguente configurazione di esempio
consente alla tua applicazione di scegliere o utilizzare in modo dinamico gli acceleratori GPU, NPU e CPU.
Ecco i componenti chiave nella configurazione di compilazione di Bazel:
cc_binaryRegola: questa è la regola Bazel fondamentale utilizzata per definire il target eseguibile C++ (ad es.name = "your_application_name").- Attributo
srcs: elenca i file sorgente C++ dell'applicazione (ad es.main.cce altri file.cco.h). dataAttributo (dipendenze di runtime): è fondamentale per il packaging di librerie e asset condivisi che l'applicazione carica in fase di runtime.- Runtime principale di LiteRT:la libreria condivisa principale dell'API LiteRT C (ad es.
//litert/c:litert_runtime_c_api_shared_lib). - Librerie di invio:librerie condivise specifiche del fornitore che LiteRT
utilizza per comunicare con i driver hardware (ad es.
//litert/vendors/qualcomm/dispatch:dispatch_api_so). - Librerie di backend GPU: le librerie condivise per l'accelerazione GPU
(ad es.
"@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so). - Librerie di backend NPU: le librerie condivise specifiche per l'accelerazione NPU, ad esempio le librerie QNN HTP di Qualcomm (ad es.
@qairt//:lib/aarch64-android/libQnnHtp.so,@qairt//:lib/hexagon-v79/unsigned/libQnnHtpV79Skel.so). - File e asset del modello:i file del modello addestrato, le immagini di test, gli shader o altri dati necessari in fase di runtime (ad es.
:model_files,:shader_files).
- Runtime principale di LiteRT:la libreria condivisa principale dell'API LiteRT C (ad es.
- Attributo
deps(dipendenze in fase di compilazione): elenca le librerie con cui deve essere compilato il codice.- API e utilità LiteRT: intestazioni e librerie statiche per i componenti LiteRT come i buffer tensore (ad es.
//litert/cc:litert_tensor_buffer). - Librerie grafiche (per GPU): dipendenze correlate alle API grafiche
se l'acceleratore GPU le utilizza (ad es.
gles_deps()).
- API e utilità LiteRT: intestazioni e librerie statiche per i componenti LiteRT come i buffer tensore (ad es.
- Attributo
linkopts: specifica le opzioni passate al linker, che possono includere il collegamento alle librerie di sistema (ad es.-landroidper le build Android o le librerie GLES congles_linkopts()).
Di seguito è riportato un esempio di regola 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
)
Carica il modello
Dopo aver ottenuto un modello LiteRT o convertito un modello nel formato .tflite, carica il modello creando un oggetto Model.
LITERT_ASSIGN_OR_RETURN(auto model, Model::CreateFromFile("mymodel.tflite"));
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 modello compilato
Utilizzando l'API CompiledModel, inizializza il runtime con l'oggetto
Model appena creato. 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 ed 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 delle API LiteRT Compiled Model.
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 diexpralhsse non produce un errore, altrimenti restituisce l'errore.Si espanderà in modo da mostrare uno snippet simile al seguente.
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)svolge la stessa funzione diLITERT_ASSIGN_OR_RETURNma interrompe il programma in caso di errore.LITERT_RETURN_IF_ERROR(expr)restituisceexprse la valutazione produce un errore.LITERT_ABORT_IF_ERROR(expr)fa la stessa cosa diLITERT_RETURN_IF_ERROR, ma interrompe il programma in caso di errore.
Per ulteriori informazioni sulle macro LiteRT, consulta litert_macros.h.
Modello compilato (CompiledModel)
L'API Compiled Model (CompiledModel) è responsabile del caricamento di un modello, dell'applicazione dell'accelerazione hardware, dell'istanza del runtime, della creazione di buffer di input e output e dell'esecuzione dell'inferenza.
Il seguente snippet di codice semplificato mostra come l'API Compiled Model
accetta un modello LiteRT (.tflite) e l'acceleratore hardware di destinazione (GPU) e
crea un modello compilato pronto per l'esecuzione dell'inferenza.
// 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));
Il seguente snippet di codice semplificato mostra come l'API Compiled Model accetta un buffer di input e output ed esegue inferenze con il modello compilato.
// 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)));
Per una visione più completa di come viene implementata l'API CompiledModel, consulta il codice sorgente di
litert_compiled_model.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 tensore 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 Inizia. È l'implementazione più semplice dell'inferenza 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));
Zero-Copy con memoria host
L'API LiteRT Compiled Model 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 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);