TensorFlow Lite على وحدة معالجة الرسومات

يدعم TensorFlow Lite العديد من مسرعات الأجهزة. يصف هذا المستند كيفية استخدام الواجهة الخلفية لوحدة معالجة الرسومات باستخدام واجهات برمجة تطبيقات تفويض TensorFlow Lite على نظام Android (يتطلب OpenCL أو OpenGL ES 3.1 والإصدارات الأحدث) ونظام iOS (يتطلب iOS 8 أو أحدث).

فوائد تسريع GPU

سرعة

تم تصميم وحدات معالجة الرسومات للحصول على إنتاجية عالية لأحمال العمل القابلة للتوازي بشكل كبير. وبالتالي ، فهي مناسبة تمامًا للشبكات العصبية العميقة ، والتي تتكون من عدد كبير من المشغلين ، كل منهم يعمل على بعض موتر (موترات) الإدخال التي يمكن تقسيمها بسهولة إلى أعباء عمل أصغر وتنفيذها بالتوازي. ينتج عن هذا التوازي عادةً وقت استجابة أقل. في أفضل السيناريوهات ، قد يعمل الاستدلال على وحدة معالجة الرسومات بسرعة كافية ليصبح مناسبًا لتطبيقات الوقت الفعلي التي لم تكن ممكنة من قبل.

دقة

تقوم وحدات معالجة الرسومات (GPU) بحساباتها بأرقام فاصلة عائمة 16 بت أو 32 بت و (على عكس وحدات المعالجة المركزية) لا تتطلب تكميمًا لتحقيق الأداء الأمثل. إذا أدى انخفاض الدقة إلى جعل التكميم غير مقبول لنماذجك ، فقد يؤدي تشغيل شبكتك العصبية على وحدة معالجة الرسومات إلى التخلص من هذا القلق.

كفاءة الطاقة

فائدة أخرى تأتي مع استدلال GPU هي كفاءة طاقتها. تقوم وحدة معالجة الرسومات (GPU) بإجراء العمليات الحسابية بطريقة فعالة ومحسّنة للغاية ، حيث تستهلك طاقة أقل وتولد حرارة أقل من نفس المهمة التي يتم تشغيلها على وحدة المعالجة المركزية.

العمليات المدعومة

يدعم TensorFlow Lite على وحدة معالجة الرسومات العمليات التالية بدقة تعويم 16 بت و 32 بت:

  • ADD
  • AVERAGE_POOL_2D
  • CONCATENATION
  • CONV_2D
  • DEPTHWISE_CONV_2D v1-2
  • EXP
  • FULLY_CONNECTED
  • LOGISTIC
  • LSTM v2 (Basic LSTM only)
  • MAX_POOL_2D
  • MAXIMUM
  • MINIMUM
  • MUL
  • PAD
  • PRELU
  • RELU
  • RELU6
  • RESHAPE
  • RESIZE_BILINEAR v1-3
  • SOFTMAX
  • STRIDED_SLICE
  • SUB
  • TRANSPOSE_CONV

بشكل افتراضي ، يتم دعم جميع العمليات فقط في الإصدار 1. يسمح تمكين دعم التكميم التجريبي بالإصدارات المناسبة ؛ على سبيل المثال ، أضف الإصدار 2.

الاستخدام الأساسي

هناك طريقتان لاستدعاء تسريع النموذج في Android اعتمادًا على ما إذا كنت تستخدم Android Studio ML Model Binding أو TensorFlow Lite Interpreter.

Android عبر TensorFlow Lite Interpreter

أضف tensorflow-lite-gpu إلى جانب حزمة tensorflow-lite الحالية في كتلة dependencies الحالية.

dependencies {
    ...
    implementation 'org.tensorflow:tensorflow-lite:2.3.0'
    implementation 'org.tensorflow:tensorflow-lite-gpu:2.3.0'
}

ثم قم بتشغيل TensorFlow Lite على GPU باستخدام TfLiteDelegate . في Java ، يمكنك تحديد GpuDelegate من خلال Interpreter.Options .

