Versions d'opérateur LiteRT

Ce document décrit le schéma de gestion des versions des opérations de LiteRT. Gestion des versions d'opérations permet aux développeurs d'ajouter de nouvelles fonctionnalités et de nouveaux paramètres aux opérations existantes. En outre, il garantit ce qui suit:

  • Rétrocompatibilité: la nouvelle implémentation LiteRT doit gérer une l'ancien fichier de modèle.
  • Compatibilité ascendante: l'ancienne implémentation de LiteRT devrait gérer une nouveau fichier de modèle généré par la nouvelle version du convertisseur, sont utilisées.
  • Détection de l'incompatibilité des transferts: si une ancienne implémentation de LiteRT lit un nouveau modèle contenant une nouvelle version d'une opération l'erreur devrait être signalée.

Exemple: ajouter une dilatation à une convolution de profondeur

Le reste de ce document explique la gestion des versions d'opérations dans TFLite en montrant comment pour ajouter des paramètres de dilatation à l'opération de convolution de profondeur.

Il n'est pas nécessaire de connaître la dilatation pour comprendre ce document. Remarques :

  • Deux nouveaux paramètres entiers seront ajoutés: dilation_width_factor et dilation_height_factor
  • Les anciens noyaux à convolution de profondeur qui ne sont pas compatibles avec la dilatation sont équivalents pour définir les facteurs de dilatation sur 1.

Modifier le schéma FlatBuffer

Pour ajouter de nouveaux paramètres à une opération, modifiez le tableau d'options dans lite/schema/schema.fbs

Par exemple, la table d'options de la convolution de profondeur se présente comme suit:

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

Lorsque vous ajoutez des paramètres:

  • Ajoutez des commentaires indiquant quels paramètres sont compatibles avec chaque version.
  • Lorsque la nouvelle implémentation obtient les valeurs par défaut des nouvelles elle doit fonctionner exactement de la même manière que l'ancienne implémentation.

Une fois les nouveaux paramètres ajoutés, le tableau se présentera comme suit:

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

Le fichier lite/schema/schema_generated.h doit être généré à nouveau pour le nouveau du schéma.

Modifier les structures C et la mise en œuvre du noyau

Dans LiteRT, l'implémentation du noyau est dissociée de FlatBuffer. définition. Les noyaux lisent le paramètre à partir des structures C définies dans lite/c/builtin_op_data.h

Le paramètre de convolution de profondeur d'origine est le suivant:

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

Comme pour le schéma FlatBuffer, ajoutez des commentaires indiquant les paramètres prises en charge à partir de quelle version. Voici le résultat:

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;

Veuillez également modifier l'implémentation du noyau pour lire les paramètres nouvellement ajoutés. des structures C. Les détails sont omis ici.

Modifier le code de lecture du FlatBuffer

La logique pour lire FlatBuffer et produire une structure C se trouve dans lite/core/api/flatbuffer_conversions.cc

Mettez à jour le fichier pour gérer les nouveaux paramètres, comme indiqué ci-dessous:

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

Il n'est pas nécessaire de vérifier la version de l'opération ici. Lorsque la nouvelle implémentation lit un ancien fichier de modèle dans lequel les facteurs de dilatation sont manquants, il utilise 1 comme valeur valeur par défaut, et le nouveau noyau fonctionnera de manière cohérente avec l’ancien noyau.

Modifier l'enregistrement du noyau

MutableOpResolver (défini dans lite/mutable_op_resolver.h) fournit quelques pour enregistrer les noyaux opérationnels. Les versions minimale et maximale sont de 1 par default:

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

Les opérations intégrées sont enregistrées dans lite/kernels/register.cc. Dans cet exemple, nous avons implémenté un nouveau noyau op capable de gérer la version 1 de DepthwiseConv2D et 2. Nous devons donc modifier cette ligne:

AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D());

par :

AddBuiltin(BuiltinOperator_DEPTHWISE_CONV_2D, Register_DEPTHWISE_CONV_2D(),
             /* min_version = */ 1,
             /* max_version = */ 2);

Modifier la version de l'opération TFLite

L'étape suivante consiste à faire en sorte que TFLite renseigne la version minimale requise pour exécuter l'opération. Dans cet exemple, cela signifie:

  • Renseignez version=1 lorsque les facteurs de dilatation sont tous 1.
  • Sinon, renseignez version=2.

Modifiez la fonction GetBuiltinOperatorVersion pour l'opérateur dans lite/tools/versioning/op_version.cc en ajoutant la nouvelle version au cas de 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;

Mettre à jour le mappage des versions d'opérateur

La dernière étape consiste à ajouter les informations de la nouvelle version dans le mappage des versions de l'opérateur. Ce est obligatoire, car nous devons générer la valeur minimale requise pour le modèle la version d'exécution basée sur ce mappage de versions.

Pour ce faire, vous devez ajouter une nouvelle entrée de carte dans lite/tools/versioning/runtime_version.cc

Dans cet exemple, vous devez ajouter l'entrée suivante à op_version_map:

{ {BuiltinOperator_DEPTHWISE_CONV_2D, 2}, %CURRENT_RUNTIME_VERSION%}

%CURRENT_RUNTIME_VERSION% correspond à la version d'exécution actuelle défini dans tensorflow/core/public/version.h.

Implémentation de la délégation

LiteRT fournit une API de délégation qui permet de déléguer des opérations à backends matériels. Dans la fonction Prepare du délégué, vérifiez si la version est compatible avec chaque nœud du code de délégation.

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

Cette étape est obligatoire même si la délégation n'accepte que les opérations de version 1. Par conséquent, le la délégation peut détecter une incompatibilité lors de l'obtention d'une opération de version supérieure.