कस्टम ऑपरेटर

चूँकि TensorFlow Lite बिल्ट-इन ऑपरेटर लाइब्रेरी केवल सीमित संख्या में TensorFlow ऑपरेटरों का समर्थन करती है, इसलिए प्रत्येक मॉडल परिवर्तनीय नहीं है। विवरण के लिए, ऑपरेटर अनुकूलता देखें।

रूपांतरण की अनुमति देने के लिए, उपयोगकर्ता TensorFlow Lite में एक असमर्थित TensorFlow ऑपरेटर का अपना स्वयं का कस्टम कार्यान्वयन प्रदान कर सकते हैं, जिसे कस्टम ऑपरेटर के रूप में जाना जाता है। यदि इसके बजाय, आप असमर्थित (या समर्थित) TensorFlow ऑपरेटरों की एक श्रृंखला को एक फ़्यूज्ड अनुकूलित कस्टम ऑपरेटर में संयोजित करना चाहते हैं, तो ऑपरेटर फ़्यूज़िंग देखें।

कस्टम ऑपरेटरों का उपयोग करने में चार चरण होते हैं।

आइए एक कस्टम ऑपरेटर tf.atan (जिसे Atan नाम दिया गया है, #create_a_tensorflow_model देखें) के साथ एक मॉडल चलाने का एक एंड-टू-एंड उदाहरण देखें, जो TensorFlow में समर्थित है, लेकिन TensorFlow Lite में असमर्थित है।

TensorFlow टेक्स्ट ऑपरेटर एक कस्टम ऑपरेटर का एक उदाहरण है। कोड उदाहरण के लिए कन्वर्ट टीएफ टेक्स्ट को टीएफ लाइट ट्यूटोरियल देखें।

उदाहरण: कस्टम 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 लाइट मॉडल में कनवर्ट करें

जैसा कि नीचे दिखाया गया है, कनवर्टर विशेषता allow_custom_ops सेट करके, कस्टम ऑपरेटरों के साथ एक TensorFlow Lite मॉडल बनाएं:

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 कस्टम ऑपरेटरों को एक सरल शुद्ध-सी एपीआई का उपयोग करके परिभाषित किया गया है जिसमें एक अपारदर्शी प्रकार ( TfLiteRegistrationExternal ) और संबंधित फ़ंक्शन शामिल हैं।

TfLiteRegistrationExternal एक अपारदर्शी प्रकार है:

typedef struct TfLiteRegistrationExternal TfLiteRegistrationExternal;

TfLiteRegistrationExternal ऑपरेटर की पहचान और कार्यान्वयन को संग्रहीत करता है। (ध्यान दें कि ऑपरेटर अपने ऑपरेंड से अलग है, जो ऑपरेटर को कॉल करने वाले नोड्स के लिए टीएफ लाइट ग्राफ नोड्स में संग्रहीत होते हैं।)

इस प्रकार के उदाहरण 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 लाइट कस्टम ऑप्स एपीआई केवल उनके पते का उपयोग करेगा। वास्तव में हम अनुशंसा करते हैं कि आप उन्हें अज्ञात नामस्थान या स्थिर फ़ंक्शन के रूप में घोषित करें।

लेकिन इन फ़ंक्शन नामों पर अपने ऑपरेटर नाम को नेमस्पेस या उपसर्ग के रूप में शामिल करना एक अच्छा विचार है:

सी++

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 है, इन "विधियों" को TfLiteRegistrationExternal प्रकार में C फ़ंक्शन पॉइंटर्स के रूप में कार्यान्वित किया जाता है, जो आपके कार्यान्वयन फ़ंक्शंस के पते को संबंधित सेटर फ़ंक्शंस TfLiteRegistrationExternalSet मेथडनेम में पास करके सेट किया जाता है:

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));

TfLiteContext और TfLiteNode पर विवरण के लिए common.h देखें। TfLiteContext सभी टेंसरों सहित त्रुटि रिपोर्टिंग सुविधाएं और वैश्विक वस्तुओं तक पहुंच प्रदान करता है। TfLiteNode ऑपरेटर कार्यान्वयन को उनके इनपुट और आउटपुट तक पहुंचने की अनुमति देता है।

जब दुभाषिया किसी मॉडल को लोड करता है, तो वह ग्राफ़ में प्रत्येक नोड के लिए एक बार Init() विधि को कॉल करता है। यदि ग्राफ़ में ऑप का उपयोग कई बार किया जाता है तो दिए गए Init() एक से अधिक बार कॉल किया जाएगा। कस्टम ऑप्स के लिए एक कॉन्फ़िगरेशन बफ़र प्रदान किया जाएगा, जिसमें एक फ्लेक्सबफ़र होगा जो पैरामीटर नामों को उनके मानों पर मैप करेगा। बिल्टिन ऑप्स के लिए बफर खाली है क्योंकि दुभाषिया ने पहले ही ऑप पैरामीटर्स को पार्स कर लिया है। कर्नेल कार्यान्वयन के लिए राज्य की आवश्यकता होती है, इसे यहां आरंभ करना चाहिए और कॉलर को स्वामित्व हस्तांतरित करना चाहिए। प्रत्येक Init() कॉल के लिए, Free() के लिए एक संबंधित कॉल होगी, जिससे कार्यान्वयन को Init() में आवंटित बफर का निपटान करने की अनुमति मिलेगी।

