מפעילים מותאמים אישית

מאחר שספריית המפעילים המובנית של TensorFlow Lite תומכת רק במספר מוגבל של מפעילי TensorFlow, לא כל דגם ניתן להמרה. לפרטים, עיין בתאימות למפעיל .

כדי לאפשר המרה, משתמשים יכולים לספק יישום מותאם אישית משלהם של אופרטור TensorFlow שאינו נתמך ב-TensorFlow Lite, המכונה אופרטור מותאם אישית. אם במקום זאת, ברצונך לשלב סדרה של אופרטורים של TensorFlow שאינם נתמכים (או נתמכים) לאופרטור מותאם מותאם יחיד מותאם, עיין ב- Fuing Operator .

שימוש באופרטורים מותאמים אישית מורכב מארבעה שלבים.

בואו נעבור על דוגמה מקצה לקצה של הפעלת מודל עם אופרטור מותאם אישית tf.atan (ששמו Atan , עיין ב-#create_a_tensorflow_model) שנתמך ב-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 "tensorflow/lite/c/c_api.h"
#include "tensorflow/lite/c/c_api_opaque.h"

אופרטורים מותאמים אישית של TensorFlow Lite מוגדרים באמצעות API פשוט טהור-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++) ביישום ה-op שלך לא חייבים להתאים לשמות הפונקציות בקטע הקוד שלמעלה, מכיוון שה-API של TF Lite custom ops ישתמש רק בכתובות שלהם. אכן, אנו ממליצים להכריז עליהם במרחב שמות אנונימי או כפונקציות סטטיות.

אבל מומלץ לכלול את שם האופרטור שלך כמרחב שמות או קידומת בשמות הפונקציות האלה:

C++

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 API, "שיטות" אלו מיושמות כמצביעי פונקציות 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() נתון ייקרא יותר מפעם אחת אם נעשה שימוש ב-op מספר פעמים בגרף. עבור פעולות מותאמות אישית יסופק מאגר תצורה, המכיל flexbuffer הממפה שמות פרמטרים לערכים שלהם. המאגר ריק עבור פעולות מובנות מכיוון שהמתורגמן כבר פרש את פרמטרי ההפעלה. יישומי ליבה הדורשים מצב צריכים לאתחל אותו כאן ולהעביר את הבעלות למתקשר. עבור כל קריאה 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
      

ג

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

כל מה שאנחנו צריכים לעשות כדי להשתמש ב-op ב-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
      

ג

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 , הוסף את ה-op המותאם אישית לפותר (ראה למטה לדוגמא). זה ירשום את המפעיל עם Tensorflow Lite כך ש-TensorFlow Lite יוכל להשתמש ביישום החדש. שים לב ששני הארגומנטים האחרונים ב- TfLiteRegistration תואמים לפונקציות AtanPrepare ו- AtanEval שהגדרת עבור ה-Op Custom. אם השתמשת בפונקציות AtanInit ו- AtanFree כדי לאתחל משתנים המשמשים ב-op וכדי לפנות מקום, בהתאמה, אז הם יתווספו לשני הארגומנטים הראשונים של 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, תוכל למקם את הרישומים שלך בקוד המעטפת של 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 , כלומר, יש להשתמש בפקודות מאקרו אלו לפני שהוקצו משאבים כלשהם שידלפו.