האצת GPU באמצעות LiteRT

מעבדים גרפיים (GPU) משמשים בדרך כלל להאצת למידה עמוקה, בגלל התפוקה המקבילית הגבוהה שלהם בהשוואה למעבדים מרכזיים (CPU). ‫LiteRT מפשטת את תהליך השימוש בהאצת GPU בכך שהיא מאפשרת למשתמשים לציין את האצת החומרה כפרמטר בעת יצירת מודל מהודר (CompiledModel).

בעזרת האצת ה-GPU של LiteRT, אתם יכולים ליצור מאגרי קלט ופלט שמתאימים ל-GPU, להשיג אפס עותקים של הנתונים בזיכרון ה-GPU ולהריץ משימות באופן אסינכרוני כדי למקסם את המקביליות.

דוגמאות להטמעה של LiteRT GPU אפשר למצוא באפליקציות ההדגמה הבאות:

הוספת תלות ב-GPU

כדי להוסיף תלות ב-GPU לאפליקציית Kotlin או C++:

Kotlin

משתמשי Kotlin יכולים להשתמש במאיץ ה-GPU באופן מובנה, בלי לבצע שלבים נוספים מעבר לאלה שמופיעים במדריך תחילת העבודה.

C++‎

משתמשי C++ צריכים לבנות את התלות של האפליקציה עם האצת GPU של LiteRT. הכלל cc_binary שאורז את הלוגיקה של אפליקציית הליבה (למשל, ‫main.cc) דורש את רכיבי זמן הריצה הבאים:

  • LiteRT C API shared library: המאפיין data צריך לכלול את הספרייה המשותפת של LiteRT C API‏ (//litert/c:litert_runtime_c_api_shared_lib) ואת הרכיבים הספציפיים ל-GPU‏ (@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so).
  • תלות בין מאפיינים: המאפיין deps כולל בדרך כלל תלות ב-GLES gles_deps(), והמאפיין linkopts כולל בדרך כלל את gles_linkopts(). שניהם רלוונטיים מאוד להאצת GPU, כי LiteRT משתמש לעיתים קרובות ב-OpenGLES ב-Android.
  • קבצי מודלים ונכסים אחרים: נכללים באמצעות המאפיין data.

דוגמה לכלל 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
)

ההגדרה הזו מאפשרת לבינארי המהודר לטעון באופן דינמי את ה-GPU ולהשתמש בו כדי להסיק מסקנות מנתונים באמצעות למידת מכונה מואצת.

שימוש ב-GPU עם CompiledModel API

כדי להתחיל להשתמש בהאצת GPU, מעבירים את פרמטר ה-GPU כשיוצרים את המודל המהודר (CompiledModel). קטע הקוד הבא מציג הטמעה בסיסית של התהליך כולו:

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()

מידע נוסף זמין במדריכים תחילת העבודה עם C++‎ או תחילת העבודה עם Kotlin.

העתקה ללא שימוש ב-GPU

שימוש בשיטת העתקה ללא העתקה מאפשר ליחידת GPU לגשת לנתונים ישירות בזיכרון שלה בלי שה-CPU יצטרך להעתיק את הנתונים באופן מפורש. היעדר העתקה של נתונים לזיכרון המעבד וממנו מאפשר לצמצם משמעותית את זמן האחזור מקצה לקצה.

הקוד הבא הוא דוגמה להטמעה של GPU עם העתקה אפסית באמצעות OpenGL, ממשק API לעיבוד של גרפיקה וקטורית. הקוד מעביר תמונות בפורמט של מאגר OpenGL ישירות אל 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());

ביצוע אסינכרוני

השיטות האסינכרוניות של LiteRT, כמו RunAsync(), מאפשרות לתזמן הסקה של GPU תוך כדי המשך ביצוע משימות אחרות באמצעות המעבד או ה-NPU. בצינורות מורכבים, לעיתים קרובות נעשה שימוש ב-GPU באופן אסינכרוני לצד CPU או NPU.

קטע הקוד הבא מבוסס על הקוד שמופיע בדוגמה של האצת GPU ללא העתקה. הקוד משתמש ב-CPU וב-GPU באופן אסינכרוני ומצרף LiteRT Event למאגר הקלט. ‫LiteRT Event אחראי לניהול סוגים שונים של פרימיטיבים של סנכרון, והקוד הבא יוצר אובייקט LiteRT מנוהל מסוג LiteRtEventTypeEglSyncFence. אובייקט Event הזה מבטיח שלא נקרא ממאגר הקלט עד שה-GPU יסיים את הפעולה. כל הפעולות האלה מתבצעות בלי להשתמש במעבד.

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));

מודלים נתמכים

‫LiteRT תומך בהאצת GPU עם הדגמים הבאים. תוצאות ההשוואה מבוססות על בדיקות שבוצעו במכשיר Samsung Galaxy S24.

