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

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

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

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

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

عامل النص TensorFlow هو مثال على عامل تشغيل مخصص. يمكنك الاطّلاع على برنامج تعليمي حول تحويل نص 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 باستخدام واجهة برمجة تطبيقات بسيطة pure-C يتكون من نوع غامض (TfLiteRegistrationExternal) ودوال ذات صلة.

TfLiteRegistrationExternal هو نوع مبهم:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

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

يتم إنشاء مثيلات من هذا النوع بواسطة استدعاءات 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.
);

يمكن لتطبيق عامل التشغيل تحديد "الطرق" بالتوقيعات التالية. جميع هذه الطرق اختيارية، ولكن يجب أن يتأكد عامل التشغيل من أن يجب على تنفيذ المشغل التحديد والضبط (باستخدام دالة setter) الدالّة) على الأقل، إلى الطريقتَين 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 custom 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.
      

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

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

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

يمكن تنفيذ العمليات المخصصة من خلال تحديد تلك "الطريقة" الدوال، ثم تحديد دالة تقوم بإرجاع مثيل لـ 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) بتسجيل الأجهزة المدمجة، وسينبغي جمع العمليات المخصصة في مكتبات مخصصة منفصلة.

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

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

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

اختبار منظِّم الجولات وتعريفه

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

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

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

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

    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، أي أنه يجب استخدام وحدات الماكرو هذه قبل أي موارد مخصصة سيتم تسربها.