Wersje operatora LiteRT

Ten dokument opisuje schemat obsługi wersji operacji LiteRT. Obsługa wersji operatorów umożliwia programistom dodawanie nowych funkcji i parametrów do istniejących operacji. Zapewnia też następujące korzyści:

  • Zgodność wsteczna: nowa implementacja LiteRT powinna obsługiwać starego pliku modelu.
  • Zgodność wyprzedzająca: stara implementacja LiteRT powinna obsługiwać nowy plik modelu wygenerowany przez nową wersję konwertera, o ile nie i związanych z nim funkcji.
  • Przekierowuj wykrywanie niezgodności: jeśli stara implementacja LiteRT odczytuje nowy model zawierający nową wersję operacji, która nie jest powinien zgłosić błąd.

Przykład: dodawanie dylatacji do splotu głębi

W pozostałej części tego dokumentu omówiono obsługę wersji operacyjnych w TFLite, pokazując, jak aby dodać parametry dylatacji do operacji splotu głębi.

Do zrozumienia tego dokumentu nie jest wymagana wiedza o rozszerzeniu. Uwaga:

  • Dodane zostaną 2 nowe parametry w postaci liczb całkowitych: dilation_width_factor oraz dilation_height_factor
  • Stare jądra splotu typu głębokiego, które nie obsługują dylatacji, są równoważne ustaw współczynniki dylatacji na 1.

Zmień schemat FlatBuffer

Aby dodać nowe parametry do operacji, zmień tabelę opcji w lite/schema/schema.fbs

Na przykład tabela opcji splotu głębokości wygląda tak:

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

Podczas dodawania nowych parametrów:

  • Dodaj komentarze wskazujące, które parametry są obsługiwane przez daną wersję.
  • Kiedy nowa implementacja otrzymuje domyślne wartości dla nowo dodanych powinna działać dokładnie tak samo jak poprzednia implementacja.

Po dodaniu nowych parametrów tabela będzie wyglądać tak:

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

Plik lite/schema/schema_generated.h należy wygenerować ponownie dla nowego schemat.

Zmienianie struktur C i implementacji jądra

W LiteRT implementacja jądra jest odłączona od FlatBuffer definicji. Jądro odczytują parametr ze struktur C zdefiniowanych w lite/c/builtin_op_data.h

Pierwotny parametr głębi splotu wygląda tak:

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

Tak jak w przypadku schematu FlatBuffer, dodaj komentarze wskazujące, które parametry są obsługiwane, począwszy od wersji. Wynik widać poniżej:

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;

Zmień też implementację jądra, aby odczytywać nowo dodane parametry. ze struktur C. Szczegóły zostały tu pominięte.

Zmień kod odczytu FlatBuffer

Algorytmy odczytujące FlatBuffer i tworzenie struktury C lite/core/api/flatbuffer_conversions.cc

Zaktualizuj plik pod kątem nowych parametrów, jak pokazano poniżej:

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

Nie musisz tu sprawdzać wersji op. Kiedy nowa implementacja odczytuje stary plik modelu, w którym brakuje współczynników dylatacji, używa 1 jako wartości a nowe jądro będzie działać spójnie ze starym jądrem.

Zmień rejestrację jądra

Polecenie MutableOp resolver (zdefiniowane w lite/mutable_op_resolver.h) udostępnia kilka do rejestrowania jąder operacyjnych. Wersja minimalna i maksymalna to 1, domyślnie:

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

Operacje wbudowane są zarejestrowane w regionie lite/kernels/register.cc. W tym przykładzie wdrożyliśmy nowe jądro systemu operacyjnego, które obsługuje system DepthwiseConv2D w wersji 1 oraz 2, więc musimy zmienić ten wiersz:

AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D());

do:

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

Zmień wersję operacji TFLite

Następnym krokiem jest ustawienie TFLite minimalnej wersji wymaganej do wykonaj op. W tym przykładzie oznacza to:

  • Podaj wartość version=1, gdy wszystkie współczynniki dylatacji wynoszą 1.
  • W przeciwnym razie uzupełnij wartość wersja=2.

Zmodyfikuj funkcję GetBuiltinOperatorVersion dla operatora w: lite/tools/versioning/op_version.cc przez dodanie nowej wersji do przypadku 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;

Zaktualizuj mapę wersji operatora

Ostatnim krokiem jest dodanie informacji o nowej wersji do mapy wersji operatora. Ten krok jest wymagany, ponieważ musimy wygenerować minimalną wymaganą wersji środowiska wykonawczego na podstawie tej mapy wersji.

Aby to zrobić, musisz dodać nowy wpis na mapie w lite/tools/versioning/runtime_version.cc

W tym przykładzie do op_version_map musisz dodać ten wpis:

{ {BuiltinOperator_DEPTHWISE_CONV_2D, 2}, %CURRENT_RUNTIME_VERSION%}

gdzie %CURRENT_RUNTIME_VERSION% odpowiada bieżącej wersji środowiska wykonawczego zdefiniowane w pliku tensorflow/core/public/version.h.

Implementacja przekazywania dostępu

LiteRT udostępnia interfejs API do przekazywania dostępu, który umożliwia przekazywanie operacji do i backendy sprzętowe. W funkcji Prepare przedstawiciela sprawdź, czy wersja to obsługiwane dla każdego węzła w kodzie przekazywania.

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

Jest to wymagane nawet wtedy, gdy przekazywanie dostępu obsługuje tylko operacje w wersji 1, więc delegowanie może wykryć niezgodność przy pobieraniu wersji wyższej.