كوتلن

    import org.tensorflow.lite.Interpreter
    import org.tensorflow.lite.gpu.CompatibilityList
    import org.tensorflow.lite.gpu.GpuDelegate

    val compatList = CompatibilityList()

    val options = Interpreter.Options().apply{
        if(compatList.isDelegateSupportedOnThisDevice){
            // if the device has a supported GPU, add the GPU delegate
            val delegateOptions = compatList.bestOptionsForThisDevice
            this.addDelegate(GpuDelegate(delegateOptions))
        } else {
            // if the GPU is not supported, run on 4 threads
            this.setNumThreads(4)
        }
    }

    val interpreter = Interpreter(model, options)

    // Run inference
    writeToInput(input)
    interpreter.run(input, output)
    readFromOutput(output)
      

جافا

    import org.tensorflow.lite.Interpreter;
    import org.tensorflow.lite.gpu.CompatibilityList;
    import org.tensorflow.lite.gpu.GpuDelegate;

    // Initialize interpreter with GPU delegate
    Interpreter.Options options = new Interpreter.Options();
    CompatibilityList compatList = CompatibilityList();

    if(compatList.isDelegateSupportedOnThisDevice()){
        // if the device has a supported GPU, add the GPU delegate
        GpuDelegate.Options delegateOptions = compatList.getBestOptionsForThisDevice();
        GpuDelegate gpuDelegate = new GpuDelegate(delegateOptions);
        options.addDelegate(gpuDelegate);
    } else {
        // if the GPU is not supported, run on 4 threads
        options.setNumThreads(4);
    }

    Interpreter interpreter = new Interpreter(model, options);

    // Run inference
    writeToInput(input);
    interpreter.run(input, output);
    readFromOutput(output);
      

Android (C / C ++)

لاستخدام C / C ++ من TensorFlow Lite GPU على Android ، يمكن إنشاء مفوض GPU باستخدام TfLiteGpuDelegateV2Create() وإتلافه باستخدام TfLiteGpuDelegateV2Delete() .

// Set up interpreter.
auto model = FlatBufferModel::BuildFromFile(model_path);
if (!model) return false;
ops::builtin::BuiltinOpResolver op_resolver;
std::unique_ptr<Interpreter> interpreter;
InterpreterBuilder(*model, op_resolver)(&interpreter);

// NEW: Prepare GPU delegate.
auto* delegate = TfLiteGpuDelegateV2Create(/*default options=*/nullptr);
if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;

// Run inference.
WriteToInputTensor(interpreter->typed_input_tensor<float>(0));
if (interpreter->Invoke() != kTfLiteOk) return false;
ReadFromOutputTensor(interpreter->typed_output_tensor<float>(0));

// NEW: Clean up.
TfLiteGpuDelegateV2Delete(delegate);

ألق نظرة على TfLiteGpuDelegateOptionsV2 لإنشاء مثيل مفوض بخيارات مخصصة. يمكنك تهيئة الخيارات الافتراضية باستخدام TfLiteGpuDelegateOptionsV2Default() ثم تعديلها حسب الضرورة.

يستخدم TFLite GPU لنظام Android C / C ++ نظام بناء Bazel . يمكن بناء المندوب ، على سبيل المثال ، باستخدام الأمر التالي:

bazel build -c opt --config android_arm64 tensorflow/lite/delegates/gpu:delegate                           # for static library
bazel build -c opt --config android_arm64 tensorflow/lite/delegates/gpu:libtensorflowlite_gpu_delegate.so  # for dynamic library

iOS (C ++)

لاستخدام TensorFlow Lite على GPU ، احصل على مندوب GPU عبر TFLGpuDelegateCreate() ثم قم بتمريره إلى Interpreter::ModifyGraphWithDelegate() (بدلاً من استدعاء Interpreter::AllocateTensors() ).

// Set up interpreter.
auto model = FlatBufferModel::BuildFromFile(model_path);
if (!model) return false;
tflite::ops::builtin::BuiltinOpResolver op_resolver;
std::unique_ptr<Interpreter> interpreter;
InterpreterBuilder(*model, op_resolver)(&interpreter);

// NEW: Prepare GPU delegate.

