Wersje operatora TensorFlow Lite

W tym dokumencie opisano schemat wersjonowania operacji TensorFlow Lite. Wersjonowanie operacji umożliwia programistom dodawanie nowych funkcjonalności i parametrów do istniejących operacji. Ponadto gwarantuje:

  • Kompatybilność wsteczna: Nowa implementacja TensorFlow Lite powinna obsługiwać stary plik modelu.
  • Kompatybilność z poprzednimi wersjami: Stara implementacja TensorFlow Lite powinna obsługiwać nowy plik modelu utworzony przez nową wersję konwertera, o ile nie są używane żadne nowe funkcje.
  • Wykrywanie niezgodności w przód: Jeśli stara implementacja TensorFlow Lite odczytuje nowy model zawierający nową wersję operacji, która nie jest obsługiwana, powinna zgłosić błąd.

Przykład: dodanie dylatacji do splotu wgłębnego

Pozostała część tego dokumentu wyjaśnia wersjonowanie operacji w TFLite, pokazując, jak dodać parametry dylatacji do operacji splotu wgłębnego.

Do zrozumienia tego dokumentu nie jest wymagana znajomość dylatacji. Pamiętaj, że:

  • Zostaną dodane 2 nowe parametry całkowite: dilation_width_factor i dilation_height_factor .
  • Stare jądra splotu głębokiego, które nie obsługują dylatacji, są równoznaczne z ustawieniem współczynników 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 wgłębnego wygląda następująco:

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 otrzyma domyślne wartości dla nowo dodanych parametrów, powinna działać dokładnie tak samo jak stara implementacja.

Po dodaniu nowych parametrów tabela będzie wyglądać następująco:

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

Należy ponownie wygenerować plik lite/schema/schema_generated.h dla nowego schematu.

Zmień struktury C i implementację jądra

W TensorFlow Lite implementacja jądra jest oddzielona od definicji FlatBuffer. Jądra odczytują parametr ze struktur C zdefiniowanych w lite/c/builtin_op_data.h .

Oryginalny parametr splotu wgłębnego jest następujący:

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

Podobnie jak w przypadku schematu FlatBuffer, dodaj komentarze wskazujące, które parametry są obsługiwane, począwszy od której 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;

Proszę także zmienić implementację jądra, aby odczytać nowo dodane parametry ze struktur C. Szczegóły zostały tutaj pominięte.

Zmień kod odczytu FlatBuffer

Logika odczytywania FlatBuffer i tworzenia struktury C znajduje się w lite/core/api/flatbuffer_conversions.cc .

Zaktualizuj plik, aby obsługiwał nowe parametry, 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;
}

Sprawdzanie wersji op nie jest tutaj wymagane. Kiedy nowa implementacja odczytuje stary plik modelu, w którym brakuje współczynników rozszerzenia, użyje 1 jako wartości domyślnej, a nowe jądro będzie działać spójnie ze starym jądrem.

Zmień rejestrację jądra

MutableOpResolver (zdefiniowany w lite/mutable_op_resolver.h ) udostępnia kilka funkcji do rejestrowania jąder op. Domyślna wersja minimalna i maksymalna to 1:

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

Wbudowane operacje są zarejestrowane w lite/kernels/register.cc . W tym przykładzie zaimplementowaliśmy nowe jądro op, które obsługuje DepthwiseConv2D w wersji 1 i 2, dlatego musimy zmienić tę linię:

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ę op TFLite

Następnym krokiem jest wypełnienie TFLite minimalnej wersji wymaganej do wykonania operacji. W tym przykładzie oznacza to:

  • Wypełnij wersję = 1, gdy wszystkie współczynniki dylatacji wynoszą 1.
  • W przeciwnym razie wypełnij wersję = 2.

Zmodyfikuj funkcję GetBuiltinOperatorVersion dla operatora w lite/tools/versioning/op_version.cc dodając nową wersję 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ą wersję środowiska uruchomieniowego modelu na podstawie tej mapy wersji.

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

W tym przykładzie musisz dodać następujący wpis do op_version_map :

{ {BuiltinOperator_DEPTHWISE_CONV_2D, 2}, %CURRENT_RUNTIME_VERSION%}

gdzie %CURRENT_RUNTIME_VERSION% odpowiada bieżącej wersji środowiska uruchomieniowego zdefiniowanej w tensorflow/core/public/version.h .

Wdrażanie delegacji

TensorFlow Lite zapewnia interfejs API delegowania, który umożliwia delegowanie operacji do zaplecza sprzętowego. W funkcji Prepare delegata sprawdź, czy wersja jest obsługiwana dla każdego węzła w kodzie delegowania.

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 jeśli delegowanie obsługuje tylko operacje w wersji 1, więc delegowanie może wykryć niezgodność podczas uzyskiwania wyższej wersji operacji.