कस्टम ऑपरेटर

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

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

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

आइए, मॉडल को चलाने का शुरू से अंत तक उदाहरण देखते हैं. ऑपरेटर tf.atan (जिसका नाम Atan है, यहां TensorFlow मॉडल बनाना देखें.) TensorFlow में काम करता है, लेकिन LiteRT में काम नहीं करता.

TensorFlow Text ऑपरेटर, कस्टम ऑपरेटर का एक उदाहरण है. ज़्यादा जानकारी के लिए, कोड का उदाहरण देखने के लिए, TF टेक्स्ट को LiteRT में बदलें ट्यूटोरियल.

उदाहरण: कस्टम Atan ऑपरेटर

चलिए, अब TensorFlow ऑपरेटर के साथ काम करने का एक उदाहरण देखते हैं LiteRT में यह सुविधा नहीं है. मान लें कि हम Atan ऑपरेटर का इस्तेमाल कर रहे हैं और हम y = atan(x + offset) फ़ंक्शन के लिए एक बहुत ही आसान मॉडल बना रहे हैं, जहां offset को ट्रेन किया जा सकता है.

TensorFlow मॉडल बनाएं

नीचे दिया गया कोड स्निपेट, TensorFlow के एक आसान मॉडल को ट्रेनिंग देता है. इस मॉडल में इसमें Atan नाम का एक कस्टम ऑपरेटर शामिल है, जो कि y = atan(x + offset) फ़ंक्शन है, जहां offset को ट्रेनिंग दी जा सकती है.

import tensorflow as tf

# Define training dataset and variables
x = [-8, 0.5, 2, 2.2, 201]
y = [-1.4288993, 0.98279375, 1.2490457, 1.2679114, 1.5658458]
offset = tf.Variable(0.0)

# Define a simple model which just contains a custom operator named `Atan`
@tf.function(input_signature=[tf.TensorSpec.from_tensor(tf.constant(x))])
def atan(x):
  return tf.atan(x + offset, name="Atan")

# Train model
optimizer = tf.optimizers.Adam(0.01)
def train(x, y):
    with tf.GradientTape() as t:
      predicted_y = atan(x)
      loss = tf.reduce_sum(tf.square(predicted_y - y))
    grads = t.gradient(loss, [offset])
    optimizer.apply_gradients(zip(grads, [offset]))

for i in range(1000):
    train(x, y)

print("The actual offset is: 1.0")
print("The predicted offset is:", offset.numpy())
The actual offset is: 1.0
The predicted offset is: 0.99999905

इस स्थिति में, अगर डिफ़ॉल्ट कन्वर्टर फ़्लैग करते हैं, तो आपको नीचे दिया गया गड़बड़ी का मैसेज मिलेगा:

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

LiteRT मॉडल में बदलें

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

converter = tf.lite.TFLiteConverter.from_concrete_functions([atan.get_concrete_function()], atan)
converter.allow_custom_ops = True
tflite_model = converter.convert()

इस समय, अगर इसे डिफ़ॉल्ट अनुवादक के साथ चलाने के लिए, इन कमांड का इस्तेमाल किया जाता है अनुसरण करता है:

interpreter = tf.lite.Interpreter(model_content=tflite_model)
interpreter.allocate_tensors()

आपको अब भी यह गड़बड़ी दिखेगी:

Encountered unresolved custom op: Atan.

ऑपरेटर बनाएं और रजिस्टर करें.

#include "third_party/tensorflow/lite/c/c_api.h"
#include "third_party/tensorflow/lite/c/c_api_opaque.h"

LiteRT कस्टम ऑपरेटर को, एक ऐसे आसान प्योर-C एपीआई का इस्तेमाल करके तय किया जाता है जो इसमें ओपेक टाइप (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.
);

ऑपरेटर को लागू करने से "तरीके" तय हो सकते हैं हस्ताक्षर के साथ. ये सभी तरीके ज़रूरी नहीं हैं, लेकिन किसी ऑपरेटर के सही तरीके से काम करने के लिए ऐसा किया जा सकता है जांच के बाद, ऑपरेटर लागू करने के तरीके को तय करना और सेट करना ज़रूरी है (सेटर का इस्तेमाल करके फ़ंक्शन) कम से कम 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);

