Akceleracja GPU za pomocą LiteRT

Procesory graficzne (GPU) są powszechnie używane do przyspieszania uczenia głębokiego ze względu na ich ogromną przepustowość równoległą w porównaniu z procesorami. LiteRT upraszcza proces korzystania z akceleracji GPU, umożliwiając użytkownikom określenie akceleracji sprzętowej jako parametru podczas tworzenia skompilowanego modelu (CompiledModel).

Dzięki akceleracji GPU w LiteRT możesz tworzyć bufory wejściowe i wyjściowe przyjazne dla GPU, osiągać zerowe kopiowanie danych w pamięci GPU i wykonywać zadania asynchronicznie, aby zmaksymalizować równoległość.

Przykładowe implementacje LiteRT GPU znajdziesz w tych aplikacjach demonstracyjnych:

Dodawanie zależności GPU

Aby dodać zależność GPU do aplikacji w języku Kotlin lub C++, wykonaj te czynności.

Kotlin

W przypadku użytkowników języka Kotlin akcelerator GPU jest wbudowany i nie wymaga dodatkowych czynności poza tymi opisanymi w przewodniku Wprowadzenie.

C++

Użytkownicy C++ muszą skompilować zależności aplikacji z akceleracją GPU LiteRT. cc_binary reguła, która pakuje podstawową logikę aplikacji (np. main.cc) wymaga tych komponentów środowiska wykonawczego:

  • Biblioteka współdzielona LiteRT C API: atrybut data musi zawierać bibliotekę współdzieloną LiteRT C API (//litert/c:litert_runtime_c_api_shared_lib) i komponenty specyficzne dla procesora graficznego (@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so).
  • Zależności atrybutów: atrybut deps zwykle zawiera zależności GLESgles_deps(), a atrybut linkopts zwykle zawieragles_linkopts(). Obie te technologie są bardzo istotne w przypadku akceleracji za pomocą procesora graficznego, ponieważ LiteRT często używa OpenGLES na Androidzie.
  • Pliki modelu i inne komponenty: uwzględnione za pomocą atrybutu data.

Oto przykład reguły 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",
    ],
    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
)

Ta konfiguracja umożliwia dynamiczne wczytywanie i używanie GPU przez skompilowany plik binarny do przyspieszonego wnioskowania w uczeniu maszynowym.

Korzystanie z GPU w interfejsie API CompiledModel

Aby zacząć korzystać z akceleratora GPU, podczas tworzenia skompilowanego modelu (CompiledModel) przekaż parametr GPU. Poniższy fragment kodu przedstawia podstawową implementację całego procesu:

C++

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

// 2. Create a compiled model targeting GPU
LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
LITERT_ASSIGN_OR_RETURN(auto compiled_model, CompiledModel::Create(env, model, kLiteRtHwAcceleratorGpu));

// 3. Prepare input/output buffers
LITERT_ASSIGN_OR_RETURN(auto input_buffers, compiled_model.CreateInputBuffers());
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());

// 4. Fill input data (if you have CPU-based data)
input_buffers[0].Write<float>(absl::MakeConstSpan(cpu_data, data_size));

// 5. Execute
compiled_model.Run(input_buffers, output_buffers);

// 6. Access model output
std::vector<float> data(output_data_size);
output_buffers.Read<float>(absl::MakeSpan(data));

Kotlin

// Load model and initialize runtime
val  model =
    CompiledModel.create(
        context.assets,
        "mymodel.tflite",
        CompiledModel.Options(Accelerator.GPU),
        env,
    )

// Preallocate input/output buffers
val inputBuffers = model.createInputBuffers()
val outputBuffers = model.createOutputBuffers()

// Fill the first input
inputBuffers[0].writeFloat(FloatArray(data_size) { data_value /* your data */ })

// Invoke
model.run(inputBuffers, outputBuffers)

// Read the output
val outputFloatArray = outputBuffers[0].readFloat()

Więcej informacji znajdziesz w przewodnikach Pierwsze kroki z C++Pierwsze kroki z Kotlinem.

Kopiowanie zerowe z akceleracją GPU

