Le unità di elaborazione grafica (GPU) vengono comunemente utilizzate per l'accelerazione del deep learning
grazie alla loro enorme velocità effettiva parallela rispetto alle CPU. LiteRT
semplifica il processo di utilizzo dell'accelerazione GPU consentendo agli utenti di
specificare l'accelerazione hardware come parametro durante la creazione di un modello compilato
(CompiledModel).
Con l'accelerazione GPU di LiteRT, puoi creare buffer di input e output compatibili con la GPU, ottenere la copia zero con i tuoi dati nella memoria GPU ed eseguire attività in modo asincrono per massimizzare il parallelismo.
Per esempi di implementazioni della GPU LiteRT, consulta le seguenti applicazioni demo:
Aggiungere la dipendenza GPU
Segui questi passaggi per aggiungere la dipendenza GPU alla tua applicazione Kotlin o C++.
Kotlin
Per gli utenti Kotlin, l'acceleratore GPU è integrato e non richiede passaggi aggiuntivi oltre alla guida Guida introduttiva.
C++
Per gli utenti C++, devi creare le dipendenze dell'applicazione con l'accelerazione GPU LiteRT. La regola cc_binary che raggruppa la logica principale dell'applicazione
(ad es. main.cc) richiede i seguenti componenti di runtime:
- Libreria condivisa dell'API LiteRT C: l'attributo
datadeve includere la libreria condivisa dell'API LiteRT C (//litert/c:litert_runtime_c_api_shared_lib) e i componenti specifici della GPU (@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so). - Dipendenze degli attributi: l'attributo
depsin genere include le dipendenze GLESgles_deps()elinkoptsin genere includegles_linkopts(). Entrambi sono molto pertinenti per l'accelerazione GPU, poiché LiteRT spesso utilizza OpenGLES su Android. - File del modello e altri asset: inclusi tramite l'attributo
data.
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",
],
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
)
Questa configurazione consente al binario compilato di caricare e utilizzare dinamicamente la GPU per l'inferenza di machine learning accelerata.
Utilizzare la GPU con l'API CompiledModel
Per iniziare a utilizzare l'acceleratore GPU, trasmetti il parametro GPU durante la creazione
del modello compilato (CompiledModel). Il seguente snippet di codice mostra un'implementazione
di base dell'intero processo:
C++
// 1. Load model
LITERT_ASSIGN_OR_RETURN(auto model, Model::CreateFromFile("mymodel.tflite"));
// 2. Create a compiled model targeting GPU
LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
LITERT_ASSIGN_OR_RETURN(auto compiled_model, CompiledModel::Create(env, model, kLiteRtHwAcceleratorGpu));
// 3. Prepare input/output buffers
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());
// 4. Fill input data (if you have CPU-based data)
input_buffers[0].Write<float>(absl::MakeConstSpan(cpu_data, data_size));
// 5. Execute
compiled_model.Run(input_buffers, output_buffers);
// 6. Access model output
std::vector<float> data(output_data_size);
output_buffers.Read<float>(absl::MakeSpan(data));
Kotlin
// Load model and initialize runtime
val model =
CompiledModel.create(
context.assets,
"mymodel.tflite",
CompiledModel.Options(Accelerator.GPU),
env,
)
// Preallocate input/output buffers
val inputBuffers = model.createInputBuffers()
val outputBuffers = model.createOutputBuffers()
// Fill the first input
inputBuffers[0].writeFloat(FloatArray(data_size) { data_value /* your data */ })
// Invoke
model.run(inputBuffers, outputBuffers)
// Read the output
val outputFloatArray = outputBuffers[0].readFloat()
Per saperne di più, consulta le guide Guida introduttiva a C++ o Guida introduttiva a Kotlin.
Zero-copy con accelerazione GPU
L'utilizzo di zero-copy consente a una GPU di accedere direttamente ai dati nella propria memoria senza che la CPU debba copiarli esplicitamente. Non copiando i dati nella memoria della CPU e dalla memoria della CPU, la copia zero può ridurre significativamente la latenza end-to-end.
Il seguente codice è un'implementazione di esempio di Zero-Copy GPU con OpenGL, un'API per il rendering di grafica vettoriale. Il codice passa le immagini nel formato buffer OpenGL direttamente a LiteRT:
// Suppose you have an OpenGL buffer consisting of:
// target (GLenum), id (GLuint), size_bytes (size_t), and offset (size_t)
// Load model and compile for GPU
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, kLiteRtHwAcceleratorGpu));
// Create a TensorBuffer that wraps the OpenGL buffer.
LITERT_ASSIGN_OR_RETURN(auto tensor_type, model.GetInputTensorType("input_tensor_name"));
LITERT_ASSIGN_OR_RETURN(auto gl_input_buffer, TensorBuffer::CreateFromGlBuffer(env,
tensor_type, opengl_buffer.target, opengl_buffer.id, opengl_buffer.size_bytes, opengl_buffer.offset));
std::vector<TensorBuffer> input_buffers{gl_input_buffer};
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());
// Execute
compiled_model.Run(input_buffers, output_buffers);
// If your output is also GPU-backed, you can fetch an OpenCL buffer or re-wrap it as an OpenGL buffer:
LITERT_ASSIGN_OR_RETURN(auto out_cl_buffer, output_buffers[0].GetOpenClBuffer());
Esecuzione asincrona
I metodi asincroni di LiteRT, come RunAsync(), ti consentono di pianificare l'inferenza della GPU
mentre continui altre attività utilizzando la CPU o la NPU. Nelle pipeline complesse, la GPU
viene spesso utilizzata in modo asincrono insieme alla CPU o alle NPU.
Il seguente snippet di codice si basa sul codice fornito nell'esempio di accelerazione GPU
zero-copy. Il codice utilizza CPU e GPU in modo asincrono e collega un LiteRT Event al buffer di input. LiteRT Event
è responsabile della gestione di diversi tipi di primitive di sincronizzazione e
il seguente codice crea un oggetto evento LiteRT gestito di tipo
LiteRtEventTypeEglSyncFence. Questo oggetto Event garantisce che non vengano letti dati dal buffer di input finché la GPU non ha terminato. Tutto questo viene fatto senza coinvolgere la CPU.
LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
LITERT_ASSIGN_OR_RETURN(auto compiled_model,
CompiledModel::Create(env, model, kLiteRtHwAcceleratorGpu));
// 1. Prepare input buffer (OpenGL buffer)
LITERT_ASSIGN_OR_RETURN(auto gl_input,
TensorBuffer::CreateFromGlBuffer(env, tensor_type, opengl_tex));
std::vector<TensorBuffer> inputs{gl_input};
LITERT_ASSIGN_OR_RETURN(auto outputs, compiled_model.CreateOutputBuffers());
// 2. If the GL buffer is in use, create and set an event object to synchronize with the GPU.
LITERT_ASSIGN_OR_RETURN(auto input_event,
Event::CreateManagedEvent(env, LiteRtEventTypeEglSyncFence));
inputs[0].SetEvent(std::move(input_event));
// 3. Kick off the GPU inference
compiled_model.RunAsync(inputs, outputs);
// 4. Meanwhile, do other CPU work...
// CPU Stays busy ..
// 5. Access model output
std::vector<float> data(output_data_size);
outputs[0].Read<float>(absl::MakeSpan(data));
Modelli supportati
LiteRT supporta l'accelerazione GPU con i seguenti modelli. I risultati del benchmark si basano su test eseguiti su un dispositivo Samsung Galaxy S24.
| Modello | Accelerazione GPU LiteRT | GPU LiteRT (ms) |
|---|---|---|
| hf_mms_300m | Completamente delegato | 19,6 |
| hf_mobilevit_small | Completamente delegato | 8.7 |
| hf_mobilevit_small_e2e | Completamente delegato | 8.0 |
| hf_wav2vec2_base_960h | Completamente delegato | 9.1 |
| hf_wav2vec2_base_960h_dynamic | Completamente delegato | 9.8 |
| isnet | Completamente delegato | 43.1 |
| timm_efficientnet | Completamente delegato | 3.7 |
| timm_nfnet | Completamente delegato | 9.7 |
| timm_regnety_120 | Completamente delegato | 12.1 |
| torchaudio_deepspeech | Completamente delegato | 4,6 |
| torchaudio_wav2letter | Completamente delegato | 4,8 |
| torchvision_alexnet | Completamente delegato | 3.3 |
| torchvision_deeplabv3_mobilenet_v3_large | Completamente delegato | 5.7 |
| torchvision_deeplabv3_resnet101 | Completamente delegato | 35.1 |
| torchvision_deeplabv3_resnet50 | Completamente delegato | 24,5 |
| torchvision_densenet121 | Completamente delegato | 13.9 |
| torchvision_efficientnet_b0 | Completamente delegato | 3,6 |
| torchvision_efficientnet_b1 | Completamente delegato | 4,7 |
| torchvision_efficientnet_b2 | Completamente delegato | 5,0 |
| torchvision_efficientnet_b3 | Completamente delegato | 6,1 |
| torchvision_efficientnet_b4 | Completamente delegato | 7,6 |
| torchvision_efficientnet_b5 | Completamente delegato | 8.6 |
| torchvision_efficientnet_b6 | Completamente delegato | 11.2 |
| torchvision_efficientnet_b7 | Completamente delegato | 14.7 |
| torchvision_fcn_resnet50 | Completamente delegato | 19,9 |
| torchvision_googlenet | Completamente delegato | 3,9 |
| torchvision_inception_v3 | Completamente delegato | 8.6 |
| torchvision_lraspp_mobilenet_v3_large | Completamente delegato | 3.3 |
| torchvision_mnasnet0_5 | Completamente delegato | 2,4 |
| torchvision_mobilenet_v2 | Completamente delegato | 2,8 |
| torchvision_mobilenet_v3_large | Completamente delegato | 2,8 |
| torchvision_mobilenet_v3_small | Completamente delegato | 2.3 |
| torchvision_resnet152 | Completamente delegato | 15,0 |
| torchvision_resnet18 | Completamente delegato | 4.3 |
| torchvision_resnet50 | Completamente delegato | 6,9 |
| torchvision_squeezenet1_0 | Completamente delegato | 2,9 |
| torchvision_squeezenet1_1 | Completamente delegato | 2,5 |
| torchvision_vgg16 | Completamente delegato | 13.4 |
| torchvision_wide_resnet101_2 | Completamente delegato | 25,0 |
| torchvision_wide_resnet50_2 | Completamente delegato | 13.4 |
| u2net_full | Completamente delegato | 98,3 |
| u2net_lite | Completamente delegato | 51,4 |
| hf_distil_whisper_small_no_cache | Parzialmente delegato | 251,90 £E |
| hf_distilbert | Parzialmente delegato | 13.7 |
| hf_tinyroberta_squad2 | Parzialmente delegato | 17.1 |
| hf_tinyroberta_squad2_dynamic_batch | Parzialmente delegato | 52.1 |
| snapml_StyleTransferNet | Parzialmente delegato | 40,9 |
| timm_efficientformer_l1 | Parzialmente delegato | 17.6 |
| timm_efficientformerv2_s0 | Parzialmente delegato | 16.1 |
| timm_pvt_v2_b1 | Parzialmente delegato | 73,5 |
| timm_pvt_v2_b3 | Parzialmente delegato | 246,7 |
| timm_resnest14d | Parzialmente delegato | 88,9 |
| torchaudio_conformer | Parzialmente delegato | 21,5 |
| torchvision_convnext_tiny | Parzialmente delegato | 8.2 |
| torchvision_maxvit_t | Parzialmente delegato | 194,0 |
| torchvision_shufflenet_v2 | Parzialmente delegato | 9,5 |
| torchvision_swin_tiny | Parzialmente delegato | 164,4 |
| torchvision_video_resnet2plus1d_18 | Parzialmente delegato | 6832.0 |
| torchvision_video_swin3d_tiny | Parzialmente delegato | 2617,8 |
| yolox_tiny | Parzialmente delegato | 11.2 |