تنفيذ تفويض مخصّص

مقياس LiteRT يتيح لك التفويض إجراء ما يلي: تشغيل نماذجك (جزئيًا أو كاملاً) على منفّذ آخر. ويمكن لهذه الآلية الاستفادة مجموعة متنوعة من المسرّعات التي تعمل على الأجهزة مثل وحدة معالجة الرسومات (GPU) أو وحدة معالجة الموتّر (Tensor) في Edge وحدة المعالجة) للاستنتاج. وهذا يوفر للمطورين مرونة منفصلة عن جدول TFLite الافتراضي لتسريع الاستنتاج.

يلخّص الرسم البياني أدناه المفوَّضين، ويتوفّر المزيد من التفاصيل في الأقسام التالية.

مندوبو TFLite

متى يجب إنشاء تفويض مخصّص؟

لدى LiteRT مجموعة واسعة من المفوَّضين للمسرِّعات المستهدفة مثل وحدة معالجة الرسومات ووحدة معالجة المطالبات (DSP) وEDGETPU

يكون إنشاء المفوَّض أمرًا مفيدًا في السيناريوهات التالية:

  • كنت تريد دمج محرك استنتاج تعلُّم جديد غير متوافق مع أي أو المفوَّض الحالي.
  • لديك مسرِّع مخصّص للأجهزة يعمل على تحسين وقت التشغيل والسيناريوهات.
  • أنت تعمل على تطوير تحسينات وحدة المعالجة المركزية (CPU) (مثل دمج المشغّلات) التي يمكن أن لتسريع نماذج معينة.

ما هي طريقة عمل المفوَّضين؟

يمكنك استخدام نموذج رسم بياني بسيط، مثل ما يلي، وطريقة تفويض "MyDelegate" تحقّق تنفيذًا أسرع لعمليات الإحالات الناجحة (2D) و"المتوسط".

الرسم البياني الأصلي

وبعد تطبيق "MyDelegate" هذا، سيتم إنشاء الرسم البياني الأصلي LiteRT كما يلي:

الرسم البياني مع المفوَّض

يتم الحصول على الرسم البياني أعلاه أثناء تقسيم LiteRT للرسم البياني الأصلي القاعدتين التاليتين:

  • يتم وضع العمليات المحددة التي يمكن للمفوِّض فيها مع الاستمرار في تلبية سير عمل الحوسبة الأصلي والتبعيات بين العمليات.
  • يحتوي كل قسم سيتم تفويضه فقط على عُقد إدخال وإخراج غير يتم التعامل معها بواسطة المفوَّض.

يتم استبدال كل قسم تتم معالجته بواسطة مفوض بعقدة مفوضة (يمكن أيضًا باسم النواة المفوَّضة) في الرسم البياني الأصلي الذي يقيّم عند طلب الاستدعاء.

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

تنفيذ التفويض المخصّص

الطريقة المفضلة لإضافة مفوّض هي استخدام SimpleDelegate API:

لإنشاء مفوَّض جديد، يجب تنفيذ واجهتَين وتوفير التنفيذ الخاص لطرق الواجهة.

1 - SimpleDelegateInterface

تمثل هذه الفئة إمكانيات المفوِّض، وهي العمليات وفئة المصنع لإنشاء نواة تغلف الرسم البياني المفوض. لمزيد من التفاصيل، راجِع الواجهة المحدّدة في هذه ملف العنوان C++. تشرح التعليقات الواردة في الرمز كل واجهة برمجة تطبيقات بالتفصيل.

2 - SimpleDelegateKernelInterface

وتضم هذه الفئة منطقًا لتهيئة / إعداد / تشغيل قسم مفوَّض.

وتضم: (راجع تعريف)

  • Init(...): سيتم طلبه مرة واحدة لإجراء أي إعداد لمرة واحدة.
  • Prepare(...): تم استدعاء كل مثيل مختلف من هذه العقدة - ويحدث ذلك في حال كان لديك أقسام مفوَّضة متعددة. عادة ما تريد القيام بمهمة التوزيعات هنا، حيث سيُطلق على ذلك اسم كل مرة يتم فيها تغيير حجم قيم العشرات.
  • الاستدعاء(...): والذي سيتم استدعاؤه للاستنتاج.

مثال

في هذا المثال، ستُنشئ تفويضًا بسيطًا جدًا يمكنه إتاحة دعم مُفوَّضين فقط. أنواع العمليات (ADD) و (SUB) مع العشرات العائمة 32 فقط.

