تُستخدَم وحدات معالجة الرسومات (GPU) بشكل شائع لتسريع عملية التعلّم العميق، وذلك بسبب معدل نقل البيانات المتوازي الهائل الذي توفّره مقارنةً بوحدات المعالجة المركزية (CPU). تسهّل LiteRT عملية استخدام تسريع وحدة معالجة الرسومات من خلال السماح للمستخدمين بتحديد تسريع الأجهزة كمعلَمة عند إنشاء "نموذج مجمّع" (CompiledModel).
باستخدام ميزة تسريع وحدة معالجة الرسومات في LiteRT، يمكنك إنشاء مخازن مؤقتة للإدخال والإخراج متوافقة مع وحدة معالجة الرسومات، وتنفيذ عمليات نسخ بدون وسيطة لبياناتك في ذاكرة وحدة معالجة الرسومات، وتنفيذ المهام بشكل غير متزامن لتحقيق أقصى قدر من التوازي.
البدء
بالنسبة إلى نماذج تعلُّم الآلة الكلاسيكية، يمكنك الاطّلاع على التطبيقات التجريبية التالية.
- تطبيق Kotlin لتقسيم الصور: الاستنتاج باستخدام وحدة المعالجة المركزية (CPU) أو وحدة معالجة الرسومات (GPU) أو وحدة المعالجة العصبية (NPU)
- تطبيق C++ لتقسيم الصور: استنتاج وحدة المعالجة المركزية/وحدة معالجة الرسومات/وحدة المعالجة العصبية مع تنفيذ غير متزامن
بالنسبة إلى نماذج الذكاء الاصطناعي التوليدي، يُرجى الاطّلاع على العروض التوضيحية والدليل التاليَين:
- تطبيق C++ لتشابه المعاني في EmbeddingGemma: الاستدلال على وحدة المعالجة المركزية (CPU) أو وحدة معالجة الرسومات (GPU) أو وحدة المعالجة العصبية (NPU)
- دليل حول تشغيل النماذج اللغوية الكبيرة باستخدام LiteRT-LM
إضافة اعتمادية وحدة معالجة الرسومات
اتّبِع الخطوات التالية لإضافة تبعية وحدة معالجة الرسومات إلى تطبيق Kotlin أو C++.
Kotlin
بالنسبة إلى مستخدمي Kotlin، فإنّ أداة تسريع وحدة معالجة الرسومات مدمجة ولا تتطلّب اتّخاذ خطوات إضافية بخلاف تلك الموضّحة في دليل البدء.
C++
بالنسبة إلى مستخدمي C++، يجب إنشاء تبعيات التطبيق باستخدام تسريع LiteRT
GPU. قاعدة cc_binary التي تحزم منطق التطبيق الأساسي
(مثلاً يتطلّب main.cc) مكوّنات وقت التشغيل التالية:
- مكتبة LiteRT C API المشترَكة: يجب أن تتضمّن السمة
dataمكتبة LiteRT C API المشترَكة (//litert/c:litert_runtime_c_api_shared_lib) والمكوّنات الخاصة بوحدة معالجة الرسومات (@litert_gpu//:jni/arm64-v8a/libLiteRtGpuAccelerator.so). - سمات التبعية: تتضمّن السمة
depsعادةً تبعيات GLESgles_deps()، وتتضمّن السمةlinkoptsعادةًgles_linkopts(). وكلاهما مهمان جدًا لتسريع وحدة معالجة الرسومات، لأنّ 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
)
يتيح هذا الإعداد للرمز الثنائي المجمَّع تحميل وحدة معالجة الرسومات واستخدامها بشكل ديناميكي لإجراء استنتاج سريع في عملية تعلُّم الآلة.
استخدام وحدة معالجة الرسومات مع واجهة برمجة التطبيقات CompiledModel
للبدء في استخدام مسرِّع وحدة معالجة الرسومات، مرِّر مَعلمة وحدة معالجة الرسومات عند إنشاء
النموذج المجمَّع (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.
عدم النسخ مع تسريع وحدة معالجة الرسومات
يتيح استخدام ميزة "النسخ بدون وسيط" لوحدة معالجة الرسومات الوصول إلى البيانات مباشرةً في الذاكرة الخاصة بها بدون أن تحتاج وحدة المعالجة المركزية إلى نسخ هذه البيانات بشكل صريح. من خلال عدم نسخ البيانات إلى ذاكرة وحدة المعالجة المركزية ومنها، يمكن أن تقلّل عملية النسخ بدون وسيط بشكل كبير من وقت الاستجابة من البداية إلى النهاية.
الرمز البرمجي التالي هو مثال على تنفيذ Zero-Copy GPU باستخدام OpenGL، وهي واجهة برمجة تطبيقات لعرض الرسومات المتجهة. تنقل التعليمات البرمجية الصور بتنسيق المخزن المؤقت 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()، جدولة استنتاج وحدة معالجة الرسومات
مع مواصلة المهام الأخرى باستخدام وحدة المعالجة المركزية أو وحدة المعالجة العصبية. في مسارات المعالجة المعقّدة، يتم غالبًا استخدام وحدة معالجة الرسومات بشكل غير متزامن مع وحدة المعالجة المركزية أو وحدات المعالجة العصبية.
يستند مقتطف الرمز البرمجي التالي إلى الرمز البرمجي المقدَّم في مثال تسريع وحدة معالجة الرسومات بدون نسخ. يستخدم الرمز البرمجي كلاً من وحدة المعالجة المركزية ووحدة معالجة الرسومات بشكل غير متزامن، كما يرفق Event LiteRT بمخزن الإدخال المؤقت. تتولّى LiteRT Event
مسؤولية إدارة أنواع مختلفة من عناصر التزامن الأساسية،
وينشئ الرمز التالي عنصر LiteRT Event مُدارًا من النوع
LiteRtEventTypeEglSyncFence. يضمن عنصر Event هذا عدم القراءة من مخزن الإدخال المؤقت إلى أن تنتهي وحدة معالجة الرسومات. ويتم كل ذلك بدون إشراك وحدة المعالجة المركزية.
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 تسريع وحدة معالجة الرسومات مع الطُرز التالية. تستند نتائج قياس الأداء إلى الاختبارات التي تم إجراؤها على جهاز Samsung Galaxy S24.