Quantification post-formation

La quantification post-formation est une technique de conversion qui peut réduire la taille du modèle tout en améliorant la latence du processeur et de l'accélérateur matériel, avec une légère dégradation de la précision du modèle. Vous pouvez quantifier un modèle TensorFlow flottant déjà entraîné lorsque vous le convertissez au format TensorFlow Lite à l'aide de TensorFlow Lite Converter .

Méthodes d'optimisation

Vous avez le choix entre plusieurs options de quantification post-formation. Voici un tableau récapitulatif des choix et des avantages qu’ils procurent :

Technique Avantages Matériel
Quantification de la plage dynamique 4x plus petit, 2x-3x accélération CPU
Quantification entière complète 4x plus petit, 3x+ accélération CPU, Edge TPU, microcontrôleurs
Quantification Float16 2x plus petit, accélération GPU Processeur, GPU

L'arbre de décision suivant peut vous aider à déterminer quelle méthode de quantification post-formation est la mieux adaptée à votre cas d'utilisation :

options d'optimisation post-formation

Quantification de la plage dynamique

La quantification de la plage dynamique est un point de départ recommandé car elle permet une utilisation réduite de la mémoire et un calcul plus rapide sans que vous ayez à fournir un ensemble de données représentatif pour l'étalonnage. Ce type de quantification quantifie statiquement uniquement les poids de la virgule flottante à l'entier au moment de la conversion, ce qui fournit une précision de 8 bits :

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
tflite_quant_model = converter.convert()

Pour réduire davantage la latence lors de l'inférence, les opérateurs de « plage dynamique » quantifient dynamiquement les activations en fonction de leur plage à 8 bits et effectuent des calculs avec des poids et des activations sur 8 bits. Cette optimisation fournit des latences proches des inférences entièrement en virgule fixe. Cependant, les sorties sont toujours stockées en virgule flottante, de sorte que la vitesse accrue des opérations à plage dynamique est inférieure à un calcul complet en virgule fixe.

Quantification entière complète

Vous pouvez obtenir des améliorations supplémentaires en matière de latence, des réductions de l'utilisation maximale de la mémoire et une compatibilité avec des périphériques matériels ou des accélérateurs à nombres entiers uniquement en vous assurant que tous les calculs du modèle sont quantifiés en nombres entiers.

Pour une quantification entière complète, vous devez calibrer ou estimer la plage, c'est-à-dire (min, max) de tous les tenseurs à virgule flottante du modèle. Contrairement aux tenseurs constants tels que les poids et les biais, les tenseurs variables tels que l'entrée du modèle, les activations (sorties des couches intermédiaires) et la sortie du modèle ne peuvent pas être calibrés à moins que nous exécutions quelques cycles d'inférence. Par conséquent, le convertisseur nécessite un ensemble de données représentatif pour les calibrer. Cet ensemble de données peut être un petit sous-ensemble (environ 100 à 500 échantillons) des données de formation ou de validation. Reportez-vous à la fonction representative_dataset() ci-dessous.

À partir de la version TensorFlow 2.7, vous pouvez spécifier l'ensemble de données représentatif via une signature , comme dans l'exemple suivant :

def representative_dataset():
  for data in dataset:
    yield {
      "image": data.image,
      "bias": data.bias,
    }

S'il existe plusieurs signatures dans le modèle TensorFlow donné, vous pouvez spécifier plusieurs ensembles de données en spécifiant les clés de signature :

def representative_dataset():
  # Feed data set for the "encode" signature.
  for data in encode_signature_dataset:
    yield (
      "encode", {
        "image": data.image,
        "bias": data.bias,
      }
    )

  # Feed data set for the "decode" signature.
  for data in decode_signature_dataset:
    yield (
      "decode", {
        "image": data.image,
        "hint": data.hint,
      },
    )

Vous pouvez générer l'ensemble de données représentatif en fournissant une liste de tenseurs d'entrée :

def representative_dataset():
  for data in tf.data.Dataset.from_tensor_slices((images)).batch(1).take(100):
    yield [tf.dtypes.cast(data, tf.float32)]

Depuis la version TensorFlow 2.7, nous recommandons d'utiliser l'approche basée sur les signatures plutôt que l'approche basée sur la liste des tenseurs d'entrée, car l'ordre des tenseurs d'entrée peut être facilement inversé.

À des fins de test, vous pouvez utiliser un ensemble de données factice comme suit :

def representative_dataset():
    for _ in range(100):
      data = np.random.rand(1, 244, 244, 3)
      yield [data.astype(np.float32)]
 

