Menjalankan LiteRT Compiled Model API di Android dengan C++

API Model yang Dikompilasi LiteRT tersedia di C++, sehingga memberikan kontrol terperinci kepada developer Android atas alokasi memori dan pengembangan tingkat rendah.

Untuk contoh aplikasi LiteRT di C++, lihat Segmentasi asinkron dengan demo C++.

Mulai

Gunakan langkah-langkah berikut untuk menambahkan LiteRT Compiled Model API ke aplikasi Android Anda.

Memperbarui konfigurasi build

Mem-build aplikasi C++ dengan LiteRT untuk akselerasi GPU, NPU, dan CPU menggunakan Bazel melibatkan penentuan aturan cc_binary untuk memastikan semua komponen yang diperlukan dikompilasi, ditautkan, dan dipaketkan. Penyiapan contoh berikut memungkinkan aplikasi Anda memilih atau menggunakan akselerator GPU, NPU, dan CPU secara dinamis.

Berikut adalah komponen utama dalam konfigurasi build Bazel Anda:

  • cc_binary Aturan: Ini adalah aturan Bazel mendasar yang digunakan untuk menentukan target yang dapat dieksekusi C++ Anda (misalnya, name = "your_application_name").
  • Atribut srcs: Mencantumkan file sumber C++ aplikasi Anda (misalnya, main.cc, dan file .cc atau .h lainnya).
  • Atribut data (Dependensi Runtime): Atribut ini sangat penting untuk memaketkan library bersama dan aset yang dimuat aplikasi Anda saat runtime.
    • LiteRT Core Runtime: Library bersama C API LiteRT utama (misalnya, //litert/c:litert_runtime_c_api_shared_lib).
    • Library Dispatch: Library bersama khusus vendor yang digunakan LiteRT untuk berkomunikasi dengan driver hardware (misalnya, //litert/vendors/qualcomm/dispatch:dispatch_api_so).
    • Library Backend GPU: Library bersama untuk akselerasi GPU (misalnya, "@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so).
    • Library Backend NPU: Library bersama khusus untuk akselerasi NPU, seperti library HTP QNN Qualcomm (misalnya, @qairt//:lib/aarch64-android/libQnnHtp.so, @qairt//:lib/hexagon-v79/unsigned/libQnnHtpV79Skel.so).
    • File & Aset Model: File model terlatih, gambar pengujian, shader, atau data lain yang diperlukan saat runtime (misalnya, :model_files, :shader_files).
  • Atribut deps (Dependensi Waktu Kompilasi): Mencantumkan library yang dibutuhkan kode Anda untuk dikompilasi.
    • API & Utilitas LiteRT: Header dan library statis untuk komponen LiteRT seperti buffer tensor (misalnya, //litert/cc:litert_tensor_buffer).
    • Library Grafis (untuk GPU): Dependensi yang terkait dengan API grafis jika akselerator GPU menggunakannya (misalnya, gles_deps()).
  • Atribut linkopts: Menentukan opsi yang diteruskan ke linker, yang dapat mencakup penautan ke library sistem (misalnya, -landroid untuk build Android, atau library GLES dengan gles_linkopts()).

Berikut adalah contoh aturan 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
)

Memuat Model

Setelah mendapatkan model LiteRT, atau mengonversi model ke format .tflite, muat model dengan membuat objek Model.

LITERT_ASSIGN_OR_RETURN(auto model, Model::CreateFromFile("mymodel.tflite"));

Buat lingkungan

Objek Environment menyediakan lingkungan runtime yang mencakup komponen seperti jalur plugin compiler dan konteks GPU. Environment diperlukan saat membuat CompiledModel dan TensorBuffer. Kode berikut membuat Environment untuk eksekusi CPU dan GPU tanpa opsi apa pun:

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

Membuat Model yang Dikompilasi

Dengan menggunakan CompiledModel API, inisialisasi runtime dengan objek Model yang baru dibuat. Anda dapat menentukan akselerasi hardware pada tahap ini (kLiteRtHwAcceleratorCpu atau kLiteRtHwAcceleratorGpu):

LITERT_ASSIGN_OR_RETURN(auto compiled_model,
  CompiledModel::Create(env, model, kLiteRtHwAcceleratorCpu));

Membuat Buffer Input dan Output

Buat struktur data (buffer) yang diperlukan untuk menyimpan data input yang akan Anda masukkan ke model untuk inferensi, dan data output yang dihasilkan model setelah menjalankan inferensi.

LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());

Jika Anda menggunakan memori CPU, isi input dengan menulis data langsung ke buffer input pertama.

input_buffers[0].Write<float>(absl::MakeConstSpan(input_data, input_size));

Memanggil model

Dengan menyediakan buffer input dan output, jalankan Model yang Dikompilasi dengan model dan akselerasi hardware yang ditentukan pada langkah-langkah sebelumnya.

compiled_model.Run(input_buffers, output_buffers);

Mengambil Output

Mengambil output dengan membaca output model langsung dari memori.

std::vector<float> data(output_data_size);
output_buffers[0].Read<float>(absl::MakeSpan(data));
// ... process output data

Konsep dan komponen utama

Lihat bagian berikut untuk mengetahui informasi tentang konsep dan komponen utama LiteRT Compiled Model API.

Penanganan Error

LiteRT menggunakan litert::Expected untuk menampilkan nilai atau menyebarkan error dengan cara yang serupa dengan absl::StatusOr atau std::expected. Anda dapat memeriksa error secara manual.

Untuk memudahkan, LiteRT menyediakan makro berikut:

  • LITERT_ASSIGN_OR_RETURN(lhs, expr) menetapkan hasil expr ke lhs jika tidak menghasilkan error dan menampilkan error jika tidak.

    Ini akan diperluas menjadi seperti cuplikan berikut.

    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) melakukan hal yang sama seperti LITERT_ASSIGN_OR_RETURN tetapi membatalkan program jika terjadi error.

  • LITERT_RETURN_IF_ERROR(expr) menampilkan expr jika evaluasinya menghasilkan error.

  • LITERT_ABORT_IF_ERROR(expr) melakukan hal yang sama seperti LITERT_RETURN_IF_ERROR, tetapi membatalkan program jika terjadi error.

Untuk mengetahui informasi selengkapnya tentang makro LiteRT, lihat litert_macros.h.

Model yang Dikompilasi (CompiledModel)

Compiled Model API (CompiledModel) bertanggung jawab untuk memuat model, menerapkan akselerasi hardware, membuat instance runtime, membuat buffer input dan output, serta menjalankan inferensi.

Cuplikan kode yang disederhanakan berikut menunjukkan cara Compiled Model API mengambil model LiteRT (.tflite) dan akselerator hardware target (GPU), lalu membuat model yang dikompilasi dan siap menjalankan inferensi.

// 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));

Cuplikan kode yang disederhanakan berikut menunjukkan cara Compiled Model API mengambil buffer input dan output, serta menjalankan inferensi dengan model yang dikompilasi.

// 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)));

Untuk melihat gambaran yang lebih lengkap tentang cara penerapan API CompiledModel, lihat kode sumber untuk litert_compiled_model.h.

Buffer Tensor (TensorBuffer)

LiteRT menyediakan dukungan bawaan untuk interoperabilitas buffer I/O, menggunakan Tensor Buffer API (TensorBuffer) untuk menangani aliran data ke dan dari model yang dikompilasi. Tensor Buffer API memberikan kemampuan untuk menulis (Write<T>()) dan membaca (Read<T>()), serta mengunci memori CPU.

Untuk melihat gambaran yang lebih lengkap tentang cara penerapan API TensorBuffer, lihat kode sumber untuk litert_tensor_buffer.h.

Persyaratan input/output model kueri

Persyaratan untuk mengalokasikan Buffer Tensor (TensorBuffer) biasanya ditentukan oleh akselerator hardware. Buffer untuk input dan output dapat memiliki persyaratan terkait perataan, langkah buffer, dan jenis memori. Anda dapat menggunakan fungsi helper seperti CreateInputBuffers untuk menangani persyaratan ini secara otomatis.

Cuplikan kode yang disederhanakan berikut menunjukkan cara Anda dapat mengambil persyaratan buffer untuk data input:

LITERT_ASSIGN_OR_RETURN(auto reqs, compiled_model.GetInputBufferRequirements(signature_index, input_index));

Untuk melihat gambaran yang lebih lengkap tentang cara penerapan API TensorBufferRequirements, lihat kode sumber untuk litert_tensor_buffer_requirements.h.

Membuat Managed Tensor Buffers (TensorBuffers)

Cuplikan kode yang disederhanakan berikut menunjukkan cara membuat Managed Tensor Buffer, dengan API TensorBuffer mengalokasikan buffer masing-masing:

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));