Używanie zerowego kopiowania umożliwia procesorowi graficznemu bezpośredni dostęp do danych w jego własnej pamięci bez konieczności jawnego kopiowania tych danych przez procesor. Dzięki temu, że nie trzeba kopiować danych do pamięci procesora ani z niej, zero-copy może znacznie skrócić opóźnienie od początku do końca.

Poniższy kod to przykładowa implementacja Zero-Copy GPU z OpenGL, interfejsem API do renderowania grafiki wektorowej. Kod przekazuje obrazy w formacie bufora OpenGL bezpośrednio do LiteRT:

// Suppose you have an OpenGL buffer consisting of:
// target (GLenum), id (GLuint), size_bytes (size_t), and offset (size_t)
// Load model and compile for GPU
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, kLiteRtHwAcceleratorGpu));

// Create a TensorBuffer that wraps the OpenGL buffer.
LITERT_ASSIGN_OR_RETURN(auto tensor_type, model.GetInputTensorType("input_tensor_name"));
LITERT_ASSIGN_OR_RETURN(auto gl_input_buffer, TensorBuffer::CreateFromGlBuffer(env,
    tensor_type, opengl_buffer.target, opengl_buffer.id, opengl_buffer.size_bytes, opengl_buffer.offset));
std::vector<TensorBuffer> input_buffers{gl_input_buffer};
LITERT_ASSIGN_OR_RETURN(auto output_buffers, compiled_model.CreateOutputBuffers());

// Execute
compiled_model.Run(input_buffers, output_buffers);

// If your output is also GPU-backed, you can fetch an OpenCL buffer or re-wrap it as an OpenGL buffer:
LITERT_ASSIGN_OR_RETURN(auto out_cl_buffer, output_buffers[0].GetOpenClBuffer());

Asynchroniczne wykonywanie

Asynchroniczne metody LiteRT, takie jak RunAsync(), umożliwiają planowanie wnioskowania na GPU podczas wykonywania innych zadań na CPU lub NPU. W złożonych potokach procesor graficzny jest często używany asynchronicznie z procesorem lub jednostkami NPU.

Ten fragment kodu jest oparty na kodzie podanym w przykładzie akceleracji GPU bez kopiowania. Kod używa asynchronicznie zarówno procesora, jak i procesora graficznego, a do bufora wejściowego dołącza LiteRT Event. LiteRT Event zarządza różnymi typami elementów synchronizacji, a poniższy kod tworzy zarządzany obiekt zdarzenia LiteRT typu LiteRtEventTypeEglSyncFence. Ten obiekt Event zapewnia, że nie będziemy odczytywać z bufora wejściowego, dopóki GPU nie zakończy pracy. Wszystko to odbywa się bez udziału procesora.

LITERT_ASSIGN_OR_RETURN(auto env, Environment::Create({}));
LITERT_ASSIGN_OR_RETURN(auto compiled_model,
    CompiledModel::Create(env, model, kLiteRtHwAcceleratorGpu));

// 1. Prepare input buffer (OpenGL buffer)
LITERT_ASSIGN_OR_RETURN(auto gl_input,
    TensorBuffer::CreateFromGlBuffer(env, tensor_type, opengl_tex));
std::vector<TensorBuffer> inputs{gl_input};
LITERT_ASSIGN_OR_RETURN(auto outputs, compiled_model.CreateOutputBuffers());

// 2. If the GL buffer is in use, create and set an event object to synchronize with the GPU.
LITERT_ASSIGN_OR_RETURN(auto input_event,
    Event::CreateManagedEvent(env, LiteRtEventTypeEglSyncFence));
inputs[0].SetEvent(std::move(input_event));

// 3. Kick off the GPU inference
compiled_model.RunAsync(inputs, outputs);

// 4. Meanwhile, do other CPU work...
// CPU Stays busy ..

// 5. Access model output
std::vector<float> data(output_data_size);
outputs[0].Read<float>(absl::MakeSpan(data));

Obsługiwane modele

LiteRT obsługuje akcelerację GPU w przypadku tych modeli: Wyniki testów porównawczych są oparte na testach przeprowadzonych na urządzeniu Samsung Galaxy S24.