// MyDelegate implements the interface of SimpleDelegateInterface.
// This holds the Delegate capabilities.
class MyDelegate : public SimpleDelegateInterface {
 public:
  bool IsNodeSupportedByDelegate(const TfLiteRegistration* registration,
                                 const TfLiteNode* node,
                                 TfLiteContext* context) const override {
    // Only supports Add and Sub ops.
    if (kTfLiteBuiltinAdd != registration->builtin_code &&
        kTfLiteBuiltinSub != registration->builtin_code)
      return false;
    // This delegate only supports float32 types.
    for (int i = 0; i < node->inputs->size; ++i) {
      auto& tensor = context->tensors[node->inputs->data[i]];
      if (tensor.type != kTfLiteFloat32) return false;
    }
    return true;
  }

  TfLiteStatus Initialize(TfLiteContext* context) override { return kTfLiteOk; }

  const char* Name() const override {
    static constexpr char kName[] = "MyDelegate";
    return kName;
  }

  std::unique_ptr<SimpleDelegateKernelInterface> CreateDelegateKernelInterface()
      override {
    return std::make_unique<MyDelegateKernel>();
  }
};

بعد ذلك، أنشئ النواة المفوَّضة عن طريق الاكتساب من SimpleDelegateKernelInterface

// My delegate kernel.
class MyDelegateKernel : public SimpleDelegateKernelInterface {
 public:
  TfLiteStatus Init(TfLiteContext* context,
                    const TfLiteDelegateParams* params) override {
    // Save index to all nodes which are part of this delegate.
    inputs_.resize(params->nodes_to_replace->size);
    outputs_.resize(params->nodes_to_replace->size);
    builtin_code_.resize(params->nodes_to_replace->size);
    for (int i = 0; i < params->nodes_to_replace->size; ++i) {
      const int node_index = params->nodes_to_replace->data[i];
      // Get this node information.
      TfLiteNode* delegated_node = nullptr;
      TfLiteRegistration* delegated_node_registration = nullptr;
      TF_LITE_ENSURE_EQ(
          context,
          context->GetNodeAndRegistration(context, node_index, &delegated_node,
                                          &delegated_node_registration),
          kTfLiteOk);
      inputs_[i].push_back(delegated_node->inputs->data[0]);
      inputs_[i].push_back(delegated_node->inputs->data[1]);
      outputs_[i].push_back(delegated_node->outputs->data[0]);
      builtin_code_[i] = delegated_node_registration->builtin_code;
    }
    return kTfLiteOk;
  }

  TfLiteStatus Prepare(TfLiteContext* context, TfLiteNode* node) override {
    return kTfLiteOk;
  }

  TfLiteStatus Eval(TfLiteContext* context, TfLiteNode* node) override {
    // Evaluate the delegated graph.
    // Here we loop over all the delegated nodes.
    // We know that all the nodes are either ADD or SUB operations and the
    // number of nodes equals ''inputs_.size()'' and inputs[i] is a list of
    // tensor indices for inputs to node ''i'', while outputs_[i] is the list of
    // outputs for node
    // ''i''. Note, that it is intentional we have simple implementation as this
    // is for demonstration.

    for (int i = 0; i < inputs_.size(); ++i) {
      // Get the node input tensors.
      // Add/Sub operation accepts 2 inputs.
      auto& input_tensor_1 = context->tensors[inputs_[i][0]];
      auto& input_tensor_2 = context->tensors[inputs_[i][1]];
      auto& output_tensor = context->tensors[outputs_[i][0]];
      TF_LITE_ENSURE_EQ(
          context,
          ComputeResult(context, builtin_code_[i], &input_tensor_1,
                        &input_tensor_2, &output_tensor),
          kTfLiteOk);
    }
    return kTfLiteOk;
  }

