عوامل التشغيل المخصّصة

بما أنّ مكتبة عوامل التشغيل TensorFlow Lite المدمَجة لا تتوافق إلا مع عدد محدود من عوامل التشغيل TensorFlow، فليس كل نموذج قابلاً للتحويل. للاطّلاع على التفاصيل، يُرجى الرجوع إلى توافق مشغّلي التشغيل.

للسماح بالإحالات الناجحة، يمكن للمستخدمين توفير عامل تشغيل TensorFlow غير متوافق في TensorFlow Lite والمعروف باسم عامل تشغيل مخصّص إذا أردت بدلاً من ذلك دمج سلسلة من عوامل تشغيل TensorFlow غير المتوافقة (أو المتوافقة) في عامل تشغيل مخصّص واحد محسَّن ومدمج، يمكنك الرجوع إلى دمج عوامل التشغيل.

يتألف استخدام عوامل التشغيل المخصّصة من أربع خطوات.

لنلقِ نظرة على مثال شامل لتشغيل نموذج باستخدام عامل تشغيل مخصّص tf.atan (باسم Atan، راجِع المقالة إنشاء نموذج TensorFlow) وهو متوافق في TensorFlow ولكنه غير متوافق مع TensorFlow Lite.

عامل TensorFlow Text هو مثال على عامل تشغيل مخصص. يمكنك الاطّلاع على الدليل التعليمي تحويل نص TF إلى TF Lite للتعرّف على مثال على الترميز.

مثال: عامل تشغيل Atan مخصّص

لنلقِ نظرة على مثال عن إتاحة عامل تشغيل TensorFlow لا تتوفّر فيه أداة TensorFlow Lite. لنفترض أننا نستخدم عامل التشغيل Atan وأننا ننشئ نموذجًا بسيطًا جدًا لدالة y = atan(x + offset)، حيث يكون offset قابلاً للتدريب.

إنشاء نموذج TensorFlow

يقوم مقتطف الرمز التالي بتدريب نموذج TensorFlow بسيطًا. يحتوي هذا النموذج فقط على عامل تشغيل مخصّص باسم Atan، وهو دالة y = atan(x + offset) حيث يمكن تدريب offset.

import tensorflow as tf

# Define training dataset and variables
x = [-8, 0.5, 2, 2.2, 201]
y = [-1.4288993, 0.98279375, 1.2490457, 1.2679114, 1.5658458]
offset = tf.Variable(0.0)

# Define a simple model which just contains a custom operator named `Atan`
@tf.function(input_signature=[tf.TensorSpec.from_tensor(tf.constant(x))])
def atan(x):
  return tf.atan(x + offset, name="Atan")

# Train model
optimizer = tf.optimizers.Adam(0.01)
def train(x, y):
    with tf.GradientTape() as t:
      predicted_y = atan(x)
      loss = tf.reduce_sum(tf.square(predicted_y - y))
    grads = t.gradient(loss, [offset])
    optimizer.apply_gradients(zip(grads, [offset]))

for i in range(1000):
    train(x, y)

print("The actual offset is: 1.0")
print("The predicted offset is:", offset.numpy())
The actual offset is: 1.0
The predicted offset is: 0.99999905

في هذه المرحلة، إذا حاولت إنشاء نموذج TensorFlow Lite باستخدام علامات المحوِّل التلقائية، ستظهر لك رسالة الخطأ التالية:

Error:
error: 'tf.Atan' op is neither a custom op nor a flex op.

التحويل إلى نموذج TensorFlow Lite

أنشئ نموذج TensorFlow Lite باستخدام عوامل تشغيل مخصّصة، وذلك من خلال ضبط سمة المحوّل allow_custom_ops كما هو موضّح أدناه:

converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan)
converter.allow_custom_ops = True
tflite_model = converter.convert()

في هذه المرحلة، إذا شغّلته باستخدام أداة الترجمة الفورية التلقائية باستخدام أوامر مثل ما يلي:

interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()

سيستمر ظهور الخطأ:

Encountered unresolved custom op: Atan.

إنشاء عامل التشغيل وتسجيله.

#include "third_party/tensorflow/lite/c/c_api.h"
#include "third_party/tensorflow/lite/c/c_api_opaque.h"

