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

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

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

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

لنتعرّف على مثال شامل لتشغيل نموذج باستخدام عامل تشغيل مخصّص tf.atan (يُسمى Atan، يُرجى الرجوع إلى إنشاء نموذج TensorFlow) متاح في TensorFlow وغير متاح في LiteRT.

يُعدّ عامل تشغيل TensorFlow Text مثالاً على عامل تشغيل مخصّص. اطّلِع على البرنامج التعليمي تحويل نص TF إلى LiteRT للحصول على مثال على الرمز البرمجي.

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

لنستعرض مثالاً على توفير عامل TensorFlow لا يتوفّر في LiteRT. لنفترض أنّنا نستخدم عامل التشغيل 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

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

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

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

أنشئ نموذج LiteRT باستخدام عوامل تشغيل مخصّصة، وذلك من خلال ضبط سمة المحوّل 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"

يتم تحديد عوامل التشغيل المخصّصة في LiteRT باستخدام واجهة برمجة تطبيقات بسيطة بلغة C تتألف من نوع مبهم (TfLiteOperator) ودوال ذات صلة.

TfLiteOperator هو نوع مبهم:

typedef struct TfLiteOperator TfLiteOperator;

يخزّن TfLiteOperator هوية المشغّل وتنفيذه. (يُرجى العِلم أنّ العامل يختلف عن المعامِلات التي يتم تخزينها في عُقد الرسم البياني LiteRT للعُقد التي تستدعي العامل).

يتم إنشاء مثيلات من هذا النوع من خلال استدعاء TfLiteOperatorCreate ويمكن إتلافها من خلال استدعاء TfLiteOperatorDelete.

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

TfLiteOperator*
TfLiteOperatorCreate(
    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++) في تنفيذ العملية مع أسماء الدوال في مقتطف الرمز البرمجي أعلاه، لأنّ واجهة برمجة التطبيقات المخصّصة للعمليات في TensorFlow Lite ستستخدم عناوينها فقط. في الواقع، ننصحك بتعريفها في مساحة اسم مجهولة أو كدوال ثابتة.

ولكن من المستحسن تضمين اسم المشغّل كمساحة اسم أو بادئة في أسماء الدوال هذه:

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، يتم تنفيذ هذه "الطرق" كمؤشرات دالة C في النوع TfLiteOperator، والتي يتم ضبطها من خلال تمرير عناوين دوال التنفيذ إلى دوال الضبط المقابلة TfLiteOperatorSetMethodName:

void TfLiteOperatorSetInit(
    TfLiteOperator* operator,
    void* (*init)(TfLiteOpaqueContext* context, const char* buffer,
                  size_t length));
void TfLiteOperatorSetFree(
    TfLiteOperator* operator,
    void (*free)(TfLiteOpaqueContext* context, void* data));
void TfLiteOperatorSetPrepare(
    TfLiteOperator* operator,
    TfLiteStatus (*prepare)(TfLiteOpaqueContext* context,
                            TfLiteOpaqueNode* node));
void TfLiteOperatorSetInvoke(
    TfLiteOperator* operator,
    TfLiteStatus (*invoke)(TfLiteOpaqueContext* context,
                           TfLiteOpaqueNode* node));
void TfLiteOperatorSetAsyncKernel(
    TfLiteOperator* operator,
    struct TfLiteAsyncKernel* (*async_kernel)(TfLiteOpaqueContext* context,
                                              TfLiteOpaqueNode* node));

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

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

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

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

يمكن تنفيذ العمليات المخصّصة من خلال تحديد وظائف "الطريقة" هذه، ثم تحديد وظيفة تعرض مثيلاً من TfLiteOperator تم إنشاؤه عن طريق استدعاء TfLiteOperatorCreate ثم طرق الضبط ذات الصلة:

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 TfLiteOperator* MyCustomOperator() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteOperator* my_custom_op = ()[] {
        TfLiteOperator* r =
            TfLiteOperatorCreate(
                kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
        TfLiteOperatorSetInit(r, Init);
        TfLiteOperatorSetFree(r, Free);
        TfLiteOperatorSetPrepare(r, Prepare);
        TfLiteOperatorSetInvoke(r, Eval);
        return r;
      };
    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 TfLiteOperator* MyCustomOpCreate() {
  const TfLiteOperator* r =
      TfLiteOperatorCreate(
          kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
  TfLiteOperatorSetInit(r, MyCustomOpInit);
  TfLiteOperatorSetFree(r, MyCustomOpFree);
  TfLiteOperatorSetPrepare(r, MyCustomOpPrepare);
  TfLiteOperatorSetInvoke(r, MyCustomOpEval);
  return r;
}

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

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

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

كل ما علينا فعله لاستخدام العملية في LiteRT هو تحديد دالتَين (Prepare وEval)، ودالة ثالثة لإنشاء TfLiteOperator:

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<float*>(TfLiteOpaqueTensorData(input));
      float* output_data = static_cast<float*>(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 TfLiteOperator* AtanOperator() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteOperator* atan_op = ()[] {
        auto* r = TfLiteOperatorCreate(
            kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
        TfLiteOperatorSetPrepare(r, Prepare);
        TfLiteOperatorSetInvoke(r, Eval);
        return r;
      };
    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<float*>(TfLiteOpaqueTensorData(input));
  float* output_data = static_cast<float*>(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 TfLiteOperator* AtanOpCreate() {
  TfLiteOperator* r = TfLiteOperatorCreate(
          kTfLiteBuiltinCustom, "ATAN", /*version=*/ 1);
  TfLiteOperatorSetPrepare(r, Prepare);
  TfLiteOperatorSetInvoke(r, Eval);
  return r;
}

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

عند تهيئة OpResolver، أضِف العملية المخصّصة إلى أداة تحليل الاسم (راجِع المثال أدناه). سيؤدي ذلك إلى تسجيل المشغّل في LiteRT كي يتمكّن LiteRT من استخدام التنفيذ الجديد.

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

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

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

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

يُرجى العِلم أنّه لضمان التوافق مع الأنظمة القديمة، تستخدم هذه الفئة النوع الملموس القديم TfLiteRegistration بدلاً من النوع غير الشفاف TfLiteOperator، ولكن يحتوي البنية TfLiteRegistration على حقل registration_external من النوع TfLiteOperator*.

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

class MutableOpResolver : public OpResolver {
 public:
  MutableOpResolver();  // Constructs an initially empty op resolver.
  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، واستدعاء tflite::AddOp (قبل تمرير أداة تحليل الاسم إلى InterpreterBuilder):

tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
tflite::AddOp(&resolver, AtanOpRegistration());

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

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

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

اختبار مشغّلك وتحديد ملفه الشخصي

لإنشاء ملف تعريف للعملية باستخدام أداة قياس الأداء LiteRT، يمكنك استخدام أداة نموذج قياس الأداء في LiteRT. لأغراض الاختبار، يمكنك تعريف الإصدار المحلي من LiteRT على العملية المخصّصة من خلال إضافة استدعاء 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، أي يجب استخدام وحدات الماكرو هذه قبل تخصيص أي موارد سيحدث فيها تسرّب.