Versionet e operatorit LiteRT

Ky dokument përshkruan skemën e versionimit të operacionit të LiteRT. Versioni i operacionit u mundëson zhvilluesve të shtojnë funksionalitete dhe parametra të rinj në operacionet ekzistuese. Përveç kësaj, ai garanton sa vijon:

  • Pajtueshmëria me versionet e prapambetura: Implementimi i ri i LiteRT duhet të trajtojë një skedar modeli të vjetër.
  • Pajtueshmëria e drejtpërdrejtë: Implementimi i vjetër i LiteRT duhet të trajtojë një skedar modeli të ri të prodhuar nga versioni i ri i konvertuesit, për sa kohë që nuk përdoren veçori të reja.
  • Zbulimi i pajtueshmërisë përpara: Nëse një implementim i vjetër i LiteRT lexon një model të ri që përmban një version të ri të një operacioni që nuk mbështetet, ai duhet të raportojë gabimin.

Shembull: Shtimi i zgjerimit në konvolucionin në thellësi

Pjesa tjetër e këtij dokumenti shpjegon versionimin e operacionit në TFLite duke treguar se si të shtohen parametrat e zgjerimit në operacionin e konvolucionit në thellësi.

Njohja e zgjerimit nuk kërkohet për të kuptuar këtë dokument. Vini re se:

  • Do të shtohen 2 parametra të rinj të numrave të plotë: dilation_width_factor dhe dilation_height_factor .
  • Bërthamat e vjetra të konvolucionit në thellësi që nuk mbështesin zgjerimin janë ekuivalente me vendosjen e faktorëve të zgjerimit në 1.

Ndrysho skemën FlatBuffer

Për të shtuar parametra të rinj në një operacion, ndryshoni tabelën e opsioneve në lite/schema/schema.fbs .

Për shembull, tabela e opsioneve të konvolucionit në thellësi duket kështu:

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

Kur shtoni parametra të rinj:

  • Shtoni komente që tregojnë se cilët parametra mbështeten nga cili version.
  • Kur implementimi i ri merr vlerat fillestare për parametrat e shtuar rishtazi, ai duhet të funksionojë saktësisht njësoj si implementimi i vjetër.

Tabela do të jetë kështu pasi të shtohen parametrat e rinj:

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

Skedari lite/schema/schema_generated.h duhet të rigjenerohet për skemën e re.

Ndryshimi i strukturave C dhe implementimi i kernelit

Në LiteRT, implementimi i kernelit është i shkëputur nga përkufizimi i FlatBuffer. Kernelet lexojnë parametrin nga strukturat C të përcaktuara në lite/c/builtin_op_data.h .

Parametri origjinal i konvolucionit në thellësi është si më poshtë:

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

Ashtu si me skemën FlatBuffer, shtoni komente që tregojnë se cilët parametra mbështeten duke filluar nga cili version. Rezultati shihet më poshtë:

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;

Ju lutemi ndryshoni edhe implementimin e kernelit për të lexuar parametrat e shtuar rishtas nga strukturat C. Detajet janë lënë jashtë këtu.

Ndryshoni kodin e leximit të FlatBuffer

Logjika për të lexuar FlatBuffer dhe për të prodhuar strukturën C është në lite/core/api/flatbuffer_conversions.cc .

Përditësoni skedarin për të trajtuar parametrat e rinj, siç tregohet më poshtë:

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

Nuk është e nevojshme të kontrolloni versionin operativ këtu. Kur implementimi i ri lexon një skedar modeli të vjetër ku mungojnë faktorët e zgjerimit, ai do të përdorë 1 si vlerën parazgjedhur dhe kerneli i ri do të funksionojë në mënyrë konsistente me kernelin e vjetër.

Ndrysho regjistrimin e kernelit

MutableOpResolver (i përcaktuar në lite/mutable_op_resolver.h ) ofron disa funksione për të regjistruar bërthamat operative. Versioni minimal dhe maksimal janë 1 si parazgjedhje:

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

Operacionet e integruara janë të regjistruara në lite/kernels/register.cc . Në këtë shembull, ne kemi implementuar një kernel të ri operacioni që mund të trajtojë versionet 1 dhe 2 DepthwiseConv2D , kështu që duhet të ndryshojmë këtë rresht:

AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D());

për:

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

Ndrysho versionin e TFLite

Hapi tjetër është që TFLite të plotësojë versionin minimal që kërkohet për të ekzekutuar operacionin. Në këtë shembull, kjo do të thotë:

  • Plotëso versionin=1 kur të gjithë faktorët e zgjerimit janë 1.
  • Plotësoni versionin=2 përndryshe.

Modifikoni funksionin GetBuiltinOperatorVersion për operatorin në lite/tools/versioning/op_version.cc duke shtuar versionin e ri në rastin e 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;

Përditësoni hartën e versionit të operatorit

Hapi i fundit është shtimi i informacionit të versionit të ri në hartën e versionit të operatorit. Ky hap është i detyrueshëm sepse duhet të gjenerojmë versionin minimal të kërkuar të kohës së ekzekutimit të modelit bazuar në këtë hartë versioni.

Për ta bërë këtë, duhet të shtoni një hyrje të re në hartë në lite/tools/versioning/runtime_version.cc .

Në këtë shembull, duhet të shtoni hyrjen e mëposhtme në op_version_map :

{ {BuiltinOperator_DEPTHWISE_CONV_2D, 2}, %CURRENT_RUNTIME_VERSION%}

ku %CURRENT_RUNTIME_VERSION% korrespondon me versionin aktual të kohës së ekzekutimit të përcaktuar në release_version.h .

Zbatimi i delegimit

LiteRT ofron një API delegimi që mundëson delegimin e operacioneve te backend-et e harduerit. Në funksionin Prepare të delegimit, kontrolloni nëse versioni mbështetet për çdo nyje në kodin e Delegimit.

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

Kjo kërkohet edhe nëse delegimi mbështet vetëm operacionet e versionit 1, kështu që delegimi mund të zbulojë papajtueshmërinë kur merr një operacion me version më të lartë.