आपके op लागू करने की प्रक्रिया में फ़ंक्शन names (या C++ के लिए नेमस्पेस प्रीफ़िक्स) ऊपर दिए गए कोड स्निपेट में फ़ंक्शन के नामों से मेल खाना ज़रूरी नहीं है, क्योंकि 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 है, इसलिए ये "तरीके" को C फ़ंक्शन पॉइंटर के तौर पर लागू किया जाता है TfLiteRegistrationExternal प्रकार, जो इसके पते पास करके सेट किए जाते हैं आपके लागू करने वाले फ़ंक्शन को उनसे जुड़े सेटर फ़ंक्शन में 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() को एक से ज़्यादा बार कॉल किया जाएगा ग्राफ़ में कई बार इस्तेमाल किया गया है. कस्टम ऑपरेशन के लिए एक कॉन्फ़िगरेशन बफ़र होगा इसमें एक फ़्लेक्सबफ़र मौजूद होता है, जो पैरामीटर के नामों को उनकी वैल्यू के हिसाब से मैप करता है. कॉन्टेंट बनाने बिल्ट-इन ऑपरेशन के लिए बफ़र खाली है, क्योंकि अनुवादक ने इसे पहले ही पार्स कर लिया है सेशन पैरामीटर. अगर कर्नेल लागू करने के लिए राज्य की ज़रूरत होती है, तो उसे शुरू किया जाना चाहिए यहां जाएं और कॉलर को मालिकाना हक ट्रांसफ़र करें. हर 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 का इस्तेमाल किया है, तो फ़ंक्शन, op में इस्तेमाल किए जाने वाले वैरिएबल को शुरू करने और जगह खाली करने के लिए करता है, है, तो उन्हें 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 संरचना में एक 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 में अपने कस्टम ऑपरेटर तय करने हैं, तो आपको फ़िलहाल अपनी पसंद के मुताबिक जेएनआई लेयर बनाएं और अपना एएआर कंपाइल करें इस जेनआई कोड में. इसी तरह, अगर आपको Python में उपलब्ध इन ऑपरेटर के बारे में बताना है, तो अपने रजिस्ट्रेशन को इसमें रखें Python रैपर कोड.

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

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

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

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

  1. स्टोरेज का बंटवारा सावधानी से करें और ऐलोकेशन को हटाएं. मेमोरी असाइन की जा रही है यह, Prepare में Invoke के मुकाबले ज़्यादा असरदार है. साथ ही, यह मेमोरी के बंटवारे में भी बेहतर नतीजे देता है बार-बार दोहराए जाने से बेहतर है. अस्थायी टेंसर डेटा का इस्तेमाल करना बल्कि ख़ुद को चकमा देने के बजाय (आइटम 2 देखें). इसके बजाय, पॉइंटर/रेफ़रंस का इस्तेमाल करें कॉपी करने की दिशा में पहला कदम है.

  2. अगर पूरी कार्रवाई के दौरान डेटा स्ट्रक्चर बना रहता है, तो हमारी सलाह है कि अस्थायी टेंसर का इस्तेमाल करके, मेमोरी का पहले से पता लगाना. आपको इसकी ज़रूरत पड़ सकती है OpData निर्देश देता है कि दूसरे फ़ंक्शन में टेंसर इंडेक्स का रेफ़रंस दिया जाए. ज़्यादा जानकारी के लिए, कॉन्वलूशन के लिए कर्नेल. सैंपल कोड स्निपेट नीचे दिया गया है.

    struct MyOpData {
      int temp_tensor_index;
      ...
    };
    
    void* Init(TfLiteOpaqueContext* context,
        const char* buffer, size_t length) {
      auto* op_data = new MyOpData{};
      ...
      return op_data;
    }
    void Free(TfLiteOpaqueContext* context, void* buffer) {
      ...
      delete reinterpret_cast<MyOpData*>(buffer);
    }
    TfLiteStatus Prepare(TfLiteOpaqueContext* context,
                         TfLiteOpaqueNode* node) {
      ...
      auto* op_data =
          reinterpret_cast<MyOpData*>(TfLiteOpaqueNodeGetUserData(node));
      const int num_temporaries = 1;
      int temporary_tensor_indices[num_temporaries];
      TfLiteOpaqueTensorBuilder* builder = TfLiteOpaqueTensorBuilderCreate();
      TfLiteOpaqueTensorBuilderSetType(builder, kTfLiteFloat32);
      TfLiteOpaqueTensorBuilderSetAllocationType(builder, kTfLiteArenaRw);
      TfLiteOpaqueContextAddTensor(context, builder,
          &temporary_tensor_indices[0]);
      TfLiteOpaqueTensorBuilderDelete(builder);
      TfLiteOpaqueNodeSetTemporaries(node, temporary_tensor_indices,
          num_temporaries);
      op_data->temp_tensor_index = temporary_tensor_indices[0];
      ...
      return kTfLiteOk;
    }
    TfLiteStatus Invoke(TfLiteOpaqueContext* context,
                        TfLiteOpaqueNode* node) {
      ...
      auto* op_data = reinterpret_cast<MyOpData*>(
          TfLiteOpaqueNodeGetUserData(node));
      TfLiteOpaqueTensor* temp_tensor =
          TfLiteOpaqueContextGetOpaqueTensor(context,
              op_data->temp_tensor_index);
      TF_LITE_OPAQUE_ENSURE(context,
          TfLiteTensorType(temp_tensor) == kTfLiteFloat32);
      TF_LITE_OPAQUE_ENSURE(context,
          TfLiteTensorGetAllocationType(temp_Tensor) == kTfLiteArenaRw);
      void *temp_data = TfLiteTensorData(temp_tensor);
      TF_LITE_OPAQUE_ENSURE(context, temp_data != nullptr);
      ...
      return kTfLiteOk;
    }
    
  3. अगर इसका खर्च बहुत ज़्यादा नहीं होता है, तो स्टैटिक तय साइज़ का इस्तेमाल करें अरे (या Resize में पहले से तय std::vector) के बजाय एक्ज़ीक्यूशन के दौरान हर बार डाइनैमिक तरीके से असाइन किया गया std::vector.

  4. स्टैंडर्ड लाइब्रेरी कंटेनर टेंप्लेट को इंस्टैंशिएट करने से बचें, जो पहले से मौजूद नहीं है मौजूद हैं, क्योंकि वे बाइनरी साइज़ पर असर डालते हैं. उदाहरण के लिए, अगर आपको किसी आपके ऑपरेशन में std::map जो अन्य कर्नेल में मौजूद नहीं है, इसका इस्तेमाल करके डायरेक्ट इंडेक्स मैपिंग की मदद से, std::vector की मदद से काम करने के दौरान, छोटा बाइनरी साइज़. देखें कि अन्य कर्नेल अहम जानकारी पाने (या पूछें) के लिए किस चीज़ का इस्तेमाल करते हैं.

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

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