Die LiteRT Compiled Model APIs sind in C++ verfügbar und bieten Android-Entwicklern eine detaillierte Kontrolle über die Speicherzuweisung und die Entwicklung auf niedriger Ebene.
Ein Beispiel für eine LiteRT-Anwendung in C++ finden Sie in der Demo zur asynchronen Segmentierung mit C++.
Jetzt starten
Führen Sie die folgenden Schritte aus, um die LiteRT Compiled Model API zu Ihrer Android-Anwendung hinzuzufügen.
Build-Konfiguration aktualisieren
Wenn Sie eine C++-Anwendung mit LiteRT für GPU-, NPU- und CPU-Beschleunigung mit Bazel erstellen, müssen Sie eine cc_binary-Regel definieren, damit alle erforderlichen Komponenten kompiliert, verknüpft und verpackt werden. Mit der folgenden Einrichtung kann Ihre Anwendung GPU-, NPU- und CPU-Beschleuniger dynamisch auswählen oder nutzen.
Hier sind die wichtigsten Komponenten in Ihrer Bazel-Build-Konfiguration:
cc_binary-Regel:Dies ist die grundlegende Bazel-Regel, mit der das ausführbare C++-Ziel definiert wird (z.B.name = "your_application_name") verwenden.srcs-Attribut:Hier werden die C++-Quelldateien Ihrer Anwendung aufgeführt, z.B.main.ccund andere.cc- oder.h-Dateien).dataAttribute (Laufzeitabhängigkeiten): Dies ist entscheidend für das Verpacken von gemeinsam genutzten Bibliotheken und Assets, die Ihre Anwendung zur Laufzeit lädt.- LiteRT Core Runtime:Die primäre gemeinsam genutzte LiteRT C-API-Bibliothek (z.B.
//litert/c:litert_runtime_c_api_shared_lib). - Dispatch-Bibliotheken:Anbieterspezifische freigegebene Bibliotheken, die LiteRT für die Kommunikation mit den Hardwaretreibern verwendet (z.B.
//litert/vendors/qualcomm/dispatch:dispatch_api_so). - GPU-Backend-Bibliotheken:Die gemeinsam genutzten Bibliotheken für die GPU-Beschleunigung (z.B.
"@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so). - NPU-Backend-Bibliotheken:Die spezifischen gemeinsam genutzten Bibliotheken für die NPU-Beschleunigung, z. B. die QNN HTP-Bibliotheken von Qualcomm (z. B.
@qairt//:lib/aarch64-android/libQnnHtp.so,@qairt//:lib/hexagon-v79/unsigned/libQnnHtpV79Skel.so). - Modelldateien und Assets:Ihre trainierten Modelldateien, Testbilder, Shader oder andere Daten, die zur Laufzeit benötigt werden (z.B.
:model_files,:shader_files).
- LiteRT Core Runtime:Die primäre gemeinsam genutzte LiteRT C-API-Bibliothek (z.B.
deps-Attribute (Kompilierungszeitabhängigkeiten): Hier werden die Bibliotheken aufgeführt, für die Ihr Code kompiliert werden muss.- LiteRT-APIs und ‑Dienstprogramme:Header und statische Bibliotheken für LiteRT-Komponenten wie Tensor-Puffer (z.B.
//litert/cc:litert_tensor_buffer). - Grafikbibliotheken (für GPU): Abhängigkeiten im Zusammenhang mit Grafik-APIs, wenn der GPU-Beschleuniger sie verwendet (z.B.
gles_deps()).
- LiteRT-APIs und ‑Dienstprogramme:Header und statische Bibliotheken für LiteRT-Komponenten wie Tensor-Puffer (z.B.
linkopts-Attribute:Gibt Optionen an, die an den Linker übergeben werden. Dazu kann auch das Verknüpfen mit Systembibliotheken gehören (z. B.-landroidfür Android-Builds oder GLES-Bibliotheken mitgles_linkopts()).
Das folgende Beispiel zeigt eine cc_binary-Regel:
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
)
Modell laden
Nachdem Sie ein LiteRT-Modell erhalten oder ein Modell in das .tflite-Format konvertiert haben, laden Sie das Modell, indem Sie ein Model-Objekt erstellen.
LITERT_ASSIGN_OR_RETURN(auto model, Model::CreateFromFile("mymodel.tflite"));
Umgebung erstellen
Das Environment-Objekt bietet eine Laufzeitumgebung, die Komponenten wie den Pfad des Compiler-Plug-ins und GPU-Kontexte enthält. Die Environment ist erforderlich, wenn Sie CompiledModel und TensorBuffer erstellen. Mit dem folgenden Code wird ein Environment für die CPU- und GPU-Ausführung ohne Optionen erstellt:
LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
Kompiliertes Modell erstellen
Initialisieren Sie die Laufzeit mit dem neu erstellten Model-Objekt über die CompiledModel API. Sie können die Hardwarebeschleunigung an dieser Stelle angeben (kLiteRtHwAcceleratorCpu oder kLiteRtHwAcceleratorGpu):
LITERT_ASSIGN_OR_RETURN(auto compiled_model,
CompiledModel::Create(env, model, kLiteRtHwAcceleratorCpu));
Eingabe- und Ausgabepuffer erstellen
Erstellen Sie die erforderlichen Datenstrukturen (Puffer) für die Eingabedaten, die Sie in das Modell für die Inferenz einfügen, und für die Ausgabedaten, die das Modell nach der Inferenz erzeugt.
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());
Wenn Sie den CPU-Arbeitsspeicher verwenden, füllen Sie die Eingaben, indem Sie Daten direkt in den ersten Eingabepuffer schreiben.
input_buffers[0].Write<float>(absl::MakeConstSpan(input_data, input_size));
Modell aufrufen
Stellen Sie die Ein- und Ausgabepuffer bereit und führen Sie das kompilierte Modell mit dem Modell und der Hardwarebeschleunigung aus, die in den vorherigen Schritten angegeben wurden.
compiled_model.Run(input_buffers, output_buffers);
Ausgaben abrufen
Ausgaben abrufen, indem die Modellausgabe direkt aus dem Speicher gelesen wird.
std::vector<float> data(output_data_size);
output_buffers[0].Read<float>(absl::MakeSpan(data));
// ... process output data
Wichtige Konzepte und Komponenten
In den folgenden Abschnitten finden Sie Informationen zu den wichtigsten Konzepten und Komponenten der LiteRT Compiled Model APIs.
Fehlerbehandlung
LiteRT verwendet litert::Expected, um entweder Werte zurückzugeben oder Fehler auf ähnliche Weise wie absl::StatusOr oder std::expected weiterzugeben. Sie können den Fehler auch manuell prüfen.
LiteRT bietet die folgenden Makros:
Mit
LITERT_ASSIGN_OR_RETURN(lhs, expr)wird das Ergebnis vonexprlhszugewiesen, wenn kein Fehler auftritt. Andernfalls wird der Fehler zurückgegeben.Es wird in etwa wie im folgenden Snippet erweitert.
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)macht dasselbe wieLITERT_ASSIGN_OR_RETURN, bricht das Programm aber im Fehlerfall ab.LITERT_RETURN_IF_ERROR(expr)gibtexprzurück, wenn bei der Auswertung ein Fehler auftritt.LITERT_ABORT_IF_ERROR(expr)hat dieselbe Funktion wieLITERT_RETURN_IF_ERROR, bricht das Programm aber im Fehlerfall ab.
Weitere Informationen zu LiteRT-Makros finden Sie unter litert_macros.h.
Kompiliertes Modell (CompiledModel)
Die Compiled Model API (CompiledModel) ist für das Laden eines Modells, das Anwenden von Hardwarebeschleunigung, das Instanziieren der Laufzeit, das Erstellen von Ein- und Ausgabepuffern und das Ausführen der Inferenz verantwortlich.
Das folgende vereinfachte Code-Snippet zeigt, wie die Compiled Model API ein LiteRT-Modell (.tflite) und den Zielhardwarebeschleuniger (GPU) verwendet und ein kompiliertes Modell erstellt, das für die Ausführung von Inferenz bereit ist.
// 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));
Das folgende vereinfachte Code-Snippet zeigt, wie die Compiled Model API einen Ein- und Ausgabepuffer verwendet und Inferenzen mit dem kompilierten Modell ausführt.
// 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)));
Eine umfassendere Ansicht der Implementierung der CompiledModel API finden Sie im Quellcode für litert_compiled_model.h.
Tensor-Puffer (TensorBuffer)
LiteRT bietet integrierte Unterstützung für die Interoperabilität von E/A-Puffern. Dazu wird die Tensor Buffer API (TensorBuffer) verwendet, um den Datenfluss in das und aus dem kompilierten Modell zu verarbeiten. Mit der Tensor Buffer API können Sie CPU-Speicher schreiben (Write<T>()), lesen (Read<T>()) und sperren.
Eine umfassendere Ansicht der Implementierung der TensorBuffer API finden Sie im Quellcode für litert_tensor_buffer.h.
Anforderungen an die Eingabe/Ausgabe von Abfragemodellen
Die Anforderungen für die Zuweisung eines Tensor-Puffers (TensorBuffer) werden in der Regel vom Hardwarebeschleuniger angegeben. Für Puffer für Ein- und Ausgaben können Anforderungen hinsichtlich Ausrichtung, Puffer-Strides und Speichertyp gelten. Mit Hilfsfunktionen wie CreateInputBuffers können Sie diese Anforderungen automatisch erfüllen.
Das folgende vereinfachte Code-Snippet zeigt, wie Sie die Pufferanforderungen für Eingabedaten abrufen können:
LITERT_ASSIGN_OR_RETURN(auto reqs, compiled_model.GetInputBufferRequirements(signature_index, input_index));
Eine umfassendere Ansicht der Implementierung der TensorBufferRequirements API finden Sie im Quellcode für litert_tensor_buffer_requirements.h.
Verwaltete Tensor-Puffer (TensorBuffers) erstellen
Das folgende vereinfachte Code-Snippet zeigt, wie verwaltete Tensor-Puffer erstellt werden, wobei die TensorBuffer API die entsprechenden Puffer zuweist:
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));
Tensor-Puffer mit Zero-Copy erstellen
Wenn Sie einen vorhandenen Puffer als Tensor-Puffer (Zero-Copy) umschließen möchten, verwenden Sie das folgende Code-Snippet:
// 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));
Aus Tensor-Puffer lesen und in Tensor-Puffer schreiben
Das folgende Snippet zeigt, wie Sie aus einem Eingabepuffer lesen und in einen Ausgabepuffer schreiben können:
// 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 */
}
Erweitert: Zero-Copy-Puffer-Interop für spezielle Hardwarepuffertypen
Bestimmte Puffertypen wie AHardwareBuffer ermöglichen die Interoperabilität mit anderen Puffertypen. Beispielsweise kann ein OpenGL-Puffer ohne Kopieren aus einem AHardwareBuffer erstellt werden. Das folgende Code-Snippet zeigt ein Beispiel:
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-Puffer können auch aus AHardwareBuffer erstellt werden:
LITERT_ASSIGN_OR_RETURN(auto cl_buffer, tensor_buffer_ahwb.GetOpenClMemory());
Auf Mobilgeräten, die die Interoperabilität zwischen OpenCL und OpenGL unterstützen, können CL-Puffer aus GL-Puffern erstellt werden:
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());
Beispielimplementierungen
Hier finden Sie Implementierungen von LiteRT in C++.
Einfache Inferenz (CPU)
Im Folgenden finden Sie eine gekürzte Version der Code-Snippets aus dem Abschnitt Erste Schritte. Dies ist die einfachste Implementierung der Inferenz mit 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 mit Hostspeicher
Die LiteRT Compiled Model API reduziert die Reibung von Inferenzpipelines, insbesondere bei der Verarbeitung mehrerer Hardware-Back-Ends und Zero-Copy-Abläufe. Im folgenden Code-Snippet wird die Methode CreateFromHostMemory beim Erstellen des Eingabepuffers verwendet, der Zero-Copy mit dem Hostspeicher nutzt.
// 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);