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 faible dégradation de la précision du modèle. Vous pouvez quantifier un modèle TensorFlow flottant déjà formé lorsque vous le convertissez au format TensorFlow Lite à l'aide du convertisseur TensorFlow Lite .

Méthodes d'optimisation

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

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

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

options d'optimisation post-formation

Quantification de plage dynamique

La quantification de la plage dynamique est un point de départ recommandé car elle réduit l'utilisation de la mémoire et accélère les calculs 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 de 8 bits. Cette optimisation fournit des latences proches des inférences entièrement en virgule fixe. Cependant, les sorties sont toujours stockées à l'aide de virgule flottante, de sorte que la vitesse accrue des opérations de plage dynamique est inférieure à un calcul complet en virgule fixe.

Quantification entière entière

Vous pouvez obtenir d'autres améliorations de la latence, des réductions de l'utilisation maximale de la mémoire et la compatibilité avec les périphériques matériels ou les accélérateurs uniquement en nombre entier en vous assurant que tous les calculs du modèle sont quantifiés en nombre entier.

Pour une quantification entière entière, 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 être calibrés que si nous exécutons quelques cycles d'inférence. Par conséquent, le convertisseur a besoin d'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 d'apprentissage ou de validation. Reportez-vous à la fonction representative_dataset() ci-dessous.

À partir de la version 2.7 de TensorFlow, 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 l'ensemble de données multiples 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 2.7 de TensorFlow, nous vous 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 jeu 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 (utilisant l'entrée/sortie flottante par défaut)

Afin de quantifier entièrement un modèle entier, mais d'utiliser des opérateurs flottants lorsqu'ils n'ont pas d'implémentation entière (pour garantir une conversion fluide), 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 pour TensorFlow Lite pour les microcontrôleurs et les TPU Coral Edge .

De plus, pour garantir la compatibilité avec les périphériques uniquement entiers (tels que les microcontrôleurs 8 bits) et les accélérateurs (tels que le TPU Coral Edge), vous pouvez appliquer la quantification entière entière pour toutes les opérations, y compris l'entrée et la sortie, en procédant 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
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 poids à 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 minimale de précision.
  • Il prend en charge certains délégués (par exemple, le délégué GPU) qui peuvent fonctionner 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 :

  • Il ne réduit pas autant la latence qu'une quantification en calcul à virgule fixe.
  • Par défaut, un modèle quantifié float16 "déquantifiera" les valeurs de poids en float32 lorsqu'il sera exécuté sur le CPU. (Notez que le délégué GPU n'effectuera pas cette déquantification, car il peut fonctionner sur des données float16.)

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 à 16 bits, les poids sont quantifiés en entier 8 bits et le biais est quantifié en entier 64 bits. Ceci est appelé quantification 16x8 plus loin.

Le principal avantage de cette quantification est qu'elle peut améliorer considérablement la précision, mais n'augmente 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 restent flottants. 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 fournies par ce schéma de quantification incluent :

  • super-résolution,
  • 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 nettement plus lente que l'entier complet 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érés par le matériel existants.

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

Précision du modèle

Étant donné que les pondérations sont quantifiées après la formation, il pourrait y avoir une perte de précision, en particulier pour les petits réseaux. Des modèles entièrement quantifiés pré-entraîné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 chute de précision est trop élevée, envisagez d'utiliser un entraînement sensible à la quantification . Cependant, cela nécessite des modifications lors de la formation du modèle pour ajouter de faux nœuds de quantification, alors que les techniques de quantification post-formation sur cette page utilisent un modèle pré-formé existant.

Représentation pour les tenseurs quantifiés

La quantification 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 (alias 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.