Aceleración de GPU con LiteRT Next

Las unidades de procesamiento de gráficos (GPU) se usan comúnmente para la aceleración del aprendizaje profundo debido a su gran capacidad de procesamiento en paralelo en comparación con las CPU. LiteRT Next 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). LiteRT Next también usa una implementación nueva y mejorada de la aceleración de GPU, que LiteRT no ofrece.

Con la aceleración de GPU de LiteRT Next, puedes crear búferes de entrada y salida compatibles con la GPU, lograr cero copias con tus datos en la memoria de la GPU y ejecutar tareas de forma asíncrona para maximizar el paralelismo.

Para ver ejemplos de implementaciones de LiteRT Next con compatibilidad con GPU, consulta las siguientes aplicaciones de demostración:

Cómo agregar una dependencia de GPU

Sigue los pasos que se indican a continuación para agregar una dependencia de GPU a tu aplicación de 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++, debes 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 del tiempo de ejecución:

  • Biblioteca compartida de la API de LiteRT C: El atributo data debe incluir la biblioteca compartida de la API de LiteRT 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: Por lo general, el atributo deps incluye dependencias de GLES gles_deps(), y linkopts suele incluir gles_linkopts(). Ambos son muy relevantes para la aceleración de GPU, ya que LiteRT a menudo usa OpenGLES en Android.
  • Archivos de modelos y otros recursos: Se incluyen a través del atributo data.

El siguiente es un ejemplo de una regla 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 el objeto binario compilado cargue y use de forma dinámica la GPU para la inferencia de aprendizaje automático acelerada.

Comenzar

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.

Acelerador de GPU LiteRT Next

El nuevo acelerador de GPU, disponible solo con LiteRT Next, está optimizado para controlar cargas de trabajo de IA, como multiplicaciones de matrices grandes y caché de KV para LLM, de manera más eficiente que las versiones anteriores. El acelerador de GPU LiteRT Next incluye las siguientes mejoras clave en comparación con la versión LiteRT:

  • Cobertura extendida del operador: Controla redes neuronales más grandes y complejas.
  • Mejor interoperabilidad de búferes: Habilita el uso directo de búferes de GPU para fotogramas de cámara, texturas 2D o estados LLM grandes.
  • Compatibilidad con la ejecución asíncrona: Superposición del procesamiento previo de la CPU con la inferencia de la GPU.

Sin copia con aceleración de GPU

El uso de cero copias permite que una GPU acceda a los datos directamente en su propia memoria sin que la CPU deba copiarlos de forma explícita. Dado que no copia datos desde ni hacia 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 sin copia 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 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());

Ejecución asíncrona

Los métodos asíncronos de LiteRT, como RunAsync(), te permiten programar la inferencia de GPU mientras continúas con otras tareas con la CPU o la NPU. En las canalizaciones complejas, a menudo se usa la GPU de forma asíncrona junto con la CPU o las NPU.

En el siguiente fragmento de código, se usa 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 Event de LiteRT al búfer de entrada. LiteRT Event es responsable de administrar diferentes tipos de primitivas de sincronización, y el siguiente código crea un objeto de evento 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 hace 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 Next admite la aceleración de GPU con los siguientes modelos. Los resultados de las comparativas se basan en pruebas que se ejecutaron en un dispositivo Samsung Galaxy S24.

Modelo Aceleración de GPU de LiteRT GPU de LiteRT (ms)
hf_mms_300m Completamente delegado 19.6
hf_mobilevit_small Completamente delegado 8.7
hf_mobilevit_small_e2e Completamente delegado 8.0
hf_wav2vec2_base_960h Completamente delegado 9.1
hf_wav2vec2_base_960h_dynamic Completamente delegado 9.8
isnet Completamente delegado 43.1
timm_efficientnet Completamente delegado 3.7
timm_nfnet Completamente delegado 9.7
timm_regnety_120 Completamente delegado 12.1
torchaudio_deepspeech Completamente delegado 4.6
torchaudio_wav2letter Completamente delegado 4.8
torchvision_alexnet Completamente delegado 3.3
torchvision_deeplabv3_mobilenet_v3_large Completamente delegado 5.7
torchvision_deeplabv3_resnet101 Completamente delegado 35.1
torchvision_deeplabv3_resnet50 Completamente delegado 24.5
torchvision_densenet121 Completamente delegado 13.9
torchvision_efficientnet_b0 Completamente delegado 3.6
torchvision_efficientnet_b1 Completamente delegado 4.7
torchvision_efficientnet_b2 Completamente delegado 5.0
torchvision_efficientnet_b3 Completamente delegado 6.1
torchvision_efficientnet_b4 Completamente delegado 7.6
torchvision_efficientnet_b5 Completamente delegado 8.6
torchvision_efficientnet_b6 Completamente delegado 11.2
torchvision_efficientnet_b7 Completamente delegado 14.7
torchvision_fcn_resnet50 Completamente delegado 19.9
torchvision_googlenet Completamente delegado 3.9
torchvision_inception_v3 Completamente delegado 8.6
torchvision_lraspp_mobilenet_v3_large Completamente delegado 3.3
torchvision_mnasnet0_5 Completamente delegado 2.4
torchvision_mobilenet_v2 Completamente delegado 2.8
torchvision_mobilenet_v3_large Completamente delegado 2.8
torchvision_mobilenet_v3_small Completamente delegado 2.3
torchvision_resnet152 Completamente delegado 15.0
torchvision_resnet18 Completamente delegado 4.3
torchvision_resnet50 Completamente delegado 6.9
torchvision_squeezenet1_0 Completamente delegado 2.9
torchvision_squeezenet1_1 Completamente delegado 2.5
torchvision_vgg16 Completamente delegado 13.4
torchvision_wide_resnet101_2 Completamente delegado 25.0
torchvision_wide_resnet50_2 Completamente delegado 13.4
u2net_full Completamente delegado 98.3
u2net_lite Completamente delegado 51.4
hf_distil_whisper_small_no_cache Delegada de forma parcial 251.9
hf_distilbert Delegada de forma parcial 13.7
hf_tinyroberta_squad2 Delegada de forma parcial 17.1
hf_tinyroberta_squad2_dynamic_batch Delegada de forma parcial 52.1
snapml_StyleTransferNet Delegada de forma parcial 40.9
timm_efficientformer_l1 Delegada de forma parcial 17.6
timm_efficientformerv2_s0 Delegada de forma parcial 16.1
timm_pvt_v2_b1 Delegada de forma parcial 73.5
timm_pvt_v2_b3 Delegada de forma parcial 246.7
timm_resnest14d Delegada de forma parcial 88.9
torchaudio_conformer Delegada de forma parcial 21.5
torchvision_convnext_tiny Delegada de forma parcial 8.2
torchvision_maxvit_t Delegada de forma parcial 194.0
torchvision_shufflenet_v2 Delegada de forma parcial 9.5
torchvision_swin_tiny Delegada de forma parcial 164.4
torchvision_video_resnet2plus1d_18 Delegada de forma parcial 6,832.0
torchvision_video_swin3d_tiny Delegada de forma parcial 2,617.8
yolox_tiny Delegada de forma parcial 11.2