اپراتورهای سفارشی

از آنجایی که کتابخانه اپراتور داخلی LiteRT تنها از تعداد محدودی از اپراتورهای TensorFlow پشتیبانی می کند، هر مدلی قابل تبدیل نیست. برای جزئیات، به سازگاری اپراتور مراجعه کنید.

برای اجازه دادن به تبدیل، کاربران می توانند پیاده سازی سفارشی خود را از یک اپراتور TensorFlow پشتیبانی نشده در LiteRT، که به عنوان یک اپراتور سفارشی شناخته می شود، ارائه دهند. در عوض، اگر می‌خواهید یک سری از عملگرهای پشتیبانی‌نشده (یا پشتیبانی‌شده) TensorFlow را در یک اپراتور سفارشی بهینه‌سازی شده واحد ترکیب کنید، به ترکیب عملگر مراجعه کنید.

استفاده از عملگرهای سفارشی شامل چهار مرحله است.

  • یک مدل TensorFlow ایجاد کنید. مطمئن شوید که مدل ذخیره شده (یا Graph Def) به عملگر LiteRT که به درستی نامگذاری شده است اشاره دارد.

  • تبدیل به مدل LiteRT. مطمئن شوید که ویژگی مبدل LiteRT مناسب را تنظیم کرده اید تا مدل را با موفقیت تبدیل کنید.

  • اپراتور را ایجاد و ثبت کنید. این به این دلیل است که زمان اجرا LiteRT بداند چگونه اپراتور و پارامترهای نمودار شما را به کدهای اجرایی C/C++ نگاشت کند.

  • اپراتور خود را تست و مشخصات اگر می خواهید فقط اپراتور سفارشی خود را آزمایش کنید، بهتر است یک مدل فقط با اپراتور سفارشی خود ایجاد کنید و از برنامه benchmark_model استفاده کنید.

بیایید از طریق یک مثال سرتاسر اجرای یک مدل با یک عملگر سفارشی tf.atan (به نام Atan مراجعه کنید، به ایجاد یک مدل TensorFlow مراجعه کنید. ) که در TensorFlow پشتیبانی می‌شود، اما در LiteRT پشتیبانی نمی‌شود.

عملگر TensorFlow Text نمونه ای از یک عملگر سفارشی است. برای مثال کد، آموزش تبدیل TF Text به 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

در این مرحله، اگر بخواهید یک مدل LiteRT با پرچم های مبدل پیش فرض تولید کنید، با پیغام خطای زیر مواجه خواهید شد:

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 با استفاده از یک API خالص 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);

نام توابع (یا پیشوندهای فضای نام، برای C++) در اجرای عملیات شما نباید با نام توابع در قطعه کد بالا مطابقت داشته باشند، زیرا API سفارشی عملیات TF Lite فقط از آدرس‌های آنها استفاده می‌کند. در واقع ما توصیه می کنیم که آنها را در یک فضای نام ناشناس یا به عنوان توابع ثابت اعلام کنید.

اما ایده خوبی است که نام اپراتور خود را به عنوان یک فضای نام یا پیشوند در نام این توابع اضافه کنید:

C++

namespace my_namespace::my_custom_op {
  void* Init(TfLiteOpaqueContext* context,
             const char* buffer, size_t length) { ... }
  // ... plus definitions of Free, Prepare, and Invoke ...
}
      

سی

void* MyCustomOpInit(TfLiteOpaqueContext* context,
                     const char* buffer, size_t length) { ... }
// ... plus definitions of MyCustomOpFree, MyCustomOpPrepare, and
// MyCustomOpInvoke.
      

از آنجایی که این یک C API است، این «روش‌ها» به‌عنوان نشانگرهای تابع C در نوع TfLiteRegistrationExternal پیاده‌سازی می‌شوند که با ارسال آدرس‌های توابع پیاده‌سازی شما به توابع تنظیم‌کننده مربوطه، TfLiteRegistrationExternalSet MethodName تنظیم می‌شوند:

void TfLiteRegistrationExternalSetInit(
    TfLiteRegistrationExternal* registration,
    void* (*init)(TfLiteOpaqueContext* context, const char* buffer,
                  size_t length));
void TfLiteRegistrationExternalSetFree(
    TfLiteRegistrationExternal* registration,
    void (*free)(TfLiteOpaqueContext* context, void* data));
