W tym dokumencie opisujemy schemat wersji operacji LiteRT. Wersjonowanie operacji umożliwia deweloperom dodawanie nowych funkcji i parametrów do istniejących operacji. Dodatkowo gwarantuje:
- Zgodność wsteczna: nowa implementacja LiteRT powinna obsługiwać stary plik modelu.
- Zgodność z przyszłymi wersjami: stara implementacja LiteRT powinna obsługiwać nowy plik modelu wygenerowany przez nową wersję konwertera, o ile nie są używane żadne nowe funkcje.
- Wykrywanie niezgodności z przyszłymi wersjami: jeśli starsza implementacja LiteRT odczytuje nowy model, który zawiera nową wersję operacji, która nie jest obsługiwana, powinna zgłosić błąd.
Przykład: dodawanie rozszerzenia do konwolucji przestrzennej
W pozostałej części tego dokumentu wyjaśniamy wersjonowanie operacji w TFLite, pokazując, jak dodać parametry rozszerzenia do operacji splotu przestrzennego.
Aby zrozumieć ten dokument, nie musisz znać pojęcia rozszerzenia. Uwaga:
- Dodamy 2 nowe parametry całkowite:
dilation_width_factoridilation_height_factor. - Starsze jądra konwolucji przestrzennej, które nie obsługują rozszerzania, są równoważne ustawieniu współczynników rozszerzania na 1.
Zmiana schematu FlatBuffer
Aby dodać nowe parametry do operacji, zmień tabelę opcji w lite/schema/schema.fbs.
Na przykład tabela opcji splotu przestrzennego 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ę.
- Gdy nowa implementacja otrzyma wartości domyślne nowo dodanych parametrów, powinna działać dokładnie tak samo jak stara.
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 schematu.
Zmiana struktur C i implementacji jądra
W LiteRT implementacja jądra jest oddzielona od definicji FlatBuffer.
Jądra odczytują parametr ze struktur C zdefiniowanych w pliku lite/c/builtin_op_data.h.
Oryginalny parametr konwolucji z podziałem na głębokość wygląda tak:
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 od której wersji. Wynik jest widoczny 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.
Zmiana kodu odczytu FlatBuffer
Logika odczytywania FlatBuffera 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;
}
Nie musisz tu sprawdzać wersji operatora. Gdy nowa implementacja odczyta stary plik modelu, w którym brakuje współczynników rozszerzenia, użyje wartości domyślnej 1, a nowe jądro będzie działać tak samo jak stare.
Zmiana rejestracji jądra
Klasa MutableOpResolver (zdefiniowana w lite/mutable_op_resolver.h) udostępnia kilka funkcji do rejestrowania jąder operacji. Minimalna i maksymalna wersja to domyślnie 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 wdrożyliśmy nowy kernel operacji, który może obsługiwać wersje DepthwiseConv2D1 i 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);
Zmiana wersji operacji TFLite
Kolejnym krokiem jest wypełnienie przez TFLite minimalnej wersji wymaganej do wykonania operacji. W tym przykładzie oznacza to:
- Wypełnij pole version=1, gdy wszystkie współczynniki rozszerzenia wynoszą 1.
- W innych przypadkach wypełnij pole version=2.
Zmodyfikuj funkcję GetBuiltinOperatorVersion dla operatora w przypadku 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;
Aktualizowanie mapy wersji operatora
Ostatnim krokiem jest dodanie informacji o nowej wersji do mapy wersji operatora. Ten krok jest wymagany, ponieważ na podstawie tej mapy wersji musimy wygenerować minimalną wymaganą wersję środowiska wykonawczego modelu.
Aby to zrobić, musisz dodać nowy wpis na mapie w lite/tools/versioning/runtime_version.cc.
W tym przykładzie musisz dodać ten wpis do elementu op_version_map:
{ {BuiltinOperator_DEPTHWISE_CONV_2D, 2}, %CURRENT_RUNTIME_VERSION%}
gdzie %CURRENT_RUNTIME_VERSION% odpowiada bieżącej wersji środowiska wykonawczego zdefiniowanej w pliku release_version.h.
Implementacja przekazywania dostępu
LiteRT udostępnia interfejs API delegowania, który umożliwia delegowanie operacji do sprzętowych backendów. W funkcji Prepare delegata sprawdź, czy wersja jest obsługiwana w przypadku 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, ®istration));
if (registration->version > kMaxVersion) {
// Reject the node if the version isn't supported.
}
Jest to wymagane nawet wtedy, gdy delegowanie obsługuje tylko operacje w wersji 1, aby delegowanie mogło wykryć niezgodność podczas pobierania operacji w wyższej wersji.