Questo documento descrive lo schema di versionamento delle operazioni di LiteRT. Il controllo delle versioni delle operazioni consente agli sviluppatori di aggiungere nuove funzionalità e parametri alle operazioni esistenti. Inoltre, garantisce quanto segue:
- Compatibilità con le versioni precedenti: la nuova implementazione di LiteRT dovrebbe gestire un file modello precedente.
- Compatibilità futura: l'implementazione precedente di LiteRT dovrebbe gestire un nuovo file modello prodotto dalla nuova versione del convertitore, a condizione che non vengano utilizzate nuove funzionalità.
- Rilevamento di incompatibilità in avanti: se un'implementazione LiteRT precedente legge un nuovo modello contenente una nuova versione di un'operazione non supportata, deve segnalare l'errore.
Esempio: aggiunta della dilatazione alla convezione in profondità
La parte rimanente di questo documento spiega il versionamento delle operazioni in TFLite mostrando come aggiungere i parametri di dilatazione all'operazione di convezione a convoluzione in profondità.
Per comprendere questo documento non è richiesta alcuna conoscenza della dilatazione. Ricorda:
- Verranno aggiunti due nuovi parametri interi:
dilation_width_factor
edilation_height_factor
. - I vecchi kernel di convoluzione in profondità che non supportano la dilatazione sono equivalenti all'impostazione dei fattori di dilatazione su 1.
Modificare lo schema FlatBuffer
Per aggiungere nuovi parametri a un'operazione, modifica la tabella delle opzioni in
lite/schema/schema.fbs
.
Ad esempio, la tabella delle opzioni della convolvezione in profondità ha il seguente aspetto:
table DepthwiseConv2DOptions {
padding:Padding;
stride_w:int;
stride_h:int;
depth_multiplier:int;
fused_activation_function:ActivationFunctionType;
}
Quando aggiungi nuovi parametri:
- Aggiungi commenti che indichino quali parametri sono supportati da quale versione.
- Quando la nuova implementazione riceve i valori predefiniti per i parametri appena aggiunti, dovrebbe funzionare esattamente come la vecchia implementazione.
La tabella avrà questo aspetto dopo l'aggiunta dei nuovi parametri:
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;
}
Il file lite/schema/schema_generated.h
deve essere rigenerato per il nuovo schema.
Modificare le strutture C e l'implementazione del kernel
In LiteRT, l'implementazione del kernel è disaccoppiata dalla definizione di FlatBuffer. I kernel leggono il parametro dalle strutture C definite in
lite/c/builtin_op_data.h
.
Il parametro di convolvezione in profondità originale è il seguente:
typedef struct {
TfLitePadding padding;
int stride_width;
int stride_height;
int depth_multiplier;
TfLiteFusedActivation activation;
} TfLiteDepthwiseConvParams;
Come per lo schema FlatBuffer, aggiungi commenti che indichino quali parametri sono supportati a partire da quale versione. Il risultato è riportato di seguito:
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;
Modifica anche l'implementazione del kernel in modo da leggere i parametri appena aggiunti dalle strutture C. I dettagli sono omessi qui.
Modificare il codice di lettura di FlatBuffer
La logica per leggere FlatBuffer e produrre la struttura C è in
lite/core/api/flatbuffer_conversions.cc
.
Aggiorna il file per gestire i nuovi parametri, come mostrato di seguito:
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;
}
Non è necessario controllare la versione dell'operatore qui. Quando la nuova implementazione legge un vecchio file del modello in cui mancano i fattori di dilatazione, utilizzerà 1 come valore predefinito e il nuovo kernel funzionerà in modo coerente con il vecchio kernel.
Modificare la registrazione del kernel
MutableOpResolver (definito in lite/mutable_op_resolver.h
) fornisce alcune funzioni per registrare i kernel op. Per impostazione predefinita, le versioni minima e massima sono 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);
Le operazioni integrate sono registrate in lite/kernels/register.cc
. In questo esempio, abbiamo implementato un nuovo kernel op che può gestire DepthwiseConv2D
versione 1 e 2, quindi dobbiamo modificare questa riga:
AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D());
a:
AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D(),
/* min_version = */ 1,
/* max_version = */ 2);
Modificare la versione dell'operazione TFLite
Il passaggio successivo consiste nel fare in modo che TFLite compili la versione minima necessaria per eseguire l'operazione. In questo esempio, significa:
- Compila la versione=1 quando i fattori di dilatazione sono tutti pari a 1.
- In caso contrario, compila la versione=2.
Modifica la funzione GetBuiltinOperatorVersion
per l'operatore in
lite/tools/versioning/op_version.cc
aggiungendo la nuova versione al caso di
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;
Aggiorna la mappa delle versioni dell'operatore
L'ultimo passaggio consiste nell'aggiungere le informazioni sulla nuova versione alla mappa delle versioni dell'operatore. Questo passaggio è necessario perché dobbiamo generare la versione di runtime minima obbligatoria del modello in base a questa mappa delle versioni.
Per farlo, devi aggiungere una nuova voce della mappa in
lite/tools/versioning/runtime_version.cc
.
In questo esempio, devi aggiungere la seguente voce a op_version_map
:
{ {BuiltinOperator_DEPTHWISE_CONV_2D, 2}, %CURRENT_RUNTIME_VERSION%}
dove %CURRENT_RUNTIME_VERSION%
corrisponde alla versione corrente del runtime
definita in release_version.h.
Implementazione della delega
LiteRT fornisce un'API di delega che consente di delegare le operazioni ai backend hardware. Nella funzione Prepare
del delegato, controlla se la versione è supportata per ogni nodo nel codice di delega.
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.
}
Questo è necessario anche se la delega supporta solo le operazioni della versione 1, in modo che la delega possa rilevare l'incompatibilità quando riceve un'operazione di una versione successiva.