Versioni operatore LiteRT

Questo documento descrive lo schema di controllo delle versioni delle operazioni di LiteRT. Controllo delle versioni dell'operazione consente agli sviluppatori di aggiungere nuove funzionalità e nuovi parametri alle operazioni esistenti. Inoltre, garantisce quanto segue:

  • Compatibilità con le versioni precedenti: la nuova implementazione LiteRT deve gestire del modello precedente.
  • Compatibilità in avanti: la vecchia implementazione LiteRT dovrebbe gestire una nuovo file del modello generato dalla nuova versione del convertitore, a condizione che nessun nuovo le funzionalità di machine learning.
  • Inoltra il rilevamento di incompatibilità: se un'implementazione LiteRT precedente legge un nuovo modello che contiene una nuova versione di un'operazione che non è supportato, dovrebbe segnalare l'errore.

Esempio: aggiungere la dilatazione nella convoluzione profondità

Il resto di questo documento spiega il controllo delle versioni operative in TFLite mostrando come per aggiungere parametri di dilatazione all'operazione di convoluzione depth.

Per comprendere questo documento non è richiesta la conoscenza della dilatazione. Ricorda:

  • Verranno aggiunti due nuovi parametri interi: dilation_width_factor e dilation_height_factor.
  • I vecchi kernel convoluzione depthwise che non supportano la dilatazione sono equivalenti all'impostazione dei fattori di dilatazione su 1.

Modifica schema FlatBuffer

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

Ad esempio, la tabella delle opzioni della convoluzione 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 indicano quali parametri sono supportati da quale versione.
  • Quando la nuova implementazione ottiene i valori predefiniti per i nuovi asset aggiunti , dovrebbe funzionare esattamente come la vecchia implementazione.

Dopo l'aggiunta dei nuovi parametri, la tabella sarà simile alla seguente:

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 .

Cambiare le strutture C e l'implementazione del kernel

In LiteRT, l'implementazione del kernel è disaccoppiata da FlatBuffer definizione di Kubernetes. I kernel leggono il parametro dalle strutture C definite in lite/c/builtin_op_data.h.

Il parametro originale della convoluzione depth:

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 supportati a partire da quale versione. Il risultato è mostrato 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 FlatBuffer

La logica per leggere FlatBuffer e produrre la struttura C è 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'operazione qui. Quando la nuova implementazione legge un vecchio file del modello in cui mancano fattori di dilatazione, utilizzerà 1 come predefinito e il nuovo kernel funzionerà in modo coerente con il vecchio kernel.

Modifica registrazione kernel

MutableOpResolver (definito in lite/mutable_op_resolver.h) fornisce alcune per registrare i kernel operativi. Le versioni minima e massima sono 1 per valore predefinito:

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

Cambia la versione operativa TFLite

Il passaggio successivo consiste nel rendere TFLite popolare la versione minima richiesta eseguire l'operazione. In questo esempio, significa:

  • Compila version=1 quando i fattori di dilatazione sono tutti pari a 1.
  • In caso contrario, compila il campo version=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 nella mappa delle versioni dell'operatore. Questo perché dobbiamo generare il valore minimo richiesto del modello di runtime 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 attuale del runtime definita in tensorflow/core/public/version.h.

Implementazione delle deleghe

LiteRT fornisce un'API di delega che consente di delegare operazioni a backend hardware. Nella funzione Prepare del delegato, verifica 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, &registration));

if (registration->version > kMaxVersion) {
  // Reject the node if the version isn't supported.
}

Questa operazione è necessaria anche se la delega supporta solo le operazioni della versione 1, quindi la delega può rilevare incompatibilità quando si ottiene un'operazione di versione successiva.