Présentation du convertisseur d'inférence Cloud TPU v5e
Présentation
Le convertisseur d'inférence Cloud TPU prépare et optimise un modèle TensorFlow 2 (TF2) pour l'inférence TPU. Le convertisseur s'exécute dans une interface système locale ou de VM TPU. L'interface système de la VM TPU est recommandée, car elle est préinstallée avec la commande les outils de ligne nécessaires au convertisseur. Il utilise un fichier SavedModel et effectue les étapes suivantes:
- Conversion TPU: elle ajoute
TPUPartitionedCall
et d'autres opérations TPU au pour le rendre diffusable sur le TPU. Par défaut, un modèle exporté pour l'inférence ne comporte pas de telles opérations et ne peut pas être diffusée sur le TPU, a été entraîné sur TPU. - Traitement par lot: ajouter des opérations de traitement par lot au modèle pour permettre le traitement par lot dans le graphique pour un meilleur débit.
- Conversion BFloat16: il convertit le format de données du modèle
De
float32
àbfloat16
pour mieux des performances de calcul et une mémoire à haut débit (HBM) plus faible sur le TPU. - Optimisation des formes d'E/S: optimise les formes de Tensor pour les données transférées entre le CPU et le TPU pour améliorer l'utilisation de la bande passante.
Lors de l'exportation d'un modèle, les utilisateurs créent des alias pour toutes les fonctions qu'ils que vous souhaitez exécuter sur le TPU. Ils transmettent ces fonctions au convertisseur, lequel Le convertisseur les place sur le TPU et les optimise.
Le convertisseur d'inférence Cloud TPU est disponible sous forme d'image Docker pouvant être exécutés dans n'importe quel environnement sur lequel Docker est installé.
Estimation du temps nécessaire pour effectuer les étapes ci-dessus: environ 20 à 30 minutes
Prérequis
- Le modèle doit être un modèle TF2 et exporté dans le SavedModel .
- Le modèle doit avoir un alias de fonction pour la fonction TPU. Consultez le
exemple de code
pour savoir comment procéder. Les exemples suivants utilisent
tpu_func
comme TPU un alias de fonction. - Assurez-vous que le processeur de votre ordinateur est compatible avec les extensions vectorielles avancées (AVX)
les instructions, comme dans la bibliothèque TensorFlow (la dépendance du Cloud TPU
Inference Converter) est compilée pour utiliser les instructions AVX.
La plupart des processeurs
être compatibles AVX.
- Vous pouvez exécuter
lscpu | grep avx
pour vérifier si l'AVX jeu d'instructions est pris en charge.
- Vous pouvez exécuter
Avant de commencer
Avant de commencer la configuration, procédez comme suit:
Créez un projet: Dans la console Google Cloud, sur la page de sélection du projet, sélectionnez ou créez un Google Cloud.
Configurez une VM TPU: Créez une VM TPU à l'aide de la console Google Cloud ou de
gcloud
, ou utilisez un une VM TPU existante pour exécuter l'inférence avec la VM sur la VM TPU.- Assurez-vous que l'image de VM TPU est basée sur TensorFlow. Par exemple,
--version=tpu-vm-tf-2.11.0
. - Le modèle converti sera chargé et diffusé sur cette VM TPU.
- Assurez-vous que l'image de VM TPU est basée sur TensorFlow. Par exemple,
Assurez-vous de disposer des outils de ligne de commande dont vous avez besoin pour utiliser Cloud TPU. Convertisseur d'inférence. Vous pouvez installer Google Cloud SDK et Docker localement, ou utiliser une VM TPU sur laquelle ce logiciel est installé par défaut. Ces outils vous permettent d'interagir avec l'image du convertisseur.
Connectez-vous à l'instance via SSH à l'aide de la commande suivante:
gcloud compute tpus tpu-vm ssh ${tpu-name} --zone ${zone} --project ${project-id}
Configuration de l'environnement
Configurez votre environnement à partir de l'interface système de votre VM TPU ou de votre interface système locale.
Shell de la VM TPU
Dans l'interface système de votre VM TPU, exécutez les commandes suivantes pour : autoriser l'utilisation de Docker non racine:
sudo usermod -a -G docker ${USER} newgrp docker
Initialisez vos assistants d'identification Docker:
gcloud auth configure-docker \ us-docker.pkg.dev
Interface système locale
Dans votre interface système locale, configurez l'environnement en procédant comme suit:
installer le SDK Cloud ; qui inclut l'outil de ligne de commande
gcloud
.Installez Docker:
Autorisez l'utilisation de Docker non racine:
sudo usermod -a -G docker ${USER} newgrp docker
Connectez-vous à votre environnement:
gcloud auth login
Initialisez vos assistants d'identification Docker:
gcloud auth configure-docker \ us-docker.pkg.dev
Récupérez l'image Docker du convertisseur d'inférence:
CONVERTER_IMAGE=us-docker.pkg.dev/cloud-tpu-images/inference/tpu-inference-converter-cli:2.13.0 docker pull ${CONVERTER_IMAGE}
Image du convertisseur
L'image sert à effectuer des conversions de modèle ponctuelles. Définir les chemins d'accès du modèle et ajuster options de conversion en fonction de vos besoins. La Exemples d'utilisation présente plusieurs cas d'utilisation courants.
docker run \ --mount type=bind,source=${MODEL_PATH},target=/tmp/input,readonly \ --mount type=bind,source=${CONVERTED_MODEL_PATH},target=/tmp/output \ ${CONVERTER_IMAGE} \ --input_model_dir=/tmp/input \ --output_model_dir=/tmp/output \ --converter_options_string=' tpu_functions { function_alias: "tpu_func" } batch_options { num_batch_threads: 2 max_batch_size: 8 batch_timeout_micros: 5000 allowed_batch_sizes: 2 allowed_batch_sizes: 4 allowed_batch_sizes: 8 max_enqueued_batches: 10 } '
Inférence avec le modèle converti dans une VM TPU
# Initialize the TPU resolver = tf.distribute.cluster_resolver.TPUClusterResolver("local") tf.config.experimental_connect_to_cluster(resolver) tf.tpu.experimental.initialize_tpu_system(resolver) # Load the model model = tf.saved_model.load(${CONVERTED_MODEL_PATH}) # Find the signature function for serving serving_signature = 'serving_default' # Change the serving signature if needed serving_fn = model.signatures[serving_signature] # Run the inference using requests. results = serving_fn(**inputs) logging.info("Serving results: %s", str(results))
Exemples d'utilisation
Ajouter un alias de fonction pour la fonction TPU
- Rechercher ou créer dans votre modèle une fonction qui englobe tout ce que vous voulez
à exécuter sur le TPU. Si
@tf.function
n'existe pas, ajoutez-le. - Lors de l'enregistrement du modèle, fournissez SaveOptions comme ci-dessous pour
model.tpu_func
est un aliasfunc_on_tpu
. - Vous pouvez transmettre cet alias de fonction au convertisseur.
class ToyModel(tf.keras.Model): @tf.function( input_signature=[tf.TensorSpec(shape=[None, 10], dtype=tf.float32)]) def tpu_func(self, x): return x * 1.0 model = ToyModel() save_options = tf.saved_model.SaveOptions(function_aliases={ 'func_on_tpu': model.tpu_func, }) tf.saved_model.save(model, model_dir, options=save_options)
Convertir un modèle avec plusieurs fonctions TPU
Vous pouvez exécuter plusieurs fonctions sur le TPU. Il suffit de créer plusieurs fonctions
et transmettez-les dans converter_options_string
au convertisseur.
tpu_functions { function_alias: "tpu_func_1" } tpu_functions { function_alias: "tpu_func_2" }
Quantification
La quantification est une technique qui réduit la précision des nombres utilisée pour représenter les paramètres d'un modèle. Cela permet d'obtenir un modèle plus petit et des calculs plus rapides. Un modèle quantifié offre des avantages débit d'inférence, et réduire l'utilisation de la mémoire et la taille de stockage, au prix d'une légère baisse de la justesse.
La nouvelle fonctionnalité de quantification post-entraînement dans TensorFlow, qui cible TPU a été développé à partir d'une fonctionnalité similaire qui existe déjà dans TensorFlow Lite. utilisée pour cibler les appareils mobiles et de périphérie. Pour en savoir plus sur en général, vous pouvez examiner Document TensorFlow Lite.
Concepts de quantification
Cette section définit des concepts spécifiquement liés à la quantification avec le convertisseur d'inférence.
Concepts liés à d'autres configurations de TPU (par exemple, hôtes, chips et TensorCores) sont décrits dans le Architecture du système TPU
Quantification post-entraînement (PTQ): technique qui permet de réduire la taille et la complexité de calcul d'un modèle de réseau de neurones sans peut affecter de manière significative sa précision. PTQ consiste à convertir les pondérations et les activations à virgule flottante d'un modèle entraîné des entiers de précision inférieure, tels que des entiers de 8 ou 16 bits. Cela peut entraîner une réduction significative de la taille du modèle et de la latence d'inférence, une légère perte de précision.
Calibrage: l'étape d'étalonnage pour la quantification correspond au processus de collecter des statistiques sur la plage de valeurs que les pondérations et et les activations d'un modèle de réseau de neurones. Ces informations sont utilisées pour déterminer les paramètres de quantification du modèle, qui sont les valeurs qui servira à convertir les pondérations et activations à virgule flottante en entiers.
Ensemble de données représentatif: ensemble de données représentatif pour la quantification est un petit ensemble de données qui représente les données d'entrée réelles du modèle. Il est utilisé lors de l'étape de calibration de la quantification pour collecter des statistiques sur la plage de valeurs que les pondérations et activations nécessaires pour le modèle. Le jeu de données représentatif doit satisfaire aux les propriétés suivantes:
- Il doit représenter correctement les entrées réelles du modèle l'inférence. Cela signifie qu'il doit couvrir la plage de valeurs que le modèle est susceptible de voir dans le monde réel.
- Il doit traverser collectivement chaque branche des expressions conditionnelles
(
tf.cond
, par exemple), le cas échéant. C'est important, car le le processus de quantification doit être capable de gérer toutes les entrées possibles au modèle, même s'ils ne sont pas explicitement représentés dans un ensemble de données représentatif. - Il doit être suffisamment grand pour recueillir suffisamment de statistiques et réduire . En règle générale, il est recommandé d'utiliser plus de 200 des échantillons représentatifs.
L'ensemble de données représentatif peut être un sous-ensemble de l'ensemble de données d'entraînement, ou un ensemble de données distinct spécialement conçu représentatifs des entrées réelles du modèle. Le choix L'ensemble de données à utiliser dépend de l'application.
Quantification de plage statique (SRG): la SRC détermine la plage de valeurs pour les pondérations et les activations d'un modèle de réseau de neurones une seule fois, de calibrage. Cela signifie que la même plage de valeurs est utilisée pour toutes les entrées au modèle. Cette valeur peut être moins précise que la plage dynamique la quantification, en particulier pour les modèles avec un large éventail de valeurs d'entrée. Cependant, la quantification de plage statique nécessite moins de calculs lors de l'exécution que la quantification de plage dynamique.
Quantification de plage dynamique (DQ): la fonction DQR détermine la plage de des valeurs de pondération et d'activation d'un modèle de réseau de neurones pour chaque entrée. Le modèle peut ainsi s'adapter à la plage de valeurs les données d'entrée, ce qui peut améliorer la précision. En revanche, la plage dynamique la quantification nécessite plus de calculs lors de l'exécution que la plage statique la quantification.
Fonctionnalité Quantification de plage statique Quantification de la plage dynamique Plage de valeurs Déterminé une fois, lors de l'étalonnage Déterminé pour chaque entrée Précision Peut être moins précis, en particulier pour les modèles avec un large éventail de valeurs d'entrée Elles peuvent être plus précises, en particulier pour les modèles avec un large éventail de valeurs d'entrée. Complexité Plus simple Plus complexe Calcul au moment de l'exécution Moins de calculs Plus de calculs Quantification pondérée: la quantification pondérée uniquement est un type de qui ne quantifie que les pondérations d'un modèle de réseau de neurones, tout en laissant les activations en virgule flottante. Cela peut être une bonne pour les modèles sensibles à la précision, car elle peut préserver la justesse du modèle.
Utiliser la quantification
Vous pouvez appliquer la quantification en configurant QuantizationOptions aux options du convertisseur. Les options notables sont les suivantes:
- tags: collection de balises identifiant le
MetaGraphDef
dans leSavedModel
à quantifier. Il n'est pas nécessaire de préciser si vous n'avez qu'un seulMetaGraphDef
. - signature_keys: séquence de clés identifiant
SignatureDef
contenant des entrées et des sorties. Si aucune valeur n'est spécifiée, la valeur ["serving_default"] est utilisée. - quantization_method: méthode de quantification à appliquer. Si ce n'est pas le cas
spécifiée, la quantification
STATIC_RANGE
sera appliquée. - op_set: doit être conservé au format XLA. Il s'agit actuellement de l'option par défaut. Vous n'avez pas besoin de la spécifier.
- représentatif_datasets: spécifie l'ensemble de données utilisé pour calibrer les paramètres de quantification.
Créer l'ensemble de données représentatif
Un ensemble de données représentatif est essentiellement un itérable d'échantillons.
Où un exemple est une carte de: {input_key: input_value}
. Exemple :
representative_dataset = [{"x": tf.random.uniform(shape=(3, 3))}
for _ in range(256)]
Les ensembles de données représentatifs doivent être enregistrés sous TFRecord
de fichiers à l'aide de la classe TfRecordRepresentativeDatasetSaver
actuellement disponibles dans le package pip tf-nightly. Exemple :
# Assumed tf-nightly installed.
import tensorflow as tf
representative_dataset = [{"x": tf.random.uniform(shape=(3, 3))}
for _ in range(256)]
tf.quantization.experimental.TfRecordRepresentativeDatasetSaver(
path_map={'serving_default': '/tmp/representative_dataset_path'}
).save({'serving_default': representative_dataset})
Examples
L'exemple suivant quantifie le modèle avec la clé de signature
de serving_default
et l'alias de fonction de tpu_func
:
docker run \ --mount type=bind,source=${MODEL_PATH},target=/tmp/input,readonly \ --mount type=bind,source=${CONVERTED_MODEL_PATH},target=/tmp/output \ ${CONVERTER_IMAGE} \ --input_model_dir=/tmp/input \ --output_model_dir=/tmp/output \ --converter_options_string=' \ tpu_functions { \ function_alias: "tpu_func" \ } \ external_feature_configs { \ quantization_options { \ signature_keys: "serving_default" \ representative_datasets: { \ key: "serving_default" \ value: { \ tfrecord_file_path: "${TF_RECORD_FILE}" \ } \ } \ } \ } '
Ajouter un traitement par lot
Le convertisseur permet d'ajouter un traitement par lot à un modèle. Pour obtenir une description de traitement par lot pouvant être réglées, reportez-vous Définition des options de traitement par lot.
Par défaut, le convertisseur regroupe toutes les fonctions TPU du modèle. Il peut aussi par lots fournis par l'utilisateur signatures et fonctions ce qui peut améliorer les performances. N'importe quelle fonction TPU, fonction fournie par l'utilisateur ou signature par lot, doivent respecter les conditions exigences strictes concernant la forme.
Le convertisseur peut également mise à jour les options de traitement par lot existantes. L'exemple suivant montre comment ajouter un traitement par lot à un modèle. Pour plus sur le traitement par lot, consultez Présentation détaillée du traitement par lot
batch_options { num_batch_threads: 2 max_batch_size: 8 batch_timeout_micros: 5000 allowed_batch_sizes: 2 allowed_batch_sizes: 4 allowed_batch_sizes: 8 max_enqueued_batches: 10 }
Désactiver les optimisations de forme bfloat16 et d'E/S
Les optimisations de forme BFloat16 et d'E/S sont activées par défaut. Si elles ne fonctionnent pas avec votre modèle, ils peuvent être désactivés.
# Disable both optimizations disable_default_optimizations: true # Or disable them individually io_shape_optimization: DISABLED bfloat16_optimization: DISABLED
Rapport sur les conversions
Vous pouvez trouver ce rapport de conversion dans le journal après avoir exécuté l'outil d'inférence Convertisseur. Vous trouverez un exemple ci-dessous.
-------- Conversion Report -------- TPU cost of the model: 96.67% (2034/2104) CPU cost of the model: 3.33% (70/2104) Cost breakdown ================================ % Cost Name -------------------------------- 3.33 70 [CPU cost] 48.34 1017 tpu_func_1 48.34 1017 tpu_func_2 --------------------------------
Ce rapport estime le coût de calcul du modèle de sortie sur le CPU et TPU, et détaille le coût du TPU pour chaque fonction, qui doit refléter votre sélection des fonctions TPU dans les options du convertisseur.
Si vous voulez mieux utiliser le TPU, vous pouvez essayer le modèle et ajuster les options du convertisseur.
Questions fréquentes
Quelle(s) fonction(s) dois-je placer sur le TPU ?
Il est préférable d'utiliser le plus grand nombre possible de modèles sur TPU, car la grande majorité des opérations s'exécutent plus rapidement sur le TPU.
Si votre modèle ne contient aucune opération, chaîne ou opération creuses incompatible avec TPU Placer l'ensemble du modèle sur le TPU est généralement la meilleure stratégie à adopter. Et vous pouvez trouver ou créer une fonction qui encapsule l'ensemble du modèle, en créant un alias de fonction et en le transmettant au convertisseur.
Si votre modèle contient des pièces qui ne peuvent pas fonctionner sur le TPU (par exemple, incompatibles avec les opérations, les chaînes ou les Tensors creux), le choix des fonctions TPU dépend de l'emplacement la partie incompatible.
- Si elle se trouve au début ou à la fin du modèle, vous pouvez refactoriser pour le garder sur le CPU. Exemples : prétraitement et post-traitement des chaînes étapes. Pour en savoir plus sur le transfert de code vers le processeur, consultez "Comment faire passer une partie du modèle au processeur ?" Il montre une méthode classique de refactorisation du modèle.
- Si elle se trouve au milieu du modèle, Diviser le modèle en trois parties et contenir toutes les opérations incompatibles avec les TPU au milieu et faire fonctionner l'application sur le CPU.
- S'il s'agit d'un Tensor creux, envisagez d'appeler
tf.sparse.to_dense
sur le CPU et transmettre le Tensor dense obtenu à la partie TPU du modèle.
Un autre facteur à prendre en compte est l'utilisation du HBM. Les représentations vectorielles continues de tableaux peuvent utiliser HBM. Si elles dépassent les limites matérielles du TPU, vous devez les utiliser sur le CPU, ainsi que les opérations de recherche.
Dans la mesure du possible, une seule fonction TPU doit exister sous une signature. Si la structure de votre modèle nécessite d'appeler plusieurs fonctions TPU par requête d'inférence entrante, vous devez tenir compte de la latence supplémentaire liée à l'envoi entre le CPU et le TPU.
Un bon moyen d'évaluer la sélection des fonctions TPU consiste à vérifier Rapport sur les conversions : Il indique le pourcentage de calcul appliqué au TPU et une du coût de chaque fonction TPU.
Comment déplacer une partie du modèle vers le processeur ?
Si votre modèle contient des parties qui ne peuvent pas être diffusées sur le TPU, vous devez refactoriser le modèle pour les déplacer vers le CPU. Voici un exemple de jouet. Le modèle est un modèle de langage avec une étape de prétraitement. Le code pour les définitions des couches et sont omises par souci de simplicité.
class LanguageModel(tf.keras.Model): @tf.function def model_func(self, input_string): word_ids = self.preprocess(input_string) return self.bert_layer(word_ids)
Ce modèle ne peut pas être diffusé directement sur le TPU pour deux raisons. Tout d'abord, le
est une chaîne. Deuxièmement, la fonction preprocess
peut contenir de nombreuses chaînes
opérations Les deux ne sont pas compatibles avec les TPU.
Pour refactoriser ce modèle, vous pouvez créer une autre fonction appelée tpu_func
afin de
qui hébergent le bert_layer
, qui utilise beaucoup de ressources de calcul. Créez ensuite un alias de fonction pour
tpu_func
et le transmettre au convertisseur. De cette façon, tout ce qui se trouve à l'intérieur
tpu_func
s'exécutera sur le TPU, et tout le reste de model_func
s'exécutera sur
le processeur.
class LanguageModel(tf.keras.Model): @tf.function def tpu_func(self, word_ids): return self.bert_layer(word_ids) @tf.function def model_func(self, input_string): word_ids = self.preprocess(input_string) return self.tpu_func(word_ids)
Que dois-je faire si le modèle comporte des opérations, des chaînes ou des Tensors creux incompatibles avec les TPU ?
La plupart des opérations TensorFlow standards sont compatibles avec TPU, mais certaines les Tensors creux et les chaînes ne sont pas acceptés. Le convertisseur ne recherchez les opérations incompatibles avec les TPU. Ainsi, un modèle contenant de telles opérations peut transmettre la conversion. Toutefois, lors de son exécution pour l'inférence, des erreurs telles que celles ci-dessous se produiront.
'tf.StringToNumber' op isn't compilable for TPU device.
Si votre modèle comporte des opérations incompatibles avec le TPU, vous devez les placer en dehors du TPU . De plus, la chaîne est un format de données non compatible avec le TPU. Donc les variables de type chaîne ne doivent pas être placées dans la fonction TPU. et l' et les valeurs renvoyées de la fonction TPU ne doivent pas être de type chaîne bien. De même, évitez de placer des Tensors creux dans la fonction TPU, y compris dans ses paramètres et les valeurs renvoyées.
Il n'est généralement pas difficile de refactoriser la partie incompatible du modèle et le déplacer vers le CPU. Voici une exemple.
Comment prendre en charge les opérations personnalisées dans le modèle ?
Si votre modèle utilise des opérations personnalisées, il est possible que le convertisseur ne les reconnaisse pas. n'aboutit pas à la conversion du modèle. En effet, la bibliothèque d'opérations de l'opération personnalisée, qui contient la définition complète de l'opération, n'est pas lié au convertisseur.
Le code du convertisseur n'étant pas encore Open Source, il ne peut pas être créés avec des opérations personnalisées.
Que dois-je faire si je dispose d'un modèle TensorFlow 1 ?
Le convertisseur n'est pas compatible avec les modèles TensorFlow 1. Les modèles TensorFlow 1 doivent migrer vers TensorFlow 2.
Dois-je activer le pont MLIR lorsque j'exécute mon modèle ?
La plupart des modèles convertis peuvent être exécutés avec le pont MLIR TF2XLA le plus récent ou avec le pont TF2XLA d'origine.
Comment convertir un modèle qui a déjà été exporté sans alias de fonction ?
Si un modèle a été exporté sans alias de fonction, le moyen le plus simple consiste à exporter
à nouveau et
Créez un alias de fonction.
S'il n'est pas possible de réexporter le modèle, vous pouvez toujours le convertir
en fournissant un concrete_function_name
. Toutefois, l'identification
concrete_function_name
nécessite quelques recherches.
Les alias de fonction sont le mappage entre une chaîne définie par l'utilisateur et une fonction concrète. son nom. Ils permettent de faire plus facilement référence à une fonction spécifique dans le modèle. La Le convertisseur accepte à la fois les alias de fonction et les noms de fonctions concrètes bruts.
Pour trouver des noms de fonctions concrètes, examinez le saved_model.pb
.
L'exemple suivant montre comment insérer une fonction concrète appelée
__inference_serve_24
sur le TPU.
sudo docker run \ --mount type=bind,source=${MODEL_PATH},target=/tmp/input,readonly \ --mount type=bind,source=${CONVERTED_MODEL_PATH},target=/tmp/output \ ${CONVERTER_IMAGE} \ --input_model_dir=/tmp/input \ --output_model_dir=/tmp/output \ --converter_options_string=' tpu_functions { concrete_function_name: "__inference_serve_24" }'
Comment résoudre une erreur de contrainte de constante de temps de compilation ?
Pour l'entraînement et l'inférence, XLA exige que les entrées de certaines opérations possèdent un une forme connue au moment de la compilation du TPU. Cela signifie que lorsque XLA compile le TPU, du programme, les entrées de ces opérations doivent avoir un type forme.
Il existe deux façons de résoudre ce problème.
- La meilleure solution consiste à mettre à jour les entrées de l'opération pour qu'elles aient une valeur
avant que XLA compile le programme TPU. Cette compilation a lieu
juste avant l'exécution de la partie TPU. Cela signifie que la forme
doit être connue de manière statique au moment de l'exécution de
TpuFunction
. - Une autre option consiste à modifier
TpuFunction
pour qu'il n'inclue plus la opération problématique.
Pourquoi une erreur de forme de traitement par lot s'affiche-t-elle ?
Le traitement par lot exigences strictes concernant la forme qui permettent de regrouper les requêtes entrantes en fonction de leur 0e dimension (c'est-à-dire de traitement par lot). Ces exigences de forme proviennent du traitement et ne peut pas être relâchée.
Le non-respect de ces exigences entraîne des erreurs telles que:
- Les Tensors d'entrée de traitement par lot doivent avoir au moins une dimension.
- Les dimensions des entrées doivent correspondre.
- Les Tensors d'entrée de traitement par lot fournis dans un appel d'opération donné doivent avoir une valeur égale Taille 0e.
- La dimension 0 du Tensor de sortie par lot n'est pas égale à la somme des valeurs 0 des dimensions des Tensors d'entrée.
Pour répondre à ces exigences, vous pouvez fournir une autre fonction ou signature par lot. Il peut également être nécessaire de modifier les fonctions existantes pour répondre à ces exigences.
Si une fonction
est en cours de traitement par lot, assurez-vous que les formes d'input_signature de sa @tf.function
n'en contient aucun dans la 0e dimension. Si une signature
est en cours de traitement par lot, assurez-vous que la valeur -1 de toutes ses entrées est égale à -1 dans la dimension 0.
Pour une explication complète sur l'origine de ces erreurs et la façon de les résoudre voir Analyse approfondie par lot :
Problèmes connus
La fonction TPU ne peut pas appeler indirectement une autre fonction TPU
Bien que le convertisseur puisse gérer la plupart des scénarios d'appel de fonction sur l'ensemble limite CPU-TPU, il existe un cas particulier rare où il échouerait. C'est lorsqu'un TPU appelle indirectement une autre fonction TPU.
En effet, le convertisseur modifie l'appelant direct d'une fonction TPU appeler la fonction TPU elle-même pour appeler un bouchon d'appel TPU. Le bouchon d'appel contient des opérations qui ne peuvent fonctionner que sur le CPU. Lorsqu'une fonction TPU appelle qui appelle directement l'appelant, ces opérations de processeur peuvent être sur le TPU à exécuter, ce qui générera des erreurs de noyau manquantes. Notez ce cas est différent d'une fonction TPU qui appelle directement une autre fonction TPU. Dans ce le convertisseur ne modifie aucune des fonctions pour appeler le bouchon d'appel. peut fonctionner.
Dans le convertisseur, nous avons implémenté la détection de ce scénario. Si vous voyez l'erreur suivante, cela signifie que votre modèle a rencontré ce cas particulier:
Unable to place both "__inference_tpu_func_2_46" and "__inference_tpu_func_4_68" on the TPU because "__inference_tpu_func_2_46" indirectly calls "__inference_tpu_func_4_68". This behavior is unsupported because it can cause invalid graphs to be generated.
La solution générale consiste à refactoriser le modèle pour éviter un tel appel de fonction. dans ce scénario. Si cela vous semble difficile, contactez l'assistance Google pour en discuter davantage.
Référence
Options du convertisseur au format Protobuf
message ConverterOptions { // TPU conversion options. repeated TpuFunction tpu_functions = 1; // The state of an optimization. enum State { // When state is set to default, the optimization will perform its // default behavior. For some optimizations this is disabled and for others // it is enabled. To check a specific optimization, read the optimization's // description. DEFAULT = 0; // Enabled. ENABLED = 1; // Disabled. DISABLED = 2; } // Batch options to apply to the TPU Subgraph. // // At the moment, only one batch option is supported. This field will be // expanded to support batching on a per function and/or per signature basis. // // // If not specified, no batching will be done. repeated BatchOptions batch_options = 100; // Global flag to disable all optimizations that are enabled by default. // When enabled, all optimizations that run by default are disabled. If a // default optimization is explicitly enabled, this flag will have no affect // on that optimization. // // This flag defaults to false. bool disable_default_optimizations = 202; // If enabled, apply an optimization that reshapes the tensors going into // and out of the TPU. This reshape operation improves performance by reducing // the transfer time to and from the TPU. // // This optimization is incompatible with input_shape_opt which is disabled. // by default. If input_shape_opt is enabled, this option should be // disabled. // // This optimization defaults to enabled. State io_shape_optimization = 200; // If enabled, apply an optimization that updates float variables and float // ops on the TPU to bfloat16. This optimization improves performance and // throughtput by reducing HBM usage and taking advantage of TPU support for // bfloat16. // // This optimization may cause a loss of accuracy for some models. If an // unacceptable loss of accuracy is detected, disable this optimization. // // This optimization defaults to enabled. State bfloat16_optimization = 201; BFloat16OptimizationOptions bfloat16_optimization_options = 203; // The settings for XLA sharding. If set, XLA sharding is enabled. XlaShardingOptions xla_sharding_options = 204; } message TpuFunction { // The function(s) that should be placed on the TPU. Only provide a given // function once. Duplicates will result in errors. For example, if // you provide a specific function using function_alias don't also provide the // same function via concrete_function_name or jit_compile_functions. oneof name { // The name of the function alias associated with the function that // should be placed on the TPU. Function aliases are created during model // export using the tf.saved_model.SaveOptions. // // This is a recommended way to specify which function should be placed // on the TPU. string function_alias = 1; // The name of the concrete function that should be placed on the TPU. This // is the name of the function as it found in the GraphDef and the // FunctionDefLibrary. // // This is NOT the recommended way to specify which function should be // placed on the TPU because concrete function names change every time a // model is exported. string concrete_function_name = 3; // The name of the signature to be placed on the TPU. The user must make // sure there is no TPU-incompatible op under the entire signature. string signature_name = 5; // When jit_compile_functions is set to True, all jit compiled functions // are placed on the TPU. // // To use this option, decorate the relevant function(s) with // @tf.function(jit_compile=True), before exporting. Then set this flag to // True. The converter will find all functions that were tagged with // jit_compile=True and place them on the TPU. // // When using this option, all other settings for the TpuFunction // will apply to all functions tagged with // jit_compile=True. // // This option will place all jit_compile=True functions on the TPU. // If only some jit_compile=True functions should be placed on the TPU, // use function_alias or concrete_function_name. bool jit_compile_functions = 4; } } message BatchOptions { // Number of scheduling threads for processing batches of work. Determines // the number of batches processed in parallel. This should be roughly in line // with the number of TPU cores available. int32 num_batch_threads = 1; // The maximum allowed batch size. int32 max_batch_size = 2; // Maximum number of microseconds to wait before outputting an incomplete // batch. int32 batch_timeout_micros = 3; // Optional list of allowed batch sizes. If left empty, // does nothing. Otherwise, supplies a list of batch sizes, causing the op // to pad batches up to one of those sizes. The entries must increase // monotonically, and the final entry must equal max_batch_size. repeated int32 allowed_batch_sizes = 4; // Maximum number of batches enqueued for processing before requests are // failed fast. int32 max_enqueued_batches = 5; // If set, disables large batch splitting which is an efficiency improvement // on batching to reduce padding inefficiency. bool disable_large_batch_splitting = 6; // Experimental features of batching. Everything inside is subject to change. message Experimental { // The component to be batched. // 1. Unset if it's for all TPU subgraphs. // 2. Set function_alias or concrete_function_name if it's for a function. // 3. Set signature_name if it's for a signature. oneof batch_component { // The function alias associated with the function. Function alias is // created during model export using the tf.saved_model.SaveOptions, and is // the recommended way to specify functions. string function_alias = 1; // The concreate name of the function. This is the name of the function as // it found in the GraphDef and the FunctionDefLibrary. This is NOT the // recommended way to specify functions, because concrete function names // change every time a model is exported. string concrete_function_name = 2; // The name of the signature. string signature_name = 3; } } Experimental experimental = 7; } message BFloat16OptimizationOptions { // Indicates where the BFloat16 optimization should be applied. enum Scope { // The scope currently defaults to TPU. DEFAULT = 0; // Apply the bfloat16 optimization to TPU computation. TPU = 1; // Apply the bfloat16 optimization to the entire model including CPU // computations. ALL = 2; } // This field indicates where the bfloat16 optimization should be applied. // // The scope defaults to TPU. Scope scope = 1; // If set, the normal safety checks are skipped. For example, if the model // already contains bfloat16 ops, the bfloat16 optimization will error because // pre-existing bfloat16 ops can cause issues with the optimization. By // setting this flag, the bfloat16 optimization will skip the check. // // This is an advanced feature and not recommended for almost all models. // // This flag is off by default. bool skip_safety_checks = 2; // Ops that should not be converted to bfloat16. // Inputs into these ops will be cast to float32, and outputs from these ops // will be cast back to bfloat16. repeated string filterlist = 3; } message XlaShardingOptions { // num_cores_per_replica for TPUReplicateMetadata. // // This is the number of cores you wish to split your model into using XLA // SPMD. int32 num_cores_per_replica = 1; // (optional) device_assignment for TPUReplicateMetadata. // // This is in a flattened [x, y, z, core] format (for // example, core 1 of the chip // located in 2,3,0 will be stored as [2,3,0,1]). // // If this is not specified, then the device assignments will utilize the same // topology as specified in the topology attribute. repeated int32 device_assignment = 2; // A serialized string of tensorflow.tpu.TopologyProto objects, used for // the topology attribute in TPUReplicateMetadata. // // You must specify the mesh_shape and device_coordinates attributes in // the topology object. // // This option is required for num_cores_per_replica > 1 cases due to // ambiguity of num_cores_per_replica, for example, // pf_1x2x1 with megacore and df_1x1 // both have num_cores_per_replica = 2, but topology is (1,2,1,1) for pf and // (1,1,1,2) for df. // - For pf_1x2x1, mesh shape and device_coordinates looks like: // mesh_shape = [1,2,1,1] // device_coordinates=flatten([0,0,0,0], [0,1,0,0]) // - For df_1x1, mesh shape and device_coordinates looks like: // mesh_shape = [1,1,1,2] // device_coordinates=flatten([0,0,0,0], [0,0,0,1]) // - For df_2x2, mesh shape and device_coordinates looks like: // mesh_shape = [2,2,1,2] // device_coordinates=flatten( // [0,0,0,0],[0,0,0,1],[0,1,0,0],[0,1,0,1] // [1,0,0,0],[1,0,0,1],[1,1,0,0],[1,1,0,1]) bytes topology = 3; }
Présentation détaillée du traitement par lot
Le traitement par lot permet d'améliorer le débit et l'utilisation du TPU. Elle permet
plusieurs demandes à traiter en même temps. Pendant l'entraînement, le traitement par lot
peut être effectué à l'aide de tf.data
. Lors de l'inférence, elle se fait généralement en ajoutant
dans le graphe qui regroupe les requêtes entrantes. L'opération attend qu'il en ait assez
ou un délai avant expiration est atteint avant qu'il ne génère un lot volumineux à partir du
des requêtes individuelles. Voir
Définition des options de traitement par lot
pour en savoir plus sur les différentes options de traitement par lot pouvant être réglées,
y compris les tailles de lot
et les délais avant expiration.
Par défaut, le convertisseur insère l'opération de traitement par lot directement avant le TPU des calculs. Elle encapsule la ou les fonctions TPU fournies par l'utilisateur et tout TPU préexistant. le calcul du modèle avec une ou plusieurs opérations de traitement par lot. Il est possible de remplacer le comportement par défaut en indiquant au convertisseur fonctions et/ou signatures doivent être regroupées.
L'exemple suivant montre comment ajouter le traitement par lot par défaut.
batch_options { num_batch_threads: 2 max_batch_size: 8 batch_timeout_micros: 5000 allowed_batch_sizes: 2 allowed_batch_sizes: 4 allowed_batch_sizes: 8 max_enqueued_batches: 10 }
Signature par lot
Le traitement par lot des signatures regroupe l'ensemble du modèle en partant des entrées de la signature et accéder aux sorties de la signature. Contrairement au traitement par lot par défaut du convertisseur, de signature, le traitement par lot de la signature par lots à la fois le calcul TPU et le CPU des calculs. Vous bénéficiez ainsi d'un gain de performances de 10% à 20% pendant l'inférence sur des modèles de ML.
Comme pour tout traitement par lot, le traitement par lot de signatures
exigences strictes concernant la forme.
Pour garantir que ces exigences sont respectées, les entrées de signature doivent comporter
les formes qui ont au moins deux dimensions. La première dimension est la taille de lot
avec une taille de -1. Par exemple, (-1, 4)
, (-1)
ou (-1,
128, 4, 10)
sont des formes de saisie valides. Si ce n'est pas possible, envisagez d'utiliser
le comportement de traitement par lot
traitement par lot des fonctions.
Pour utiliser le traitement par lot des signatures, indiquez le ou les noms de signature en tant que signature_name
(s)
à l'aide de BatchOptions
.
batch_options { num_batch_threads: 2 max_batch_size: 8 batch_timeout_micros: 5000 allowed_batch_sizes: 2 allowed_batch_sizes: 4 allowed_batch_sizes: 8 max_enqueued_batches: 10 experimental { signature_name: "serving_default" } }
Traitement par lot des fonctions
Vous pouvez utiliser le traitement par lot des fonctions pour indiquer au convertisseur quelles fonctions doivent être par lot. Par défaut, le convertisseur regroupe toutes les fonctions TPU. Fonction le traitement par lot ignore ce comportement par défaut.
Le traitement par lot des fonctions peut être utilisé pour traiter des calculs processeur par lot. De nombreux modèles présentent d'amélioration des performances lorsque les calculs de CPU sont regroupés. Le meilleur moyen de le calcul par lot du processeur utilise le traitement par lot des signatures, mais il peut ne pas fonctionner pour certains des modèles de ML. Dans ce cas, le traitement par lot des fonctions peut être utilisé pour traiter par lot une partie du processeur en plus du calcul TPU. Notez que l'opération de traitement par lot ne peut pas s'exécuter sur le TPU, de sorte que toute fonction de traitement par lot fournie doit être appelée sur le CPU
Le traitement par lot des fonctions peut également être utilisé pour satisfaire exigences strictes concernant la forme imposées par l'opération de traitement par lot. Dans les cas où les fonctions TPU ne répondent pas aux exigences les exigences de forme de l'opération de traitement par lot, le traitement par lot des fonctions peut être utilisé pour indiquer Convertisseur en lots de différentes fonctions.
Pour l'utiliser, générez un function_alias
pour la fonction qui doit être
par lot. Pour ce faire, recherchez ou créez une fonction dans votre modèle
qui englobe tous les éléments
à regrouper par lot. Assurez-vous que cette fonction respecte les
exigences strictes concernant la forme
imposées par l'opération de traitement par lot. Si ce n'est pas déjà fait, ajoutez @tf.function
.
Il est important de fournir le input_signature
à @tf.function
. Le 0
doit être None
, car il s'agit de la dimension de lot. Il ne peut donc pas s'agir
de taille fixe. Par exemple, [None, 4]
, [None]
ou [None, 128, 4, 10]
sont tous
des formes de saisie valides. Lors de l'enregistrement du modèle, fournissez des SaveOptions
semblables à ceux indiqués
ci-dessous pour donner un alias "batch_func
" à model.batch_func
. Vous pouvez ensuite transmettre ce
vers le convertisseur.
class ToyModel(tf.keras.Model): @tf.function(input_signature=[tf.TensorSpec(shape=[None, 10], dtype=tf.float32)]) def batch_func(self, x): return x * 1.0 ... model = ToyModel() save_options = tf.saved_model.SaveOptions(function_aliases={ 'batch_func': model.batch_func, }) tf.saved_model.save(model, model_dir, options=save_options)
Ensuite, transmettez les function_alias
à l'aide des BatchOptions.
batch_options { num_batch_threads: 2 max_batch_size: 8 batch_timeout_micros: 5000 allowed_batch_sizes: 2 allowed_batch_sizes: 4 allowed_batch_sizes: 8 max_enqueued_batches: 10 experimental { function_alias: "batch_func" } }
Définition des options de traitement par lot
num_batch_threads
: (entier) nombre de threads de planification pour traiter des lots de travail. Détermine le nombre de lots traités dans en parallèle. Cela devrait correspondre à peu près au nombre de cœurs de TPU disponibles.max_batch_size
: (entier) taille de lot maximale autorisée. Peut être plus grande queallowed_batch_sizes
pour effectuer un fractionnement par lot volumineux.batch_timeout_micros
: (entier) nombre maximal de microsecondes d'attente avant de générer un lot incomplet.allowed_batch_sizes
: (liste d'entiers) : si la liste n'est pas vide, pour remplir les lots jusqu'à la taille la plus proche dans la liste. Cette liste doit être augmentant de manière monotone et l'élément final doit être inférieur ou égal àmax_batch_size
max_enqueued_batches
: (entier) nombre maximal de lots mis en file d'attente pour avant qu'elles n'échouent rapidement.
Mettre à jour les options de traitement par lot existantes
Vous pouvez ajouter ou mettre à jour des options de traitement par lot en exécutant l'image Docker en spécifiant
batch_options et en définissant disable_default_optimizations
sur "true" à l'aide de
--converter_options_string
. Les options de traitement par lot s'appliquent
Fonction TPU ou opération par lot préexistante.
batch_options { num_batch_threads: 2 max_batch_size: 8 batch_timeout_micros: 5000 allowed_batch_sizes: 2 allowed_batch_sizes: 4 allowed_batch_sizes: 8 max_enqueued_batches: 10 } disable_default_optimizations=True
Exigences concernant la forme de traitement par lot
Les lots sont créés en concaténant les Tensors d'entrée des requêtes le long de leur lot (0e) dimension. Les Tensors de sortie sont divisés en fonction de leur dimension 0. Dans la commande pour effectuer ces opérations, l'opération de traitement par lot a des exigences strictes de forme pour de ses entrées et sorties.
Tutoriel
Pour comprendre ces exigences, il est utile de comprendre d'abord
par lot. Dans l'exemple ci-dessous, nous effectuons le traitement par lot d'une
tf.matmul
op.
def my_func(A, B) return tf.matmul(A, B)
La première requête d'inférence produit les entrées A et B avec les formes (1, 3,
2)
et (1, 2, 4)
, respectivement. La deuxième requête d'inférence génère
les entrées A et B avec les formes (2, 3, 2)
et (2, 2, 4)
.
Le délai avant expiration du traitement par lot est atteint. Le modèle accepte une taille de lot de 3,
les requêtes d'inférence n° 1 et n° 2 sont regroupées sans aucun remplissage. La
les Tensors par lot sont formés en concaténant les requêtes n° 1 et n° 2 avec le lot (0)
. Puisque le A de la figure 1 a la forme suivante : (1, 3, 2)
et que le A de la figure 2 a la forme suivante :
(2, 3, 2)
, lorsqu'ils sont concaténés avec la dimension de lot (0),
la forme obtenue est (3, 3, 2)
.
Le tf.matmul
est exécuté et produit une sortie ayant la forme (3, 3,
4)
.
La sortie de la fonction tf.matmul
est mise en lots et doit donc être répartie
des requêtes distinctes. Pour ce faire, l'opération de traitement par lot s'effectue en divisant le lot (0)
de chaque Tensor de sortie. Il décide comment diviser la 0ème dimension en fonction
en fonction de la forme des entrées d'origine. Étant donné que les formes de la requête 1 ont un 0,
dimension de 1, sa sortie a une dimension 0 de 1 pour une forme de (1, 3, 4)
.
Étant donné que les formes de la requête #2 ont une dimension 0 de 2, sa sortie a une 0e
de 2 pour une forme de (2, 3, 4)
.
Exigences concernant les formes
Pour effectuer la concaténation des entrées et la division de la sortie décrites ci-dessus, la forme de l'opération de traitement par lot est la suivante:
Les entrées pour le traitement par lot ne peuvent pas être scalaires. Afin de concaténer 0e dimension, les Tensors doivent avoir au moins deux dimensions.
Dans le tutoriel ci-dessus. Ni A, ni B ne sont des scalaires.
Si vous ne respectez pas cette exigence, une erreur de ce type se produira:
Batching input tensors must have at least one dimension
. Une solution simple à cette erreur consiste à transformer le scalaire en vecteur.entre différentes requêtes d'inférence (par exemple, différentes exécutions de session) ; ), les Tensors d'entrée portant le même nom ont la même taille pour chacun à l'exception de la 0. Cela permet aux entrées d'être proprement concaténées le long de leur 0e dimension.
Dans le tutoriel ci-dessus, la forme A de la requête 1 n° 1 est
(1, 3, 2)
. Cela signifie que toute requête future doit générer une forme avec le modèle(X, 3, 2)
La demande 2 répond à cette exigence avec(2, 3, 2)
. De même, la forme B de la requête 1 est(1, 2, 4)
. Toutes les requêtes ultérieures doivent donc générez une forme avec le motif(X, 2, 4)
.Si vous ne respectez pas cette exigence, une erreur de ce type se produira:
Dimensions of inputs should match
.Pour une requête d'inférence donnée, toutes les entrées doivent avoir le même 0 de la dimension. Si différents Tensors d'entrée de l'opération de traitement par lot ont des dimensions 0 différentes, l'opération de traitement par lot ne sait pas comment diviser Tensors de sortie.
Dans le tutoriel ci-dessus, les Tensors de la requête n°1 ont tous une dimension 0. est de 1. Cela permet à l'opération de traitement par lot de savoir que sa sortie doit avoir un 0 de dimension égale à 1. De même, les Tensors de la requête 2 ont une dimension 0. taille de 2, de sorte que sa sortie aura une taille 0 de dimension de 2. Lorsque L'opération par lot divise la forme finale de
(3, 3, 4)
, elle génère(1, 3, 4)
. pour la requête n° 1 et(2, 3, 4)
pour la requête n° 2.Si vous ne respectez pas cette exigence, des erreurs de ce type se produiront:
Batching input tensors supplied in a given op invocation must have equal 0th-dimension size
.La taille de dimension 0 de la forme de chaque Tensor de sortie doit être la somme des tous les Tensors d'entrée 0e taille de dimension (plus toute marge intérieure introduite par l'opération de traitement par lot pour qu'elle rencontre l'
allowed_batch_size
suivant le plus grand). Cela permet l'opération de traitement par lot pour diviser les Tensors de sortie en fonction de leur 0e dimension sur la 0e dimension des Tensors d'entrée.Dans le tutoriel ci-dessus, les Tensors d'entrée ont une dimension 0 de 1. de la requête n°1 et n°2 de la requête n°2. Par conséquent, chaque Tensor de sortie doit ont une 0ème dimension de 3 car 1+2=3. Tensor de sortie
(3, 3, 4)
répond à cette exigence. Si 3 n'était pas une taille de lot valide, mais que 4 l'était, l'opération de traitement par lot aurait dû remplir la 0ème dimension des entrées de 3 à 4. Dans ce cas, chaque Tensor de sortie doit avoir une taille 0 de dimension de 4.Si vous ne respectez pas cette exigence, une erreur de ce type se produira:
Batched output tensor's 0th dimension does not equal the sum of the 0th dimension sizes of the input tensors
.
Résoudre les erreurs liées aux exigences de tracé
Pour répondre à ces exigences, vous pouvez fournir une autre fonction ou signature par lot. Il peut également être nécessaire de modifier les fonctions existantes pour répondre à ces exigences.
Si un
fonction
est en cours de traitement par lot, assurez-vous que les formes d'input_signature de sa @tf.function
comportent None
dans sa 0e dimension (également appelée dimension de lot) ; Si un
signature
est en cours de traitement par lot, assurez-vous que la valeur -1 de toutes ses entrées est égale à -1 dans la dimension 0.
L'opération BatchFunction n'accepte pas SparseTensors
comme entrées ou sorties.
En interne, chaque Tensor creux est représenté par trois Tensors distincts pouvant
avoir des 0èmes dimensions différentes.