कस्टम ऑपरेटर

TensorFlow Lite की पहले से मौजूद ऑपरेटर लाइब्रेरी, सीमित संख्या में TensorFlow ऑपरेटर के साथ काम करती है. इसलिए, हर मॉडल को कन्वर्ट नहीं किया जा सकता. ज़्यादा जानकारी के लिए, ऑपरेटर के साथ काम करने की सुविधा देखें.

कन्वर्ज़न की अनुमति देने के लिए, उपयोगकर्ता TensorFlow Lite में, TensorFlow के काम न करने वाले ऑपरेटर को पसंद के मुताबिक लागू करने की सुविधा दे सकते हैं. TensorFlow Lite को कस्टम ऑपरेटर कहा जाता है. इसके बजाय, अगर इसके बजाय, काम न करने वाले या काम करने वाले TensorFlow ऑपरेटर की सीरीज़ को एक फ़्यूज़्ड ऑप्टिमाइज़ किए गए कस्टम ऑपरेटर में जोड़ना है, तो ऑपरेटर फ़्यूज़िंग देखें.

कस्टम ऑपरेटर का इस्तेमाल करने के चार चरण होते हैं.

आइए, कस्टम ऑपरेटर tf.atan पर मॉडल चलाने के एक उदाहरण के बारे में जानते हैं. मॉडल का नाम Atan है और यह TensorFlow मॉडल बनाएं को देखें. यह मॉडल 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 लाइट मॉडल में बदलें

कस्टम ऑपरेटर की मदद से 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 "third_party/tensorflow/lite/c/c_api.h"
#include "third_party/tensorflow/lite/c/c_api_opaque.h"

TensorFlow Lite के कस्टम ऑपरेटर, एक आसान शुद्ध-C API का इस्तेमाल करके तय किए जाते हैं. इसमें एक ओपेक टाइप (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);

आपके ऑपरेटर जोड़ने पर लागू होने वाले फ़ंक्शन names (या C++ के लिए नेमस्पेस प्रीफ़िक्स) का ऊपर दिए गए कोड स्निपेट में फ़ंक्शन के नामों से मेल खाना ज़रूरी नहीं है, क्योंकि TF Lite कस्टम 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.
      

यह एक C API है, इसलिए इन "तरीकों" को TfLiteRegistrationExternal टाइप में C फ़ंक्शन पॉइंटर के तौर पर लागू किया जाता है. ये तरीके, आपके लागू करने के फ़ंक्शन के पतों को उनसे जुड़े सेटर फ़ंक्शन में पास करके सेट किए जाते हैं 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));

TfLiteContext और TfLiteNode के बारे में जानकारी के लिए, common.h देखें. TfLiteContext, गड़बड़ी की रिपोर्ट करने की सुविधाएं और सभी टेन्सर के साथ-साथ ग्लोबल ऑब्जेक्ट का ऐक्सेस देता है. TfLiteNode ऑपरेटर को लागू करने के दौरान, उनके इनपुट और आउटपुट को ऐक्सेस करने की अनुमति देता है.

जब अनुवादक किसी मॉडल को लोड करता है, तब यह ग्राफ़ में हर नोड के लिए Init() तरीके को एक बार कॉल करता है. अगर ग्राफ़ में ऑप को एक से ज़्यादा बार इस्तेमाल किया गया है, तो किसी दिए गए Init() को एक से ज़्यादा बार कॉल किया जाएगा. कस्टम ऑपरेशन के लिए एक कॉन्फ़िगरेशन बफ़र दिया जाएगा, जिसमें एक ऐसा फ़्लेक्सर होगा जो पैरामीटर के नामों को उनकी वैल्यू तक मैप करेगा. पहले से मौजूद ऑपरेशन के लिए बफ़र खाली है, क्योंकि अनुवादक ने पहले ही सेशन पैरामीटर पार्स कर लिया है. जिस कर्नेल इंप्लीमेंटेशन को स्टेट की ज़रूरत होती है उसे यहां से शुरू करना चाहिए. साथ ही, उसका मालिकाना हक कॉलर को ट्रांसफ़र करना चाहिए. हर Init() कॉल के लिए, Free() के लिए एक कॉल किया जाएगा. इससे, लागू करने के लिए Init() में असाइन किए गए बफ़र को नष्ट किया जा सकेगा.

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

आखिर में, हर बार अनुमान चलाने पर, अनुवादक Invoke() तरीके को कॉल करने वाले ग्राफ़ को ट्रैवर्स करता है और यहां भी स्थिति, TfLiteOpaqueNodeGetUserData(node) के तौर पर उपलब्ध होती है.

