ما هو مندوب TensorFlow Lite؟
يسمح لك مندوب TensorFlow Lite بتشغيل نماذجك (جزئيًا أو كليًا) على منفذ آخر. يمكن لهذه الآلية الاستفادة من مجموعة متنوعة من المسرعات على الجهاز مثل GPU أو Edge TPU (وحدة معالجة Tensor) للاستدلال. يوفر هذا للمطورين طريقة مرنة ومنفصلة عن TFLite الافتراضي لتسريع الاستدلال.
الرسم البياني أدناه يلخص المندوبين ، مزيد من التفاصيل في الأقسام أدناه.
متى يجب إنشاء مفوض مخصص؟
يحتوي TensorFlow Lite على مجموعة متنوعة من المفوضين للمسرعات المستهدفة مثل GPU و DSP و EdgeTPU وأطر مثل Android NNAPI.
يعد إنشاء مفوض خاص بك مفيدًا في السيناريوهات التالية:
- تريد دمج محرك استدلال ML جديد لا يدعمه أي مفوض موجود.
- لديك مسرع أجهزة مخصص يعمل على تحسين وقت التشغيل للسيناريوهات المعروفة.
- أنت تقوم بتطوير تحسينات وحدة المعالجة المركزية (مثل دمج المشغل) التي يمكنها تسريع نماذج معينة.
كيف يعمل المندوبون؟
ضع في اعتبارك رسمًا بيانيًا نموذجيًا بسيطًا مثل ما يلي ، ومندوب "MyDelegate" الذي يتمتع بتطبيق أسرع لعمليات Conv2D و Mean.
بعد تطبيق "MyDelegate" هذا ، سيتم تحديث الرسم البياني الأصلي TensorFlow Lite كما يلي:
يتم الحصول على الرسم البياني أعلاه حيث يقسم TensorFlow Lite الرسم البياني الأصلي باتباع قاعدتين:
- يتم وضع العمليات المحددة التي يمكن للمفوض معالجتها في قسم مع استمرار تلبية تبعيات سير عمل الحوسبة الأصلية بين العمليات.
- يحتوي كل قسم يتم تفويضه فقط على عقد إدخال وإخراج لا تتم معالجتها بواسطة المفوض.
يتم استبدال كل قسم تتم معالجته بواسطة مفوض بواسطة عقدة مفوض (يمكن تسميتها أيضًا باسم kernel للمفوض) في الرسم البياني الأصلي الذي يقيم القسم في استدعاء الاستدعاء الخاص به.
اعتمادًا على النموذج ، يمكن أن ينتهي الرسم البياني النهائي بعقد واحد أو أكثر ، وهذا الأخير يعني أن بعض العمليات غير مدعومة من قبل المفوض. بشكل عام ، لا تريد أن يتم التعامل مع أقسام متعددة بواسطة المفوض ، لأنه في كل مرة تقوم فيها بالتبديل من المفوض إلى الرسم البياني الرئيسي ، يكون هناك عبء لتمرير النتائج من الرسم البياني الفرعي المفوض إلى الرسم البياني الرئيسي الذي ينتج بسبب الذاكرة نسخ (على سبيل المثال ، GPU إلى CPU). قد يؤدي هذا الحمل إلى تعويض مكاسب الأداء خاصةً عند وجود عدد كبير من نسخ الذاكرة.
تنفيذ المفوض المخصص الخاص بك
الطريقة المفضلة لإضافة مفوض هي استخدام SimpleDelegate API .
لإنشاء مفوض جديد ، تحتاج إلى تنفيذ واجهتين وتوفير التنفيذ الخاص بك لأساليب الواجهة.
1 - SimpleDelegateInterface
تمثل هذه الفئة قدرات المفوض والعمليات المدعومة وفئة المصنع لإنشاء نواة تغلف الرسم البياني المفوض. لمزيد من التفاصيل ، راجع الواجهة المحددة في ملف الرأس C ++ هذا. توضح التعليقات الموجودة في الكود كل واجهة برمجة تطبيقات بالتفصيل.
2 - SimpleDelegateKernelInterface
تحتوي هذه الفئة على منطق تهيئة / تحضير / وتشغيل القسم المفوض.
لديها: (انظر التعريف )
- التهيئة (...): والتي سيتم استدعاؤها مرة واحدة للقيام بأي تهيئة لمرة واحدة.
- تحضير (...): يتم استدعاؤه لكل مثيل مختلف لهذه العقدة - يحدث هذا إذا كان لديك عدة أقسام مفوضة. عادةً ما تريد إجراء عمليات تخصيص الذاكرة هنا ، حيث سيتم استدعاء هذا في كل مرة يتم فيها تغيير حجم الموترات.
- استدعاء (...): الذي سيتم استدعاؤه للاستدلال.
مثال
في هذا المثال ، ستنشئ مفوضًا بسيطًا جدًا يمكنه دعم نوعين فقط من العمليات (إضافة) و (SUB) باستخدام موترات float32 فقط.
// 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>();
}
};
بعد ذلك ، قم بإنشاء kernel الخاص بالمفوض بالوراثة من 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.
- Model Benchmark Tool : تأخذ الأداة نموذج TFLite ، وتولد مدخلات عشوائية ، ثم تقوم بتشغيل النموذج بشكل متكرر لعدد محدد من عمليات التشغيل. يقوم بطباعة إحصائيات زمن الوصول المجمعة في النهاية.
- أداة Inference Diff Tool : بالنسبة لنموذج معين ، تقوم الأداة بتوليد بيانات Gaussian عشوائية وتمريرها من خلال مترجمين مختلفين TFLite ، أحدهما يقوم بتشغيل نواة وحدة المعالجة المركزية المفردة المترابطة والآخر باستخدام مواصفات يحددها المستخدم. يقيس الفرق المطلق بين موتر الإخراج من كل مترجم ، على أساس كل عنصر. يمكن أن تكون هذه الأداة مفيدة أيضًا في تصحيح مشكلات الدقة.
- هناك أيضًا أدوات تقييم خاصة بالمهام ، لتصنيف الصور واكتشاف الأشياء. يمكن العثور على هذه الأدوات هنا
بالإضافة إلى ذلك ، يحتوي TFLite على مجموعة كبيرة من اختبارات وحدة kernel و op التي يمكن إعادة استخدامها لاختبار المندوب الجديد بتغطية أكبر ولضمان عدم كسر مسار تنفيذ TFLite المعتاد.
لتحقيق إعادة استخدام اختبارات وأدوات TFLite للمفوض الجديد ، يمكنك استخدام أي من الخيارين التاليين:
- الاستفادة من آلية المفوض المسجل .
- الاستفادة من آلية المندوب الخارجي .
اختيار أفضل نهج
كلا النهجين يتطلب بعض التغييرات كما هو مفصل أدناه. ومع ذلك ، فإن النهج الأول يربط المندوب بشكل ثابت ويتطلب إعادة بناء أدوات الاختبار والقياس المعياري والتقييم. في المقابل ، يجعل الثاني المفوض كمكتبة مشتركة ويتطلب منك عرض طرق الإنشاء / الحذف من المكتبة المشتركة.
نتيجة لذلك ، ستعمل آلية المندوب الخارجي مع ثنائيات أدوات Tensorflow Lite المدمجة مسبقًا من TFLite . لكنها أقل وضوحًا وقد يكون إعدادها في اختبارات التكامل الآلي أكثر تعقيدًا. استخدم نهج المفوض المسجل للحصول على وضوح أفضل.
الخيار 1: الاستفادة من مسجل المفوض
يحتفظ المسجل المفوض بقائمة بموفري المفوضين ، كل منها يوفر طريقة سهلة لإنشاء مفوضين TFLite استنادًا إلى علامات سطر الأوامر ، وبالتالي فهي ملائمة للأدوات. لتوصيل المفوض الجديد بجميع أدوات Tensorflow Lite المذكورة أعلاه ، عليك أولاً إنشاء موفر مفوض جديد مثل هذا ، ثم إجراء بعض التغييرات فقط على قواعد 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" مع أداة Benchmark وأداة الاستدلال ، عرّف "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 الخاص بك لإنشاء إصدار من أداة Benchmark Tool و Inference Tool ، وأدوات التقييم الأخرى التي يمكن تشغيلها مع مفوضك الخاص.
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 kernel كما هو موضح هنا .
الخيار 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 ++ المقابل للمندوب في ربط Tensorflow Lite Python كما هو موضح هنا . لذلك ، يمكن استخدام مكتبة مهايئ المندوب الخارجي الديناميكي التي تم إنشاؤها هنا مباشرةً مع واجهات برمجة تطبيقات Tensorflow Lite Python.
موارد
روابط تنزيل لثنائيات أدوات TFLite المدمجة ليلاً
نظام التشغيل | قوس | BINARY_NAME |
لينكس | إلى x86_64 | |
ذراع | ||
aarch64 | ||
ذكري المظهر | ذراع | |
aarch64 |