Accelerazione GPU con LiteRT

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 data deve 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 deps in genere include le dipendenze GLES gles_deps() e linkopts in genere include gles_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