Aceleração de GPU com o LiteRT Next

As unidades de processamento gráfico (GPUs) são comumente usadas para acelerar o aprendizado profundo devido à capacidade paralela massiva em comparação com as CPUs. O LiteRT Next simplifica o processo de uso da aceleração da GPU, permitindo que os usuários especifiquem a aceleração de hardware como um parâmetro ao criar um modelo compilado (CompiledModel). O LiteRT Next também usa uma implementação de aceleração de GPU nova e aprimorada, não oferecida pelo LiteRT.

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

Para exemplos de implementações do LiteRT Next com suporte a GPU, consulte os seguintes aplicativos de demonstração:

Adicionar dependência de GPU

Siga as etapas abaixo 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 requer outras etapas além do guia Primeiros passos.

C++

Para usuários do 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 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 normalmente inclui dependências de GLES gles_deps(), e linkopts normalmente inclui gles_linkopts(). Ambos são altamente relevantes para a aceleração de GPU, já que o LiteRT geralmente usa 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 a GPU dinamicamente para inferência de machine learning acelerada.

Primeiros passos

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 saber mais, consulte os guias Primeiros passos com o C++ ou Primeiros passos com o Kotlin.

Acelerador de GPU LiteRT Next

O novo GPU Accelerator, disponível apenas com o LiteRT Next, é otimizado para processar cargas de trabalho de IA, como multiplicações de matrizes grandes e cache KV para LLMs, de maneira mais eficiente do que as versões anteriores. O Acelerador de GPU LiteRT Next apresenta as seguintes melhorias importantes em relação à versão do LiteRT:

  • Cobertura estendida de operadores:processe redes neurais maiores e mais complexas.
  • Melhor interoperabilidade de buffer:ative o uso direto de buffers de GPU para frames de câmera, texturas 2D ou estados de LLM grandes.
  • Suporte a execução assíncrona:sobreposição do pré-processamento da CPU com a inferência da GPU.

Zero-copy com aceleração de GPU

O uso de zero-copy permite que uma GPU acesse dados diretamente na própria memória sem a necessidade de a CPU 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 sem cópia com OpenGL, uma API para renderizar gráficos vetoriais. O código transmite imagens no formato de buffer OpenGL diretamente para o 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());

Execução assíncrona

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

O snippet de código abaixo 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 do LiteRT ao buffer de entrada. O Event do LiteRT é responsável por gerenciar diferentes tipos de primitivas de sincronização, e o código abaixo cria um objeto de evento LiteRT gerenciado do tipo LiteRtEventTypeEglSyncFence. Esse objeto Event garante que não lemos 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 Next oferece suporte à aceleração de GPU com os seguintes modelos. Os resultados de comparação de mercado são baseados em testes executados em um dispositivo Samsung Galaxy S24.

Modelo Aceleração de GPU do LiteRT GPU do LiteRT (ms)
hf_mms_300m Totalmente delegada 19,6
hf_mobilevit_small Totalmente delegada 8,7
hf_mobilevit_small_e2e Totalmente delegada 8.0
hf_wav2vec2_base_960h Totalmente delegada 9.1
hf_wav2vec2_base_960h_dynamic Totalmente delegada 9,8
isnet Totalmente delegada 43.1
timm_efficientnet Totalmente delegada 3.7
timm_nfnet Totalmente delegada 9.7
timm_regnety_120 Totalmente delegada 12.1
torchaudio_deepspeech Totalmente delegada 4,6
torchaudio_wav2letter Totalmente delegada 4,8
torchvision_alexnet Totalmente delegada 3.3
torchvision_deeplabv3_mobilenet_v3_large Totalmente delegada 5.7
torchvision_deeplabv3_resnet101 Totalmente delegada 35,1
torchvision_deeplabv3_resnet50 Totalmente delegada 24,5
torchvision_densenet121 Totalmente delegada 13,9
torchvision_efficientnet_b0 Totalmente delegada 3.6
torchvision_efficientnet_b1 Totalmente delegada 4,7
torchvision_efficientnet_b2 Totalmente delegada 5.0
torchvision_efficientnet_b3 Totalmente delegada 6.1
torchvision_efficientnet_b4 Totalmente delegada 7,6
torchvision_efficientnet_b5 Totalmente delegada 8.6
torchvision_efficientnet_b6 Totalmente delegada 11.2
torchvision_efficientnet_b7 Totalmente delegada 14.7
torchvision_fcn_resnet50 Totalmente delegada 19,9
torchvision_googlenet Totalmente delegada 3,9
torchvision_inception_v3 Totalmente delegada 8.6
torchvision_lraspp_mobilenet_v3_large Totalmente delegada 3.3
torchvision_mnasnet0_5 Totalmente delegada 2.4
torchvision_mobilenet_v2 Totalmente delegada 2.8
torchvision_mobilenet_v3_large Totalmente delegada 2.8
torchvision_mobilenet_v3_small Totalmente delegada 2.3
torchvision_resnet152 Totalmente delegada 15
torchvision_resnet18 Totalmente delegada 4.3
torchvision_resnet50 Totalmente delegada 6,9
torchvision_squeezenet1_0 Totalmente delegada 2.9
torchvision_squeezenet1_1 Totalmente delegada 2,5
torchvision_vgg16 Totalmente delegada 13,4
torchvision_wide_resnet101_2 Totalmente delegada 25.0
torchvision_wide_resnet50_2 Totalmente delegada 13,4
u2net_full Totalmente delegada 98,3
u2net_lite Totalmente delegada 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