auto* delegate = TFLGpuDelegateCreate(/*default options=*/nullptr);
if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;

// Run inference.
WriteToInputTensor(interpreter->typed_input_tensor<float>(0));
if (interpreter->Invoke() != kTfLiteOk) return false;
ReadFromOutputTensor(interpreter->typed_output_tensor<float>(0));

// Clean up.
TFLGpuDelegateDelete(delegate);

الاستخدام المتقدم

خيارات التفويض لنظام iOS

يقبل المُنشئ لمفوض GPU struct من الخيارات. ( Swift API ، Objective-C API ، C API )

يؤدي تمرير nullptr (C API) أو لا شيء (Objective-C و Swift API) إلى المُهيئ إلى تعيين الخيارات الافتراضية (التي تم شرحها في مثال الاستخدام الأساسي أعلاه).

سويفت

    // THIS:
    var options = MetalDelegate.Options()
    options.isPrecisionLossAllowed = false
    options.waitType = .passive
    options.isQuantizationEnabled = true
    let delegate = MetalDelegate(options: options)

    // IS THE SAME AS THIS:
    let delegate = MetalDelegate()
      

ج موضوعية

    // THIS:
    TFLMetalDelegateOptions* options = [[TFLMetalDelegateOptions alloc] init];
    options.precisionLossAllowed = false;
    options.waitType = TFLMetalDelegateThreadWaitTypePassive;
    options.quantizationEnabled = true;

    TFLMetalDelegate* delegate = [[TFLMetalDelegate alloc] initWithOptions:options];

    // IS THE SAME AS THIS:
    TFLMetalDelegate* delegate = [[TFLMetalDelegate alloc] init];
      

ج

    // THIS:
    const TFLGpuDelegateOptions options = {
      .allow_precision_loss = false,
      .wait_type = TFLGpuDelegateWaitType::TFLGpuDelegateWaitTypePassive,
      .enable_quantization = true,
    };

    TfLiteDelegate* delegate = TFLGpuDelegateCreate(options);

    // IS THE SAME AS THIS:
    TfLiteDelegate* delegate = TFLGpuDelegateCreate(nullptr);
      

في حين أنه من الملائم استخدام nullptr أو المنشئات الافتراضية ، فإننا نوصي بتعيين الخيارات صراحةً ، لتجنب أي سلوك غير متوقع إذا تم تغيير القيم الافتراضية في المستقبل.

تشغيل النماذج الكمية على وحدة معالجة الرسومات

يشرح هذا القسم كيف يقوم مفوض GPU بتسريع النماذج الكمية 8 بت. وهذا يشمل جميع نكهات التكميم ، بما في ذلك:

لتحسين الأداء ، استخدم النماذج التي تحتوي على موترات إدخال ونقطة عائمة.

كيف يعمل هذا؟

نظرًا لأن الواجهة الخلفية لوحدة معالجة الرسومات لا تدعم سوى تنفيذ النقطة العائمة ، فإننا نشغل النماذج الكمية من خلال إعطائها "عرض النقطة العائمة" للنموذج الأصلي. على مستوى عالٍ ، يستلزم ذلك الخطوات التالية:

  • الموترات الثابتة (مثل الأوزان / التحيزات) يتم تفكيكها مرة واحدة في ذاكرة وحدة معالجة الرسومات. يحدث هذا عندما يتم تطبيق المندوب على مترجم TFLite.

  • المدخلات والمخرجات إلى برنامج GPU ، إذا تم تحديد مقدار 8 بتات كمياً ، يتم تفكيكها وتقنينها (على التوالي) لكل استنتاج. يتم ذلك على وحدة المعالجة المركزية باستخدام نوى TFLite المحسنة.

  • يتم تعديل برنامج GPU لتقليد السلوك الكمي عن طريق إدخال محاكيات التكميم بين العمليات. هذا ضروري للنماذج حيث تتوقع العمليات أن تتبع عمليات التنشيط الحدود التي تم تعلمها أثناء التكميم.

يمكن تمكين هذه الميزة باستخدام خيارات التفويض على النحو التالي:

ذكري المظهر

