מעבדים גרפיים (GPU) משמשים בדרך כלל להאצת למידת עומק, בגלל הקיבולת העצומה שלהם לעיבוד מקביל בהשוואה למעבדים מרכזיים (CPU). LiteRT Next מפשט את התהליך של שימוש בהאצה של GPU, ומאפשר למשתמשים לציין את האצת החומרה כפרמטר כשיוצרים מודל מוערך (CompiledModel
). בנוסף, LiteRT Next משתמש בהטמעה חדשה ומשופרת של האצת GPU, שלא זמינה ב-LiteRT.
בעזרת האצת ה-GPU של LiteRT Next, אפשר ליצור מאגרי קלט ופלט שמותאמים ל-GPU, להשיג אפס העתקות של הנתונים בזיכרון ה-GPU ולבצע משימות באופן אסינכררוני כדי למקסם את היתירות.
להטמעות לדוגמה של LiteRT Next עם תמיכה ב-GPU, אפשר לעיין באפליקציות ההדגמה הבאות:
הוספת יחסי תלות ל-GPU
כך מוסיפים תלות ב-GPU לאפליקציה ב-Kotlin או ב-C++.
Kotlin
למשתמשי Kotlin, מואץ ה-GPU מובנה ולא נדרשים שלבים נוספים מעבר למדריך תחילת העבודה.
C++
משתמשים ב-C++ צריכים ליצור את יחסי התלות של האפליקציה עם האצת GPU של LiteRT. כלל cc_binary
שמארז את הלוגיקה של הליבה של האפליקציה (למשל, main.cc
) דורשים את רכיבי סביבת זמן הריצה הבאים:
- ספרייה משותפת של LiteRT C API: המאפיין
data
חייב לכלול את הספרייה המשותפת של LiteRT C API (//litert/c:litert_runtime_c_api_shared_lib
) ואת הרכיבים הספציפיים ל-GPU (@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so
). - יחסי תלות בין מאפיינים: המאפיין
deps
כולל בדרך כלל יחסי תלות של GLESgles_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, מעבירים את הפרמטר 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.
LiteRT Next GPU Accelerator
ה-GPU Accelerator החדש, שזמין רק ב-LiteRT Next, עבר אופטימיזציה כדי לטפל בעומסי עבודה של AI, כמו כפל מטריצות גדולות ומטמון KV ל-LLM, בצורה יעילה יותר מאשר בגרסאות קודמות. מאיץ ה-GPU של LiteRT Next כולל את השיפורים העיקריים הבאים בהשוואה לגרסה של LiteRT:
- הרחבת הכיסוי של האופרטורים: טיפול ברשתות עצביות גדולות ומורכבות יותר.
- יכולת פעולה הדדית משופרת של מאגרים: הפעלת שימוש ישיר במאגרי GPU לפריימים של מצלמה, למרקמים דו-ממדיים או למצבים גדולים של LLM.
- תמיכה בהרצה אסינכרוני:חפיפה בין העיבוד המקדים של המעבד המרכזי (CPU) לבין הסקת המסקנות של המעבד הגרפי (GPU).
העברה ללא העתקה (zero-copy) עם האצה של GPU
השימוש ב-zero-copy מאפשר ל-GPU לגשת לנתונים ישירות בזיכרון שלו, בלי שה-CPU יצטרך להעתיק את הנתונים האלה באופן מפורש. מכיוון שלא מתבצעת העתקה של נתונים לזיכרון המעבד וממנו, השימוש ב-zero-copy יכול לצמצם משמעותית את זמן האחזור מקצה לקצה.
הקוד הבא הוא דוגמה להטמעה של GPU ללא העתקה (Zero-Copy) באמצעות OpenGL, ממשק API לעיבוד גרפיקה וקטורית. הקוד מעביר תמונות בפורמט מאגר של OpenGL ישירות ל-LiteRT Next:
// 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 תוך המשך משימות אחרות באמצעות ה-CPU או ה-NPU. בצינורות עיבוד נתונים מורכבים, לרוב משתמשים ב-GPU באופן אסינכרוני לצד מעבדים או יחידות NPUs.
קטע הקוד הבא מבוסס על הקוד שסופק בדוגמה של Zero-copy GPU acceleration. הקוד משתמש גם ב-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 Next יש תמיכה בשיפור המהירות באמצעות GPU בדגמים הבאים. תוצאות הבדיקות מבוססות על בדיקות שבוצעו במכשיר Samsung Galaxy S24.