কাস্টম অপারেটর

যেহেতু LiteRT বিল্টইন অপারেটর লাইব্রেরি শুধুমাত্র সীমিত সংখ্যক TensorFlow অপারেটরকে সমর্থন করে, তাই প্রতিটি মডেল পরিবর্তনযোগ্য নয়। বিস্তারিত জানার জন্য, অপারেটর সামঞ্জস্যতা পড়ুন।

রূপান্তরের অনুমতি দেওয়ার জন্য, ব্যবহারকারীরা LiteRT-তে একটি অসমর্থিত TensorFlow অপারেটরের নিজস্ব কাস্টম বাস্তবায়ন প্রদান করতে পারে, যা একটি কাস্টম অপারেটর হিসাবে পরিচিত। যদি পরিবর্তে, আপনি অসমর্থিত (বা সমর্থিত) টেনসরফ্লো অপারেটরগুলির একটি একক ফিউজড অপ্টিমাইজড কাস্টম অপারেটরে একত্রিত করতে চান, অপারেটর ফিউজিং দেখুন।

কাস্টম অপারেটর ব্যবহার করা চারটি ধাপ নিয়ে গঠিত।

আসুন একটি কাস্টম অপারেটর tf.atan এর সাথে একটি মডেল চালানোর একটি এন্ড-টু-এন্ড উদাহরণের মাধ্যমে চলুন ( Atan নামে নাম, একটি টেনসরফ্লো মডেল তৈরি করুন। ) যা TensorFlow-এ সমর্থিত, কিন্তু LiteRT-তে অসমর্থিত।

টেনসরফ্লো টেক্সট অপারেটর একটি কাস্টম অপারেটরের উদাহরণ। কোডের উদাহরণের জন্য TF Text to LiteRT টিউটোরিয়াল রূপান্তর করুন

উদাহরণ: কাস্টম Atan অপারেটর

আসুন একটি টেনসরফ্লো অপারেটরকে সমর্থন করার একটি উদাহরণের মধ্য দিয়ে চলুন যা LiteRT এর নেই। ধরে নিন আমরা Atan অপারেটর ব্যবহার করছি এবং আমরা একটি ফাংশন y = atan(x + offset) এর জন্য একটি খুব সাধারণ মডেল তৈরি করছি, যেখানে offset প্রশিক্ষণযোগ্য।

একটি টেনসরফ্লো মডেল তৈরি করুন

নিম্নলিখিত কোড স্নিপেট একটি সাধারণ টেনসরফ্লো মডেলকে প্রশিক্ষণ দেয়। এই মডেলটিতে শুধু Atan নামে একটি কাস্টম অপারেটর রয়েছে, যা একটি ফাংশন y = atan(x + offset) , যেখানে offset প্রশিক্ষণযোগ্য।

import tensorflow as tf

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

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

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

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

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

এই মুহুর্তে, আপনি যদি ডিফল্ট রূপান্তরকারী পতাকাগুলির সাথে একটি LiteRT মডেল তৈরি করার চেষ্টা করেন, আপনি নিম্নলিখিত ত্রুটি বার্তাটি পাবেন:

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

একটি LiteRT মডেলে রূপান্তর করুন

নিচে দেখানো হিসাবে কনভার্টার অ্যাট্রিবিউট allow_custom_ops সেট করে কাস্টম অপারেটরদের সাথে একটি LiteRT মডেল তৈরি করুন:

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 API ব্যবহার করে সংজ্ঞায়িত করা হয় যা একটি অস্বচ্ছ প্রকার ( 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);

আপনার অপ বাস্তবায়নে ফাংশনের নামগুলি (বা নামস্থান উপসর্গগুলি, C++ এর জন্য) উপরের কোড স্নিপেটে ফাংশনের নামের সাথে মিলতে হবে না, যেহেতু TF Lite কাস্টম অপস API শুধুমাত্র তাদের ঠিকানাগুলি ব্যবহার করবে। প্রকৃতপক্ষে আমরা সুপারিশ করি যে আপনি তাদের একটি বেনামী নামস্থানে বা স্ট্যাটিক ফাংশন হিসাবে ঘোষণা করুন।

