Versioni operatore TensorFlow Lite

Questo documento descrive lo schema di controllo delle versioni operativo di TensorFlow Lite. 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 TensorFlow Lite dovrebbe gestire un vecchio file di modello.
  • Compatibilità futura: la vecchia implementazione di TensorFlow Lite dovrebbe gestire un nuovo file di modello prodotto dalla nuova versione del convertitore, purché non vengano utilizzate nuove funzionalità.
  • Rilevamento di incompatibilità diretta: se una vecchia implementazione di TensorFlow Lite legge un nuovo modello che contiene una nuova versione di un'operazione che non è supportata, dovrebbe segnalare l'errore.

Esempio: aggiunta di dilatazione alla convoluzione profonda

La parte restante di questo documento spiega il funzionamento delle versioni in TFLite mostrando come aggiungere parametri di dilatazione all'operazione di convoluzione in profondità.

Per comprendere questo documento non è necessaria la conoscenza della dilatazione. Notare che:

  • Verranno aggiunti 2 nuovi parametri interi: dilation_width_factor e dilation_height_factor .
  • I vecchi kernel di convoluzione profonda che non supportano la dilatazione equivalgono a impostare i fattori di dilatazione su 1.

Modifica lo schema FlatBuffer

Per aggiungere nuovi parametri in un'operazione, modificare la tabella delle opzioni in lite/schema/schema.fbs .

Ad esempio, la tabella delle opzioni della convoluzione profonda è simile alla seguente:

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

Quando si aggiungono nuovi parametri:

  • Aggiungi commenti indicando quali parametri sono supportati da quale versione.
  • Quando la nuova implementazione ottiene i valori predefiniti per i parametri appena aggiunti, dovrebbe funzionare esattamente come la vecchia implementazione.

La tabella sarà così 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 dovrebbe essere rigenerato per il nuovo schema.

Modificare le strutture C e l'implementazione del kernel

In TensorFlow Lite, l'implementazione del kernel è disaccoppiata dalla definizione FlatBuffer. I kernel leggono il parametro dalle strutture C definite in lite/c/builtin_op_data.h .

Il parametro di convoluzione profonda 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 indicano quali parametri sono supportati a partire da quale versione. Il risultato è visto 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;

Modificare anche l'implementazione del kernel per leggere i parametri appena aggiunti dalle strutture C. I dettagli sono omessi qui.

Cambia il codice di lettura del 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 operativa qui. Quando la nuova implementazione legge un vecchio file di 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.

Cambia la registrazione del kernel

Il MutableOpResolver (definito in lite/mutable_op_resolver.h ) fornisce alcune funzioni per registrare i kernel operativi. La versione minima e massima sono 1 per impostazione predefinita:

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 operativo in grado di 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);

Cambia la versione operativa di TFLite

Il passaggio successivo è fare in modo che TFLite popoli la versione minima richiesta per eseguire l'operazione. In questo esempio, significa:

  • Compila versione=1 quando i fattori di dilatazione sono tutti 1.
  • Compila la versione=2 altrimenti.

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 nella mappa delle versioni dell'operatore. Questo passaggio è obbligatorio perché dobbiamo generare la versione runtime minima richiesta del modello in base a questa mappa delle versioni.

Per fare ciò, devi aggiungere una nuova voce sulla mappa in lite/tools/versioning/runtime_version.cc .

In questo esempio, devi aggiungere la seguente voce in op_version_map :

{ {BuiltinOperator_DEPTHWISE_CONV_2D, 2}, %CURRENT_RUNTIME_VERSION%}

dove %CURRENT_RUNTIME_VERSION% corrisponde alla versione runtime corrente definita in tensorflow/core/public/version.h .

Attuazione della delega

TensorFlow Lite 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 della delega.

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

Ciò è necessario anche se la delega supporta solo le operazioni della versione 1, in modo che la delega possa rilevare l'incompatibilità quando si ottiene una versione operativa superiore.