web-dev-qa-db-fra.com

Serveur Tensorflow: je ne souhaite pas initialiser de variables globales pour chaque session

EDIT2: Le lien Github ci-dessous contient des solutions possibles au problème d’appel du modèle TF à partir du processus. Ils comprennent une exécution rapide et un processus de serveur dédié, servant les prédictions du modèle TF via des requêtes http. Je me demande si, avec le serveur personnalisé et les requêtes demandées, je gagne à tout moment par rapport à l'initialisation des variables globales à chaque fois et à l'appel de tf.train.Server, mais cela semble être plus élégant.

Je vais enquêter sur une fuite de mémoire, et si elle est partie, fermez cette question. 

EDIT: Ajout d'un exemple simple reproductible du problème:

https://github.com/hcl14/Tensorflow-server-launched-from-child-process


Background: J'exécute le serveur Tensorflow et me connecte à celui-ci à partir de processus 'forkés'. Créer (et détruire) des processus de manière dynamique est essentiel pour moi - j'y ai déplacé une partie de code très chargée à cause d'un étrange fuite de mémoire , non visible par les profileurs Python (les threads ne résolvent pas le problème). Par conséquent, je veux que les processus soient initialisés rapidement et commencent immédiatement à fonctionner. La mémoire n'est libérée que lorsque le processus est détruit.

En faisant des expériences, j'ai trouvé une solution lorsque le modèle et le graphique chargés sont enregistrés dans une variable globale, puis pris par un processus enfant (qui utilise le mode "fork" par défaut), puis le serveur est appelé.

Problème: Ce qui est étrange pour moi, c'est qu'après le chargement de modèles keras, je ne peux pas verrouiller le graphe que je ne m'attends pas à modifier, et je dois exécuter tf.global_variables_initializer() chaque fois que j'ouvre une nouvelle session dans un processus enfant. Cependant, l'exécution factice dans le flux principal sans aucune création de session fonctionne correctement. Je sais que dans ce cas, tensorflow utilise une session par défaut, mais toutes les variables d'un graphique doivent être initialisées après l'exécution du modèle. Je m'attendais donc à ce que la nouvelle session fonctionne Ok avec le graphique défini précédemment.

Ainsi, je pense que la modification du modèle amène Python à utiliser beaucoup le processus enfant (mode "fork"), ce qui crée une surcharge de calcul et de mémoire.

S'il vous plaît, excusez-moi pour beaucoup de code. Le modèle que j'utilise est l'héritage et la boîte noire pour moi, il est donc possible que mon problème y soit lié. La version de Tensorflow est 1.2 (je ne peux pas la mettre à niveau, le modèle n'est pas compatible), Python 3.6.5.

Aussi, peut-être que ma solution est inefficace et qu'il y en a une meilleure, je vous serais reconnaissant de vos conseils.

Ma configuration est la suivante:

1. Le serveur Tensorflow a démarré dans le processus principal:

Initialiser le serveur:

def start_tf_server():
    import tensorflow as tf
    cluster = tf.train.ClusterSpec({"local": [tf_server_address]})
    server = tf.train.Server(cluster, job_name="local", task_index=0)    
    server.join() # block process from exiting

Dans le processus principal:

p = multiprocessing.Process(target=start_tf_server)
p.daemon=True
p.start() # this process never ends, unless tf server crashes

# WARNING! Graph initialization must be made only after Tf server start!
# Otherwise everything will hang
# I suppose this is because of another session will be 
# created before the server one

# init model graph before branching processes
# share graph in the current process scope
interests = init_interests_for_process()
global_vars.multiprocess_globals["interests"] = interests

2 .init_interests_for_process() est un initialiseur de modèle, qui charge mon modèle existant et le partage dans la variable globale. Je fais une passe factice pour que tout soit initialisé sur le graphique, puis que je souhaite verrouiller le graphique. Mais ça ne fonctionne pas:

def init_interests_for_process():
    # Prevent errors on my GPU and disable tensorflow 
    # complaining about CPU instructions
    import os
    os.environ["CUDA_VISIBLE_DEVICES"]= ""
    os.environ['TF_CPP_MIN_LOG_LEVEL'] = '2'

    import tensorflow as tf

    from tensorflow.contrib.keras import models

    # create tensorflow graph
    graph = tf.get_default_graph()

    with graph.as_default():

        TOKENIZER = joblib.load(TOKENIZER_FILE)

        NN1_MODEL = models.load_model(NN1_MODEL_FILE)

        with open(NN1_CATEGORY_NAMES_FILE, 'r') as f:
            NN1_CATEGORY_NAMES = f.read().splitlines()

        NN2_MODEL = models.load_model(NN2_MODEL_FILE)

        with open(NN2_CATEGORY_NAMES_FILE, 'r') as f:
            NN2_CATEGORY_NAMES = f.read().splitlines()
        # global variable with all the data to be shared
        interests = {}

        interests["TOKENIZER"] = TOKENIZER
        interests["NN1_MODEL"] = NN1_MODEL
        interests["NN1_CATEGORY_NAMES"] = NN1_CATEGORY_NAMES
        interests["NN2_MODEL"] = NN2_MODEL
        interests["NN2_CATEGORY_NAMES"] = NN2_CATEGORY_NAMES
        interests['all_category_names'] = NN1_CATEGORY_NAMES + \
                                          NN2_CATEGORY_NAMES
        # Reconstruct a Python object from a file persisted with joblib.dump.
        interests["INTEREST_SETTINGS"] = joblib.load(INTEREST_SETTINGS_FILE)

        # dummy run to create graph
        x = tf.contrib.keras.preprocessing.sequence.pad_sequences(
                         TOKENIZER.texts_to_sequences("Dummy srting"),
                         maxlen=interests["INTEREST_SETTINGS"]["INPUT_LENGTH"]
                         )
        y1 = NN1_MODEL.predict(x)
        y2 = NN2_MODEL.predict(x)

        # PROBLEM: I want, but cannot lock graph, as child process 
        # wants to run its own tf.global_variables_initializer()
        # graph.finalize()

        interests["GRAPH"] = graph

        return interests

