LiteRT CompiledModel C++ API

API LiteRT CompiledModel доступен на C++, предоставляя разработчикам точный контроль над выделением памяти и низкоуровневой разработкой. Пример можно посмотреть в приложении для сегментации изображений на C++ .

В этом руководстве показан базовый алгоритм выполнения операций на ЦП с использованием API CompiledModel Kotlin. Для получения информации о расширенных функциях ускорения см. руководство по ускорению на графическом процессоре и нейронном процессоре .

Добавить зависимость сборки

Выберите путь, подходящий для вашего проекта:

  • Используйте предварительно собранную библиотеку (Android) : Используйте предварительно собранную библиотеку из пакета LiteRT Maven для мгновенной настройки. См. как использовать предварительно собранную библиотеку C++ из LiteRT Maven .

  • Сборка из исходного кода (кроссплатформенная) : Сборка из исходного кода обеспечивает полный контроль и поддержку нескольких платформ (Android, iOS, macOS, Linux, Windows). См. следующие инструкции.

Сборка из исходного кода с помощью Bazel.

Создание приложения на C++ с использованием LiteRT для ускорения работы на GPU, NPU и CPU с помощью Bazel включает в себя определение правила cc_binary , гарантирующего компиляцию, компоновку и упаковку всех необходимых компонентов. Приведенный ниже пример настройки позволяет вашему приложению динамически выбирать или использовать ускорители GPU, NPU и CPU.

Вот основные компоненты вашей конфигурации сборки Bazel:

  • Правило cc_binary : Это основное правило Bazel, используемое для определения целевого исполняемого файла 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 ).
    • Библиотеки диспетчеризации: специфичные для производителя общие библиотеки, которые LiteRT использует для связи с драйверами оборудования (например, //litert/vendors/qualcomm/dispatch:dispatch_api_so ).
    • Библиотеки для работы с графическим процессором: разделяемые библиотеки для ускорения работы с графическим процессором (например, "@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 ).
  • Атрибут deps (зависимости времени компиляции): В этом списке перечислены библиотеки, с которыми должен компилироваться ваш код.
    • LiteRT и утилиты: Заголовочные файлы и статические библиотеки для компонентов LiteRT, таких как буферы тензоров (например, //litert/cc:litert_tensor_buffer ).
    • Графические библиотеки (для GPU): Зависимости, связанные с графическим API, если графический ускоритель их использует (например, gles_deps() ).
  • Атрибут 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 предоставляет среду выполнения, которая включает такие компоненты, как путь к плагину компилятора и контексты GPU. Environment необходим при создании CompiledModel и TensorBuffer . Следующий код создает Environment для выполнения на CPU и GPU без каких-либо параметров:

LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));

Создайте CompiledModel

Используя 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 CompiledModel обратитесь к следующим разделам.

Обработка ошибок

LiteRT использует litert::Expected либо для возврата значений, либо для распространения ошибок аналогично absl::StatusOr или std::expected . Вы можете проверить наличие ошибки вручную.

Для удобства LiteRT предоставляет следующие макросы:

  • LITERT_ASSIGN_OR_RETURN(lhs, expr) присваивает результат функции expr переменной lhs если она не вызывает ошибку, и возвращает ошибку в противном случае.

    В результате получится фрагмент кода, похожий на следующий.

    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 .

Тензорный буфер (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 CompiledModel упрощает работу конвейеров вывода, особенно при использовании нескольких аппаратных бэкэндов и потоков с нулевым копированием. В следующем фрагменте кода используется метод 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);