 private:
  // Computes the result of addition of 'input_tensor_1' and 'input_tensor_2'
  // and store the result in 'output_tensor'.
  TfLiteStatus ComputeResult(TfLiteContext* context, int builtin_code,
                             const TfLiteTensor* input_tensor_1,
                             const TfLiteTensor* input_tensor_2,
                             TfLiteTensor* output_tensor) {
    if (NumElements(input_tensor_1) != NumElements(input_tensor_2) ||
        NumElements(input_tensor_1) != NumElements(output_tensor)) {
      return kTfLiteDelegateError;
    }
    // This code assumes no activation, and no broadcasting needed (both inputs
    // have the same size).
    auto* input_1 = GetTensorData<float>(input_tensor_1);
    auto* input_2 = GetTensorData<float>(input_tensor_2);
    auto* output = GetTensorData<float>(output_tensor);
    for (int i = 0; i < NumElements(input_tensor_1); ++i) {
      if (builtin_code == kTfLiteBuiltinAdd)
        output[i] = input_1[i] + input_2[i];
      else
        output[i] = input_1[i] - input_2[i];
    }
    return kTfLiteOk;
  }

  // Holds the indices of the input/output tensors.
  // inputs_[i] is list of all input tensors to node at index 'i'.
  // outputs_[i] is list of all output tensors to node at index 'i'.
  std::vector<std::vector<int>> inputs_, outputs_;
  // Holds the builtin code of the ops.
  // builtin_code_[i] is the type of node at index 'i'
  std::vector<int> builtin_code_;
};

قياس أداء المفوَّض الجديد وتقييمه

وتتضمّن منصة TFLite مجموعة من الأدوات التي يمكنك اختبارها بسرعة وفقًا لنموذج TFLite.

  • أداة قياس أداء النماذج: تستخدم الأداة نموذج TFLite، وتنشئ إدخالات عشوائية، ثم لتشغيل النموذج لعدد محدد من عمليات التشغيل. يطبع وقت الاستجابة المجمّع الإحصائيات في النهاية.
  • أداة اختلاف الاستنتاج: بالنسبة لنموذج معين، تقوم الأداة بإنشاء بيانات غاوسية عشوائية وتمريرها من خلال مترجمَين مختلفَين لتقنية TFLite، أحدهما يعمل بوحدة معالجة مركزية واحدة ذات سلاسل محادثات والأخرى باستخدام مواصفات يحددها المستخدم. إنه يقيس القيمة المطلقة الفرق بين متوترات المخرجات من كل مترجم، على أساس كل عنصر. ويمكن أن تكون هذه الأداة مفيدة أيضًا لتصحيح الأخطاء. المشكلات.
  • هناك أيضًا أدوات تقييم خاصة بالمهام، لتصنيف الصور رصد الأجسام. يمكن العثور على هذه الأدوات هنا

بالإضافة إلى ذلك، لدى TFLite مجموعة كبيرة من اختبارات النواة (النواة) ووحدة العمليات والتي يمكن لاختبار المفوَّض الجديد بمزيد من التغطية وضمان الحصول على لم يتم إيقاف مسار تنفيذ TFLite.

لإعادة استخدام اختبارات وأدوات TFLite للمفوض الجديد، يمكنك استخدام أحد الخيارين التاليين:

يعد اختيار أفضل نهج

يتطلب كلا النهجين بعض التغييرات كما هو موضح أدناه. ومع ذلك، فإن الأول يربط بين المفوَّض بشكل ثابت ويتطلب إعادة إنشاء الاختبار، لقياس الأداء والتقييم. وعلى النقيض من ذلك، فإن الخيار الثاني يمثل تفويض كمكتبة مشتركة ويتطلب منك عرض الإنشاء/الحذف من المكتبة المشتركة.

ونتيجة لذلك، ستعمل آلية التفويض الخارجي مع حلول TFLite برامج ثنائية مصممة مسبقًا لأدوات LiteRT. إلا أنّها أقلّ وضوحًا وقد يكون إعدادها أكثر تعقيدًا في نظام التشغيل الآلي اختبارات التكامل. استخدِم أسلوب جهة التسجيل المفوَّضة لمزيد من الوضوح.

الخيار 1: الاستفادة من جهة التسجيل المفوَّضة

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

بافتراض أن لديك مفوّضًا ينفّذ واجهات برمجة التطبيقات SimpleDelegate حرف "C" خارجي واجهات برمجة التطبيقات لإنشاء/حذف هذا "الوهمي" تفويض كما هو موضح أدناه:

// Returns default options for DummyDelegate.
DummyDelegateOptions TfLiteDummyDelegateOptionsDefault();

// Creates a new delegate instance that need to be destroyed with
// `TfLiteDummyDelegateDelete` when delegate is no longer used by TFLite.
// When `options` is set to `nullptr`, the above default values are used:
TfLiteDelegate* TfLiteDummyDelegateCreate(const DummyDelegateOptions* options);

