Accelerazione GPU con LiteRT Next

Le unità di elaborazione grafica (GPU) sono comunemente utilizzate per l'accelerazione del deep learning grazie al loro enorme throughput parallelo rispetto alle CPU. LiteRT Next 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). LiteRT Next utilizza inoltre un'implementazione dell'accelerazione GPU nuova e migliorata, non offerta da LiteRT.

Con l'accelerazione GPU di LiteRT Next, puoi creare buffer di input e output adatti alle GPU, eseguire la copia zero con i dati nella memoria GPU ed eseguire le attività in modo asincrono per massimizzare il parallelismo.

Per esempi di implementazioni di LiteRT Next con supporto GPU, consulta le seguenti applicazioni di dimostrazione:

Aggiungere una dipendenza GPU

Segui questa procedura per aggiungere la dipendenza dalla GPU alla tua applicazione Kotlin o C++.

Kotlin

Per gli utenti di Kotlin, l'acceleratore GPU è integrato e non richiede passaggi aggiuntivi oltre a quelli descritti nella guida Inizia.

C++

Gli utenti C++ devono compilare le dipendenze dell'applicazione con l'accelerazione GPU LiteRT. La regola cc_binary che impacchetta la logica di base dell'applicazione (ad es. main.cc) richiede i seguenti componenti di runtime:

  • Libreria condivisa dell'API C LiteRT: l'attributo data deve includere la libreria condivisa dell'API C LiteRT (//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 GLESgles_deps() e linkopts in genere includegles_linkopts(). Entrambi sono molto pertinenti per l'accelerazione GPU, poiché LiteRT utilizza spesso OpenGLES su Android.
  • File 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 codice binario compilato di caricare e utilizzare dinamicamente la GPU per l'inferenza del machine learning accelerata.

Inizia

Per iniziare a utilizzare l'acceleratore GPU, passa il parametro GPU durante la creazione del modello compilato (CompiledModel). Lo snippet di codice seguente mostra un'implementazione di base dell'intera procedura:

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 ulteriori informazioni, consulta le guide Guida introduttiva a C++ o Guida introduttiva a Kotlin.

Acceleratore GPU LiteRT Next

Il nuovo acceleratore GPU, disponibile solo con LiteRT Next, è ottimizzato per gestire i carichi di lavoro di IA, come le moltiplicazioni di matrici di grandi dimensioni e la cache KV per i modelli LLM, in modo più efficiente rispetto alle versioni precedenti. L'acceleratore GPU LiteRT Next presenta i seguenti miglioramenti chiave rispetto alla versione LiteRT:

  • Copertura estesa dell'operatore:gestisci reti neurali più grandi e complesse.
  • Maggiore interoperabilità dei buffer:consente l'utilizzo diretto dei buffer della GPU per frame della fotocamera, texture 2D o stati LLM di grandi dimensioni.
  • Supporto dell'esecuzione asincrona:sovrapponi la pre-elaborazione della CPU all'inferenza della GPU.

Zero-copy con accelerazione GPU

L'utilizzo di zero-copy consente a una GPU di accedere ai dati direttamente nella propria memoria senza che la CPU debba copiarli esplicitamente. Se non copi i dati nella memoria della CPU e da essa, la copia zero può ridurre notevolmente la latenza end-to-end.

Il codice seguente è un esempio di implementazione della GPU zero-copy con OpenGL, un'API per il rendering di grafica vettoriale. Il codice passa le immagini nel formato del buffer OpenGL direttamente a LiteRT Next:

// 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 GPU mantenendo attive altre attività che utilizzano la CPU o la NPU. Nelle pipeline complesse, la GPU viene spesso utilizzata in modo asincrono insieme a CPU o NPUs.

Il seguente snippet di codice si basa sul codice fornito nell'esempio di accelerazione GPU senza copia. Il codice utilizza sia la CPU che la GPU in modo asincrono e collega un LiteRT Event al buffer di input. LiteRT Event è responsabile della gestione dei diversi tipi di primitive di sincronizzazione e il seguente codice crea un oggetto LiteRT Event gestito di tipo LiteRtEventTypeEglSyncFence. Questo oggetto Event garantisce che non venga eseguita alcuna lettura dal buffer di input finché la GPU non ha terminato. Il tutto 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 Next 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,9
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