कस्टम ऑपरेशन को लागू करने के लिए, "method" फ़ंक्शन तय किए जा सकते हैं. इसके बाद, ऐसा फ़ंक्शन तय किया जा सकता है जो TfLiteRegistrationExternalCreate को कॉल करके, TfLiteRegistrationExternal का इंस्टेंस दिखाता है. इसके बाद, सेटर के काम के तरीके तय किए जाते हैं:

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 टारगेट से उपलब्ध) बिल्ट-इन के रजिस्ट्रेशन का काम करता है, लेकिन कस्टम ऑपरेशन को अलग-अलग कस्टम लाइब्रेरी में इकट्ठा करना होगा.

TensorFlow Lite रनटाइम में कर्नेल की जानकारी देना

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
      

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(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;
  ...
};

ध्यान दें कि पुराने सिस्टम के साथ काम करने के लिए, इस क्लास में ओपेक टाइप 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 का इस्तेमाल किया जा सकता है. साथ ही, रिज़ॉल्वर को InterpreterBuilder में पास करने से पहले, AddCustom को कॉल किया जा सकता है:

tflite::ops::builtin::MutableOpResolver resolver;
resolver.AddAll(tflite::ops::builtin::BuiltinOpResolver());
resolver.AddCustom("Atan", AtanOpRegistration());

अगर बिल्ट-इन ऑपरेशन का सेट बहुत बड़ा माना जाता है, तो ऑपरेशन के दिए गए सबसेट के आधार पर नया OpResolver कोड जनरेट किया जा सकता है. हो सकता है कि सिर्फ़ दिए गए मॉडल में ही OpResolver का कोड जनरेट हो. यह TensorFlow के चुनिंदा रजिस्ट्रेशन के बराबर है (और इसका सामान्य वर्शन tools डायरेक्ट्री में उपलब्ध है).

अगर आपको Java में अपने कस्टम ऑपरेटर तय करने हैं, तो आपको फ़िलहाल अपनी पसंद के मुताबिक जेएनआई लेयर बनानी होगी और इस जेनी कोड में अपना खुद का एएआर भी कंपाइल करना होगा. इसी तरह, अगर आपको Python में उपलब्ध इन ऑपरेटर के बारे में बताना है, तो अपने रजिस्ट्रेशन Python रैपर कोड में डालें.

ध्यान दें कि एक ऑपरेटर के बजाय, कुछ खास कार्रवाइयों का इस्तेमाल करने के लिए, ऊपर बताई गई प्रोसेस से मिलती-जुलती प्रोसेस अपनाई जा सकती है. आप जितने चाहें उतने AddCustom ऑपरेटर जोड़ें. इसके अलावा, MutableOpResolver आपको AddBuiltin का इस्तेमाल करके, बिल्टइन को लागू करने की प्रक्रिया को बदलने की भी अनुमति देता है.

अपने ऑपरेटर की जांच करें और उसे प्रोफ़ाइल करें

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

सबसे सही तरीके

  1. मेमोरी के बंटवारे और ऐलोकेशन को सावधानी से ऑप्टिमाइज़ करें. Invoke के मुकाबले, Prepare में मेमोरी असाइन करना ज़्यादा आसान होता है. साथ ही, लूप से पहले मेमोरी तय करना, हर इटरेशन से पहले बेहतर होता है. खुद को बंद करने के बजाय अस्थायी टेन्सर डेटा का इस्तेमाल करें (आइटम 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 चाहिए जो अन्य kernel में मौजूद नहीं है, तो सीधे इंडेक्स मैपिंग के साथ std::vector का इस्तेमाल करने से, बाइनरी के साइज़ को छोटा रखते हुए काम कर सकता है. देखें कि अहम जानकारी पाने (या पूछने) के लिए दूसरे कर्नेल किन चीज़ों का इस्तेमाल करते हैं.

  5. malloc से मिली मेमोरी के लिए पॉइंटर देखें. अगर यह पॉइंटर nullptr है, तो इस पॉइंटर का इस्तेमाल करके कोई कार्रवाई नहीं की जानी चाहिए. अगर फ़ंक्शन में malloc है और एग्ज़िट करते समय कोई गड़बड़ी होती है, तो बाहर निकलने से पहले मेमोरी की जानकारी दें.

  6. किसी खास स्थिति की जांच करने के लिए, TF_LITE_OPAQUE_ENSURE(context, condition) का इस्तेमाल करें. TF_LITE_OPAQUE_ENSURE का इस्तेमाल करते समय आपके कोड की मेमोरी को हैंगिंग नहीं करना चाहिए. इसका मतलब है कि अगर किसी संसाधन का इस्तेमाल किया जाता है, तो उससे पहले इन मैक्रो का इस्तेमाल किया जाना चाहिए.