نسخه های اپراتور LiteRT

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

  • سازگاری با نسخه‌های قبلی: پیاده‌سازی جدید LiteRT باید یک فایل مدل قدیمی را مدیریت کند.
  • سازگاری رو به جلو: پیاده‌سازی قدیمی LiteRT باید یک فایل مدل جدید تولید شده توسط نسخه جدید مبدل را مدیریت کند، مادامی که از ویژگی‌های جدید استفاده نشده باشد.
  • تشخیص عدم سازگاری رو به جلو: اگر یک پیاده‌سازی قدیمی LiteRT مدل جدیدی را بخواند که حاوی نسخه جدیدی از عملیاتی است که پشتیبانی نمی‌شود، باید خطا را گزارش کند.

مثال: اضافه کردن اتساع به کانولوشن عمقی

ادامه‌ی این سند، با نشان دادن نحوه‌ی افزودن پارامترهای اتساع به عملیات پیچش عمقی، نسخه‌بندی عملیات در TFLite را توضیح می‌دهد.

برای درک این سند نیازی به آشنایی با اتساع نیست. توجه داشته باشید که:

  • دو پارامتر عدد صحیح جدید اضافه خواهند شد: dilation_width_factor و dilation_height_factor .
  • هسته‌های کانولوشن عمقی قدیمی که از اتساع پشتیبانی نمی‌کنند، معادل تنظیم ضرایب اتساع روی ۱ هستند.

تغییر طرحواره FlatBuffer

برای افزودن پارامترهای جدید به یک عملیات، جدول گزینه‌ها را در lite/schema/schema.fbs تغییر دهید.

برای مثال، جدول گزینه‌های کانولوشن عمقی به شکل زیر است:

table DepthwiseConv2DOptions {
  padding:Padding;
  stride_w:int;
  stride_h:int;
  depth_multiplier:int;
  fused_activation_function:ActivationFunctionType;
}

هنگام اضافه کردن پارامترهای جدید:

  • توضیحاتی اضافه کنید که نشان دهد کدام پارامترها توسط کدام نسخه پشتیبانی می‌شوند.
  • وقتی پیاده‌سازی جدید مقادیر پیش‌فرض را برای پارامترهای تازه اضافه شده دریافت می‌کند، باید دقیقاً مانند پیاده‌سازی قدیمی کار کند.

جدول پس از اضافه شدن پارامترهای جدید به صورت زیر خواهد بود:

table DepthwiseConv2DOptions {
  // Parameters for DepthwiseConv version 1 or above.
  padding:Padding;
  stride_w:int;
  stride_h:int;
  depth_multiplier:int;
  fused_activation_function:ActivationFunctionType;
  // Parameters for DepthwiseConv version 2 or above.
  dilation_w_factor:int = 1;
  dilation_h_factor:int = 1;
}

فایل lite/schema/schema_generated.h باید برای طرحواره جدید دوباره تولید شود.

تغییر ساختارهای C و پیاده‌سازی هسته

در LiteRT، پیاده‌سازی هسته از تعریف FlatBuffer جدا شده است. هسته‌ها پارامتر را از ساختارهای C تعریف شده در lite/c/builtin_op_data.h می‌خوانند.

پارامتر کانولوشن عمقی اصلی به صورت زیر است:

typedef struct {
  TfLitePadding padding;
  int stride_width;
  int stride_height;
  int depth_multiplier;
  TfLiteFusedActivation activation;
} TfLiteDepthwiseConvParams;

همانند طرحواره FlatBuffer، توضیحاتی اضافه کنید که نشان دهد کدام پارامترها از کدام نسخه پشتیبانی می‌شوند. نتیجه در زیر مشاهده می‌شود:

typedef struct {
  // Parameters for DepthwiseConv version 1 or above.
  TfLitePadding padding;
  int stride_width;
  int stride_height;
  int depth_multiplier;
  TfLiteFusedActivation activation;
  // Parameters for DepthwiseConv version 2 or above.
  int dilation_width_factor;
  int dilation_height_factor;
} TfLiteDepthwiseConvParams;

لطفاً پیاده‌سازی هسته را نیز طوری تغییر دهید که پارامترهای تازه اضافه شده را از ساختارهای C بخواند. جزئیات در اینجا ذکر نشده است.

کد خواندن FlatBuffer را تغییر دهید

منطق خواندن FlatBuffer و تولید ساختار C در lite/core/api/flatbuffer_conversions.cc قرار دارد.

فایل را برای مدیریت پارامترهای جدید، مطابق شکل زیر، به‌روزرسانی کنید:

TfLiteStatus ParseDepthwiseConv2D(const Operator* op,
                                  ErrorReporter* error_reporter,
                                  BuiltinDataAllocator* allocator,
                                  void** builtin_data) {
  CheckParsePointerParams(op, error_reporter, allocator, builtin_data);

  SafeBuiltinDataAllocator safe_allocator(allocator);

  std::unique_ptr<TfLiteDepthwiseConvParams,
                  SafeBuiltinDataAllocator::BuiltinDataDeleter>
      params = safe_allocator.Allocate<TfLiteDepthwiseConvParams>();
  TF_LITE_ENSURE(error_reporter, params != nullptr);

  const DepthwiseConv2DOptions* schema_params =
      op->builtin_options_as_DepthwiseConv2DOptions();

  if (schema_params != nullptr) {
    params->padding = ConvertPadding(schema_params->padding());
    params->stride_width = schema_params->stride_w();
    params->stride_height = schema_params->stride_h();
    params->depth_multiplier = schema_params->depth_multiplier();
    params->activation =
        ConvertActivation(schema_params->fused_activation_function());

    params->dilation_width_factor = schema_params->dilation_w_factor();
    params->dilation_height_factor = schema_params->dilation_h_factor();
  }

  *builtin_data = params.release();
  return kTfLiteOk;
}

بررسی نسخه عملیاتی در اینجا الزامی نیست. وقتی پیاده‌سازی جدید، فایل مدل قدیمی را می‌خواند که در آن ضرایب انبساط وجود ندارد، از مقدار پیش‌فرض ۱ استفاده می‌کند و هسته جدید به طور مداوم با هسته قدیمی کار خواهد کرد.

تغییر ثبت هسته

MutableOpResolver (تعریف شده در lite/mutable_op_resolver.h ) چند تابع برای ثبت هسته‌های عملیاتی ارائه می‌دهد. حداقل و حداکثر نسخه به طور پیش‌فرض ۱ است:

void AddBuiltin(tflite::BuiltinOperator op, TfLiteRegistration* registration,
                int min_version = 1, int max_version = 1);
void AddCustom(const char* name, TfLiteRegistration* registration,
               int min_version = 1, int max_version = 1);

عملیات‌های داخلی در lite/kernels/register.cc ثبت شده‌اند. در این مثال، ما یک هسته عملیات جدید پیاده‌سازی کرده‌ایم که می‌تواند DepthwiseConv2D نسخه ۱ و ۲ را مدیریت کند، بنابراین باید این خط را تغییر دهیم:

AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D());

به:

AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D(),
             /* min_version = */ 1,
             /* max_version = */ 2);

نسخه TFLite را تغییر دهید

مرحله بعدی این است که TFLite حداقل نسخه مورد نیاز برای اجرای عملیات را پر کند. در این مثال، به این معنی است:

  • وقتی همه ضرایب انبساط ۱ هستند، نسخه ۱ را وارد کنید.
  • در غیر این صورت نسخه ۲ را وارد کنید.

تابع GetBuiltinOperatorVersion مربوط به عملگر موجود در lite/tools/versioning/op_version.cc را با اضافه کردن نسخه جدید به مورد DepthwiseConv2D تغییر دهید:

case BuiltinOperator_DEPTHWISE_CONV_2D:
  auto depthwise_conv_params =
      reinterpret_cast<TfLiteDepthwiseConvParams*>(op_sig.builtin_data);
  TFLITE_DCHECK(depthwise_conv_params != nullptr);
  if (depthwise_conv_params->dilation_width_factor != 1 ||
       depthwise_conv_params->dilation_height_factor != 1) {
    return 2;
  }
  return 1;

نقشه نسخه اپراتور را به‌روزرسانی کنید

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

برای انجام این کار، باید یک ورودی نقشه جدید در lite/tools/versioning/runtime_version.cc اضافه کنید.

در این مثال، باید ورودی زیر را به op_version_map اضافه کنید:

{ {BuiltinOperator_DEPTHWISE_CONV_2D, 2}, %CURRENT_RUNTIME_VERSION%}

که در آن %CURRENT_RUNTIME_VERSION% مربوط به نسخه زمان اجرای فعلی است که در release_version.h تعریف شده است.

اجرای تفویض اختیار

LiteRT یک API برای واگذاری وظایف (delegation API) ارائه می‌دهد که امکان واگذاری عملیات به سخت‌افزارهای backend را فراهم می‌کند. در تابع Prepare مربوط به delegate، بررسی کنید که آیا نسخه برای هر گره در کد Delegation پشتیبانی می‌شود یا خیر.

const int kMaxVersion = 1;
TfLiteNode* node;
TfLiteRegistration* registration = nullptr;
TF_LITE_ENSURE_STATUS(context->GetNodeAndRegistration(context, node_index, &node, &registration));

if (registration->version > kMaxVersion) {
  // Reject the node if the version isn't supported.
}

این حتی اگر نماینده فقط از نسخه ۱ عملیات پشتیبانی کند، لازم است تا نماینده بتواند هنگام دریافت نسخه بالاتر عملیات، ناسازگاری را تشخیص دهد.