void TfLiteRegistrationExternalSetPrepare(
    TfLiteRegistrationExternal* registration,
    TfLiteStatus (*prepare)(TfLiteOpaqueContext* context,
                            TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetInvoke(
    TfLiteRegistrationExternal* registration,
    TfLiteStatus (*invoke)(TfLiteOpaqueContext* context,
                           TfLiteOpaqueNode* node));
void TfLiteRegistrationExternalSetAsyncKernel(
    TfLiteRegistrationExternal* registration,
    struct TfLiteAsyncKernel* (*async_kernel)(TfLiteOpaqueContext* context,
                                              TfLiteOpaqueNode* node));

برای جزئیات در مورد TfLiteContext و TfLiteNode به common.h مراجعه کنید. TfLiteContext امکانات گزارش خطا و دسترسی به اشیاء سراسری، از جمله تمام تانسورها را فراهم می کند. TfLiteNode به پیاده سازی اپراتورها اجازه می دهد تا به ورودی ها و خروجی های خود دسترسی داشته باشند.

هنگامی که مفسر یک مدل را بارگذاری می کند، متد Init() یک بار برای هر گره در گراف فراخوانی می کند. یک Init() داده شده بیش از یک بار فراخوانی می شود اگر op چندین بار در نمودار استفاده شود. برای عملیات سفارشی یک بافر پیکربندی ارائه خواهد شد که حاوی یک فلکسبافر است که نام پارامترها را به مقادیر آنها نگاشت می کند. بافر برای عملیات داخلی خالی است زیرا مفسر قبلاً پارامترهای op را تجزیه کرده است. پیاده‌سازی‌های هسته که به حالت نیاز دارند باید آن را در اینجا مقداردهی اولیه کنند و مالکیت را به تماس‌گیرنده منتقل کنند. برای هر فراخوانی Init() یک فراخوانی متناظر با Free() وجود خواهد داشت که به پیاده سازی ها اجازه می دهد بافری را که ممکن است در Init() تخصیص داده باشند، از بین ببرند.

هر زمان که اندازه تانسورهای ورودی تغییر کند، مفسر از طریق نمودار به اجرای تغییرات اطلاع می دهد. این به آنها این فرصت را می دهد که اندازه بافر داخلی خود را تغییر دهند، اعتبار اشکال و انواع ورودی را بررسی کنند و اشکال خروجی را دوباره محاسبه کنند. همه این کارها از طریق متد Prepare() انجام می شود و پیاده سازی ها می توانند با استفاده از TfLiteOpaqueNodeGetUserData(node) به وضعیت خود دسترسی داشته باشند.

در نهایت، هر بار که استنتاج اجرا می‌شود، مفسر گراف را طی می‌کند و متد Invoke() فراخوانی می‌کند و در اینجا نیز وضعیت به‌عنوان TfLiteOpaqueNodeGetUserData(node) در دسترس است.

عملیات سفارشی را می توان با تعریف آن توابع "متد" و سپس تعریف تابعی که نمونه ای از TfLiteRegistrationExternal ساخته شده با فراخوانی TfLiteRegistrationExternalCreate و سپس متدهای تنظیم کننده مربوطه را برمی گرداند، پیاده سازی کرد:

C++

namespace my_namespace::my_custom_op {
  namespace {
    void* Init(TfLiteOpaqueContext* context,
               const char* buffer, size_t length) { ... }
    void Free(TfLiteOpaqueContext* context, void* buffer) { ... }
    TfLiteStatus Prepare(TfLiteOpaqueContext* context,
                         TfLiteOpaqueNode* node) { ... }
    TfLiteStatus Invoke(TfLiteOpaqueContext* context,
                        TfLiteOpaqueNode* node) {... }
  };

  const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() {
    // Singleton instance, intentionally never destroyed.
    static const TfLiteRegistrationExternal* my_custom_op = ()[] {
        TfLiteRegistrationExternal* r =
            TfLiteRegistrationExternalCreate(
                kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
        TfLiteRegistrationExternalSetInit(r, Init);
        TfLiteRegistrationExternalSetFree(r, Free);
        TfLiteRegistrationExternalSetPrepare(r, Prepare);
        TfLiteRegistrationExternalSetInvoke(r, Eval);
        return r;
      };
    return my_custom_op;
  }

  const TfLiteRegistration* MyCustomOpRegistration() {
    static const TfLiteRegistration my_custom_op {
      .registration_external = MyCustomOpRegistrationExternal();
    };
    return my_custom_op;
  }
}  // namespace my_namespace
      

سی

static void* MyCustomOpInit(TfLiteOpaqueContext* context, const char* buffer,
                     size_t length) { ... }
static void MyCustomOpFree(TfLiteOpaqueContext* context, void* buffer) { ... }
static TfLiteStatus MyCustomOpPrepare(TfLiteOpaqueContext* context,
                                      TfLiteOpaqueNode* node) { ... }
static TfLiteStatus MyCustomOpInvoke(TfLiteOpaqueContext* context,
                                     TfLiteOpaqueNode* node) {... }

static TfLiteRegistrationExternal* MyCustomOpCreate() {
  const TfLiteRegistrationExternal* r =
      TfLiteRegistrationExternalCreate(
          kTfLiteBuiltinCustom, "MyCustomOp", /*version=*/ 1);
  TfLiteRegistrationExternalSetInit(r, MyCustomOpInit);
  TfLiteRegistrationExternalSetFree(r, MyCustomOpFree);
  TfLiteRegistrationExternalSetPrepare(r, MyCustomOpPrepare);
  TfLiteRegistrationExternalSetInvoke(r, MyCustomOpEval);
  return r;
}

const TfLiteRegistrationExternal* MyCustomOpRegistrationExternal() {
  // Singleton instance, intentionally never destroyed.
  static const TfLiteRegistrationExternal* my_custom_op = MyCustomOpCreate();
  return my_custom_op;
}

const TfLiteRegistration MyCustomOpRegistration() {
  static const TfLiteRegistration my_custom_op {
    .registration_external = MyCustomOpRegistrationExternal();
  };
  return my_custom_op;
}
      

توجه داشته باشید که ثبت نام خودکار نیست و باید یک تماس صریح با عملکرد MyCustomOpRegistration شما برقرار شود (جزئیات را در زیر ببینید). در حالی که BuiltinOpResolver استاندارد (موجود در هدف :builtin_ops ) از ثبت داخلی ها مراقبت می کند، عملیات های سفارشی باید در کتابخانه های سفارشی جداگانه جمع آوری شوند.

تعریف کرنل در زمان اجرا LiteRT

تنها کاری که برای استفاده از op در 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(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 ، عملیات سفارشی را به حل کننده اضافه کنید (برای مثال به زیر مراجعه کنید). این اپراتور را با 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;
  ...
};

توجه داشته باشید که برای سازگاری با عقب، این کلاس از نوع بتن قدیمی تر TfLiteRegistration به جای نوع مات TfLiteRegistrationExternal استفاده می کند، اما ساختار TfLiteRegistration حاوی یک فیلد registration_external از نوع TfLiteRegistrationExternal* است.

کلاس های MutableOpResolver و BuiltinOpResolver از OpResolver مشتق شده اند:

class MutableOpResolver : public OpResolver {
 public:
  MutableOpResolver();  // Constructs an initially empty op resolver.
  void AddBuiltin(tflite::BuiltinOperator op, const TfLiteRegistration* registration) = 0;
  void AddCustom(const char* op, const TfLiteRegistration* registration) = 0;
  void AddAll(const MutableOpResolver& other);
  ...
};

class BuiltinOpResolver : public MutableOpResolver {
 public:
  BuiltinOpResolver();  // Constructs an op resolver with all the builtin ops.
};

برای استفاده منظم (بدون عملیات سفارشی) باید از BuiltinOpResolver استفاده کنید و بنویسید:

tflite::ops::builtin::BuiltinOpResolver resolver;

برای افزودن عملیات سفارشی ایجاد شده در بالا، می‌توانید به جای آن از یک MutableOpResolver استفاده کنید و AddCustom فراخوانی کنید (قبل از اینکه حل‌کننده را به InterpreterBuilder ارسال کنید):

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

اگر مجموعه عملیات داخلی بیش از حد بزرگ در نظر گرفته شود، یک OpResolver جدید می‌تواند براساس زیرمجموعه معینی از عملیات‌ها، احتمالاً فقط موارد موجود در یک مدل خاص، کد تولید شود. این معادل ثبت انتخابی TensorFlow است (و یک نسخه ساده از آن در فهرست tools موجود است).

اگر می خواهید اپراتورهای سفارشی خود را در جاوا تعریف کنید، در حال حاضر باید لایه JNI سفارشی خود را بسازید و AAR خود را در این کد jni کامپایل کنید. به طور مشابه، اگر می‌خواهید این عملگرها را در پایتون تعریف کنید، می‌توانید ثبت‌های خود را در کد پوشش پایتون قرار دهید.

توجه داشته باشید که فرآیند مشابهی مانند بالا را می توان برای پشتیبانی از مجموعه ای از عملیات به جای یک اپراتور منفرد دنبال کرد. فقط هر تعداد اپراتور AddCustom که نیاز دارید اضافه کنید. علاوه بر این، MutableOpResolver همچنین به شما اجازه می دهد تا با استفاده از AddBuiltin ، پیاده سازی های داخلی را نادیده بگیرید.

اپراتور خود را تست و مشخصات

برای نمایه سازی عملیات خود با ابزار معیار LiteRT، می توانید از ابزار مدل معیار برای LiteRT استفاده کنید. برای اهداف آزمایشی، می‌توانید با افزودن تماس AddCustom مناسب (همانطور که در بالا نشان داده شده است) به register.cc ، ساخت محلی LiteRT خود را از عملیات سفارشی خود آگاه کنید.

بهترین شیوه ها

  1. تخصیص حافظه و عدم تخصیص را با احتیاط بهینه کنید. تخصیص حافظه در Prepare کارآمدتر از Invoke است و تخصیص حافظه قبل از حلقه بهتر از هر تکرار است. از داده‌های تانسور موقت به‌جای اینکه خودتان را مخفی کنید، استفاده کنید (به مورد 2 مراجعه کنید). تا حد امکان به جای کپی کردن از اشاره گرها / مراجع استفاده کنید.

  2. اگر یک ساختار داده در طول کل عملیات باقی بماند، توصیه می کنیم که حافظه را از قبل با استفاده از تانسورهای موقت تخصیص دهید. ممکن است لازم باشد از یک ساختار OpData برای ارجاع به شاخص های تانسور در سایر توابع استفاده کنید. مثال را در هسته برای کانولوشن ببینید. نمونه قطعه کد در زیر آمده است.

    struct MyOpData {
      int temp_tensor_index;
      ...
    };
    
    void* Init(TfLiteOpaqueContext* context,
        const char* buffer, size_t length) {
      auto* op_data = new MyOpData{};
      ...
      return op_data;
    }
    void Free(TfLiteOpaqueContext* context, void* buffer) {
      ...
      delete reinterpret_cast<MyOpData*>(buffer);
    }
    TfLiteStatus Prepare(TfLiteOpaqueContext* context,
                         TfLiteOpaqueNode* node) {
      ...
      auto* op_data =
          reinterpret_cast<MyOpData*>(TfLiteOpaqueNodeGetUserData(node));
      const int num_temporaries = 1;
      int temporary_tensor_indices[num_temporaries];
      TfLiteOpaqueTensorBuilder* builder = TfLiteOpaqueTensorBuilderCreate();
      TfLiteOpaqueTensorBuilderSetType(builder, kTfLiteFloat32);
      TfLiteOpaqueTensorBuilderSetAllocationType(builder, kTfLiteArenaRw);
      TfLiteOpaqueContextAddTensor(context, builder,
          &temporary_tensor_indices[0]);
      TfLiteOpaqueTensorBuilderDelete(builder);
      TfLiteOpaqueNodeSetTemporaries(node, temporary_tensor_indices,
          num_temporaries);
      op_data->temp_tensor_index = temporary_tensor_indices[0];
      ...
      return kTfLiteOk;
    }
    TfLiteStatus Invoke(TfLiteOpaqueContext* context,
                        TfLiteOpaqueNode* node) {
      ...
      auto* op_data = reinterpret_cast<MyOpData*>(
          TfLiteOpaqueNodeGetUserData(node));
      TfLiteOpaqueTensor* temp_tensor =
          TfLiteOpaqueContextGetOpaqueTensor(context,
              op_data->temp_tensor_index);
      TF_LITE_OPAQUE_ENSURE(context,
          TfLiteTensorType(temp_tensor) == kTfLiteFloat32);
      TF_LITE_OPAQUE_ENSURE(context,
          TfLiteTensorGetAllocationType(temp_Tensor) == kTfLiteArenaRw);
      void *temp_data = TfLiteTensorData(temp_tensor);
      TF_LITE_OPAQUE_ENSURE(context, temp_data != nullptr);
      ...
      return kTfLiteOk;
    }
    
  3. اگر هزینه زیادی برای حافظه تلف نمی شود، ترجیح دهید از یک آرایه با اندازه ثابت ثابت (یا یک std::vector از پیش تخصیص داده شده در Resize ) به جای استفاده از std::vector اختصاص داده شده به صورت پویا در هر تکرار اجرا استفاده کنید.

  4. از نمونه‌سازی الگوهای استاندارد کانتینر کتابخانه‌ای که قبلاً وجود ندارند، خودداری کنید، زیرا بر اندازه باینری تأثیر می‌گذارند. به عنوان مثال، اگر در عملیات خود به یک std::map نیاز دارید که در هسته های دیگر وجود ندارد، استفاده از یک std::vector با نگاشت نمایه سازی مستقیم می تواند در عین کوچک نگه داشتن اندازه باینری کار کند. ببینید هسته های دیگر از چه چیزهایی برای به دست آوردن بینش استفاده می کنند (یا بپرسید).

  5. نشانگر حافظه بازگشتی توسط malloc را بررسی کنید. اگر این اشاره گر nullptr باشد، هیچ عملیاتی نباید با استفاده از آن نشانگر انجام شود. اگر در یک تابع malloc دارید و خطای خروجی دارید، قبل از خروج، حافظه را اختصاص دهید.

  6. از TF_LITE_OPAQUE_ENSURE(context, condition) برای بررسی یک وضعیت خاص استفاده کنید. هنگام استفاده از TF_LITE_OPAQUE_ENSURE ، کد شما نباید حافظه را معلق بگذارد، به عنوان مثال، این ماکروها باید قبل از تخصیص هرگونه منبعی که نشت می کند استفاده شوند.