Membuat Buffer Tensor dengan zero-copy

Untuk membungkus buffer yang ada sebagai Tensor Buffer (tanpa penyalinan), gunakan cuplikan kode berikut:

// 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));

Membaca dan menulis dari Tensor Buffer

Cuplikan berikut menunjukkan cara membaca dari buffer input dan menulis ke buffer output:

// 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 */
}

Lanjutan: Interop buffer tanpa salinan untuk jenis buffer hardware khusus

Jenis buffer tertentu, seperti AHardwareBuffer, memungkinkan interoperabilitas dengan jenis buffer lainnya. Misalnya, buffer OpenGL dapat dibuat dari AHardwareBuffer dengan salinan nol. Cuplikan kode berikut menunjukkan contoh:

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());

Buffer OpenCL juga dapat dibuat dari AHardwareBuffer:

LITERT_ASSIGN_OR_RETURN(auto cl_buffer, tensor_buffer_ahwb.GetOpenClMemory());

Pada perangkat seluler yang mendukung interoperabilitas antara OpenCL dan OpenGL, buffer CL dapat dibuat dari buffer 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());

Contoh penerapan

Lihat penerapan LiteRT berikut di C++.

Inferensi Dasar (CPU)

Berikut adalah versi ringkas dari cuplikan kode dari bagian Mulai. Ini adalah penerapan inferensi paling sederhana dengan 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));

Zero-Copy dengan Memori Host

LiteRT Compiled Model API mengurangi gesekan pipeline inferensi, terutama saat menangani beberapa backend hardware dan alur salinan nol. Cuplikan kode berikut menggunakan metode CreateFromHostMemory saat membuat buffer input, yang menggunakan zero-copy dengan memori 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);