يتم تحديد عوامل التشغيل المخصّصة في TensorFlow Lite باستخدام واجهة برمجة تطبيقات بسيطة فقط تتألف من نوع مبهم (TfLiteRegistrationExternal) ووظائف ذات صلة.

TfLiteRegistrationExternal هو نوع مبهم:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

يخزِّن TfLiteRegistrationExternal هوية المشغّل وعمليات التنفيذ. (لاحظ أن العامل يختلف عن معاملاته، والتي يتم تخزينها في عقد الرسم البياني TF Lite للعُقد التي تستدعي العامل).

ويتم إنشاء المثيلات من هذا النوع باستخدام الطلبات الموجّهة إلى TfLiteRegistrationExternalCreate، ويمكن إتلافها من خلال طلب الرقم TfLiteRegistrationExternalDelete.

يتم ضبط هوية عامل التشغيل من خلال المعلَمات على الدالة الإنشائية TfLiteRegistrationExternalCreate:

TfLiteRegistrationExternal*
TfLiteRegistrationExternalCreate(
    TfLiteBuiltinOperator builtin_code,  // Normally `TfLiteBuiltinCustom`.
    const char* custom_name,  // The name of the custom op.
    int version  // Normally `1` for the first version of a custom op.
);

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

// Initializes the op from serialized data.
void* Init(TfLiteOpaqueContext* context, const char* buffer, size_t length);

// Deallocates the op.
// The pointer `buffer` is the data previously returned by an Init invocation.
void Free(TfLiteOpaqueContext* context, void* buffer);

// Called when the inputs that this node depends on have been resized.
TfLiteStatus Prepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node);

// Called when the node is executed. (Should read node inputs and write to
// node outputs).
TfLiteStatus Invoke(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node);

// Retrieves the async kernel.
TfLiteAsyncKernel AsyncKernel(TfLiteOpaqueContext* context,
                              TfLiteOpaqueNode* node);

إنّ الدالة names الدالة (أو بادئات مساحة الاسم لـ C++ ) في تنفيذ العملية لا يجب أن تتطابق مع أسماء الدوال في مقتطف الرمز أعلاه، لأنّ واجهة برمجة التطبيقات TF Lite المخصّصة ops API ستستخدم عناوينها فقط. وننصح بالإشارة إليها في مساحة اسم مجهولة المصدر أو كدوالّ ثابتة.

لكن من الجيد تضمين اسم العامل كمساحة اسم أو بادئة في أسماء الدوال التالية:

C++

namespace my_namespace::my_custom_op {
  void* Init(TfLiteOpaqueContext* context,
             const char* buffer, size_t length) { ... }
  // ... plus definitions of Free, Prepare, and Invoke ...
}
      

C

void* MyCustomOpInit(TfLiteOpaqueContext* context,
                     const char* buffer, size_t length) { ... }
// ... plus definitions of MyCustomOpFree, MyCustomOpPrepare, and
// MyCustomOpInvoke.
      

بما أنّ واجهة برمجة التطبيقات C API، يتم تنفيذ هذه "الطرق" كمؤشرات دالة C في النوع TfLiteRegistrationExternal، والتي يتم ضبطها من خلال تمرير عناوين دوال التنفيذ إلى دوال setter المقابلة. TfLiteRegistrationExternalSetMethodName:

void TfLiteRegistrationExternalSetInit(
    TfLiteRegistrationExternal* registration,
    void* (*init)(TfLiteOpaqueContext* context, const char* buffer,
                  size_t length));
void TfLiteRegistrationExternalSetFree(
    TfLiteRegistrationExternal* registration,
    void (*free)(TfLiteOpaqueContext* context, void* data));