تدعم واجهات برمجة تطبيقات Android النماذج الكمية بشكل افتراضي. للتعطيل ، قم بما يلي:

واجهة برمجة تطبيقات C ++

TfLiteGpuDelegateOptionsV2 options = TfLiteGpuDelegateOptionsV2Default();
options.experimental_flags = TFLITE_GPU_EXPERIMENTAL_FLAGS_NONE;

auto* delegate = TfLiteGpuDelegateV2Create(options);
if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;

جافا API

GpuDelegate delegate = new GpuDelegate(new GpuDelegate.Options().setQuantizedModelsAllowed(false));

Interpreter.Options options = (new Interpreter.Options()).addDelegate(delegate);

iOS

تدعم واجهات برمجة تطبيقات iOS النماذج الكمية بشكل افتراضي. للتعطيل ، قم بما يلي:

سويفت

    var options = MetalDelegate.Options()
    options.isQuantizationEnabled = false
    let delegate = MetalDelegate(options: options)
      

ج موضوعية

    TFLMetalDelegateOptions* options = [[TFLMetalDelegateOptions alloc] init];
    options.quantizationEnabled = false;
      

ج

    TFLGpuDelegateOptions options = TFLGpuDelegateOptionsDefault();
    options.enable_quantization = false;

    TfLiteDelegate* delegate = TFLGpuDelegateCreate(options);
      

مخازن الإدخال / الإخراج (iOS و C ++ API فقط)

لإجراء عملية حسابية على وحدة معالجة الرسومات ، يجب إتاحة البيانات لوحدة معالجة الرسومات. يتطلب هذا غالبًا إجراء نسخة من الذاكرة. من المستحسن عدم عبور حدود ذاكرة وحدة المعالجة المركزية / وحدة معالجة الرسومات إن أمكن ، لأن ذلك قد يستغرق وقتًا طويلاً. عادةً ما يكون هذا العبور أمرًا لا مفر منه ، ولكن في بعض الحالات الخاصة ، يمكن حذف أحدهما أو الآخر.

إذا كان إدخال الشبكة عبارة عن صورة تم تحميلها بالفعل في ذاكرة وحدة معالجة الرسومات (على سبيل المثال ، نسيج GPU الذي يحتوي على موجز الكاميرا) ، فيمكنه البقاء في ذاكرة وحدة معالجة الرسومات بدون إدخال ذاكرة وحدة المعالجة المركزية على الإطلاق. وبالمثل ، إذا كان إخراج الشبكة في شكل صورة قابلة للعرض (على سبيل المثال ، نقل نمط الصورة ) فيمكن عرضها مباشرة على الشاشة.

لتحقيق أفضل أداء ، يتيح TensorFlow Lite للمستخدمين القراءة والكتابة مباشرة من المخزن المؤقت لجهاز TensorFlow وتجاوز نسخ الذاكرة التي يمكن تجنبها.

