As APIs LiteRT Next estão disponíveis em C++ e podem oferecer aos desenvolvedores Android maior controle sobre a alocação de memória e o desenvolvimento de baixo nível do que as APIs Kotlin.
Para conferir um exemplo de um aplicativo do LiteRT Next em C++, consulte a Demonstração de segmentação assíncrona com C++.
Começar
Siga as etapas abaixo para adicionar o LiteRT Next ao seu app Android.
Atualizar a configuração do build
Criar um aplicativo C++ com o LiteRT para aceleração de GPU, NPU e CPU usando o
Bazel envolve definir uma regra cc_binary
para garantir que todos
os componentes necessários sejam compilados, vinculados e empacotados. A configuração de exemplo
a seguir permite que o aplicativo escolha ou utilize dinamicamente aceleradores de GPU, NPU e CPU.
Estes são os principais componentes da configuração de build do Bazel:
- Regra
cc_binary
:essa é a regra fundamental do Bazel usada para definir o destino executável do C++ (por exemplo,name = "your_application_name"
). - Atributo
srcs
:lista os arquivos de origem C++ do aplicativo (por exemplo,main.cc
e outros arquivos.cc
ou.h
). - Atributo
data
(dependências de execução): isso é crucial para empacotar bibliotecas compartilhadas e recursos que o aplicativo carrega no momento da execução.- LiteRT Core Runtime:a biblioteca compartilhada principal da API LiteRT C (por exemplo,
//litert/c:litert_runtime_c_api_shared_lib
). - Bibliotecas de despacho:bibliotecas compartilhadas específicas do fornecedor que o LiteRT
usa para se comunicar com os drivers de hardware (por exemplo,
//litert/vendors/qualcomm/dispatch:dispatch_api_so
). - Bibliotecas de back-end de GPU:as bibliotecas compartilhadas para aceleração de GPU
(por exemplo,
"@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so
). - Bibliotecas de back-end de NPU:as bibliotecas compartilhadas específicas para aceleração
de NPU, como as bibliotecas QNN HTP da Qualcomm (por exemplo,
@qairt//:lib/aarch64-android/libQnnHtp.so
,@qairt//:lib/hexagon-v79/unsigned/libQnnHtpV79Skel.so
). - Arquivos e recursos do modelo:seus arquivos de modelo treinado, imagens de teste,
shaders ou qualquer outro dado necessário no momento da execução (por exemplo,
:model_files
,:shader_files
).
- LiteRT Core Runtime:a biblioteca compartilhada principal da API LiteRT C (por exemplo,
- Atributo
deps
(dependências de tempo de compilação): lista as bibliotecas que seu código precisa compilar.- APIs e utilitários do LiteRT:cabeçalhos e bibliotecas estáticas para componentes do LiteRT,
como buffers de tensor (por exemplo,
//litert/cc:litert_tensor_buffer
). - Bibliotecas gráficas (para GPU): dependências relacionadas a APIs gráficas
se o acelerador de GPU as usa (por exemplo,
gles_deps()
).
- APIs e utilitários do LiteRT:cabeçalhos e bibliotecas estáticas para componentes do LiteRT,
como buffers de tensor (por exemplo,
- Atributo
linkopts
:especifica opções transmitidas ao vinculador, que podem incluir a vinculação a bibliotecas do sistema (por exemplo,-landroid
para builds do Android ou bibliotecas GLES comgles_linkopts()
.
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",
# NPU accelerator shared library
"//litert/vendors/qualcomm/dispatch:dispatch_api_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
)
Carregar o modelo
Depois de receber um modelo do LiteRT ou converter um modelo para o formato .tflite
,
carregue o modelo criando um objeto Model
.
LITERT_ASSIGN_OR_RETURN(auto model, Model::CreateFromFile("mymodel.tflite"));
Criar o ambiente
O objeto Environment
fornece um ambiente de execução que inclui componentes
como o caminho do plug-in do compilador e os contextos da GPU. O Environment
é
necessário ao criar CompiledModel
e TensorBuffer
. O código abaixo
cria uma Environment
para execução de CPU e GPU sem opções:
LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
Criar o modelo compilado
Usando a API CompiledModel
, inicialize o ambiente de execução com o objeto
Model
recém-criado. É possível especificar a aceleração de hardware neste ponto
(kLiteRtHwAcceleratorCpu
ou kLiteRtHwAcceleratorGpu
):
LITERT_ASSIGN_OR_RETURN(auto compiled_model,
CompiledModel::Create(env, model, kLiteRtHwAcceleratorCpu));
Criar buffers de entrada e saída
Crie as estruturas de dados necessárias (buffers) para armazenar os dados de entrada que você vai alimentar no modelo para inferência e os dados de saída que o modelo produz após a execução da inferência.
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());
Se você estiver usando a memória da CPU, preencha as entradas gravando dados diretamente no primeiro buffer de entrada.
input_buffers[0].Write<float>(absl::MakeConstSpan(input_data, input_size));
Invocar o modelo
Fornecendo os buffers de entrada e saída, execute o modelo compilado com o modelo e a aceleração de hardware especificados nas etapas anteriores.
compiled_model.Run(input_buffers, output_buffers);
Recuperar saídas
Extrair saídas lendo diretamente a saída do modelo da memória.
std::vector<float> data(output_data_size);
output_buffers[0].Read<float>(absl::MakeSpan(data));
// ... process output data
Principais conceitos e componentes
Consulte as seções a seguir para informações sobre os principais conceitos e componentes das APIs LiteRT Next.
Tratamento de erros
O LiteRT usa litert::Expected
para retornar valores ou propagar erros de maneira
semelhante a absl::StatusOr
ou std::expected
. Você pode verificar o erro manualmente.
Para sua conveniência, o LiteRT fornece as seguintes macros:
LITERT_ASSIGN_OR_RETURN(lhs, expr)
atribui o resultado deexpr
alhs
se ele não produzir um erro. Caso contrário, o erro será retornado.Ele vai se expandir para algo como o snippet abaixo.
auto maybe_model = Model::CreateFromFile("mymodel.tflite"); if (!maybe_model) { return maybe_model.Error(); } auto model = std::move(maybe_model.Value());
LITERT_ASSIGN_OR_ABORT(lhs, expr)
faz o mesmo queLITERT_ASSIGN_OR_RETURN
, mas aborta o programa em caso de erro.LITERT_RETURN_IF_ERROR(expr)
retornaexpr
se a avaliação produzir um erro.O
LITERT_ABORT_IF_ERROR(expr)
faz o mesmo que oLITERT_RETURN_IF_ERROR
, mas interrompe o programa em caso de erro.
Para mais informações sobre macros do LiteRT, consulte litert_macros.h
.
Modelo compilado (CompiledModel)
A API Compiled Model (CompiledModel
) é responsável por carregar um modelo,
aplicar a aceleração de hardware, instanciar o ambiente de execução, criar buffers de entrada e
saída e executar a inferência.
O snippet de código simplificado a seguir demonstra como a API Compiled Model
usa um modelo LiteRT (.tflite
) e o acelerador de hardware de destino (GPU) e
cria um modelo compilado pronto para executar a inferência.
// Load model and initialize runtime
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, kLiteRtHwAcceleratorCpu));
O snippet de código simplificado a seguir demonstra como a API Compiled Model recebe um buffer de entrada e saída e executa inferências com o modelo compilado.
// Preallocate input/output buffers
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());
// Fill the first input
float input_values[] = { /* your data */ };
LITERT_RETURN_IF_ERROR(
input_buffers[0].Write<float>(absl::MakeConstSpan(input_values, /*size*/)));
// Invoke
LITERT_RETURN_IF_ERROR(compiled_model.Run(input_buffers, output_buffers));
// Read the output
std::vector<float> data(output_data_size);
LITERT_RETURN_IF_ERROR(
output_buffers[0].Read<float>(absl::MakeSpan(data)));
Para uma visão mais completa de como a API CompiledModel
é implementada, consulte o
código-fonte de
litert_compiled_model.h.
Tensor Buffer (TensorBuffer)
O LiteRT Next oferece suporte integrado à interoperabilidade de buffer de E/S usando a
API Tensor Buffer (TensorBuffer
) para processar o fluxo de dados para dentro e para fora do
modelo compilado. A API Tensor Buffer permite gravar
(Write<T>()
) e ler (Read<T>()
) e bloquear a memória da CPU.
Para uma visão mais completa de como a API TensorBuffer
é implementada, consulte o
código-fonte de
litert_tensor_buffer.h.
Consultar requisitos de entrada/saída do modelo
Os requisitos para alocar um buffer de tensor (TensorBuffer
) normalmente
são especificados pelo acelerador de hardware. Os buffers para entradas e saídas podem ter
requisitos relacionados ao alinhamento, ao passo do buffer e ao tipo de memória. É possível usar
funções auxiliares como CreateInputBuffers
para processar automaticamente esses
requisitos.
O snippet de código simplificado a seguir demonstra como recuperar os requisitos de buffer para dados de entrada:
LITERT_ASSIGN_OR_RETURN(auto reqs, compiled_model.GetInputBufferRequirements(signature_index, input_index));
Para uma visão mais completa de como a API TensorBufferRequirements
é
implementada, consulte o código-fonte de
litert_tensor_buffer_requirements.h.
Criar buffers de tensor gerenciados (TensorBuffers)
O snippet de código simplificado a seguir demonstra como criar buffers de tensor gerenciados, em que a API TensorBuffer
aloca os respectivos buffers:
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_cpu,
TensorBuffer::CreateManaged(env, /*buffer_type=*/kLiteRtTensorBufferTypeHostMemory,
ranked_tensor_type, buffer_size));
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_gl, TensorBuffer::CreateManaged(env,
/*buffer_type=*/kLiteRtTensorBufferTypeGlBuffer, ranked_tensor_type, buffer_size));
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_ahwb, TensorBuffer::CreateManaged(env,
/*buffer_type=*/kLiteRtTensorBufferTypeAhwb, ranked_tensor_type, buffer_size));
Criar buffers de tensor sem cópia
Para agrupar um buffer como um Tensor Buffer (cópia zero), use o snippet de código a seguir:
// Create a TensorBuffer from host memory
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_from_host,
TensorBuffer::CreateFromHostMemory(env, ranked_tensor_type,
ptr_to_host_memory, buffer_size));
// Create a TensorBuffer from GlBuffer
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_from_gl,
TensorBuffer::CreateFromGlBuffer(env, ranked_tensor_type, gl_target, gl_id,
size_bytes, offset));
// Create a TensorBuffer from AHardware Buffer
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_from_ahwb,
TensorBuffer::CreateFromAhwb(env, ranked_tensor_type, ahardware_buffer, offset));
Como ler e gravar do buffer do Tensor
O snippet a seguir demonstra como ler de um buffer de entrada e gravar em um buffer de saída:
// Example of reading to input buffer:
std::vector<float> input_tensor_data = {1,2};
LITERT_ASSIGN_OR_RETURN(auto write_success,
input_tensor_buffer.Write<float>(absl::MakeConstSpan(input_tensor_data)));
if(write_success){
/* Continue after successful write... */
}
// Example of writing to output buffer:
std::vector<float> data(total_elements);
LITERT_ASSIGN_OR_RETURN(auto read_success,
output_tensor_buffer.Read<float>(absl::MakeSpan(data)));
if(read_success){
/* Continue after successful read */
}
Avançado: interoperabilidade de buffer sem cópia para tipos de buffer de hardware especializados
Alguns tipos de buffer, como AHardwareBuffer
, permitem a interoperabilidade com
outros tipos de buffer. Por exemplo, um buffer OpenGL pode ser criado a partir de um
AHardwareBuffer
com zero cópia. O snippet de código a seguir mostra um exemplo:
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_ahwb,
TensorBuffer::CreateManaged(env, kLiteRtTensorBufferTypeAhwb,
ranked_tensor_type, buffer_size));
// Buffer interop: Get OpenGL buffer from AHWB,
// internally creating an OpenGL buffer backed by AHWB memory.
LITERT_ASSIGN_OR_RETURN(auto gl_buffer, tensor_buffer_ahwb.GetGlBuffer());
Os buffers do OpenCL também podem ser criados a partir de AHardwareBuffer
:
LITERT_ASSIGN_OR_RETURN(auto cl_buffer, tensor_buffer_ahwb.GetOpenClMemory());
Em dispositivos móveis com suporte à interoperabilidade entre o OpenCL e o OpenGL, os buffers CL podem ser criados a partir de buffers GL:
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_from_gl,
TensorBuffer::CreateFromGlBuffer(env, ranked_tensor_type, gl_target, gl_id,
size_bytes, offset));
// Creates an OpenCL buffer from the OpenGL buffer, zero-copy.
LITERT_ASSIGN_OR_RETURN(auto cl_buffer, tensor_buffer_from_gl.GetOpenClMemory());
Exemplos de implementações
Consulte as seguintes implementações do LiteRT Next em C++.
Inferência básica (CPU)
Confira a seguir uma versão condensada dos snippets de código da seção Começar. É a implementação mais simples de inferência com o LiteRT Next.
// Load model and initialize runtime
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,
kLiteRtHwAcceleratorCpu));
// Preallocate input/output buffers
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());
// Fill the first input
float input_values[] = { /* your data */ };
input_buffers[0].Write<float>(absl::MakeConstSpan(input_values, /*size*/));
// Invoke
compiled_model.Run(input_buffers, output_buffers);
// Read the output
std::vector<float> data(output_data_size);
output_buffers[0].Read<float>(absl::MakeSpan(data));
Zero-copy com a memória do host
A API LiteRT Next Compiled Model reduz a fricção dos pipelines de inferência,
principalmente quando se trata de vários back-ends de hardware e fluxos sem cópia. O
snippet de código abaixo usa o método CreateFromHostMemory
ao criar o
buffer de entrada, que usa cópia zero com a memória do host.
// Define an LiteRT environment to use existing EGL display and context.
const std::vector<Environment::Option> environment_options = {
{OptionTag::EglDisplay, user_egl_display},
{OptionTag::EglContext, user_egl_context}};
LITERT_ASSIGN_OR_RETURN(auto env,
Environment::Create(absl::MakeConstSpan(environment_options)));
// Load model1 and initialize runtime.
LITERT_ASSIGN_OR_RETURN(auto model1, Model::CreateFromFile("model1.tflite"));
LITERT_ASSIGN_OR_RETURN(auto compiled_model1, CompiledModel::Create(env, model1, kLiteRtHwAcceleratorGpu));
// Prepare I/O buffers. opengl_buffer is given outside from the producer.
LITERT_ASSIGN_OR_RETURN(auto tensor_type, model.GetInputTensorType("input_name0"));
// Create an input TensorBuffer based on tensor_type that wraps the given OpenGL Buffer.
LITERT_ASSIGN_OR_RETURN(auto tensor_buffer_from_opengl,
litert::TensorBuffer::CreateFromGlBuffer(env, tensor_type, opengl_buffer));
// Create an input event and attach it to the input buffer. Internally, it creates
// and inserts a fence sync object into the current EGL command queue.
LITERT_ASSIGN_OR_RETURN(auto input_event, Event::CreateManaged(env, LiteRtEventTypeEglSyncFence));
tensor_buffer_from_opengl.SetEvent(std::move(input_event));
std::vector<TensorBuffer> input_buffers;
input_buffers.push_back(std::move(tensor_buffer_from_opengl));
// Create an output TensorBuffer of the model1. It's also used as an input of the model2.
LITERT_ASSIGN_OR_RETURN(auto intermedidate_buffers, compiled_model1.CreateOutputBuffers());
// Load model2 and initialize runtime.
LITERT_ASSIGN_OR_RETURN(auto model2, Model::CreateFromFile("model2.tflite"));
LITERT_ASSIGN_OR_RETURN(auto compiled_model2, CompiledModel::Create(env, model2, kLiteRtHwAcceleratorGpu));
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model2.CreateOutputBuffers());
compiled_model1.RunAsync(input_buffers, intermedidate_buffers);
compiled_model2.RunAsync(intermedidate_buffers, output_buffers);