Aceleração de GPU com LiteRT

As unidades de processamento gráfico (GPUs) são usadas com frequência para aceleração de aprendizado profundo devido à capacidade paralela enorme em comparação com as CPUs. O LiteRT simplifica o processo de uso da aceleração de GPU, permitindo que os usuários especifiquem a aceleração de hardware como um parâmetro ao criar um modelo compilado (CompiledModel).

Com a aceleração de GPU do LiteRT, é possível criar buffers de entrada e saída compatíveis com GPU, alcançar cópia zero com seus dados na memória da GPU e executar tarefas de forma assíncrona para maximizar o paralelismo.

Para exemplos de implementações de GPU LiteRT, consulte os seguintes aplicativos de demonstração:

Adicionar dependência de GPU

Siga estas etapas para adicionar a dependência de GPU ao seu aplicativo Kotlin ou C++.

Kotlin

Para usuários do Kotlin, o acelerador de GPU é integrado e não exige etapas adicionais além do guia Começar.

C++

Para usuários de C++, é necessário criar as dependências do aplicativo com a aceleração de GPU do LiteRT. A regra cc_binary que empacota a lógica principal do aplicativo (por exemplo, main.cc) requer os seguintes componentes de tempo de execução:

  • Biblioteca compartilhada da API LiteRT C: o atributo data precisa incluir a biblioteca compartilhada da API LiteRT C (//litert/c:litert_runtime_c_api_shared_lib) e componentes específicos da GPU (@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so).
  • Dependências de atributos: o atributo deps geralmente inclui dependências do GLES gles_deps(), e linkopts geralmente inclui gles_linkopts(). Ambos são altamente relevantes para a aceleração de GPU, já que o LiteRT geralmente usa o OpenGLES no Android.
  • Arquivos de modelo e outros recursos: incluídos pelo atributo data.

Confira abaixo um exemplo de regra 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
)

Essa configuração permite que o binário compilado carregue e use dinamicamente a GPU para inferência de machine learning acelerada.

Usar GPU com a API CompiledModel

Para começar a usar o acelerador de GPU, transmita o parâmetro de GPU ao criar o modelo compilado (CompiledModel). O snippet de código a seguir mostra uma implementação básica de todo o 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()

Para mais informações, consulte os guias Começar a usar C++ ou Começar a usar Kotlin.

Cópia zero com aceleração de GPU

O uso de cópia zero permite que uma GPU acesse dados diretamente na própria memória sem que a CPU precise copiar esses dados explicitamente. Ao não copiar dados para e da memória da CPU, a cópia zero pode reduzir significativamente a latência de ponta a ponta.

O código a seguir é um exemplo de implementação de GPU de cópia zero com OpenGL, uma API para renderizar gráficos vetoriais. O código transmite imagens no formato de buffer do OpenGL diretamente para o 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());

Execução assíncrona

Os métodos assíncronos do LiteRT, como RunAsync(), permitem programar a inferência da GPU enquanto continuam outras tarefas usando a CPU ou a NPU. Em pipelines complexos, a GPU é usada de forma assíncrona com a CPU ou NPUs.

O snippet de código a seguir se baseia no código fornecido no exemplo de aceleração de GPU sem cópia. O código usa CPU e GPU de forma assíncrona e anexa um Event LiteRT ao buffer de entrada. O LiteRT Event é responsável por gerenciar diferentes tipos de primitivos de sincronização, e o código a seguir cria um objeto de evento LiteRT gerenciado do tipo LiteRtEventTypeEglSyncFence. Esse objeto Event garante que não vamos ler do buffer de entrada até que a GPU termine. Tudo isso é feito sem envolver a 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 compatíveis

O LiteRT é compatível com a aceleração de GPU com os seguintes modelos. Os resultados de comparativos de mercado são baseados em testes executados em um dispositivo Samsung Galaxy S24.