بافتراض أن إدخال الصورة في ذاكرة GPU ، يجب أولاً تحويلها إلى كائن MTLBuffer لـ Metal. يمكنك ربط TfLiteTensor بجهاز MTLBuffer معد بواسطة المستخدم مع TFLGpuDelegateBindMetalBufferToTensor() . لاحظ أنه يجب استدعاء TFLGpuDelegateBindMetalBufferToTensor ( TFLGpuDelegateBindMetalBufferToTensor() بعد Interpreter::ModifyGraphWithDelegate() . بالإضافة إلى ذلك ، يتم نسخ إخراج الاستدلال ، افتراضيًا ، من ذاكرة وحدة معالجة الرسومات إلى ذاكرة وحدة المعالجة المركزية. يمكن إيقاف تشغيل هذا السلوك عن طريق استدعاء Interpreter::SetAllowBufferHandleOutput(true) أثناء التهيئة.

#include "tensorflow/lite/delegates/gpu/metal_delegate.h"
#include "tensorflow/lite/delegates/gpu/metal_delegate_internal.h"

// ...

// Prepare GPU delegate.
auto* delegate = TFLGpuDelegateCreate(nullptr);

if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;

interpreter->SetAllowBufferHandleOutput(true);  // disable default gpu->cpu copy
if (!TFLGpuDelegateBindMetalBufferToTensor(
        delegate, interpreter->inputs()[0], user_provided_input_buffer)) {
  return false;
}
if (!TFLGpuDelegateBindMetalBufferToTensor(
        delegate, interpreter->outputs()[0], user_provided_output_buffer)) {
  return false;
}

// Run inference.
if (interpreter->Invoke() != kTfLiteOk) return false;

تسلسل مفوض GPU

يمكن أن يؤدي استخدام تسلسل رمز GPU kernel وبيانات النموذج من عمليات التهيئة السابقة إلى تقليل زمن انتقال تهيئة مفوض GPU بنسبة تصل إلى 90٪. يتم تحقيق هذا التحسين من خلال تبادل مساحة القرص لتوفير الوقت. يمكنك تمكين هذه الميزة من خلال عدد قليل من خيارات التكوين ، كما هو موضح في أمثلة التعليمات البرمجية التالية:

سي ++

    TfLiteGpuDelegateOptionsV2 options = TfLiteGpuDelegateOptionsV2Default();
    options.experimental_flags |= TFLITE_GPU_EXPERIMENTAL_FLAGS_ENABLE_SERIALIZATION;
    options.serialization_dir = kTmpDir;
    options.model_token = kModelToken;

    auto* delegate = TfLiteGpuDelegateV2Create(options);
    if (interpreter->ModifyGraphWithDelegate(delegate) != kTfLiteOk) return false;
      

جافا

    GpuDelegate delegate = new GpuDelegate(
      new GpuDelegate.Options().setSerializationParams(
        /* serializationDir= */ serializationDir,
        /* modelToken= */ modelToken));

    Interpreter.Options options = (new Interpreter.Options()).addDelegate(delegate);
      

عند استخدام ميزة التسلسل ، تأكد من أن التعليمات البرمجية تتوافق مع قواعد التنفيذ التالية:

  • قم بتخزين بيانات التسلسل في دليل لا يمكن للتطبيقات الأخرى الوصول إليه. على أجهزة Android ، استخدم getCodeCacheDir() الذي يشير إلى موقع خاص بالتطبيق الحالي.
  • يجب أن يكون رمز النموذج فريدًا للجهاز بالنسبة للطراز المحدد. يمكنك حساب رمز مميز للنموذج عن طريق إنشاء بصمة من بيانات النموذج (على سبيل المثال باستخدام farmhash::Fingerprint64 ).

النصائح والحيل

  • قد تكون بعض العمليات البسيطة على وحدة المعالجة المركزية عالية التكلفة على وحدة معالجة الرسومات. تتضمن إحدى فئات هذه العمليات أشكالًا مختلفة من عمليات إعادة الشكل (بما في ذلك BATCH_TO_SPACE و SPACE_TO_BATCH و SPACE_TO_DEPTH وعملية مماثلة). إذا لم تكن هذه العمليات مطلوبة (على سبيل المثال ، فقد تم إدراجها لمساعدة مهندس الشبكة على تفسير النظام ولكن لا تؤثر على المخرجات بطريقة أخرى) ، فإن الأمر يستحق إزالتها للأداء.

  • في وحدة معالجة الرسومات (GPU) ، يتم تقسيم بيانات الموتر إلى 4 قنوات. وبالتالي ، فإن حسابًا على موتر الشكل [B, H, W, 5] سيؤدي إلى نفس الأداء تقريبًا على موتر الشكل [B, H, W, 8] ، ولكنه أسوأ بكثير من [B, H, W, 4] .

    • على سبيل المثال ، إذا كانت أجهزة الكاميرا تدعم إطارات الصور في RGBA ، فإن تغذية الإدخال ذي 4 قنوات يكون أسرع بشكل ملحوظ ، لأنه يمكن تجنب نسخ الذاكرة (من 3 قنوات RGB إلى 4 قنوات RGBX).
  • للحصول على أفضل أداء ، لا تتردد في إعادة تدريب المصنف الخاص بك باستخدام بنية شبكة محسّنة للجوّال. هذا جزء مهم من تحسين الاستدلال على الجهاز.