void TfLiteRegistrationExternalSetPrepare(
    TfLiteRegistrationExternal* registration,
    TfLiteStatus (*prepare)(TfLiteOpaqueContext* context,
                            TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetInvoke(
    TfLiteRegistrationExternal* registration,
    TfLiteStatus (*invoke)(TfLiteOpaqueContext* context,
                           TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetAsyncKernel(
    TfLiteRegistrationExternal* registration,
    struct TfLiteAsyncKernel* (*async_kernel)(TfLiteOpaqueContext* context,
                                              TfLiteOpaqueNode* node));

راجِع common.h للحصول على تفاصيل عن TfLiteContext وTfLiteNode. توفر TfLiteContext مرافق الإبلاغ عن الأخطاء والوصول إلى الكائنات الشاملة، بما في ذلك جميع المتوترات. تسمح TfLiteNode لعمليات تنفيذ عوامل التشغيل بالوصول إلى مدخلاتها ومخرجاتها.

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

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

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

يمكن تنفيذ العمليات المخصّصة من خلال تحديد دوال "method" (الطريقة)، ثم تحديد دالة تُرجع مثيل TfLiteRegistrationExternal التي يتم إنشاؤها عن طريق استدعاء TfLiteRegistrationExternalCreate ثم طرق الضبط ذات الصلة:

C++

namespace my_namespace::my_custom_op {
  namespace {
    void* Init(TfLiteOpaqueContext* context,
               const char* buffer, size_t length) { ... }
    void Free(TfLiteOpaqueContext* context, void* buffer) { ... }
    TfLiteStatus Prepare(TfLiteOpaqueContext* context,
                         TfLiteOpaqueNode* node) { ... }
    TfLiteStatus Invoke(TfLiteOpaqueContext* context,
                        TfLiteOpaqueNode* node) {... }
  };

  const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteRegistrationExternal* my_custom_op = ()[] {
        TfLiteRegistrationExternal* r =
            TfLiteRegistrationExternalCreate(
                kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
        TfLiteRegistrationExternalSetInit(r, Init);
        TfLiteRegistrationExternalSetFree(r, Free);
        TfLiteRegistrationExternalSetPrepare(r, Prepare);
        TfLiteRegistrationExternalSetInvoke(r, Eval);
        return r;
      };
    return my_custom_op;
  }

  const TfLiteRegistration* MyCustomOpRegistration() {
    static const TfLiteRegistration my_custom_op {
      .registration_external = MyCustomOpRegistrationExternal();
    };
    return my_custom_op;
  }
}  // namespace my_namespace
      

C

static void* MyCustomOpInit(TfLiteOpaqueContext* context, const char* buffer,
                     size_t length) { ... }
static void MyCustomOpFree(TfLiteOpaqueContext* context, void* buffer) { ... }
static TfLiteStatus MyCustomOpPrepare(TfLiteOpaqueContext* context,
                                      TfLiteOpaqueNode* node) { ... }
static TfLiteStatus MyCustomOpInvoke(TfLiteOpaqueContext* context,
                                     TfLiteOpaqueNode* node) {... }

static TfLiteRegistrationExternal* MyCustomOpCreate() {
  const TfLiteRegistrationExternal* r =
      TfLiteRegistrationExternalCreate(
          kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
  TfLiteRegistrationExternalSetInit(r, MyCustomOpInit);
  TfLiteRegistrationExternalSetFree(r, MyCustomOpFree);
  TfLiteRegistrationExternalSetPrepare(r, MyCustomOpPrepare);
  TfLiteRegistrationExternalSetInvoke(r, MyCustomOpEval);
  return r;
}

const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() {
  // Singleton instance, intentionally never destroyed.
  static const TfLiteRegistrationExternal* my_custom_op = MyCustomOpCreate();
  return my_custom_op;
}

const TfLiteRegistration MyCustomOpRegistration() {
  static const TfLiteRegistration my_custom_op {
    .registration_external = MyCustomOpRegistrationExternal();
  };
  return my_custom_op;
}
      

يُرجى ملاحظة أنّ التسجيل لا يتم تلقائيًا ويجب إجراء طلب صريح لدالة MyCustomOpRegistration (انظر التفاصيل أدناه). على الرغم من أنّ معيار BuiltinOpResolver (المتاح من هدف :builtin_ops) يتولّى تسجيل الملفات المضمَّنة، يجب جمع العمليات المخصّصة في مكتبات مخصّصة منفصلة.

تحديد النواة في وقت التشغيل TensorFlow Lite

لاستخدام العملية في TensorFlow Lite، ما علينا سوى تحديد دالتَين (Prepare وEval)، والدالة الثالثة لإنشاء TfLiteRegistrationExternal:

C++

namespace atan_op {
  namespace {
    TfLiteStatus AtanPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
      TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumInputs(node), 1);
      TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumOutputs(node), 1);

      const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
      TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

      int num_dims = TfLiteOpaqueTensorNumDimensions(input);

      TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims);
      for (int i=0; i < num_dims; ++i) {
        output_size->data[i] = input->dims->data[i];
      }

      return TfLiteOpaqueContextResizeTensor(context, output, output_size);
    }

    TfLiteStatus AtanEval(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
      const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
      TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

      float* input_data = static_cast(TfLiteOpaqueTensorData(input));
      float* output_data = static_cast(TfLiteOpaqueTensorData(output));

      size_t count = 1;
      int num_dims = TfLiteOpaqueTensorNumDimensions(input);
      for (int i = 0; i < num_dims; ++i) {
        count *= input->dims->data[i];
      }

      for (size_t i = 0; i < count; ++i) {
        output_data[i] = atan(input_data[i]);
      }
      return kTfLiteOk;
    }
  }  // anonymous namespace

  const TfLiteRegistrationExternal* AtanOpRegistrationExternal() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteRegistrationExternal* atan_op = ()[] {
        auto* r = TfLiteRegistrationExternalCreate(
            kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
        TfLiteRegistrationExternalSetPrepare(r, Prepare);
        TfLiteRegistrationExternalSetInvoke(r, Eval);
        return r;
      };
    return atan_op;
  }

  const TfLiteRegistration AtanOpRegistration() {
    static const TfLiteRegistration atan_op {
      .registration_external = AtanOpRegistrationExternal();
    };
    return atan_op;
  }
}  // namespace atan_op
      

C

static TfLiteStatus AtanPrepare(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
  TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumInputs(node), 1);
  TF_LITE_OPAQUE_ENSURE_EQ(context, TfLiteOpaqueNodeNumOutputs(node), 1);

  const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
  TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

  int num_dims = TfLiteOpaqueTensorNumDimensions(input);

  TfLiteIntArray* output_size = TfLiteIntArrayCreate(num_dims);
  for (int i = 0; i < num_dims; ++i) {
    output_size->data[i] = input->dims->data[i];
  }

  return TfLiteOpaqueContextResizeTensor(context, output, output_size);
}