מודל האצת GPU של LiteRT LiteRT GPU (ms)
hf_mms_300m הועברה הרשאה מלאה 19.6
hf_mobilevit_small הועברה הרשאה מלאה 8.7
hf_mobilevit_small_e2e הועברה הרשאה מלאה ‫8.0
hf_wav2vec2_base_960h הועברה הרשאה מלאה ‫9.1
hf_wav2vec2_base_960h_dynamic הועברה הרשאה מלאה 9.8
isnet הועברה הרשאה מלאה 43.1
timm_efficientnet הועברה הרשאה מלאה ‫3.7
timm_nfnet הועברה הרשאה מלאה 9.7
timm_regnety_120 הועברה הרשאה מלאה 12.1
torchaudio_deepspeech הועברה הרשאה מלאה 4.6
torchaudio_wav2letter הועברה הרשאה מלאה 4.8
torchvision_alexnet הועברה הרשאה מלאה 3.3
torchvision_deeplabv3_mobilenet_v3_large הועברה הרשאה מלאה 5.7
torchvision_deeplabv3_resnet101 הועברה הרשאה מלאה 35.1
torchvision_deeplabv3_resnet50 הועברה הרשאה מלאה 24.5
torchvision_densenet121 הועברה הרשאה מלאה 13.9
torchvision_efficientnet_b0 הועברה הרשאה מלאה 3.6
torchvision_efficientnet_b1 הועברה הרשאה מלאה 4.7
torchvision_efficientnet_b2 הועברה הרשאה מלאה 5.0
torchvision_efficientnet_b3 הועברה הרשאה מלאה 6.1
torchvision_efficientnet_b4 הועברה הרשאה מלאה 7.6
torchvision_efficientnet_b5 הועברה הרשאה מלאה 8.6
torchvision_efficientnet_b6 הועברה הרשאה מלאה ‫11.2
torchvision_efficientnet_b7 הועברה הרשאה מלאה ‫14.7
torchvision_fcn_resnet50 הועברה הרשאה מלאה 19.9
torchvision_googlenet הועברה הרשאה מלאה 3.9
torchvision_inception_v3 הועברה הרשאה מלאה 8.6
torchvision_lraspp_mobilenet_v3_large הועברה הרשאה מלאה 3.3
torchvision_mnasnet0_5 הועברה הרשאה מלאה 2.4
torchvision_mobilenet_v2 הועברה הרשאה מלאה 2.8
torchvision_mobilenet_v3_large הועברה הרשאה מלאה 2.8
torchvision_mobilenet_v3_small הועברה הרשאה מלאה 2.3
torchvision_resnet152 הועברה הרשאה מלאה 15.0
torchvision_resnet18 הועברה הרשאה מלאה 4.3
torchvision_resnet50 הועברה הרשאה מלאה 6.9
torchvision_squeezenet1_0 הועברה הרשאה מלאה ‫2.9
torchvision_squeezenet1_1 הועברה הרשאה מלאה 2.5
torchvision_vgg16 הועברה הרשאה מלאה ‫13.4
torchvision_wide_resnet101_2 הועברה הרשאה מלאה 25.0
torchvision_wide_resnet50_2 הועברה הרשאה מלאה ‫13.4
u2net_full הועברה הרשאה מלאה 98.3
u2net_lite הועברה הרשאה מלאה ‫51.4
hf_distil_whisper_small_no_cache הוענקו הרשאות חלקיות 251.9
hf_distilbert הוענקו הרשאות חלקיות 13.7
hf_tinyroberta_squad2 הוענקו הרשאות חלקיות 17.1
hf_tinyroberta_squad2_dynamic_batch הוענקו הרשאות חלקיות 52.1
snapml_StyleTransferNet הוענקו הרשאות חלקיות 40.9
timm_efficientformer_l1 הוענקו הרשאות חלקיות 17.6
timm_efficientformerv2_s0 הוענקו הרשאות חלקיות 16.1
timm_pvt_v2_b1 הוענקו הרשאות חלקיות 73.5
timm_pvt_v2_b3 הוענקו הרשאות חלקיות 246.7
timm_resnest14d הוענקו הרשאות חלקיות 88.9
torchaudio_conformer הוענקו הרשאות חלקיות 21.5
torchvision_convnext_tiny הוענקו הרשאות חלקיות 8.2
torchvision_maxvit_t הוענקו הרשאות חלקיות 194.0
torchvision_shufflenet_v2 הוענקו הרשאות חלקיות 9.5
torchvision_swin_tiny הוענקו הרשאות חלקיות 164.4
torchvision_video_resnet2plus1d_18 הוענקו הרשאות חלקיות 6832.0
torchvision_video_swin3d_tiny הוענקו הרשאות חלקיות 2617.8
yolox_tiny הוענקו הרשאות חלקיות ‫11.2