المشغلين المخصصين

نظرًا لأن مكتبة مشغل TensorFlow Lite المضمنة تدعم فقط عددًا محدودًا من مشغلي TensorFlow، فليس كل طراز قابل للتحويل. للحصول على التفاصيل، راجع توافق المشغل .

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

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

دعونا نستعرض مثالًا شاملاً لتشغيل نموذج باستخدام عامل تشغيل مخصص tf.atan (يسمى Atan ، راجع #create_a_tensorflow_model) وهو مدعوم في TensorFlow، ولكنه غير مدعوم في TensorFlow Lite.

يعد عامل تشغيل TensorFlow Text مثالاً على عامل تشغيل مخصص. راجع البرنامج التعليمي لتحويل TF Text إلى 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 "tensorflow/lite/c/c_api.h"
#include "tensorflow/lite/c/c_api_opaque.h"

يتم تعريف عوامل تشغيل TensorFlow Lite المخصصة باستخدام واجهة برمجة تطبيقات بسيطة خالصة C تتكون من نوع غير شفاف ( 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);

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

ولكن من الجيد تضمين اسم عامل التشغيل الخاص بك كمساحة اسم أو بادئة في أسماء الوظائف هذه:

سي ++

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

ج

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

نظرًا لأن هذه واجهة برمجة تطبيقات C، يتم تنفيذ هذه "الطرق" كمؤشرات دالة C في النوع TfLiteRegistrationExternal ، والتي يتم تعيينها عن طريق تمرير عناوين وظائف التنفيذ الخاصة بك إلى وظائف الضبط المقابلة TfLiteRegistrationExternalSet MethodName :

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() المحدد أكثر من مرة إذا تم استخدام المرجع عدة مرات في الرسم البياني. بالنسبة للعمليات المخصصة، سيتم توفير مخزن مؤقت للتكوين، يحتوي على مخزن مؤقت مرن يربط أسماء المعلمات بقيمها. المخزن المؤقت فارغ للعمليات المضمنة لأن المترجم قد قام بالفعل بتحليل معلمات العملية. يجب أن تقوم تطبيقات Kernel التي تتطلب الحالة بتهيئتها هنا ونقل الملكية إلى المتصل. لكل استدعاء Init() ، سيكون هناك استدعاء مناظر لـ Free() ، مما يسمح للتطبيقات بالتخلص من المخزن المؤقت الذي ربما تكون قد خصصته في Init() .

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

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

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

سي ++

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
      

ج

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 :

سي ++

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
      

ج

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 في هذا المثال.

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

نحتاج الآن إلى تسجيل المشغل في مكتبة kernel. ويتم ذلك باستخدام 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، فيمكنك وضع تسجيلاتك في رمز مجمّع Python .

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

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

لتكوين ملف تعريف للعملية الخاصة بك باستخدام أداة قياس الأداء TensorFlow Lite، يمكنك استخدام أداة النموذج المعياري لـ TensorFlow Lite. لأغراض الاختبار، يمكنك جعل الإصدار المحلي الخاص بك من TensorFlow Lite على دراية بالعملية المخصصة الخاصة بك عن طريق إضافة استدعاء AddCustom المناسب (كما هو موضح أعلاه) للتسجيل.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 ، أي أنه يجب استخدام وحدات الماكرو هذه قبل تخصيص أي موارد قد تتسرب.