Interfejs LiteRT CompiledModel API jest dostępny w C++, co daje deweloperom szczegółową kontrolę nad alokacją pamięci i programowaniem niskiego poziomu. Przykład znajdziesz w aplikacji C++ do segmentacji obrazu.
Poniższy przewodnik pokazuje podstawowe wnioskowanie na procesorze za pomocą interfejsu CompiledModel Kotlin API. Więcej informacji o zaawansowanych funkcjach akceleracji znajdziesz w przewodnikach dotyczących akceleracji przez GPU i akceleracji przez NPU.
Dodawanie zależności kompilacji
Wybierz ścieżkę odpowiednią dla Twojego projektu:
Użyj gotowej biblioteki (wieloplatformowej): użyj gotowej biblioteki LiteRT, aby błyskawicznie skonfigurować system. Dowiedz się, jak używać gotowej biblioteki C++ z pakietu LiteRT Maven na Androidzie, lub pobierz/zintegruj gotowy plik binarny C++ na Androidzie, iOS, macOS, Linuxie i Windowsie.
Kompilacja ze źródeł (wieloplatformowa): kompilacja ze źródeł za pomocą CMake, która zapewnia pełną kontrolę i obsługę wielu platform (Android, iOS, macOS, Linux, Windows). Szczegółowe informacje znajdziesz w tym przewodniku.
Podstawowe wnioskowanie
W tej sekcji pokazujemy, jak przeprowadza się podstawowe wnioskowanie.
Tworzenie środowiska
Obiekt Environment zapewnia środowisko wykonawcze, które zawiera komponenty takie jak ścieżka wtyczki kompilatora i konteksty GPU. Właściwość Environment jest wymagana podczas tworzenia właściwości CompiledModel i TensorBuffer. Poniższy kod tworzy element Environment do wykonywania na procesorze i GPU bez żadnych opcji:
LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
Utwórz CompiledModel
Po uzyskaniu modelu LiteRT lub przekonwertowaniu modelu do formatu .tflite zainicjuj środowisko wykonawcze za pomocą pliku modelu, korzystając z interfejsu CompiledModel API.
W tym momencie możesz określić akcelerację sprzętową (kLiteRtHwAcceleratorCpu lub kLiteRtHwAcceleratorGpu):
LITERT_ASSIGN_OR_RETURN(auto compiled_model,
CompiledModel::Create(env, model, kLiteRtHwAcceleratorCpu));
Tworzenie buforów wejściowych i wyjściowych
Utwórz niezbędne struktury danych (bufory) do przechowywania danych wejściowych, które będziesz przekazywać do modelu na potrzeby wnioskowania, oraz danych wyjściowych, które model generuje po uruchomieniu wnioskowania.
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());
Jeśli używasz pamięci procesora, wypełnij pola wejściowe, wpisując dane bezpośrednio do pierwszego bufora wejściowego.
input_buffers[0].Write<float>(absl::MakeConstSpan(input_data, input_size));
Wywoływanie modelu
Podaj bufory wejściowe i wyjściowe, a następnie uruchom skompilowany model z modelem i akceleracją sprzętową określonymi w poprzednich krokach.
compiled_model.Run(input_buffers, output_buffers);
Pobieranie danych wyjściowych
Pobieraj dane wyjściowe, odczytując je bezpośrednio z pamięci.
std::vector<float> data(output_data_size);
output_buffers[0].Read<float>(absl::MakeSpan(data));
// ... process output data
Kluczowe pojęcia i komponenty
W kolejnych sekcjach znajdziesz informacje o kluczowych koncepcjach i komponentach interfejsu LiteRT CompiledModel API.
Obsługa błędów
LiteRT używa litert::Expected do zwracania wartości lub propagowania błędów w sposób podobny do absl::StatusOr lub std::expected. Możesz samodzielnie sprawdzić, czy występuje błąd.
Aby ułatwić Ci pracę, LiteRT udostępnia te makra:
Funkcja
LITERT_ASSIGN_OR_RETURN(lhs, expr)przypisuje wynik funkcjiexprdo funkcjilhs, jeśli nie generuje ona błędu, a w przeciwnym razie zwraca błąd.Rozwinie się do postaci podobnej do poniższego fragmentu kodu.
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)działa tak samo jakLITERT_ASSIGN_OR_ABORT(lhs, expr), ale w przypadku błędu przerywa działanie programu.LITERT_ASSIGN_OR_RETURNLITERT_RETURN_IF_ERROR(expr)zwracaexpr, jeśli ocena spowoduje błąd.LITERT_ABORT_IF_ERROR(expr)działa tak samo jakLITERT_RETURN_IF_ERROR, ale w przypadku błędu przerywa działanie programu.
Więcej informacji o makrach LiteRT znajdziesz w litert_macros.h.
Bufor tensora (TensorBuffer)
LiteRT zapewnia wbudowaną obsługę interoperacyjności buforów wejścia/wyjścia za pomocą interfejsu Tensor Buffer API (TensorBuffer), który obsługuje przepływ danych do i z skompilowanego modelu. Interfejs Tensor Buffer API umożliwia zapisywanie (Write<T>()) i odczytywanie (Read<T>()) danych oraz blokowanie pamięci procesora.
Aby uzyskać pełniejszy obraz implementacji interfejsu TensorBuffer API, zapoznaj się z kodem źródłowym pliku litert_tensor_buffer.h.
Wymagania dotyczące danych wejściowych i wyjściowych modelu zapytania
Wymagania dotyczące przydzielania bufora tensora (TensorBuffer) są zwykle określane przez akcelerator sprzętowy. Bufory danych wejściowych i wyjściowych mogą mieć wymagania dotyczące wyrównania, kroków bufora i typu pamięci. Możesz użyć funkcji pomocniczych, takich jak CreateInputBuffers, aby automatycznie spełniać te wymagania.
Poniższy uproszczony fragment kodu pokazuje, jak pobrać wymagania dotyczące bufora danych wejściowych:
LITERT_ASSIGN_OR_RETURN(auto reqs, compiled_model.GetInputBufferRequirements(signature_index, input_index));
Aby uzyskać pełniejszy obraz implementacji interfejsu TensorBufferRequirements API, zapoznaj się z kodem źródłowym pliku litert_tensor_buffer_requirements.h.
Tworzenie zarządzanych buforów tensorów (TensorBuffers)
Poniższy uproszczony fragment kodu pokazuje, jak utworzyć zarządzane bufory tensorów, w których interfejs TensorBuffer API przydziela odpowiednie bufory:
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));
Tworzenie buforów tensorowych bez kopiowania
Aby opakować istniejący bufor jako bufor tensora (bez kopiowania), użyj tego fragmentu kodu:
// 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));
Odczytywanie i zapisywanie w buforze tensora
Poniższy fragment kodu pokazuje, jak odczytywać dane z bufora wejściowego i zapisywać je w buforze wyjściowym:
// 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 */
}
Zaawansowane: interoperacyjność bufora bez kopiowania w przypadku specjalistycznych typów buforów sprzętowych
Niektóre typy buforów, np. AHardwareBuffer, umożliwiają współdziałanie z innymi typami buforów. Na przykład bufor OpenGL można utworzyć z obiektu AHardwareBuffer bez kopiowania. Poniższy fragment kodu pokazuje przykład:
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());
Bufory OpenCL można też tworzyć na podstawie tych elementów:AHardwareBuffer
LITERT_ASSIGN_OR_RETURN(auto cl_buffer, tensor_buffer_ahwb.GetOpenClMemory());
Na urządzeniach mobilnych, które obsługują współdziałanie OpenCL i OpenGL, bufory CL można tworzyć z buforów 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());
Przykładowe implementacje
Zapoznaj się z tymi implementacjami LiteRT w C++.
Podstawowe wnioskowanie (CPU)
Poniżej znajdziesz skróconą wersję fragmentów kodu z sekcji Rozpocznij. Jest to najprostsza implementacja wnioskowania za pomocą 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));
Kopiowanie zerowe z pamięcią hosta
Interfejs LiteRT CompiledModel API zmniejsza trudności związane z potokami wnioskowania, szczególnie w przypadku wielu backendów sprzętowych i przepływów bez kopiowania. Poniższy fragment kodu używa metody CreateFromHostMemory podczas tworzenia bufora wejściowego, który wykorzystuje kopiowanie zerowe z pamięcią hosta.
// 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);