À propos du traitement d'optimisation du graphe de calcul dans tf.function

Ceci est l'article du 22ème jour du Calendrier de l'Avent TensorFlow2.0 2019.

Dans cet article, lors de la construction d'un graphique de calcul TensorFlow à partir d'un programme Python avec tf.function, TensorFlow présentera le processus d'optimisation interne du graphique de calcul et comment contrôler l'optimisation.

Cet article explique ce qui est fait derrière tf.function, veuillez donc vous référer à d'autres articles pour savoir comment utiliser tf.function lui-même. TensorFlow2.0 Advent Calender 2019 a également publié des articles sur tf.function.

tf.function

La fonction décorée avec «@ tf.function» est convertie en un graphe de calcul TensorFlow. Par exemple, considérez le programme suivant.

import tensorflow as tf

@tf.function
def simple_func(arg):
    a = tf.constant(7.9)
    b = tf.constant(6.3)
    c = arg + a
    d = a * b
    ret = c + d
    
    return ret

arg = tf.constant(8.9)
print(simple_func(arg))

Le graphe de calcul créé par la fonction simple_func ressemble à ceci:

user_graph.png

Processus d'optimisation du graphe de calcul

Le graphe calculé converti par tf.function est optimisé à l'intérieur de TensorFlow (couche C ++). Ce processus d'optimisation est également utilisé en mode graphique, qui était le mode par défaut pour TensorFlow 1.x, et la technologie d'optimisation de graphique de calcul cultivée dans TensorFlow 1.x est également utilisée dans tf.function.

Il s'agit d'un ancien article pour TensorFlow 1.13, mais si vous êtes intéressé par le processus d'optimisation du graphe de calcul effectué dans TensorFlow, veuillez également vous reporter à l'article suivant. Dans TensorFlow 2.0, en plus des optimisations présentées dans l'article, de nouvelles optimisations telles que la précision mixte automatique ont été ajoutées. À un autre moment, j'aimerais vous présenter le dernier processus d'optimisation.

Maintenant, en revenant au graphe de calcul précédent, le graphe de calcul après optimisation du graphe de calcul généré par tf.function est le suivant.

optimized_graph.png

Si vous regardez le graphique de calcul généré, vous pouvez voir que certains nœuds du graphique de calcul ont été supprimés. L'optimisation effectuée ici s'appelle Constant Folding, où toutes les entrées de nœud sont des valeurs constantes. Evaluez lors de la construction du graphe de calcul et remplacez-le par le nœud Const. Au moment de la construction du graphe de calcul, le temps de traitement du graphe de calcul dans son ensemble peut être raccourci en évaluant les parties qui peuvent être évaluées à l'avance.

Vérifiez le graphique de calcul optimisé

Vous pouvez vérifier le graphique de calcul optimisé à l'aide de TensorBoard. Afin de générer des données de synthèse pour TensorBoard, vous devez appeler tf.summary.trace_on () avant d'appeler le graphe de calcul construit par tf.function. Notez que le graphe de calcul optimisé ne sera pas affiché à moins que «True» ne soit spécifié pour les arguments »graph» et «profiler» de «tf.summary.trace_on ()». Ensuite, vous pouvez sortir le graphe de calcul optimisé en appelant tf.summary.trace_export () après avoir exécuté le graphe de calcul que vous voulez vérifier. Le code source qui génère le graphique de calcul optimisé pour TensorBoard est indiqué ci-dessous.

import tensorflow as tf

@tf.function
def simple_func(arg):
    a = tf.constant(7.9)
    b = tf.constant(6.3)
    c = arg + a
    d = a * b
    ret = c + d
    
    return ret

#Activer la collecte de données récapitulatives pour TensorBoard
writer = tf.summary.create_file_writer("summary")
#Vous pouvez vérifier le graphique de calcul optimisé en spécifiant True pour le graphique d'arguments et le profileur.
tf.summary.trace_on(graph=True, profiler=True)

arg = tf.constant(8.9)
print(simple_func(arg))

#Produire les données récapitulatives collectées
with writer.as_default():
    tf.summary.trace_export("summary", step=0, profiler_outdir="summary")

