Las unidades de procesamiento de gráficos (GPU) se usan comúnmente para la aceleración del aprendizaje profundo debido a su capacidad de procesamiento paralelo masiva en comparación con las CPU. LiteRT simplifica el proceso de uso de la aceleración de GPU, ya que permite a los usuarios especificar la aceleración de hardware como un parámetro cuando crean un modelo compilado (CompiledModel).
Con la aceleración por GPU de LiteRT, puedes crear búferes de entrada y salida compatibles con la GPU, lograr una copia cero con tus datos en la memoria de la GPU y ejecutar tareas de forma asíncrona para maximizar el paralelismo.
Para ver ejemplos de implementación de la GPU de LiteRT, consulta las siguientes aplicaciones de demostración:
Agrega la dependencia de GPU
Sigue estos pasos para agregar la dependencia de GPU a tu aplicación en Kotlin o C++.
Kotlin
Para los usuarios de Kotlin, el acelerador de GPU está integrado y no requiere pasos adicionales más allá de la guía de Primeros pasos.
C++
En el caso de los usuarios de C++, deben compilar las dependencias de la aplicación con la aceleración de GPU de LiteRT. La regla cc_binary que empaqueta la lógica principal de la aplicación (p.ej., main.cc) requiere los siguientes componentes de tiempo de ejecución:
- Biblioteca compartida de la API de LiteRT en C: El atributo
datadebe incluir la biblioteca compartida de la API de LiteRT en C (//litert/c:litert_runtime_c_api_shared_lib) y los componentes específicos de la GPU (@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so). - Dependencias de atributos: El atributo
depssuele incluir dependencias de GLESgles_deps(), ylinkoptssuele incluirgles_linkopts(). Ambos son muy relevantes para la aceleración de GPU, ya que LiteRT suele usar OpenGLES en Android. - Archivos de modelos y otros recursos: Se incluyen a través del atributo
data.
A continuación, se muestra un ejemplo de una regla de 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
)
Esta configuración permite que tu archivo binario compilado cargue y use la GPU de forma dinámica para la inferencia acelerada del aprendizaje automático.
Usa la GPU con la API de CompiledModel
Para comenzar a usar el acelerador de GPU, pasa el parámetro de GPU cuando crees el modelo compilado (CompiledModel). En el siguiente fragmento de código, se muestra una implementación básica de todo el proceso:
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()
Para obtener más información, consulta las guías Cómo comenzar a usar C++ o Cómo comenzar a usar Kotlin.
Copia cero con aceleración de GPU
El uso de la copia cero permite que una GPU acceda a los datos directamente en su propia memoria sin necesidad de que la CPU copie esos datos de forma explícita. Al no copiar datos hacia y desde la memoria de la CPU, la copia cero puede reducir significativamente la latencia de extremo a extremo.
El siguiente código es un ejemplo de implementación de GPU de copia cero con OpenGL, una API para renderizar gráficos vectoriales. El código pasa imágenes en el formato de búfer de OpenGL directamente 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());
Ejecución asíncrona
Los métodos asíncronos de LiteRT, como RunAsync(), te permiten programar la inferencia de la GPU mientras continúas con otras tareas usando la CPU o la NPU. En las canalizaciones complejas, la GPU se suele usar de forma asíncrona junto con la CPU o las NPU.
El siguiente fragmento de código se basa en el código proporcionado en el ejemplo de aceleración de GPU sin copia. El código usa la CPU y la GPU de forma asíncrona y adjunta un LiteRT Event al búfer de entrada. LiteRT Event se encarga de administrar diferentes tipos de primitivas de sincronización, y el siguiente código crea un objeto Event de LiteRT administrado de tipo LiteRtEventTypeEglSyncFence. Este objeto Event garantiza que no leamos del búfer de entrada hasta que la GPU termine. Todo esto se realiza sin involucrar a 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));
Modelos compatibles
LiteRT admite la aceleración por GPU con los siguientes modelos. Los resultados de las comparativas se basan en pruebas ejecutadas en un dispositivo Samsung Galaxy S24.
| Modelo | Aceleración de GPU de LiteRT | GPU de LiteRT (ms) |
|---|---|---|
| hf_mms_300m | Completamente delegada | 19.6 |
| hf_mobilevit_small | Completamente delegada | 8.7 |
| hf_mobilevit_small_e2e | Completamente delegada | 8.0 |
| hf_wav2vec2_base_960h | Completamente delegada | 9.1 |
| hf_wav2vec2_base_960h_dynamic | Completamente delegada | 9.8 |
| isnet | Completamente delegada | 43.1 |
| timm_efficientnet | Completamente delegada | 3.7 |
| timm_nfnet | Completamente delegada | 9.7 |
| timm_regnety_120 | Completamente delegada | 12.1 |
| torchaudio_deepspeech | Completamente delegada | 4.6 |
| torchaudio_wav2letter | Completamente delegada | 4.8 |
| torchvision_alexnet | Completamente delegada | 3.3 |
| torchvision_deeplabv3_mobilenet_v3_large | Completamente delegada | 5.7 |
| torchvision_deeplabv3_resnet101 | Completamente delegada | 35.1 |
| torchvision_deeplabv3_resnet50 | Completamente delegada | 24.5 |
| torchvision_densenet121 | Completamente delegada | 13.9 |
| torchvision_efficientnet_b0 | Completamente delegada | 3.6 |
| torchvision_efficientnet_b1 | Completamente delegada | 4.7 |
| torchvision_efficientnet_b2 | Completamente delegada | 5.0 |
| torchvision_efficientnet_b3 | Completamente delegada | 6.1 |
| torchvision_efficientnet_b4 | Completamente delegada | 7.6 |
| torchvision_efficientnet_b5 | Completamente delegada | 8.6 |
| torchvision_efficientnet_b6 | Completamente delegada | 11.2 |
| torchvision_efficientnet_b7 | Completamente delegada | 14.7 |
| torchvision_fcn_resnet50 | Completamente delegada | 19.9 |
| torchvision_googlenet | Completamente delegada | 3.9 |
| torchvision_inception_v3 | Completamente delegada | 8.6 |
| torchvision_lraspp_mobilenet_v3_large | Completamente delegada | 3.3 |
| torchvision_mnasnet0_5 | Completamente delegada | 2.4 |
| torchvision_mobilenet_v2 | Completamente delegada | 2.8 |
| torchvision_mobilenet_v3_large | Completamente delegada | 2.8 |
| torchvision_mobilenet_v3_small | Completamente delegada | 2.3 |
| torchvision_resnet152 | Completamente delegada | 15.0 |
| torchvision_resnet18 | Completamente delegada | 4.3 |
| torchvision_resnet50 | Completamente delegada | 6.9 |
| torchvision_squeezenet1_0 | Completamente delegada | 2.9 |
| torchvision_squeezenet1_1 | Completamente delegada | 2.5 |
| torchvision_vgg16 | Completamente delegada | 13.4 |
| torchvision_wide_resnet101_2 | Completamente delegada | 25.0 |
| torchvision_wide_resnet50_2 | Completamente delegada | 13.4 |
| u2net_full | Completamente delegada | 98.3 |
| u2net_lite | Completamente delegada | 51.4 |
| hf_distil_whisper_small_no_cache | Delegación parcial | 251.9 |
| hf_distilbert | Delegación parcial | 13.7 |
| hf_tinyroberta_squad2 | Delegación parcial | 17.1 |
| hf_tinyroberta_squad2_dynamic_batch | Delegación parcial | 52.1 |
| snapml_StyleTransferNet | Delegación parcial | 40.9 |
| timm_efficientformer_l1 | Delegación parcial | 17.6 |
| timm_efficientformerv2_s0 | Delegación parcial | 16.1 |
| timm_pvt_v2_b1 | Delegación parcial | 73.5 |
| timm_pvt_v2_b3 | Delegación parcial | 246.7 |
| timm_resnest14d | Delegación parcial | 88.9 |
| torchaudio_conformer | Delegación parcial | 21.5 |
| torchvision_convnext_tiny | Delegación parcial | 8.2 |
| torchvision_maxvit_t | Delegación parcial | 194.0 |
| torchvision_shufflenet_v2 | Delegación parcial | 9.5 |
| torchvision_swin_tiny | Delegación parcial | 164.4 |
| torchvision_video_resnet2plus1d_18 | Delegación parcial | 6832.0 |
| torchvision_video_swin3d_tiny | Delegación parcial | 2,617.8 |
| yolox_tiny | Delegación parcial | 11.2 |