// Destroys a delegate created with `TfLiteDummyDelegateCreate` call.
void TfLiteDummyDelegateDelete(TfLiteDelegate* delegate);

لدمج "DummyDelegate" مع "أداة قياس الأداء" و"أداة الاستنتاج"، حدِّد كمفوض، كما هو موضح أدناه:

class DummyDelegateProvider : public DelegateProvider {
 public:
  DummyDelegateProvider() {
    default_params_.AddParam("use_dummy_delegate",
                             ToolParam::Create<bool>(false));
  }

  std::vector<Flag> CreateFlags(ToolParams* params) const final;

  void LogParams(const ToolParams& params) const final;

  TfLiteDelegatePtr CreateTfLiteDelegate(const ToolParams& params) const final;

  std::string GetName() const final { return "DummyDelegate"; }
};
REGISTER_DELEGATE_PROVIDER(DummyDelegateProvider);

std::vector<Flag> DummyDelegateProvider::CreateFlags(ToolParams* params) const {
  std::vector<Flag> flags = {CreateFlag<bool>("use_dummy_delegate", params,
                                              "use the dummy delegate.")};
  return flags;
}

void DummyDelegateProvider::LogParams(const ToolParams& params) const {
  TFLITE_LOG(INFO) << "Use dummy test delegate : ["
                   << params.Get<bool>("use_dummy_delegate") << "]";
}

TfLiteDelegatePtr DummyDelegateProvider::CreateTfLiteDelegate(
    const ToolParams& params) const {
  if (params.Get<bool>("use_dummy_delegate")) {
    auto default_options = TfLiteDummyDelegateOptionsDefault();
    return TfLiteDummyDelegateCreateUnique(&default_options);
  }
  return TfLiteDelegatePtr(nullptr, [](TfLiteDelegate*) {});
}

تُعد تعريفات قاعدة BUILD مهمة حيث تحتاج للتأكد من أن تكون المكتبة مرتبطة دائمًا ولا يتم حذفها بواسطة المحسِّن.

#### The following are for using the dummy test delegate in TFLite tooling ####
cc_library(
    name = "dummy_delegate_provider",
    srcs = ["dummy_delegate_provider.cc"],
    copts = tflite_copts(),
    deps = [
        ":dummy_delegate",
        "//tensorflow/lite/tools/delegates:delegate_provider_hdr",
    ],
    alwayslink = 1, # This is required so the optimizer doesn't optimize the library away.
)

أضف الآن قاعدتي تضمين هاتين في ملف BUILD لإنشاء نسخة من أداة قياس الأداء وأداة الاستنتاج، وأدوات التقييم الأخرى التي يمكن تشغيلها مع مفوَّضك الخاص.

cc_binary(
    name = "benchmark_model_plus_dummy_delegate",
    copts = tflite_copts(),
    linkopts = task_linkopts(),
    deps = [
        ":dummy_delegate_provider",
        "//tensorflow/lite/tools/benchmark:benchmark_model_main",
    ],
)

cc_binary(
    name = "inference_diff_plus_dummy_delegate",
    copts = tflite_copts(),
    linkopts = task_linkopts(),
    deps = [
        ":dummy_delegate_provider",
        "//tensorflow/lite/tools/evaluation/tasks:task_executor_main",
        "//tensorflow/lite/tools/evaluation/tasks/inference_diff:run_eval_lib",
    ],
)

cc_binary(
    name = "imagenet_classification_eval_plus_dummy_delegate",
    copts = tflite_copts(),
    linkopts = task_linkopts(),
    deps = [
        ":dummy_delegate_provider",
        "//tensorflow/lite/tools/evaluation/tasks:task_executor_main",
        "//tensorflow/lite/tools/evaluation/tasks/imagenet_image_classification:run_eval_lib",
    ],
)

cc_binary(
    name = "coco_object_detection_eval_plus_dummy_delegate",
    copts = tflite_copts(),
    linkopts = task_linkopts(),
    deps = [
        ":dummy_delegate_provider",
        "//tensorflow/lite/tools/evaluation/tasks:task_executor_main",
        "//tensorflow/lite/tools/evaluation/tasks/coco_object_detection:run_eval_lib",
    ],
)

يمكنك أيضًا توصيل هذا الموفّر المفوَّض باختبارات النواة في TFLite كما هو موضّح. هنا.

الخيار 2: الاستفادة من المفوَّض الخارجي

