Le API LiteRT Next sono disponibili in C++ e possono offrire agli sviluppatori Android un maggiore controllo sull'allocazione della memoria e sullo sviluppo a basso livello rispetto alle API Kotlin.
Per un esempio di applicazione LiteRT Next in C++, consulta la demo di segmentazione asincrona con C++.
Inizia
Per aggiungere LiteRT Next alla tua applicazione Android, segui questa procedura.
Aggiorna la configurazione di compilazione
La creazione di un'applicazione C++ con LiteRT per l'accelerazione GPU, NPU e CPU utilizzando
Bazel prevede la definizione di una regola cc_binary
per garantire che tutti
gli elementi necessari vengano compilati, collegati e pacchettizzati. La configurazione di seguito consente all'applicazione di scegliere o utilizzare dinamicamente gli acceleratori GPU, NPU e CPU.
Ecco i componenti chiave della configurazione di compilazione di Bazel:
cc_binary
Regola:questa è la regola di Bazel di base utilizzata per definire il target eseguibile C++ (ad es.name = "your_application_name"
).- Attributo
srcs
: elenca i file di origine C++ dell'applicazione (ad es.main.cc
e altri file.cc
o.h
). - Attributo
data
(dipendenze di runtime): è fondamentale per il packaging delle librerie e degli asset condivisi che l'applicazione carica in fase di runtime.- LiteRT Core Runtime:la libreria condivisa dell'API C LiteRT principale (ad es.
//litert/c:litert_runtime_c_api_shared_lib
). - Librerie di inoltro: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 HTP QNN 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 qualsiasi altro dato necessario in fase di esecuzione (ad es.
:model_files
,:shader_files
).
- LiteRT Core Runtime:la libreria condivisa dell'API C LiteRT principale (ad es.
- Attributo
deps
(dipendenze di compilazione): vengono elencate le librerie con cui il codice deve essere compilato.- API e utilità LiteRT: intestazioni e librerie statiche per i componenti LiteRT come i buffer di tensori (ad es.
//litert/cc:litert_tensor_buffer
). - Librerie grafiche (per GPU): dipendenze relative alle API di grafica 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 di tensori (ad es.
- Attributo
linkopts
: specifica le opzioni passate al linker, che possono includere il collegamento alle librerie di sistema (ad es.-landroid
per le compilazioni Android o 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 aver convertito un modello nel formato .tflite
, caricalo 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. Environment
è obbligatorio quando crei CompiledModel
e TensorBuffer
. Il seguente codice
crea un Environment
per l'esecuzione su CPU e GPU senza opzioni:
LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
Crea il modello compilato
Utilizza l'API CompiledModel
per inizializzare 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 alimenterai nel modello per l'inferenza e i dati di output prodotti dal modello 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));
Richiama il modello
Fornendo 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);
Recupera 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
Fai riferimento alle sezioni seguenti per informazioni sui concetti e sui componenti chiave delle API LiteRT Next.
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 la presenza dell'errore.
Per comodità, LiteRT fornisce le seguenti macro:
LITERT_ASSIGN_OR_RETURN(lhs, expr)
assegna il risultato diexpr
alhs
se non genera un errore e altrimenti restituisce l'errore.Verrà visualizzato 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)
ha lo stesso effetto diLITERT_ASSIGN_OR_RETURN
, ma interrompe il programma in caso di errore.LITERT_RETURN_IF_ERROR(expr)
restituisceexpr
se la relativa valutazione produce un errore.LITERT_ABORT_IF_ERROR(expr)
fa lo stesso 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 Modello compilato (CompiledModel
) è responsabile del caricamento di un modello, dell'applicazione dell'accelerazione hardware, dell'inizializzazione del runtime, della creazione di buffer di input e di output e dell'esecuzione dell'inferenza.
Il seguente snippet di codice semplificato mostra come l'API Modello compilato prenda un modello LiteRT (.tflite
) e l'acceleratore hardware di destinazione (GPU) e crei 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 Modello compilato prenda un buffer di input e di output ed esegua deduzioni 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 dell'implementazione dell'API CompiledModel
, consulta il codice sorgente di litert_compiled_model.h.
Buffer di tensori (TensorBuffer)
LiteRT Next fornisce il supporto integrato per l'interoperabilità dei buffer I/O, utilizzando l'API Tensor Buffer (TensorBuffer
) per gestire il flusso di dati all'interno e all'esterno del modello compilato. L'API Tensor Buffer offre la possibilità di scrivere
(Write<T>()
) e leggere (Read<T>()
) e di bloccare la memoria della CPU.
Per una visione più completa dell'implementazione dell'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 tensore buffer (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 supporto 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 dell'implementazione dell'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 Managed Tensor
Buffers, in cui l'API TensorBuffer
alloca i rispettivi buffer:
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 buffer di tensori con zero-copy
Per avvolgere un buffer esistente come tensore (senza copia), utilizza lo snippet di codice seguente:
// 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 da Tensor Buffer
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à dei buffer a copia zero 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 zero-copy. 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());
È anche possibile creare buffer OpenCL 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 Next in C++.
Interruzione di base (CPU)
Di seguito è riportata una versione condensata degli snippet di codice della sezione Inizia. È l'implementazione più semplice dell'inferenza con LiteRT Next.
// 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 la memoria dell'host
L'API LiteRT Next Compiled Model riduce le difficoltà delle pipeline di inferenza, soprattutto quando si ha a che fare con più backend hardware e flussi di zero-copy. Il
seguente snippet di codice utilizza il metodo CreateFromHostMemory
per creare il
buffer di input, che utilizza la copia zero con la memoria dell'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);