Accélération GPU avec LiteRT

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 data doit 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 deps inclut généralement les dépendances GLES gles_deps(), et linkopts inclut généralement gles_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