Entier avec repli flottant (en utilisant l'entrée/sortie flottante par défaut)

Afin de quantifier entièrement un modèle en entier, mais d'utiliser des opérateurs flottants lorsqu'ils n'ont pas d'implémentation d'entier (pour garantir que la conversion se déroule sans problème), procédez comme suit :

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
tflite_quant_model = converter.convert()

Entier uniquement

La création de modèles entiers uniquement est un cas d'utilisation courant de TensorFlow Lite pour microcontrôleurs et Coral Edge TPU .

De plus, pour garantir la compatibilité avec les appareils à nombre entier uniquement (tels que les microcontrôleurs 8 bits) et les accélérateurs (tels que le Coral Edge TPU), vous pouvez appliquer une quantification entière complète pour toutes les opérations, y compris l'entrée et la sortie, en suivant les étapes suivantes :

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.representative_dataset = representative_dataset
converter.target_spec.supported_ops = [tf.lite.OpsSet.TFLITE_BUILTINS_INT8]
converter.inference_input_type = tf.int8  # or tf.uint8
converter.inference_output_type = tf.int8  # or tf.uint8
tflite_quant_model = converter.convert()

Quantification Float16

Vous pouvez réduire la taille d'un modèle à virgule flottante en quantifiant les pondérations en float16, la norme IEEE pour les nombres à virgule flottante 16 bits. Pour activer la quantification float16 des poids, procédez comme suit :

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_types = [tf.float16]
tflite_quant_model = converter.convert()

Les avantages de la quantification float16 sont les suivants :

  • Il réduit la taille du modèle jusqu'à la moitié (puisque tous les poids deviennent la moitié de leur taille d'origine).
  • Cela entraîne une perte minime de précision.
  • Il prend en charge certains délégués (par exemple le délégué GPU) qui peuvent opérer directement sur les données float16, ce qui entraîne une exécution plus rapide que les calculs float32.

Les inconvénients de la quantification float16 sont les suivants :

  • Cela ne réduit pas autant la latence qu’une quantification mathématique en virgule fixe.
  • Par défaut, un modèle quantifié float16 « déquantisera » les valeurs de pondération en float32 lorsqu'il sera exécuté sur le processeur. (Notez que le délégué GPU n'effectuera pas cette déquantification, puisqu'il peut opérer sur les données float16.)

Nombre entier uniquement : activations 16 bits avec poids 8 bits (expérimental)

Il s'agit d'un schéma de quantification expérimental. Il est similaire au schéma « entier uniquement », mais les activations sont quantifiées en fonction de leur plage jusqu'à 16 bits, les poids sont quantifiés en entier de 8 bits et le biais est quantifié en entier de 64 bits. C'est ce qu'on appelle plus loin la quantification 16x8.

Le principal avantage de cette quantification est qu’elle peut améliorer considérablement la précision, mais n’augmenter que légèrement la taille du modèle.

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.representative_dataset = representative_dataset
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_ops = [tf.lite.OpsSet.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8]
tflite_quant_model = converter.convert()

Si la quantification 16x8 n'est pas prise en charge pour certains opérateurs du modèle, le modèle peut toujours être quantifié, mais les opérateurs non pris en charge sont conservés en flottant. L'option suivante doit être ajoutée à target_spec pour permettre cela.

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
converter.representative_dataset = representative_dataset
converter.optimizations = [tf.lite.Optimize.DEFAULT]
converter.target_spec.supported_ops = [tf.lite.OpsSet.EXPERIMENTAL_TFLITE_BUILTINS_ACTIVATIONS_INT16_WEIGHTS_INT8,
tf.lite.OpsSet.TFLITE_BUILTINS]
tflite_quant_model = converter.convert()

Exemples de cas d'utilisation où les améliorations de précision apportées par ce schéma de quantification incluent :

  • super-résolution,
  • le traitement du signal audio tel que la suppression du bruit et la formation de faisceaux,
  • débruitage d'image,
  • Reconstruction HDR à partir d'une seule image.

L'inconvénient de cette quantification est :

  • Actuellement, l'inférence est sensiblement plus lente qu'un entier complet de 8 bits en raison du manque d'implémentation optimisée du noyau.
  • Actuellement, il est incompatible avec les délégués TFLite à accélération matérielle existants.

Un tutoriel pour ce mode de quantification peut être trouvé ici .

Précision du modèle

Étant donné que les poids sont quantifiés après la formation, il pourrait y avoir une perte de précision, en particulier pour les petits réseaux. Des modèles pré-entraînés entièrement quantifiés sont fournis pour des réseaux spécifiques sur TensorFlow Hub . Il est important de vérifier la précision du modèle quantifié pour vérifier que toute dégradation de la précision se situe dans des limites acceptables. Il existe des outils pour évaluer la précision du modèle TensorFlow Lite .

Alternativement, si la baisse de précision est trop importante, envisagez d'utiliser un entraînement prenant en compte la quantification . Cependant, cela nécessite des modifications pendant la formation du modèle pour ajouter de faux nœuds de quantification, alors que les techniques de quantification post-formation présentées sur cette page utilisent un modèle pré-entraîné existant.

Représentation des tenseurs quantifiés

La quantification sur 8 bits se rapproche des valeurs à virgule flottante à l'aide de la formule suivante.

\[real\_value = (int8\_value - zero\_point) \times scale\]

La représentation comporte deux parties principales :

  • Poids par axe (c'est-à-dire par canal) ou par tenseur représentés par des valeurs de complément à deux int8 dans la plage [-127, 127] avec le point zéro égal à 0.

  • Activations/entrées par tenseur représentées par des valeurs de complément à deux int8 dans la plage [-128, 127], avec un point zéro dans la plage [-128, 127].

Pour une vue détaillée de notre schéma de quantification, veuillez consulter nos spécifications de quantification . Les fournisseurs de matériel qui souhaitent se connecter à l'interface déléguée de TensorFlow Lite sont encouragés à mettre en œuvre le schéma de quantification qui y est décrit.