Model Akceleracja GPU LiteRT GPU LiteRT (ms)
hf_mms_300m W pełni przekazane 19,6
hf_mobilevit_small W pełni przekazane 8.7
hf_mobilevit_small_e2e W pełni przekazane z Androidem 8.0
hf_wav2vec2_base_960h W pełni przekazane 9.1
hf_wav2vec2_base_960h_dynamic W pełni przekazane 9.8
isnet W pełni przekazane 43,1
timm_efficientnet W pełni przekazane 3.7
timm_nfnet W pełni przekazane 9.7
timm_regnety_120 W pełni przekazane 12.1
torchaudio_deepspeech W pełni przekazane 4.6
torchaudio_wav2letter W pełni przekazane 4.8
torchvision_alexnet W pełni przekazane 3.3
torchvision_deeplabv3_mobilenet_v3_large W pełni przekazane 5.7
torchvision_deeplabv3_resnet101 W pełni przekazane 35,1
torchvision_deeplabv3_resnet50 W pełni przekazane 24,5
torchvision_densenet121 W pełni przekazane 13,9
torchvision_efficientnet_b0 W pełni przekazane 3.6
torchvision_efficientnet_b1 W pełni przekazane 4,7
torchvision_efficientnet_b2 W pełni przekazane 5,0
torchvision_efficientnet_b3 W pełni przekazane 6.1
torchvision_efficientnet_b4 W pełni przekazane 7.6
torchvision_efficientnet_b5 W pełni przekazane 8.6
torchvision_efficientnet_b6 W pełni przekazane 11.2
torchvision_efficientnet_b7 W pełni przekazane 14.7
torchvision_fcn_resnet50 W pełni przekazane 19,9
torchvision_googlenet W pełni przekazane 3,9
torchvision_inception_v3 W pełni przekazane 8.6
torchvision_lraspp_mobilenet_v3_large W pełni przekazane 3.3
torchvision_mnasnet0_5 W pełni przekazane 2.4
torchvision_mobilenet_v2 W pełni przekazane 2.8
torchvision_mobilenet_v3_large W pełni przekazane 2.8
torchvision_mobilenet_v3_small W pełni przekazane 2.3
torchvision_resnet152 W pełni przekazane 15,0
torchvision_resnet18 W pełni przekazane 4,3
torchvision_resnet50 W pełni przekazane 6.9
torchvision_squeezenet1_0 W pełni przekazane 2.9
torchvision_squeezenet1_1 W pełni przekazane 2,5
torchvision_vgg16 W pełni przekazane 13.4
torchvision_wide_resnet101_2 W pełni przekazane 25,0
torchvision_wide_resnet50_2 W pełni przekazane 13.4
u2net_full W pełni przekazane 98,3
u2net_lite W pełni przekazane 51,4
hf_distil_whisper_small_no_cache Częściowo przekazane 251,90 EGP
hf_distilbert Częściowo przekazane 13.7
hf_tinyroberta_squad2 Częściowo przekazane 17.1
hf_tinyroberta_squad2_dynamic_batch Częściowo przekazane 52,1
snapml_StyleTransferNet Częściowo przekazane 40,9
timm_efficientformer_l1 Częściowo przekazane 17,6
timm_efficientformerv2_s0 Częściowo przekazane 16.1
timm_pvt_v2_b1 Częściowo przekazane 73,5
timm_pvt_v2_b3 Częściowo przekazane 246,70 EGP
timm_resnest14d Częściowo przekazane 88,9
torchaudio_conformer Częściowo przekazane 21,5
torchvision_convnext_tiny Częściowo przekazane 8.2
torchvision_maxvit_t Częściowo przekazane 194,0
torchvision_shufflenet_v2 Częściowo przekazane 9.5
torchvision_swin_tiny Częściowo przekazane 164,4
torchvision_video_resnet2plus1d_18 Częściowo przekazane 6832,0
torchvision_video_swin3d_tiny Częściowo przekazane 2617,80 EGP
yolox_tiny Częściowo przekazane 11.2