API-интерфейсы скомпилированной модели LiteRT доступны на языке C++, предоставляя разработчикам Android точный контроль над распределением памяти и низкоуровневой разработкой.
Пример приложения LiteRT на C++ см. в демонстрации Асинхронная сегментация с использованием C++ .
Начать
Чтобы добавить API скомпилированной модели LiteRT в ваше приложение Android, выполните следующие действия.
Обновите конфигурацию сборки
Создание приложения на C++ с использованием LiteRT для ускорения GPU, NPU и CPU с использованием Bazel включает определение правила cc_binary , которое гарантирует компиляцию, линковку и упаковку всех необходимых компонентов. Следующий пример настройки позволяет вашему приложению динамически выбирать или использовать ускорители GPU, NPU и CPU.
Вот ключевые компоненты конфигурации сборки Bazel:
- Правило
cc_binary: это фундаментальное правило Базеля, используемое для определения целевого исполняемого файла C++ (например,name = "your_application_name"). - Атрибут
srcs: список исходных файлов C++ вашего приложения (например,main.ccи другие файлы.ccили.h). - Атрибут
data(зависимости времени выполнения): это критически важно для упаковки общих библиотек и ресурсов, которые ваше приложение загружает во время выполнения.- LiteRT Core Runtime: основная общая библиотека LiteRT C API (например,
//litert/c:litert_runtime_c_api_shared_lib). - Библиотеки Dispatch: общие библиотеки, специфичные для поставщиков, которые LiteRT использует для связи с драйверами оборудования (например,
//litert/vendors/qualcomm/dispatch:dispatch_api_so). - Библиотеки GPU Backend: общие библиотеки для ускорения GPU (например,
"@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so). - Библиотеки бэкэнда NPU: специальные общие библиотеки для ускорения NPU, такие как библиотеки QNN HTP от Qualcomm (например,
@qairt//:lib/aarch64-android/libQnnHtp.so,@qairt//:lib/hexagon-v79/unsigned/libQnnHtpV79Skel.so). - Файлы модели и ресурсы: файлы обученной модели, тестовые изображения, шейдеры или любые другие данные, необходимые во время выполнения (например,
:model_files,:shader_files).
- LiteRT Core Runtime: основная общая библиотека LiteRT C API (например,
- Атрибут
deps(зависимости времени компиляции): в нем перечислены библиотеки, с которыми необходимо скомпилировать ваш код.- API и утилиты LiteRT: заголовочные файлы и статические библиотеки для компонентов LiteRT, таких как тензорные буферы (например,
//litert/cc:litert_tensor_buffer). - Графические библиотеки (для GPU): зависимости, связанные с графическими API, если ускоритель GPU их использует (например,
gles_deps()).
- API и утилиты LiteRT: заголовочные файлы и статические библиотеки для компонентов LiteRT, таких как тензорные буферы (например,
- Атрибут
linkopts: указывает параметры, передаваемые компоновщику, которые могут включать компоновку с системными библиотеками (например,-landroidдля сборок Android или библиотеки GLES сgles_linkopts()).
Ниже приведен пример правила 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
)
Загрузить модель
После получения модели LiteRT или преобразования модели в формат .tflite загрузите модель, создав объект Model .
LITERT_ASSIGN_OR_RETURN(auto model, Model::CreateFromFile("mymodel.tflite"));
Создайте окружающую среду
Объект Environment предоставляет среду выполнения, включающую такие компоненты, как путь к плагину компилятора и контексты графического процессора. Environment требуется при создании CompiledModel и TensorBuffer . Следующий код создаёт Environment для выполнения на CPU и GPU без каких-либо параметров:
LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
Создать скомпилированную модель
Используя API CompiledModel , инициализируйте среду выполнения с помощью только что созданного объекта Model . На этом этапе можно указать аппаратное ускорение ( kLiteRtHwAcceleratorCpu или kLiteRtHwAcceleratorGpu ):
LITERT_ASSIGN_OR_RETURN(auto compiled_model,
CompiledModel::Create(env, model, kLiteRtHwAcceleratorCpu));
Создание входных и выходных буферов
Создайте необходимые структуры данных (буферы) для хранения входных данных, которые вы будете передавать в модель для вывода, и выходных данных, которые модель выдает после выполнения вывода.
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());
Если вы используете память ЦП, заполните входы, записав данные непосредственно в первый входной буфер.
input_buffers[0].Write<float>(absl::MakeConstSpan(input_data, input_size));
Вызов модели
Предоставив входные и выходные буферы, запустите скомпилированную модель с использованием модели и аппаратного ускорения, указанных на предыдущих шагах.
compiled_model.Run(input_buffers, output_buffers);
Получить выходы
Извлекайте выходные данные, напрямую считывая выходные данные модели из памяти.
std::vector<float> data(output_data_size);
output_buffers[0].Read<float>(absl::MakeSpan(data));
// ... process output data
Ключевые концепции и компоненты
Информацию о ключевых концепциях и компонентах API скомпилированной модели LiteRT см. в следующих разделах.
Обработка ошибок
LiteRT использует litert::Expected для возврата значений или передачи ошибок аналогично absl::StatusOr или std::expected . Вы можете вручную проверить наличие ошибки.
Для удобства LiteRT предоставляет следующие макросы:
LITERT_ASSIGN_OR_RETURN(lhs, expr)присваивает результатexprlhs, если это не приводит к ошибке, в противном случае возвращает ошибку.Он будет расширен до примерно следующего фрагмента.
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)делает то же самое, что иLITERT_ASSIGN_OR_RETURN, но прерывает программу в случае ошибки.LITERT_RETURN_IF_ERROR(expr)возвращаетexprесли его вычисление приводит к ошибке.LITERT_ABORT_IF_ERROR(expr)делает то же самое, что иLITERT_RETURN_IF_ERROR, но прерывает программу в случае ошибки.
Дополнительную информацию о макросах LiteRT см. в файле litert_macros.h .
Скомпилированная модель (CompiledModel)
API скомпилированной модели ( CompiledModel ) отвечает за загрузку модели, применение аппаратного ускорения, создание среды выполнения, создание входных и выходных буферов и запуск вывода.
В следующем упрощенном фрагменте кода показано, как API скомпилированной модели берет модель LiteRT ( .tflite ) и целевой аппаратный ускоритель (GPU) и создает скомпилированную модель, готовую к выполнению вывода.
// 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));
Следующий упрощенный фрагмент кода демонстрирует, как API скомпилированной модели принимает входной и выходной буферы и выполняет выводы с использованием скомпилированной модели.
// 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)));
Более полное представление о реализации API CompiledModel см. в исходном коде litert_compiled_model.h .
Тензорный буфер (TensorBuffer)
LiteRT предоставляет встроенную поддержку взаимодействия с буферами ввода-вывода, используя API Tensor Buffer ( TensorBuffer ) для управления потоком данных в скомпилированную модель и из неё. API Tensor Buffer предоставляет возможность записи ( Write<T>() ) и чтения ( Read<T>() ), а также блокировки памяти процессора.
Более полное представление о реализации API TensorBuffer см. в исходном коде litert_tensor_buffer.h .
Требования к вводу/выводу модели запроса
Требования к выделению тензорного буфера ( TensorBuffer ) обычно определяются аппаратным ускорителем. Буферы для входов и выходов могут иметь требования к выравниванию, шагам буфера и типу памяти. Для автоматической обработки этих требований можно использовать вспомогательные функции, такие как CreateInputBuffers .
Следующий упрощенный фрагмент кода демонстрирует, как можно получить требования к буферу для входных данных:
LITERT_ASSIGN_OR_RETURN(auto reqs, compiled_model.GetInputBufferRequirements(signature_index, input_index));
Более полное представление о реализации API TensorBufferRequirements см. в исходном коде litert_tensor_buffer_requirements.h .
Создание управляемых тензорных буферов (TensorBuffers)
Следующий упрощенный фрагмент кода демонстрирует, как создавать управляемые тензорные буферы, где API TensorBuffer выделяет соответствующие буферы:
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));
Создание тензорных буферов с нулевым копированием
Чтобы обернуть существующий буфер как тензорный буфер (нулевое копирование), используйте следующий фрагмент кода:
// 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));
Чтение и запись из тензорного буфера
В следующем фрагменте показано, как можно читать данные из входного буфера и записывать данные в выходной буфер:
// 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 */
}
Дополнительно: взаимодействие с буфером без копирования для специализированных типов аппаратных буферов
Некоторые типы буферов, такие как AHardwareBuffer , допускают взаимодействие с другими типами буферов. Например, буфер OpenGL можно создать из AHardwareBuffer с нулевым копированием. Следующий фрагмент кода демонстрирует пример:
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());
Буферы OpenCL также можно создать из AHardwareBuffer :
LITERT_ASSIGN_OR_RETURN(auto cl_buffer, tensor_buffer_ahwb.GetOpenClMemory());
На мобильных устройствах, поддерживающих взаимодействие OpenCL и OpenGL, буферы CL можно создавать из буферов 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());
Примеры реализаций
Ознакомьтесь со следующими реализациями LiteRT на языке C++.
Базовый вывод (ЦП)
Ниже представлена сокращённая версия фрагментов кода из раздела «Начало работы» . Это простейшая реализация вывода с помощью LiteRT.
// 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));
Нулевое копирование с хост-памятью
API скомпилированной модели LiteRT упрощает работу конвейеров вывода, особенно при работе с несколькими аппаратными бэкендами и потоками с нулевым копированием. В следующем фрагменте кода используется метод CreateFromHostMemory для создания входного буфера, который использует нулевое копирование с памятью хоста.
// 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);