Quantification post-entraînement

La quantification post-entraînement est une technique de conversion qui permet de 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 justesse du modèle. Vous pouvez quantifier un modèle TensorFlow à virgule flottante déjà entraîné lorsque vous le convertissez au format TensorFlow Lite à l'aide du convertisseur TensorFlow Lite.

Méthodes d'optimisation

Plusieurs options de quantification post-entraînement sont disponibles. Voici un tableau récapitulatif des choix et des avantages qu'ils offrent:

Technique Avantages Matériel
La quantification de la plage dynamique 4 fois plus petit, 2x-3x plus rapide CPU
Quantification complète des entiers 4x plus petit, 3x+ vitesse CPU, Edge TPU, microcontrôleurs
Quantification Float16 2 fois plus petit, accélération GPU Processeur, GPU

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

options d'optimisation post-entraînement

Aucune quantification

Nous vous recommandons de commencer par passer à un modèle TFLite sans quantification. Un modèle TFLite avec float sera alors généré.

import tensorflow as tf
converter = tf.lite.TFLiteConverter.from_saved_model(saved_model_dir)
tflite_quant_model = converter.convert()

Nous vous recommandons d'effectuer cette opération en tant qu'étape initiale pour vérifier que les opérateurs du modèle TF d'origine sont compatibles avec TFLite et peuvent également être utilisés comme référence pour déboguer les erreurs de quantification introduites par les méthodes de quantification post-entraînement ultérieures. Par exemple, si un modèle TFLite quantifié produit des résultats inattendus, alors que le modèle TFLite à virgule flottante est précis, nous pouvons limiter le problème aux erreurs introduites par la version quantifiée des opérateurs TFLite.

Quantification de plage dynamique

La quantification dynamique permet de réduire l'utilisation de la mémoire et d'accélérer les calculs sans qu'il soit nécessaire de fournir un ensemble de données représentatif pour l'étalonnage. Ce type de quantification ne quantifie de manière statique que les pondérations de la virgule flottante en nombre entier au moment de la conversion, ce qui offre 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 de 8 bits et effectuent des calculs avec des pondérations et des activations de 8 bits. Cette optimisation fournit des latences proches des inférences à point entièrement fixe. Cependant, les sorties sont toujours stockées à l'aide de la virgule flottante. L'augmentation de la vitesse des opérations de plage dynamique est donc inférieure à celle d'un calcul complet à virgule fixe.

Quantification complète

Vous pouvez encore améliorer la latence, réduire les pics d'utilisation de la mémoire et assurer la compatibilité avec les périphériques matériels ou les accélérateurs utilisant uniquement des entiers en vous assurant que tous les calculs mathématiques du modèle sont quantifiés sous forme d'entiers.

Pour la quantification complète, vous devez calibrer ou estimer la plage, c'est-à-dire (min, max) de tous les Tensors à virgule flottante du modèle. Contrairement aux Tensors constants tels que les pondérations et les biais, les Tensors 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 d'exécuter 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) de données d'entraînement 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 à l'aide d'une signature, comme dans l'exemple suivant:

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

Si le modèle TensorFlow donné contient plusieurs signatures, 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 Tensors 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 une approche basée sur des signatures plutôt que sur une liste de Tensors d'entrée, car l'ordre des Tensors 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 création de remplacement de type float (utilisation de l'entrée/sortie de type float par défaut)

Pour quantifier entièrement un modèle, mais utiliser des opérateurs de type float en l'absence d'implémentation de type entier (pour garantir la fluidité de la conversion), 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 ne comportant que des entiers est un cas d'utilisation courant pour TensorFlow Lite for Microcontrollers et TPU Coral Edge.

En outre, pour assurer la compatibilité avec les appareils ne pouvant contenir que des entiers (tels que les microcontrôleurs 8 bits) et les accélérateurs (tels que le TPU Coral Edge), vous pouvez appliquer une quantification complète pour toutes les opérations, y compris les entrées et les sorties, 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 pondérations sur float16, la norme IEEE pour les nombres à virgule flottante 16 bits. Pour activer la quantification float16 des pondérations, 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:

  • Elle réduit la taille du modèle jusqu'à deux fois (puisque toutes les pondérations deviennent la moitié de leur taille d'origine).
  • Elle entraîne une perte de précision minimale.
  • Il prend en charge certains délégués (par exemple, le délégué de GPU) qui peuvent opérer directement sur des données float16, ce qui accélère l'exécution par rapport aux calculs float32.

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

  • Elle ne réduit pas la latence autant qu'une quantification sur des opérations mathématiques à point fixe.
  • Par défaut, un modèle quantifié float16 "déquantifie" les valeurs de pondération sur float32 lorsqu'il est exécuté sur le processeur. (Notez que le délégué de GPU n'effectue pas cette déquantification, car il peut fonctionner sur des données float16.)

Entier uniquement: activations 16 bits avec des pondérations de 8 bits (expérimental)

Il s'agit d'un schéma de quantification expérimental. Il est semblable au schéma "Entiers uniquement", mais les activations sont quantifiées en fonction de leur plage jusqu'à 16 bits, les pondérations sont quantifiées en entier 8 bits et le biais est quantifié en entier de 64 bits. C'est ce que l'on appelle la quantification 16x8.

Le principal avantage de cette quantification est qu'elle peut améliorer considérablement la justesse, mais qu'elle 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 compatible avec certains opérateurs du modèle, le modèle peut toujours être quantifié, mais les opérateurs non compatibles sont conservés dans une valeur à virgule flottante. 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()

Voici quelques exemples de cas d'utilisation dans lesquels ce schéma de quantification permet d'améliorer la précision:

  • super-resolution,
  • le traitement du signal audio, comme la suppression du bruit et le beamforming,
  • la suppression du bruit dans les images,
  • Reconstitution HDR à partir d'une seule image.

L'inconvénient de cette quantification est le suivant:

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

Un tutoriel pour ce mode de quantification est disponible ici.

Précision du modèle

Étant donné que les pondérations sont quantifiées après l'entraînement, il peut y avoir une perte de justesse, en particulier pour les réseaux plus petits. 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 justesse du modèle quantifié pour s'assurer que toute dégradation de la justesse est dans des limites acceptables. Des outils permettent d'évaluer la précision du modèle TensorFlow Lite.

Si la baisse de justesse est trop importante, envisagez d'utiliser un entraînement qui tient compte de la quantification. Toutefois, cela nécessite des modifications lors de l'entraînement du modèle pour ajouter de faux nœuds de quantification, tandis que les techniques de quantification post-entraînement décrites sur cette page utilisent un modèle pré-entraîné existant.

Représentation de Tensors 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:

  • Les pondérations par axe (ou canal) ou par Tensor représentées par des valeurs de type int8 deux complètent la plage [-127, 127], le point zéro étant égal à 0.

  • Activations/entrées par Tensor représentées par des valeurs complémentaires d'int8 dans la plage [-128, 127], avec un point zéro dans la plage [-128, 127].

Pour obtenir 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 décrit ici.