في هذا البديل، تقوم أولاً بإنشاء محول تفويض خارجي external_delegate_adaptor.cc كما هو موضح أدناه. تجدر الإشارة إلى أنّ هذا الأسلوب أقل تفضيلاً مقارنةً الخيار 1 كما ذُكر سابقًا

TfLiteDelegate* CreateDummyDelegateFromOptions(char** options_keys,
                                               char** options_values,
                                               size_t num_options) {
  DummyDelegateOptions options = TfLiteDummyDelegateOptionsDefault();

  // Parse key-values options to DummyDelegateOptions.
  // You can achieve this by mimicking them as command-line flags.
  std::unique_ptr<const char*> argv =
      std::unique_ptr<const char*>(new const char*[num_options + 1]);
  constexpr char kDummyDelegateParsing[] = "dummy_delegate_parsing";
  argv.get()[0] = kDummyDelegateParsing;

  std::vector<std::string> option_args;
  option_args.reserve(num_options);
  for (int i = 0; i < num_options; ++i) {
    option_args.emplace_back("--");
    option_args.rbegin()->append(options_keys[i]);
    option_args.rbegin()->push_back('=');
    option_args.rbegin()->append(options_values[i]);
    argv.get()[i + 1] = option_args.rbegin()->c_str();
  }

  // Define command-line flags.
  // ...
  std::vector<tflite::Flag> flag_list = {
      tflite::Flag::CreateFlag(...),
      ...,
      tflite::Flag::CreateFlag(...),
  };

  int argc = num_options + 1;
  if (!tflite::Flags::Parse(&argc, argv.get(), flag_list)) {
    return nullptr;
  }

  return TfLiteDummyDelegateCreate(&options);
}

#ifdef __cplusplus
extern "C" {
#endif  // __cplusplus

// Defines two symbols that need to be exported to use the TFLite external
// delegate. See tensorflow/lite/delegates/external for details.
TFL_CAPI_EXPORT TfLiteDelegate* tflite_plugin_create_delegate(
    char** options_keys, char** options_values, size_t num_options,
    void (*report_error)(const char*)) {
  return tflite::tools::CreateDummyDelegateFromOptions(
      options_keys, options_values, num_options);
}

TFL_CAPI_EXPORT void tflite_plugin_destroy_delegate(TfLiteDelegate* delegate) {
  TfLiteDummyDelegateDelete(delegate);
}

#ifdef __cplusplus
}
#endif  // __cplusplus

والآن، أنشئ هدف BUILD المقابل لإنشاء مكتبة ديناميكية كما هو موضح أدناه:

cc_binary(
    name = "dummy_external_delegate.so",
    srcs = [
        "external_delegate_adaptor.cc",
    ],
    linkshared = 1,
    linkstatic = 1,
    deps = [
        ":dummy_delegate",
        "//tensorflow/lite/c:common",
        "//tensorflow/lite/tools:command_line_flags",
        "//tensorflow/lite/tools:logging",
    ],
)

بعد إنشاء ملف .so الخارجي المفوَّض هذا، يمكنك إنشاء برامج ثنائية أو استخدام تلك التي تم إنشاؤها مسبقًا لتشغيلها مع المفوَّض الجديد طالما كان البرنامج الثنائي مرتبطًا الـ external_delegate_provider التي تدعم علامات سطر الأوامر كما هو موضح هنا ملاحظة: سبق أن تم ربط موفِّر المفوَّض الخارجي هذا بالحسابات الحالية. البرامج الثنائية للاختبار والأدوات.

ارجع إلى الأوصاف هنا لتوضيح كيفية قياس المفوَّض الوهمي من خلال نهج التفويض الخارجي. يمكنك استخدام أوامر مماثلة للاختبار أدوات التقييم المذكورة سابقًا.

وتجدر الإشارة إلى أن المُفوَّض الخارجي هو مُمثِّل C++ المقابل تنفيذ دالة delegate في ربط LiteRT Python على النحو الموضّح هنا ومن ثم، يمكن إنشاء مكتبة مهايئ المفوَّض الخارجي الديناميكي التي تم إنشاؤها هنا ويُستخدم مباشرةً مع واجهات برمجة تطبيقات LiteRT Python API.

الموارد

نظام التشغيل ARCH BINARY_NAME
Linux ×86_64
مجموعة التجربة
aarch64
Android مجموعة التجربة
aarch64