Ekzekutoni API-n e Modelit të Kompiluar LiteRT në Android me C++

API-të e Modelit të Kompiluar LiteRT janë të disponueshme në C++, duke u dhënë zhvilluesve të Android kontroll të hollësishëm mbi alokimin e memories dhe zhvillimin e nivelit të ulët.

Për një shembull të një aplikacioni LiteRT në C++, shihni demonstrimin e Segmentimit Asinkron me C++ .

Filloni

Ndiqni hapat e mëposhtëm për të shtuar API-n e Modelit të Kompiluar LiteRT në aplikacionin tuaj Android.

Përditëso konfigurimin e ndërtimit

Ndërtimi i një aplikacioni C++ me LiteRT për përshpejtimin e GPU-së, NPU-së dhe CPU-së duke përdorur Bazel përfshin përcaktimin e një rregulli cc_binary për të siguruar që të gjithë komponentët e nevojshëm të kompilohen, lidhen dhe paketohen. Shembulli i mëposhtëm i konfigurimit i lejon aplikacionit tuaj të zgjedhë ose përdorë dinamikisht përshpejtuesit e GPU-së, NPU-së dhe CPU-së.

Këtu janë komponentët kryesorë në konfigurimin tuaj të ndërtimit të Bazel:

  • Rregulla cc_binary : Ky është rregulli themelor i Bazel që përdoret për të përcaktuar objektivin tuaj të ekzekutueshëm në C++ (p.sh., name = "your_application_name" ).
  • Atributi srcs : Liston skedarët burimorë C++ të aplikacionit tuaj (p.sh., main.cc dhe skedarë të tjerë .cc ose .h ).
  • Atributi i data (Varësitë e kohës së ekzekutimit): Ky është thelbësor për paketimin e bibliotekave dhe aseteve të përbashkëta që aplikacioni juaj ngarkon gjatë kohës së ekzekutimit.
    • LiteRT Core Runtime: Biblioteka kryesore e përbashkët e LiteRT C API (p.sh., //litert/c:litert_runtime_c_api_shared_lib ).
    • Bibliotekat e Dispatch-it: Biblioteka të përbashkëta specifike për shitësit që LiteRT përdor për të komunikuar me drajverët e harduerit (p.sh., //litert/vendors/qualcomm/dispatch:dispatch_api_so ).
    • Bibliotekat e Backend-it të GPU-së: Bibliotekat e përbashkëta për përshpejtimin e GPU-së (p.sh., "@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so ".
    • Bibliotekat e Backend-it të NPU-së: Bibliotekat specifike të përbashkëta për përshpejtimin e NPU-së, të tilla si bibliotekat HTP QNN të Qualcomm-it (p.sh., @qairt//:lib/aarch64-android/libQnnHtp.so , @qairt//:lib/hexagon-v79/unsigned/libQnnHtpV79Skel.so ).
    • Skedarët dhe Asetet e Modelit: Skedarët e modelit tuaj të trajnuar, imazhet e testimit, shader-at ose çdo të dhënë tjetër e nevojshme gjatë kohës së ekzekutimit (p.sh., :model_files , :shader_files ).
  • Atributi deps (Varësitë në kohën e kompilimit): Kjo rendit libraritë me të cilat kodi juaj duhet të kompilohet.
    • API-të dhe shërbimet e LiteRT: Tituj dhe librari statike për komponentët LiteRT si buferë tensorësh (p.sh., //litert/cc:litert_tensor_buffer ).
    • Bibliotekat Grafike (për GPU): Varësitë që lidhen me API-të grafike nëse përshpejtuesi i GPU-së i përdor ato (p.sh., gles_deps() ).
  • Atributi linkopts : Specifikon opsionet që i kalohen lidhësit, të cilat mund të përfshijnë lidhjen me libraritë e sistemit (p.sh., -landroid për versionet Android, ose libraritë GLES me gles_linkopts() ).

Më poshtë është një shembull i një rregulli 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
)

Ngarko modelin

Pasi të keni marrë një model LiteRT, ose pasi ta keni konvertuar një model në formatin .tflite , ngarkoni modelin duke krijuar një objekt Model .

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

Krijo mjedisin

Objekti Environment ofron një mjedis ekzekutimi që përfshin komponentë të tillë si shtegu i plugin-it të kompajlerit dhe kontekstet e GPU-së. Environment kërkohet kur krijohen CompiledModel dhe TensorBuffer . Kodi i mëposhtëm krijon një Environment për ekzekutimin e CPU-së dhe GPU-së pa asnjë opsion:

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

Krijo modelin e kompiluar

Duke përdorur API-n CompiledModel , inicializoni kohën e ekzekutimit me objektin Model të sapokrijuar. Mund të specifikoni përshpejtimin e harduerit në këtë pikë ( kLiteRtHwAcceleratorCpu ose kLiteRtHwAcceleratorGpu ):

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

Krijo bufera hyrëse dhe dalëse

Krijoni strukturat e nevojshme të të dhënave (buferat) për të mbajtur të dhënat hyrëse që do t'i ushqeni modelit për përfundim, dhe të dhënat dalëse që modeli prodhon pas ekzekutimit të përfundimit.

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

Nëse përdorni memorien e CPU-së, plotësoni të dhënat hyrëse duke shkruar të dhëna direkt në memorjen e parë të hyrjes.

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

Thirrni modelin

Duke siguruar buffer-at e hyrjes dhe daljes, ekzekutoni Modelin e Kompiluar me modelin dhe përshpejtimin e harduerit të specifikuar në hapat e mëparshëm.

compiled_model.Run(input_buffers, output_buffers);

Merrni Rezultatet

Merrni rezultatet duke lexuar drejtpërdrejti daljen e modelit nga memoria.

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

Konceptet dhe përbërësit kryesorë

Referojuni seksioneve të mëposhtme për informacion mbi konceptet dhe komponentët kryesorë të API-ve të Modelit të Kompiluar LiteRT.

Trajtimi i Gabimeve

LiteRT përdor litert::Expected për të kthyer vlera ose për të përhapur gabimet në një mënyrë të ngjashme me absl::StatusOr ose std::expected . Mund ta kontrolloni manualisht vetë për gabimin.

Për lehtësi, LiteRT ofron makrot e mëposhtme:

  • LITERT_ASSIGN_OR_RETURN(lhs, expr) ia cakton rezultatin e expr lhs nëse nuk prodhon gabim dhe përndryshe e kthen gabimin.

    Do të zgjerohet në diçka si fragmenti i mëposhtëm.

    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) bën të njëjtën gjë si LITERT_ASSIGN_OR_RETURN por e ndërpret programin në rast gabimi.

  • LITERT_RETURN_IF_ERROR(expr) kthen expr nëse vlerësimi i saj prodhon një gabim.

  • LITERT_ABORT_IF_ERROR(expr) bën të njëjtën gjë si LITERT_RETURN_IF_ERROR por e ndërpret programin në rast gabimi.

Për më shumë informacion mbi makrot LiteRT, shihni litert_macros.h .

Modeli i kompiluar (CompiledModel)

API-ja e Modelit të Kompiluar ( CompiledModel ) është përgjegjëse për ngarkimin e një modeli, aplikimin e përshpejtimit të harduerit, krijimin e instancave të kohës së ekzekutimit, krijimin e buffer-ave hyrëse dhe dalëse dhe ekzekutimin e inferencës.

Fragmenti i mëposhtëm i kodit të thjeshtuar tregon se si API-ja e Modelit të Kompiluar merr një model LiteRT ( .tflite ) dhe përshpejtuesin e harduerit të synuar (GPU) dhe krijon një model të kompiluar që është gati për të ekzekutuar inferencën.

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

Fragmenti i mëposhtëm i kodit të thjeshtuar tregon se si API-i i Modelit të Kompiluar merr një buffer hyrës dhe dalës dhe ekzekuton konkluzione me modelin e kompiluar.

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

Për një pamje më të plotë se si zbatohet API-ja CompiledModel , shihni kodin burimor për littert_compiled_model.h .

Tamponi i Tensorit (TensorBuffer)

LiteRT ofron mbështetje të integruar për ndërveprimin e buffer-it I/O, duke përdorur API-në Tensor Buffer ( TensorBuffer ) për të trajtuar rrjedhën e të dhënave brenda dhe jashtë modelit të kompajluar. API-ja Tensor Buffer ofron mundësinë për të shkruar ( Write<T>() ) dhe lexuar ( Read<T>() ), si dhe për të bllokuar memorien e CPU-së.

Për një pamje më të plotë se si zbatohet API-ja TensorBuffer , shihni kodin burimor për littert_tensor_buffer.h .

Kërkesat e hyrjes/daljes së modelit të pyetjes

Kërkesat për ndarjen e një Tensor Buffer ( TensorBuffer ) zakonisht specifikohen nga përshpejtuesi i harduerit. Buffer-at për hyrjet dhe daljet mund të kenë kërkesa në lidhje me shtrirjen, hapat e buffer-it dhe llojin e memories. Mund të përdorni funksione ndihmëse si CreateInputBuffers për të trajtuar automatikisht këto kërkesa.

Fragmenti i mëposhtëm i kodit të thjeshtuar tregon se si mund të merrni kërkesat e buffer-it për të dhënat hyrëse:

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

Për një pamje më të plotë se si zbatohet API-ja TensorBufferRequirements , shihni kodin burimor për littert_tensor_buffer_requirements.h .

Krijo Bufera Tensorësh të Menaxhuar (TensorBuffers)

Fragmenti i mëposhtëm i thjeshtuar i kodit tregon se si të krijoni Bufera Tensor të Menaxhuar, ku API-ja TensorBuffer ndan buferat përkatës:

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

Krijo Tampona Tensor me kopje zero

Për të mbështjellë një buffer ekzistues si një Tensor Buffer (zero-kopje), përdorni fragmentin e mëposhtëm të kodit:

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

Leximi dhe shkrimi nga Tensor Buffer

Fragmenti i mëposhtëm tregon se si mund të lexoni nga një buffer hyrës dhe të shkruani në një buffer dalës:

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

I avancuar: Ndërveprimi i bufferit zero-kopje për llojet e specializuara të bufferit të harduerit

Disa lloje të caktuara të buffer-ave, siç është AHardwareBuffer , lejojnë ndërveprimin me lloje të tjera të buffer-ave. Për shembull, një buffer OpenGL mund të krijohet nga një AHardwareBuffer me kopje zero. Fragmenti i mëposhtëm i kodit tregon një shembull:

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

Buferat OpenCL mund të krijohen gjithashtu nga AHardwareBuffer :

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

Në pajisjet mobile që mbështesin ndërveprimin midis OpenCL dhe OpenGL, buffer-at CL mund të krijohen nga buffer-at 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());

Shembuj implementimesh

Referojuni implementimeve të mëposhtme të LiteRT në C++.

Inferenca Bazë (CPU)

Më poshtë është një version i shkurtuar i fragmenteve të kodit nga seksioni "Fillimi" . Është implementimi më i thjeshtë i inferencës me 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-kopje me memorien pritëse

API-ja e Modelit të Kompiluar LiteRT zvogëlon fërkimin e tubacioneve të inferencës, veçanërisht kur merret me backend-e të shumëfishta harduerike dhe rrjedha zero-kopje. Fragmenti i mëposhtëm i kodit përdor metodën CreateFromHostMemory kur krijon buffer-in e hyrjes, i cili përdor zero-kopje me memorien pritëse.

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