কিন্তু এই ফাংশন নামের উপর আপনার অপারেটরের নাম একটি নামস্থান বা উপসর্গ হিসাবে অন্তর্ভুক্ত করা একটি ভাল ধারণা:

সি++

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

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 টার্গেট থেকে পাওয়া যায়) বিল্টিনগুলির নিবন্ধনের যত্ন নেয়, কাস্টম অপগুলি আলাদা কাস্টম লাইব্রেরিতে সংগ্রহ করতে হবে।

LiteRT রানটাইমে কার্নেল সংজ্ঞায়িত করা

LiteRT-তে op ব্যবহার করার জন্য আমাদের যা করতে হবে তা হল দুটি ফাংশন ( 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<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
      

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 struct-এ 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 ডিরেক্টরিতে পাওয়া যায়)।

আপনি যদি জাভাতে আপনার কাস্টম অপারেটরগুলিকে সংজ্ঞায়িত করতে চান তবে আপনাকে বর্তমানে আপনার নিজস্ব কাস্টম JNI স্তর তৈরি করতে হবে এবং এই jni কোডে আপনার নিজস্ব AAR কম্পাইল করতে হবে। একইভাবে, আপনি যদি পাইথনে উপলব্ধ এই অপারেটরগুলিকে সংজ্ঞায়িত করতে চান তাহলে আপনি Python র্যাপার কোডে আপনার নিবন্ধনগুলি স্থাপন করতে পারেন৷

উল্লেখ্য যে উপরের মত একটি অনুরূপ প্রক্রিয়া একটি একক অপারেটরের পরিবর্তে অপারেশনের সেট সমর্থন করার জন্য অনুসরণ করা যেতে পারে। শুধু আপনার প্রয়োজন হিসাবে অনেক AddCustom অপারেটর যোগ করুন. এছাড়াও, MutableOpResolver আপনাকে AddBuiltin ব্যবহার করে বিল্টিনগুলির বাস্তবায়ন ওভাররাইড করার অনুমতি দেয়।

পরীক্ষা এবং আপনার অপারেটর প্রোফাইল

LiteRT বেঞ্চমার্ক টুলের সাথে আপনার অপশনটি প্রোফাইল করতে, আপনি LiteRT এর জন্য বেঞ্চমার্ক মডেল টুল ব্যবহার করতে পারেন। পরীক্ষার উদ্দেশ্যে, আপনি register.cc এ উপযুক্ত AddCustom কল (উপরে দেখানো হিসাবে) যোগ করে আপনার LiteRT-এর স্থানীয় বিল্ডকে আপনার কাস্টম অপের বিষয়ে সচেতন করতে পারেন।

সর্বোত্তম অনুশীলন

  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 প্রয়োজন হয় যা অন্য কার্নেলে বিদ্যমান নেই, একটি std::vector ব্যবহার করে সরাসরি ইন্ডেক্সিং ম্যাপিং বাইনারি আকার ছোট রেখে কাজ করতে পারে। অন্যান্য কার্নেলগুলি অন্তর্দৃষ্টি পেতে (বা জিজ্ঞাসা করুন) কী ব্যবহার করে তা দেখুন।

  5. malloc দ্বারা ফিরে মেমরির পয়েন্টার পরীক্ষা করুন। যদি এই পয়েন্টারটি nullptr হয়, তাহলে সেই পয়েন্টার ব্যবহার করে কোনো অপারেশন করা উচিত নয়। যদি আপনি একটি ফাংশনে malloc এবং একটি ত্রুটি প্রস্থান হয়, আপনি প্রস্থান করার আগে মেমরি ডিলোকেট করুন।

  6. একটি নির্দিষ্ট শর্ত পরীক্ষা করতে TF_LITE_OPAQUE_ENSURE(context, condition) ব্যবহার করুন। যখন TF_LITE_OPAQUE_ENSURE ব্যবহার করা হয় তখন আপনার কোড মেমরি হ্যাং করা যাবে না, অর্থাত্, লিক হবে এমন কোনো রিসোর্স বরাদ্দ করার আগে এই ম্যাক্রোগুলি ব্যবহার করা উচিত।