#Désactiver la collecte des données de synthèse
tf.summary.trace_off()

Utilisez TensorBoard pour lire les données récapitulatives de TensorBoard qui ont été générées en exécutant le programme. Tout d'abord, examinons le graphique défini par l'utilisateur. Avec l'onglet GRAPHIQUES du TensorBoard sélectionné, vous pouvez afficher un graphique défini par l'utilisateur en sélectionnant Graphique dans la case d'option sur la gauche.

Le graphe défini par l'utilisateur est le graphe calculé lui-même défini dans simple_func.

user_defined_graph.png

Ensuite, vérifions le graphe de calcul après optimisation. Avec l'onglet "GRAPHIQUES" de TensorBoard sélectionné, vous pouvez afficher le graphique de calcul optimisé en sélectionnant "Profil" dans la case radio sur la gauche.

Dans le graphique de calcul optimisé, un nœud Mul et son nœud Constant d'entrée sont ombrés. Les nœuds ombrés sont des nœuds qui n'ont pas été calculés dans TensorFlow. En raison de l'optimisation du graphe de calcul dans TensorFlow, il est probable que ces nœuds ombrés ne soient plus calculés.

optimized_graph.png

De cette manière, en utilisant TensorBoard, vous pouvez vérifier la différence entre le graphique de calcul défini par l'utilisateur et le graphique de calcul optimisé.

Contrôler le processus d'optimisation du graphe de calcul

Le processus d'optimisation du graphe de calcul effectué dans TensorFlow peut être activé / désactivé en appelant tf.config.optimizer.set_experimental_options (). Notez que le seul processus d'optimisation qui peut être activé / désactivé par tf.config.optimizer.set_experimental_options () est Processus d'optimisation effectué par Grappler. est. Notez que l'optimisation avec GraphOptimizationPass et GraphOptimizer sera toujours effectuée. S'il vous plaît.

Avant d'expliquer le processus d'optimisation du graphe de calcul, qui est un programme qui bascule entre activer / désactiver, vérifions les paramètres d'optimisation par défaut. Vous pouvez vérifier les paramètres liés à l'optimisation du graphe de calcul en appelant tf.config.optimizer.get_experimental.options ().

import tensorflow as tf

tf.config.optimizer.get_experimental_options()

Si vous faites ce qui précède, vous obtiendrez les résultats suivants:

{'disable_meta_optimizer': False, 'disable_model_pruning': False}

