Aceleración de GPU con LiteRT

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 data debe 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 deps suele incluir dependencias de GLES gles_deps(), y linkopts suele incluir gles_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