जब भी इनपुट टेंसर का आकार बदला जाता है, तो दुभाषिया परिवर्तन के कार्यान्वयन को सूचित करते हुए ग्राफ़ के माध्यम से जाएगा। इससे उन्हें अपने आंतरिक बफर का आकार बदलने, इनपुट आकृतियों और प्रकारों की वैधता की जांच करने और आउटपुट आकृतियों की पुनर्गणना करने का मौका मिलता है। यह सब Prepare() विधि के माध्यम से किया जाता है, और कार्यान्वयन TfLiteOpaqueNodeGetUserData(node) का उपयोग करके अपने राज्य तक पहुंच सकते हैं।

अंत में, हर बार जब अनुमान चलता है, तो दुभाषिया Invoke() विधि को कॉल करते हुए ग्राफ़ को पार करता है, और यहां भी स्थिति TfLiteOpaqueNodeGetUserData(node) के रूप में उपलब्ध है।

कस्टम ऑप्स को उन "विधि" फ़ंक्शंस को परिभाषित करके कार्यान्वित किया जा सकता है, और फिर एक फ़ंक्शन को परिभाषित किया जा सकता है जो TfLiteRegistrationExternalCreate और फिर प्रासंगिक सेटर विधियों को कॉल करके निर्मित TfLiteRegistrationExternal का एक उदाहरण देता है:

सी++

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 पर सेट हैं।

ऑपरेटर को कर्नेल लाइब्रेरी के साथ पंजीकृत करें

अब हमें ऑपरेटर को कर्नेल लाइब्रेरी के साथ पंजीकृत करने की आवश्यकता है। यह OpResolver के साथ किया जाता है। पर्दे के पीछे, दुभाषिया कर्नेल की एक लाइब्रेरी लोड करेगा जिसे मॉडल में प्रत्येक ऑपरेटर को निष्पादित करने के लिए सौंपा जाएगा। जबकि डिफ़ॉल्ट लाइब्रेरी में केवल बिल्टिन कर्नेल होते हैं, इसे कस्टम लाइब्रेरी ऑप ऑपरेटरों के साथ बदलना/संवर्धित करना संभव है।

OpResolver वर्ग, जो ऑपरेटर कोड और नामों को वास्तविक कोड में अनुवादित करता है, को इस प्रकार परिभाषित किया गया है:

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

ध्यान दें कि बैकवर्ड संगतता के लिए, यह वर्ग अपारदर्शी प्रकार TfLiteRegistrationExternal के बजाय पुराने ठोस प्रकार TfLiteRegistration का उपयोग करता है, लेकिन TfLiteRegistration संरचना में TfLiteRegistrationExternal* प्रकार का एक registration_external फ़ील्ड शामिल है।

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 निर्देशिका में उपलब्ध है)।

यदि आप जावा में अपने कस्टम ऑपरेटरों को परिभाषित करना चाहते हैं, तो आपको वर्तमान में अपनी स्वयं की कस्टम जेएनआई परत बनाने और इस जेएनआई कोड में अपना खुद का एएआर संकलित करने की आवश्यकता होगी। इसी तरह, यदि आप पायथन में उपलब्ध इन ऑपरेटरों को परिभाषित करना चाहते हैं तो आप अपना पंजीकरण पायथन रैपर कोड में रख सकते हैं।

ध्यान दें कि एकल ऑपरेटर के बजाय संचालन के एक सेट का समर्थन करने के लिए उपरोक्त समान प्रक्रिया का पालन किया जा सकता है। आपको जितनी आवश्यकता हो उतने AddCustom ऑपरेटर जोड़ें। इसके अलावा, MutableOpResolver आपको AddBuiltin उपयोग करके बिल्टिन के कार्यान्वयन को ओवरराइड करने की भी अनुमति देता है।

अपने ऑपरेटर का परीक्षण करें और प्रोफ़ाइल बनाएं

TensorFlow Lite बेंचमार्क टूल के साथ अपने ऑपरेशन को प्रोफाइल करने के लिए, आप TensorFlow Lite के लिए बेंचमार्क मॉडल टूल का उपयोग कर सकते हैं। परीक्षण उद्देश्यों के लिए, आप रजिस्टर.सीसी में उचित AddCustom कॉल (जैसा कि ऊपर दिखाया गया है) जोड़कर अपने TensorFlow Lite के स्थानीय बिल्ड को अपने कस्टम ऑप से अवगत करा सकते हैं।

सर्वोत्तम प्रथाएं

  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 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 का उपयोग किया जाता है तो आपके कोड को मेमोरी को हैंग नहीं करना चाहिए, अर्थात, किसी भी संसाधन को आवंटित करने से पहले इन मैक्रोज़ का उपयोग किया जाना चाहिए जो लीक हो जाएगा।