static TfLiteStatus AtanEval(TfLiteOpaqueContext* context, TfLiteOpaqueNode* node) {
  const TfLiteOpaqueTensor* input = TfLiteOpaqueNodeGetInput(context, node, 0);
  TfLiteOpaqueTensor* output = TfLiteOpaqueNodeGetOutput(context, node, 0);

  float* input_data = static_cast(TfLiteOpaqueTensorData(input));
  float* output_data = static_cast(TfLiteOpaqueTensorData(output));

  size_t count = 1;
  int num_dims = TfLiteOpaqueTensorNumDimensions(input);
  for (int i = 0; i < num_dims; ++i) {
    count *= input->dims->data[i];
  }

  for (size_t i = 0; i < count; ++i) {
    output_data[i] = atan(input_data[i]);
  }
  return kTfLiteOk;
}

static const TfLiteRegistrationExternal* AtanOpCreate() {
  TfLiteRegistrationExternal* r = TfLiteRegistrationExternalCreate(
          kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
  TfLiteRegistrationExternalSetPrepare(r, Prepare);
  TfLiteRegistrationExternalSetInvoke(r, Eval);
  return r;
}

const TfLiteRegistrationExternal* AtanOpRegistrationExternal() {
  // Singleton instance, intentionally never destroyed.
  static const TfLiteRegistrationExternal* atan_op = AtanOpCreate();
  return atan_op;
}

const TfLiteRegistration AtanOpRegistration() {
  static const TfLiteRegistration atan_op {
    .registration_external = AtanOpRegistrationExternal();
  };
  return atan_op;
}
      

عند إعداد OpResolver، أضِف العملية المخصّصة إلى برنامج التعيين (انظر أدناه للاطّلاع على مثال). سيؤدي هذا إلى تسجيل العامل في Tensorflow Lite حتى يتمكن TensorFlow Lite من استخدام طريقة التنفيذ الجديدة. لاحظ أن الوسيطتين الأخيرتين في TfLiteRegistration تتجاوبان مع الدالتين AtanPrepare وAtanEval التي حددتها للحالة المخصصة. وإذا استخدمت الدالتين AtanInit وAtanFree لتهيئة المتغيرات المستخدمة في العملية ولإخلاء بعض المساحة، على التوالي، ستتم إضافتها إلى أول وسيطتين من TfLiteRegistration؛ تم ضبط هذه الوسيطات على nullptr في هذا المثال.

تسجيل عامل التشغيل في مكتبة النواة

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

يتم تعريف الفئة OpResolver التي تترجم رموز عوامل التشغيل وأسمائها إلى رموز فعلية على النحو التالي:

class OpResolver {
 public:
  virtual TfLiteRegistration* FindOp(tflite::BuiltinOperator op) const = 0;
  virtual TfLiteRegistration* FindOp(const char* op) const = 0;
  ...
};

بالنسبة إلى التوافق مع الأنظمة القديمة، تستخدم هذه الفئة النوع الملموس القديم TfLiteRegistration بدلاً من النوع المُعتم TfLiteRegistrationExternal، لكن بنية TfLiteRegistration تحتوي على الحقل registration_external من النوع TfLiteRegistrationExternal*.

يتم اشتقاق فئتَي MutableOpResolver وBuiltinOpResolver من OpResolver:

class MutableOpResolver : public OpResolver {
 public:
  MutableOpResolver();  // Constructs an initially empty op resolver.
  void AddBuiltin(tflite::BuiltinOperator op, const TfLiteRegistration* registration) = 0;
  void AddCustom(const char* op, const TfLiteRegistration* registration) = 0;
  void AddAll(const MutableOpResolver& other);
  ...
};

class BuiltinOpResolver : public MutableOpResolver {
 public:
  BuiltinOpResolver();  // Constructs an op resolver with all the builtin ops.
};

يتطلب الاستخدام المنتظم (بدون عمليات مخصصة) استخدام BuiltinOpResolver وكتابة:

tflite::ops::builtin::BuiltinOpResolver resolver;

لإضافة العملية المخصّصة التي تم إنشاؤها أعلاه، يمكنك بدلاً من ذلك استخدام MutableOpResolver، واستدعاء AddCustom (قبل تمرير برنامج التعيين إلى InterpreterBuilder):

tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
resolver.AddCustom("Atan", AtanOpRegistration());

إذا تم اعتبار مجموعة العمليات المضمّنة كبيرة جدًا، قد يتم إنشاء OpResolver جديدة باستخدام رمز استنادًا إلى مجموعة فرعية معيّنة من العمليات، وربما تلك التي يتضمّنها نموذج معيّن فقط. يعادل ذلك التسجيل الانتقائي في TensorFlow (وتتوفّر نسخة بسيطة منه في دليل tools).

إذا كنت تريد تحديد عوامل التشغيل المخصّصة في Java، عليك حاليًا إنشاء طبقة JNI المخصّصة وتجميع AAR الخاص بك في رمز jni هذا. وبالمثل، إذا كنت ترغب في تحديد عوامل التشغيل هذه المتوفرة في بايثون، يمكنك وضع تسجيلاتك في رمز برنامج تضمين Python.

لاحظ أنه يمكن اتباع عملية مماثلة كما أشرنا سلفًا لدعم مجموعة من العمليات بدلاً من عامل تشغيل واحد. ما عليك سوى إضافة أكبر عدد ممكن من عوامل التشغيل AddCustom. بالإضافة إلى ذلك، يتيح لك MutableOpResolver أيضًا إلغاء عمليات تنفيذ البنية الأساسية باستخدام AddBuiltin.

اختبار عامل التشغيل وملفه الشخصي

لتحديد تجربتك باستخدام أداة قياس الأداء TensorFlow Lite، يمكنك استخدام أداة نموذج قياس الأداء لتطبيق TensorFlow Lite. لأغراض الاختبار، يمكنك إعلام إصدارك المحلي من TensorFlow Lite بالخيار المخصّص من خلال إضافة طلب AddCustom المناسب (كما هو موضح أعلاه) إلى register.cc.

أفضل الممارسات

  1. حسِّن عمليات تخصيص الذاكرة وعمليات إلغاء التخصيص بحذر. إنّ تخصيص الذاكرة في "Prepare" أكثر فعالية مقارنةً بـ "Invoke"، كما أنّ تخصيص الذاكرة قبل التكرار الحلقي أفضل منه في كل تكرار. استخدم بيانات الموترات المؤقتة بدلاً من التركيز على وضع نفسك (انظر البند 2). استخدم المؤشرات/المراجع بدلاً من النسخ قدر الإمكان.

  2. إذا كانت بنية البيانات ستستمر خلال العملية بأكملها، فإننا ننصح بتخصيص الذاكرة مسبقًا باستخدام الموترات المؤقتة. قد تحتاج إلى استخدام هيكل OpData للإشارة إلى مؤشرات المتوتر في دوال أخرى. انظر المثال في نواة الالتفاف. في ما يلي نموذج لمقتطف رمز.

    struct MyOpData {
      int temp_tensor_index;
      ...
    };
    
    void* Init(TfLiteOpaqueContext* context,
        const char* buffer, size_t length) {
      auto* op_data = new MyOpData{};
      ...
      return op_data;
    }
    void Free(TfLiteOpaqueContext* context, void* buffer) {
      ...
      delete reinterpret_cast<MyOpData*>(buffer);
    }
    TfLiteStatus Prepare(TfLiteOpaqueContext* context,
                         TfLiteOpaqueNode* node) {
      ...
      auto* op_data =
          reinterpret_cast<MyOpData*>(TfLiteOpaqueNodeGetUserData(node));
      const int num_temporaries = 1;
      int temporary_tensor_indices[num_temporaries];
      TfLiteOpaqueTensorBuilder* builder = TfLiteOpaqueTensorBuilderCreate();
      TfLiteOpaqueTensorBuilderSetType(builder, kTfLiteFloat32);
      TfLiteOpaqueTensorBuilderSetAllocationType(builder, kTfLiteArenaRw);
      TfLiteOpaqueContextAddTensor(context, builder,
          &temporary_tensor_indices[0]);
      TfLiteOpaqueTensorBuilderDelete(builder);
      TfLiteOpaqueNodeSetTemporaries(node, temporary_tensor_indices,
          num_temporaries);
      op_data->temp_tensor_index = temporary_tensor_indices[0];
      ...
      return kTfLiteOk;
    }
    TfLiteStatus Invoke(TfLiteOpaqueContext* context,
                        TfLiteOpaqueNode* node) {
      ...
      auto* op_data = reinterpret_cast<MyOpData*>(
          TfLiteOpaqueNodeGetUserData(node));
      TfLiteOpaqueTensor* temp_tensor =
          TfLiteOpaqueContextGetOpaqueTensor(context,
              op_data->temp_tensor_index);
      TF_LITE_OPAQUE_ENSURE(context,
          TfLiteTensorType(temp_tensor) == kTfLiteFloat32);
      TF_LITE_OPAQUE_ENSURE(context,
          TfLiteTensorGetAllocationType(temp_Tensor) == kTfLiteArenaRw);
      void *temp_data = TfLiteTensorData(temp_tensor);
      TF_LITE_OPAQUE_ENSURE(context, temp_data != nullptr);
      ...
      return kTfLiteOk;
    }
    
  3. إذا لم يكن عليك إهدار الكثير من الذاكرة، ننصحك باستخدام مصفوفة ثابتة ذات حجم ثابت (أو مصفوفة std::vector مخصّصة مسبقًا في Resize) بدلاً من استخدام مصفوفة std::vector مخصّصة ديناميكيًا في كل مرة تكرار تنفيذ.

  4. تجنَّب إنشاء مثيل لنماذج حاويات المكتبة العادية غير المتوفّرة مسبقًا، لأنّها تؤثر في الحجم الثنائي. على سبيل المثال، إذا كنت بحاجة إلى استخدام std::map في عمليتك غير متوفر في نواة أخرى، قد يُجدي استخدام std::vector مع الربط المباشر للفهرسة مع إبقاء الحجم الثنائي صغيرًا. تعرّف على ما تستخدمه النواة الأخرى للحصول على إحصاءات (أو الاستفسار).

  5. تحقَّق من المؤشر الذي يشير إلى الذكرى التي يعرضها "malloc". أما إذا كان هذا المؤشر هو nullptr، فمن المفترض ألا يتم إجراء أي عمليات باستخدام هذا المؤشر. في حال malloc في إحدى الدوال وظهور رسالة خطأ، احرص على تخصيص الذاكرة قبل الخروج.

  6. استخدم TF_LITE_OPAQUE_ENSURE(context, condition) للتحقق من حالة معينة. يجب ألا يترك الرمز الذاكرة معلّقة عند استخدام TF_LITE_OPAQUE_ENSURE، بمعنى أنه يجب استخدام وحدات الماكرو هذه قبل تخصيص أي موارد تسبّب تسرّبها.