3.Maintenant, je lance le processus (en fait, le processus provient d'un autre processus - la hiérarchie est compliquée):

def foo(q):
     result = call_function_which_uses_interests_model(some_data) 
     q.put(result)
     return # I've read it is essential for destroying local variables
q = Queue()
p = Process(target=foo,args=(q,))
p.start()
p.join()
result = q.get() # retrieve data

4.Et dans ce processus, j'appelle le modèle:

# retrieve model from global variable
interests = global_vars.multiprocess_globals["interests"]

tokenizer = interests["TOKENIZER"]
nn1_model = interests["NN1_MODEL"]
nn1_category_names = interests["NN1_CATEGORY_NAMES"]
nn2_model = interests["NN2_MODEL"]
nn2_category_names = interests["NN2_CATEGORY_NAMES"]
input_length = interests["INTEREST_SETTINGS"]["INPUT_LENGTH"]

# retrieve graph
graph = interests["GRAPH"]

# open session for server
logger.debug('Trying tf server at ' + 'grpc://'+tf_server_address)
sess = tf.Session('grpc://'+tf_server_address, graph=graph)

# PROBLEM: and I need to run variables initializer:
sess.run(tf.global_variables_initializer())


tf.contrib.keras.backend.set_session(sess)

# finally, make a call to server:
with sess.as_default():        
    x = tf.contrib.keras.preprocessing.sequence.pad_sequences(
                            tokenizer.texts_to_sequences(input_str),
                            maxlen=input_length)
    y1 = nn1_model.predict(x)
    y2 = nn2_model.predict(x)

Tout fonctionne Ok si je ne verrouille pas le graphique et n’exécute pas d’initialisation variable à chaque nouveau processus. (Sauf qu'il existe une fuite de mémoire d'environ 30 à 90 Mo pour chaque appel, non visible pour les profileurs de mémoire python). Lorsque je souhaite verrouiller le graphique, des erreurs surviennent au sujet des variables non initialisées:

FailedPreconditionError (see above for traceback): 
Attempting to use uninitialized value gru_1/bias
       [[Node: gru_1/bias/read = Identity[T=DT_FLOAT, _class=["loc:@gru_1/bias"],
       _device="/job:local/replica:0/task:0/cpu:0"](gru_1/bias)]]

Merci d'avance!

15
Slowpoke

Avez-vous envisagé de servir TensorFlow? https://www.tensorflow.org/serving/

Généralement, vous voudriez mettre en cache les sessions, ce qui, je crois, est la stratégie utilisée par TF Serving. Ce sera de loin la meilleure expérience pour déployer un modèle TF dans un centre de données.

Vous pouvez également aller dans l’autre direction et tf.enable_eager_execution() , ce qui élimine le besoin de sessions. Les variables sont toujours initialisées, bien que cela se produise dès que les objets variables Python sont créés.

Mais si vous voulez vraiment créer et détruire des sessions, vous pouvez remplacer les variables du graphique par des constantes ( "geler" le ). Dans ce cas, je souhaiterais également désactiver les optimisations graphiques, car le premier appel session.run avec un nouvel ensemble de flux et d'extractions passera par défaut à optimiser le graphique (configuré via une variable RewriterConfig dans un proto GraphOptions).

(Développé à partir d'un commentaire sur la question)

1
Allen Lavoie

Je ne suis pas sûr que cela puisse vous aider, mais il faut savoir que dans tensorflow, les variables ne sont initialisées que pour une Session donnée. Une variable doit être initialisée dans chaque Session elle est utilisée - même dans le scénario le plus simple possible:

import tensorflow as tf

x = tf.Variable(0.)

with tf.Session() as sess:
    tf.global_variables_initializer().run()
    # x is initialized -- no issue here
    x.eval()

with tf.Session() as sess:
    x.eval()
    # Error -- x was never initialized in this session, even though
    # it has been initialized before in another session
0
P-Gn