API-të LiteRT Next janë të disponueshme në C++ dhe mund t'u ofrojnë zhvilluesve Android kontroll më të madh mbi shpërndarjen e kujtesës dhe zhvillimin e nivelit të ulët sesa API-të Kotlin.
Për një shembull të një aplikacioni LiteRT Next në C++, shihni segmentimin asinkron me demonstrimin e C++ .
Filloni
Përdorni hapat e mëposhtëm për të shtuar LiteRT Next në aplikacionin tuaj Android.
Përditësoni konfigurimin e ndërtimit
Ndërtimi i një aplikacioni C++ me LiteRT për përshpejtimin e GPU, NPU dhe CPU 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 janë përpiluar, lidhur dhe paketuar. Konfigurimi i shembullit të mëposhtëm lejon që aplikacioni juaj të zgjedhë ose të përdorë në mënyrë dinamike përshpejtuesit GPU, NPU dhe CPU.
Këtu janë komponentët kryesorë në konfigurimin e ndërtimit të Bazel:
- Rregulla
cc_binary
: Ky është rregulli themelor i Bazelit që përdoret për të përcaktuar objektivin tuaj të ekzekutueshëm në C++ (p.sh.,name = "your_application_name"
). -
srcs
Atributi: Liston skedarët burim 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): Kjo është thelbësore për paketimin e bibliotekave dhe aseteve të përbashkëta që aplikacioni juaj ngarkon në kohën e 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 dispeçimit: Bibliotekat e përbashkëta specifike të shitësve që LiteRT përdor për të komunikuar me drejtuesit e harduerit (p.sh.,
//litert/vendors/qualcomm/dispatch:dispatch_api_so
). - Bibliotekat e GPU Backend: Bibliotekat e përbashkëta për përshpejtimin e GPU-së (p.sh.,
"@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so
). - Bibliotekat e fundit të NPU: Bibliotekat specifike të përbashkëta për përshpejtimin e NPU, të tilla si bibliotekat QNN HTP të Qualcomm (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ët ose çdo të dhënë tjetër që nevojitet në kohën e ekzekutimit (p.sh.,
:model_files
,:shader_files
).
- LiteRT Core Runtime: Biblioteka kryesore e përbashkët e LiteRT C API (p.sh.,
- Atributi
deps
(Varshmëritë në kohën e përpilimit): Kjo liston bibliotekat ku kodi juaj duhet të përpilohet.- API-të dhe shërbimet e LiteRT: Titujt dhe bibliotekat statike për komponentët e LiteRT si bufferat tensor (p.sh.,
//litert/cc:litert_tensor_buffer
). - Bibliotekat grafike (për GPU): Varësi që lidhen me API-të grafike nëse përshpejtuesi GPU i përdor ato (p.sh.,
gles_deps()
).
- API-të dhe shërbimet e LiteRT: Titujt dhe bibliotekat statike për komponentët e LiteRT si bufferat tensor (p.sh.,
- atributi
linkopts
: Përcakton opsionet që i kalohen lidhësit, të cilat mund të përfshijnë lidhjen kundër bibliotekave të sistemit (p.sh.-landroid
për ndërtimet e Android, ose bibliotekat GLES megles_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
)
Ngarkoni modelin
Pas marrjes së një modeli LiteRT, ose konvertimit të një modeli në formatin .tflite
, ngarkoni modelin duke krijuar një objekt Model
.
LITERT_ASSIGN_OR_RETURN(auto model, Model::CreateFromFile("mymodel.tflite"));
Krijoni mjedisin
Objekti Environment
ofron një mjedis ekzekutimi që përfshin komponentë të tillë si shtegu i shtojcës së përpiluesit dhe kontekstet GPU. Environment
kërkohet kur krijoni CompiledModel
dhe TensorBuffer
. Kodi i mëposhtëm krijon një Environment
për ekzekutimin e CPU dhe GPU pa asnjë opsion:
LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
Krijoni modelin e përpiluar
Duke përdorur API- CompiledModel
, inicializoni kohën e ekzekutimit me objektin Model
të sapokrijuar. Ju 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));
Krijoni bufera hyrëse dhe dalëse
Krijoni strukturat e nevojshme të të dhënave (buffers) për të mbajtur të dhënat hyrëse që do të futni në model për konkluzion, dhe të dhënat dalëse që modeli prodhon pas ekzekutimit të konkluzionit.
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());
Nëse jeni duke përdorur memorie CPU, plotësoni hyrjet duke shkruar të dhënat direkt në buferin e parë të hyrjes.
input_buffers[0].Write<float>(absl::MakeConstSpan(input_data, input_size));
Thirrni modelin
Duke siguruar buferët e hyrjes dhe daljes, ekzekutoni Modelin e Përpiluar me modelin dhe përshpejtimin e harduerit të specifikuar në hapat e mëparshëm.
compiled_model.Run(input_buffers, output_buffers);
Merr rezultatet
Merrni rezultatet duke lexuar drejtpërdrejt 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 komponentët kryesorë
Referojuni seksioneve të mëposhtme për informacion mbi konceptet dhe përbërësit kryesorë të LiteRT Next API.
Trajtimi i gabimeve
LiteRT përdor litert::Expected
ose për të kthyer vlerat ose për të përhapur gabime në një mënyrë të ngjashme me absl::StatusOr
ose std::expected
. Ju mund ta kontrolloni vetë gabimin manualisht.
Për lehtësi, LiteRT ofron makrot e mëposhtme:
LITERT_ASSIGN_OR_RETURN(lhs, expr)
cakton rezultatin eexpr
nëlhs
nëse nuk prodhon një gabim dhe përndryshe 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ë siLITERT_ASSIGN_OR_RETURN
por anulon programin në rast gabimi.LITERT_RETURN_IF_ERROR(expr)
kthenexpr
nëse vlerësimi i tij prodhon një gabim.LITERT_ABORT_IF_ERROR(expr)
bën të njëjtën gjë siLITERT_RETURN_IF_ERROR
, por anulon programin në rast gabimi.
Për më shumë informacion mbi makrot LiteRT, shihni litert_macros.h
.
Modeli i përpiluar (Model i përpiluar)
API-ja e modelit të përpiluar ( CompiledModel
) është përgjegjëse për ngarkimin e një modeli, aplikimin e përshpejtimit të harduerit, instancimin e kohës së funksionimit, krijimin e buferave të hyrjes dhe daljes dhe ekzekutimin e konkluzioneve.
Pjesa e mëposhtme e thjeshtuar e kodit tregon sesi API-ja e Modelit të përpiluar merr një model LiteRT ( .tflite
) dhe përshpejtuesin e harduerit të synuar (GPU) dhe krijon një model të përpiluar që është gati për të ekzekutuar konkluzionet.
// 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));
Pjesa e mëposhtme e thjeshtuar e kodit demonstron se si API-ja e Modelit të Përpiluar merr një bufer hyrëse dhe dalëse dhe ekzekuton konkluzionet me modelin e përpiluar.
// 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 CompiledModel
, shihni kodin burimor për litert_compiled_model.h .
Buffer tensor (TensorBuffer)
LiteRT Next ofron mbështetje të integruar për ndërveprimin e tamponit I/O, duke përdorur API-në e Bufferit Tensor ( TensorBuffer
) për të trajtuar rrjedhën e të dhënave brenda dhe jashtë modelit të përpiluar. API Tensor Buffer ofron mundësinë për të shkruar ( Write<T>()
) dhe për të lexuar ( Read<T>()
), dhe për të kyçur kujtesën e CPU.
Për një pamje më të plotë se si zbatohet API TensorBuffer
, shihni kodin burimor për litert_tensor_buffer.h .
Kërkoni kërkesat e hyrjes/daljes së modelit
Kërkesat për caktimin e një Buffer Tensor ( TensorBuffer
) zakonisht specifikohen nga përshpejtuesi i harduerit. Buferët për hyrjet dhe daljet mund të kenë kërkesa në lidhje me shtrirjen, hapat e tamponit dhe llojin e memories. Ju mund të përdorni funksione ndihmëse si CreateInputBuffers
për të trajtuar automatikisht këto kërkesa.
Pjesa e mëposhtme e thjeshtuar e kodit tregon se si mund të rikuperoni kërkesat e buferit 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 TensorBufferRequirements
, shihni kodin burimor për litert_tensor_buffer_requirements.h .
Krijo buffers të menaxhuar tensor (TensorBuffers)
Pjesa e mëposhtme e thjeshtuar e kodit demonstron se si të krijohen Buffer-at e Menaxuar Tensor, ku API TensorBuffer
shpërndan buferët 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 Buffers tensor me zero-kopje
Për të mbështjellë një buffer ekzistues si një buffer tensor (zero-kopje), përdorni copëzimin e kodit të mëposhtëm:
// 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ë bufer hyrëse dhe të shkruani në një bufer dalëse:
// 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 */
}
E avancuar: Ndërveprimi i tamponit me kopjim zero për llojet e specializuara të tamponit të harduerit
Lloje të caktuara buffer, të tilla si AHardwareBuffer
, lejojnë ndërveprim me llojet e tjera të tamponëve. Për shembull, një buffer OpenGL mund të krijohet nga një AHardwareBuffer
me zero-kopje. Pjesa e mëposhtme e 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());
Buferët OpenCL mund të krijohen gjithashtu nga AHardwareBuffer
:
LITERT_ASSIGN_OR_RETURN(auto cl_buffer, tensor_buffer_ahwb.GetOpenClMemory());
Në pajisjet celulare që mbështesin ndërveprimin midis OpenCL dhe OpenGL, buferat CL mund të krijohen nga buferat 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 të zbatimeve
Referojuni zbatimeve të mëposhtme të LiteRT Next në C++.
Konkluzioni bazë (CPU)
Më poshtë është një version i kondensuar i pjesëve të kodit nga seksioni Fillo . Është zbatimi më i thjeshtë i konkluzionit me 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-kopjimi me memorien e hostit
LiteRT Next Compiled Model API zvogëlon fërkimin e tubacioneve të konkluzioneve, veçanërisht kur kemi të bëjmë me mbështetëse të shumta harduerike dhe flukse me zero kopje. Pjesa e mëposhtme e kodit përdor metodën CreateFromHostMemory
kur krijon buferin e hyrjes, i cili përdor zero-kopjimin me memorien e hostit.
// 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);