Die LiteRT CompiledModel API ist in C++ verfügbar und bietet Entwicklern eine detaillierte Kontrolle über die Speicherzuweisung und die Low-Level-Entwicklung. Ein Beispiel finden Sie in der C++-App zur Bildsegmentierung.
Im folgenden Leitfaden wird die grundlegende CPU-Inferenz der CompiledModel Kotlin API beschrieben. Weitere Informationen zu erweiterten Beschleunigungsfunktionen finden Sie im Leitfaden zur GPU-Beschleunigung und zur NPU-Beschleunigung.
Build-Abhängigkeit hinzufügen
Wählen Sie den Pfad aus, der zu Ihrem Projekt passt:
Vorkompilierte Bibliothek verwenden (plattformübergreifend): Verwenden Sie die vorkompilierte LiteRT-Bibliothek für die sofortige Einrichtung. Hier erfahren Sie, wie Sie die vorgefertigte C++-Bibliothek aus dem LiteRT-Maven-Paket unter Android verwenden. Sie können die vorgefertigte C++-Binärdatei auch unter Android, iOS, macOS, Linux und Windows herunterladen und einbinden.
Aus Quelle erstellen (plattformübergreifend): Mit CMake aus der Quelle erstellen, um die volle Kontrolle zu haben und mehrere Plattformen (Android, iOS, macOS, Linux, Windows) zu unterstützen. Weitere Informationen finden Sie in diesem Leitfaden.
Einfache Inferenz
In diesem Abschnitt wird beschrieben, wie die grundlegende Inferenz durchgeführt wird.
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({}));
CompiledModel erstellen
Nachdem Sie ein LiteRT-Modell erhalten oder ein Modell in das .tflite-Format konvertiert haben, initialisieren Sie die Laufzeit mit der Modelldatei ü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 die Ausgabedaten, die das Modell nach der Ausführung 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 CompiledModel API.
Fehlerbehandlung
LiteRT verwendet litert::Expected, um entweder Werte zurückzugeben oder Fehler ähnlich 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 = CompiledModel::Create(env, "mymodel.tflite", HwAccelerators::kCpu); 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.
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 Pufferarten wie AHardwareBuffer ermöglichen die Interoperabilität mit anderen Pufferarten. 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 env, Environment::Create({}));
LITERT_ASSIGN_OR_RETURN(auto compiled_model, CompiledModel::Create(env, "mymodel.tflite",
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 CompiledModel 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 compiled_model1, CompiledModel::Create(env, "model1.tflite", 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 compiled_model2, CompiledModel::Create(env, "model2.tflite", 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);