disable_meta_optimizer est un paramètre qui désactive Optimiser le traitement effectué par Grappler, et False est spécifié par défaut. À partir de là, nous pouvons voir que le processus d'optimisation par Grappler est activé par défaut. De plus, étant donné que d'autres optimisations ne sont pas définies, chaque [Paramètre d'optimisation par défaut](https://qiita.com/nuka137/items/f1b0fe9c820e4d5f80cc#grappler%E3%81%AB%E3%82%88 % E3% 82% 8B% E6% 9C% 80% E9% 81% A9% E5% 8C% 96% E9% A0% 85% E7% 9B% AE) a été appliqué.

Maintenant, vérifions l'effet en donnant un exemple d'activation / désactivation de l'optimisation.

Désactiver l'optimisation "Debug Stripper"

Debug Stripper est un processus d'optimisation qui supprime les nœuds (tels que Assert) utilisé à des fins de débogage. Debug Stripper est désactivé par défaut, donc les nœuds Assert ajoutés par tf.Assert ne seront pas supprimés. En conséquence, le code ci-dessous lève une exception à tf.Assert.

import tensorflow as tf

@tf.function
def assert_func():
    a = tf.constant(1.2)
    computation_graph = tf.Assert(tf.less_equal(a, 1.0), [a])   #Une exception "InvalidArgumentError" se produit
    return a

print(assert_func())

D'un autre côté, si vous activez Debug Stripper et que vous l'exécutez, le nœud Assert ajouté par tf.Assert sera supprimé et l'exception levée ci-dessus ne se produira plus.

import tensorflow as tf

#Activer "Debug Stripper"
tf.config.optimizer.set_experimental_options({'debug_stripper': True})

@tf.function
def assert_func():
    a = tf.constant(1.2)
    computation_graph = tf.Assert(tf.less_equal(a, 1.0), [a])   #Pas exception
    return a

print(assert_func())

Les assertions ajoutées à des fins de débogage prennent du temps à traiter, comme la nécessité de vérifier les données du tenseur, ce qui affecte le temps d'exécution du graphe de calcul. Une fois le débogage terminé, il est bon de supprimer le tf.Assert etc. ajouté dans le but de déboguer un par un, mais simplement en activant le Debug Stripper par la méthode indiquée ici, le calcul à des fins de débogage Sera supprimé, alors pourquoi ne pas en profiter?

Désactive toutes les optimisations de graphes de calcul effectuées par Grappler

J'ai écrit que tout le traitement d'optimisation effectué par Grappler est désactivé en définissant disable_meta_optimizer sur True, mais ici, je voudrais voir l'effet.

Commençons par vérifier le graphique calculé après optimisation avec les paramètres d'optimisation par défaut. Dans le code source ci-dessous, Transpose crée un graphe de calcul continu.

import tensorflow as tf
import numpy as np

@tf.function
def optimized(arg):
    a = arg * 2

    #Supprimé par "Optimiseur arithmétique"
    b = tf.transpose(a, perm=[1, 0])
    ret = tf.transpose(b, perm=[1, 0])

    return ret

writer = tf.summary.create_file_writer("summary")
tf.summary.trace_on(graph=True, profiler=True)

arg = tf.constant(np.random.normal(size=(30, 40)))
optimized(arg)

with writer.as_default():
    tf.summary.trace_export("summary", step=0, profiler_outdir="summary")

tf.summary.trace_off()

Si vous vérifiez le graphique de calcul après l'optimisation avec TensorBoard, vous pouvez voir que le nœud Transposer a été supprimé. Cela est fait par RemoveIdentityTranspose de Arithmetic Optimizer C'est parce que nous avons supprimé les paires d'inversion qui s'annulent. Vous pouvez également voir que le nœud Identité, qui n'affecte pas l'opération, a été supprimé par l'optimisation.

meta_optimizer_enabled.png

Ensuite, exécutez le même graphique de calcul avec disable_meta_optimizer défini sur True, et vérifiez le graphique de calcul une fois qu'il a été optimisé par TensorBoard.

import tensorflow as tf
import numpy as np

#Désactive tous les traitements d'optimisation des graphiques de calcul effectués par Grappler
tf.config.optimizer.set_experimental_options({'disable_meta_optimizer': True})

@tf.function
def not_optimized(arg):
    a = arg * 2
    b = tf.transpose(a, perm=[1, 0])
    ret = tf.transpose(b, perm=[1, 0])

    return ret

writer = tf.summary.create_file_writer("summary")
tf.summary.trace_on(graph=True, profiler=True)

arg = tf.constant(np.random.normal(size=(30, 40)))
not_optimized(arg)

with writer.as_default():
    tf.summary.trace_export("summary", step=0, profiler_outdir="summary")

tf.summary.trace_off()

Si vous regardez le graphique calculé après son optimisation, vous pouvez voir que le nœud Transposer reste intact et que l'optimiseur arithmétique est désactivé. Notez également que le nœud d'identité n'a pas été supprimé.

meta_optimizer_disabled.png

Résumé

Nous avons présenté comment le graphe de calcul construit par tf.function est optimisé dans TensorFlow et expliqué comment contrôler cette optimisation. Il est prévu que la fonction d'optimisation du graphe de calcul continuera à être développée, comme l'ajout de nouvelles optimisations même récemment. Attendons-le avec impatience dans le futur.

Le contenu présenté dans cet article a été fusionné avec succès l'autre jour avec la Pull Request publiée en version anglaise du document. Je pense qu'il sera officiellement publié en tant que document TensorFlow, j'espère donc que vous pourrez constater que l'optimisation du graphique de calcul fonctionne réellement sur Google Colab. Je pense que le document fusionné cette fois sera Je veux le traduire en japonais et le contribuer au document TensorFlow, alors attendez-le avec impatience.

Recommended Posts

À propos du traitement d'optimisation du graphe de calcul dans tf.function
À propos de l'optimisation du continent vanille