Modelo Aceleração de GPU do LiteRT GPU LiteRT (ms)
hf_mms_300m Totalmente delegado 19,6
hf_mobilevit_small Totalmente delegado 8,7
hf_mobilevit_small_e2e Totalmente delegado 8.0
hf_wav2vec2_base_960h Totalmente delegado 9.1
hf_wav2vec2_base_960h_dynamic Totalmente delegado 9,8
isnet Totalmente delegado 43.1
timm_efficientnet Totalmente delegado 3.7
timm_nfnet Totalmente delegado 9.7
timm_regnety_120 Totalmente delegado 12.1
torchaudio_deepspeech Totalmente delegado 4,6
torchaudio_wav2letter Totalmente delegado 4,8
torchvision_alexnet Totalmente delegado 3.3
torchvision_deeplabv3_mobilenet_v3_large Totalmente delegado 5.7
torchvision_deeplabv3_resnet101 Totalmente delegado 35,1
torchvision_deeplabv3_resnet50 Totalmente delegado 24,5
torchvision_densenet121 Totalmente delegado 13,9
torchvision_efficientnet_b0 Totalmente delegado 3.6
torchvision_efficientnet_b1 Totalmente delegado 4,7
torchvision_efficientnet_b2 Totalmente delegado 5.0
torchvision_efficientnet_b3 Totalmente delegado 6.1
torchvision_efficientnet_b4 Totalmente delegado 7,6
torchvision_efficientnet_b5 Totalmente delegado 8.6
torchvision_efficientnet_b6 Totalmente delegado 11.2
torchvision_efficientnet_b7 Totalmente delegado 14.7
torchvision_fcn_resnet50 Totalmente delegado 19,9
torchvision_googlenet Totalmente delegado 3,9
torchvision_inception_v3 Totalmente delegado 8.6
torchvision_lraspp_mobilenet_v3_large Totalmente delegado 3.3
torchvision_mnasnet0_5 Totalmente delegado 2.4
torchvision_mobilenet_v2 Totalmente delegado 2.8
torchvision_mobilenet_v3_large Totalmente delegado 2.8
torchvision_mobilenet_v3_small Totalmente delegado 2.3
torchvision_resnet152 Totalmente delegado 15
torchvision_resnet18 Totalmente delegado 4.3
torchvision_resnet50 Totalmente delegado 6,9
torchvision_squeezenet1_0 Totalmente delegado 2.9
torchvision_squeezenet1_1 Totalmente delegado 2,5
torchvision_vgg16 Totalmente delegado 13,4
torchvision_wide_resnet101_2 Totalmente delegado 25.0
torchvision_wide_resnet50_2 Totalmente delegado 13,4
u2net_full Totalmente delegado 98,3
u2net_lite Totalmente delegado 51,4
hf_distil_whisper_small_no_cache Parcialmente delegada 251,9
hf_distilbert Parcialmente delegada 13.7
hf_tinyroberta_squad2 Parcialmente delegada 17,1
hf_tinyroberta_squad2_dynamic_batch Parcialmente delegada 52,1
snapml_StyleTransferNet Parcialmente delegada 40,9
timm_efficientformer_l1 Parcialmente delegada 17,6
timm_efficientformerv2_s0 Parcialmente delegada 16.1
timm_pvt_v2_b1 Parcialmente delegada 73,5
timm_pvt_v2_b3 Parcialmente delegada 246,7
timm_resnest14d Parcialmente delegada 88,9
torchaudio_conformer Parcialmente delegada 21,5
torchvision_convnext_tiny Parcialmente delegada 8.2
torchvision_maxvit_t Parcialmente delegada 194,0
torchvision_shufflenet_v2 Parcialmente delegada 9,5
torchvision_swin_tiny Parcialmente delegada 164,4
torchvision_video_resnet2plus1d_18 Parcialmente delegada 6832,0
torchvision_video_swin3d_tiny Parcialmente delegada 2617,8
yolox_tiny Parcialmente delegada 11.2