Les processeurs graphiques (GPU) sont couramment utilisés pour accélérer le deep learning en raison de leur débit parallèle massif par rapport aux processeurs. LiteRT simplifie l'utilisation de l'accélération GPU en permettant aux utilisateurs de spécifier l'accélération matérielle en tant que paramètre lors de la création d'un modèle compilé (CompiledModel).
Grâce à l'accélération GPU de LiteRT, vous pouvez créer des tampons d'entrée et de sortie compatibles avec les GPU, obtenir une copie zéro avec vos données dans la mémoire GPU et exécuter des tâches de manière asynchrone pour maximiser le parallélisme.
Pour obtenir des exemples d'implémentations de LiteRT GPU, consultez les applications de démonstration suivantes :
Ajouter une dépendance GPU
Suivez les étapes ci-dessous pour ajouter une dépendance GPU à votre application Kotlin ou C++.
Kotlin
Pour les utilisateurs de Kotlin, l'accélérateur GPU est intégré et ne nécessite aucune étape supplémentaire au-delà du guide Premiers pas.
C++
Pour les utilisateurs de C++, vous devez créer les dépendances de l'application avec l'accélération GPU LiteRT. La règle cc_binary qui regroupe la logique d'application principale (par exemple, main.cc) nécessite les composants d'exécution suivants :
- Bibliothèque partagée de l'API LiteRT C : l'attribut
datadoit inclure la bibliothèque partagée de l'API LiteRT C (//litert/c:litert_runtime_c_api_shared_lib) et les composants spécifiques au GPU (@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so). - Dépendances des attributs : l'attribut
depsinclut généralement les dépendances GLESgles_deps(), etlinkoptsinclut généralementgles_linkopts(). Les deux sont très pertinents pour l'accélération GPU, car LiteRT utilise souvent OpenGLES sur Android. - Fichiers de modèle et autres composants : inclus via l'attribut
data.
Voici un exemple de règle 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
)
Cette configuration permet à votre binaire compilé de charger et d'utiliser dynamiquement le GPU pour l'inférence de machine learning accélérée.
Utiliser un GPU avec l'API CompiledModel
Pour commencer à utiliser l'accélérateur GPU, transmettez le paramètre GPU lors de la création du modèle compilé (CompiledModel). L'extrait de code suivant montre une implémentation de base de l'ensemble du processus :
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()
Pour en savoir plus, consultez les guides Premiers pas avec C++ ou Premiers pas avec Kotlin.
Sans copie avec accélération GPU
L'utilisation de la copie zéro permet à un GPU d'accéder directement aux données dans sa propre mémoire sans que le CPU ait besoin de copier explicitement ces données. En évitant de copier les données vers et depuis la mémoire du processeur, la copie zéro peut réduire considérablement la latence de bout en bout.
Le code suivant est un exemple d'implémentation de GPU sans copie avec OpenGL, une API pour le rendu de graphiques vectoriels. Le code transmet les images au format de tampon OpenGL directement à 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());
Exécution asynchrone
Les méthodes asynchrones de LiteRT, comme RunAsync(), vous permettent de planifier l'inférence GPU tout en poursuivant d'autres tâches à l'aide du CPU ou de la NPU. Dans les pipelines complexes, le GPU est souvent utilisé de manière asynchrone aux côtés du CPU ou des NPU.
L'extrait de code suivant s'appuie sur le code fourni dans l'exemple Accélération GPU sans copie. Le code utilise le CPU et le GPU de manière asynchrone, et associe un Event LiteRT au tampon d'entrée. LiteRT Event est responsable de la gestion de différents types de primitives de synchronisation. Le code suivant crée un objet LiteRT Event géré de type LiteRtEventTypeEglSyncFence. Cet objet Event garantit que nous ne lisons pas à partir du tampon d'entrée tant que le GPU n'a pas terminé. Tout cela se fait sans impliquer le processeur.
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));
Modèles compatibles
LiteRT est compatible avec l'accélération GPU avec les modèles suivants. Les résultats des tests comparatifs sont basés sur des tests exécutés sur un appareil Samsung Galaxy S24.
| Modèle | Accélération GPU LiteRT | GPU LiteRT (ms) |
|---|---|---|
| hf_mms_300m | Délégation complète | 19.6 |
| hf_mobilevit_small | Délégation complète | 8.7 |
| hf_mobilevit_small_e2e | Délégation complète | 8.0 |
| hf_wav2vec2_base_960h | Délégation complète | 9.1 |
| hf_wav2vec2_base_960h_dynamic | Délégation complète | 9,8 |
| isnet | Délégation complète | 43.1 |
| timm_efficientnet | Délégation complète | 3.7 |
| timm_nfnet | Délégation complète | 9,7 |
| timm_regnety_120 | Délégation complète | 12.1 |
| torchaudio_deepspeech | Délégation complète | 4.6 |
| torchaudio_wav2letter | Délégation complète | 4,8 |
| torchvision_alexnet | Délégation complète | 3.3 |
| torchvision_deeplabv3_mobilenet_v3_large | Délégation complète | 5.7 |
| torchvision_deeplabv3_resnet101 | Délégation complète | 35.1 |
| torchvision_deeplabv3_resnet50 | Délégation complète | 24,5 |
| torchvision_densenet121 | Délégation complète | 13.9 |
| torchvision_efficientnet_b0 | Délégation complète | 3.6 |
| torchvision_efficientnet_b1 | Délégation complète | 4.7 |
| torchvision_efficientnet_b2 | Délégation complète | 5.0 |
| torchvision_efficientnet_b3 | Délégation complète | 6,1 |
| torchvision_efficientnet_b4 | Délégation complète | 7,6 |
| torchvision_efficientnet_b5 | Délégation complète | 8.6 |
| torchvision_efficientnet_b6 | Délégation complète | 11.2 |
| torchvision_efficientnet_b7 | Délégation complète | 14.7 |
| torchvision_fcn_resnet50 | Délégation complète | 19.9 |
| torchvision_googlenet | Délégation complète | 3.9 |
| torchvision_inception_v3 | Délégation complète | 8.6 |
| torchvision_lraspp_mobilenet_v3_large | Délégation complète | 3.3 |
| torchvision_mnasnet0_5 | Délégation complète | 2.4 |
| torchvision_mobilenet_v2 | Délégation complète | 2,8 |
| torchvision_mobilenet_v3_large | Délégation complète | 2,8 |
| torchvision_mobilenet_v3_small | Délégation complète | 2.3 |
| torchvision_resnet152 | Délégation complète | 15,0 |
| torchvision_resnet18 | Délégation complète | 4.3 |
| torchvision_resnet50 | Délégation complète | 6,9 |
| torchvision_squeezenet1_0 | Délégation complète | 2.9 |
| torchvision_squeezenet1_1 | Délégation complète | 2.5 |
| torchvision_vgg16 | Délégation complète | 13,4 |
| torchvision_wide_resnet101_2 | Délégation complète | 25,0 |
| torchvision_wide_resnet50_2 | Délégation complète | 13,4 |
| u2net_full | Délégation complète | 98,3 |
| u2net_lite | Délégation complète | 51.4 |
| hf_distil_whisper_small_no_cache | Délégation partielle | 251,9 |
| hf_distilbert | Délégation partielle | 13.7 |
| hf_tinyroberta_squad2 | Délégation partielle | 17,1 |
| hf_tinyroberta_squad2_dynamic_batch | Délégation partielle | 52.1 |
| snapml_StyleTransferNet | Délégation partielle | 40.9 |
| timm_efficientformer_l1 | Délégation partielle | 17,6 |
| timm_efficientformerv2_s0 | Délégation partielle | 16.1 |
| timm_pvt_v2_b1 | Délégation partielle | 73,5 |
| timm_pvt_v2_b3 | Délégation partielle | 246,7 |
| timm_resnest14d | Délégation partielle | 88,9 |
| torchaudio_conformer | Délégation partielle | 21.5 |
| torchvision_convnext_tiny | Délégation partielle | 8.2 |
| torchvision_maxvit_t | Délégation partielle | 194.0 |
| torchvision_shufflenet_v2 | Délégation partielle | 9,5 |
| torchvision_swin_tiny | Délégation partielle | 164,4 |
| torchvision_video_resnet2plus1d_18 | Délégation partielle | 6832,0 |
| torchvision_video_swin3d_tiny | Délégation partielle | 2617.8 |
| yolox_tiny | Délégation partielle | 11.2 |