web-dev-qa-db-fra.com

Comment former plusieurs modèles dans un même script dans TensorFlow lorsqu'il y a des GPU?

Disons que j'ai accès à un certain nombre de GPU sur une seule machine (pour les besoins de l'hypothèse, supposons que 8 GPU chacun avec une mémoire maximale de 8 Go chacun dans une seule machine avec une certaine quantité de RAM et disque). Je voulais exécuter dans n seul script et dans une seule machine un programme qui évalue plusieurs modèles (disons 50 ou 200) dans TensorFlow, chacun avec un paramétrage hyper différent (par exemple, taille de pas, décroissance taux, la taille des lots, les époques/itérations, etc.) À la fin de la formation, supposons que nous enregistrons juste sa précision et nous débarrassons du modèle (si vous voulez supposer que le modèle est pointé de temps en temps, il est donc bien de simplement lancer Éloignez le modèle et commencez l'entraînement à partir de zéro. Vous pouvez également supposer que d'autres données peuvent être enregistrées comme les paramètres hyper spécifiques, le train, la validation, les erreurs de train sont enregistrées pendant que nous nous entraînons, etc.).

Actuellement, j'ai un (pseudo-) script qui ressemble à ceci:

def train_multiple_modles_in_one_script_with_gpu(arg):
    '''
    trains multiple NN models in one session using GPUs correctly.

    arg = some obj/struct with the params for trianing each of the models.
    '''
    #### try mutliple models
    for mdl_id in range(100):
        #### define/create graph
        graph = tf.Graph()
        with graph.as_default():
            ### get mdl
            x = tf.placeholder(float_type, get_x_shape(arg), name='x-input')
            y_ = tf.placeholder(float_type, get_y_shape(arg))
            y = get_mdl(arg,x)
            ### get loss and accuracy
            loss, accuracy = get_accuracy_loss(arg,x,y,y_)
            ### get optimizer variables
            opt = get_optimizer(arg)
            train_step = opt.minimize(loss, global_step=global_step)
        #### run session
        with tf.Session(graph=graph) as sess:
            # train
            for i in range(nb_iterations):
                batch_xs, batch_ys = get_batch_feed(X_train, Y_train, batch_size)
                sess.run(fetches=train_step, feed_dict={x: batch_xs, y_: batch_ys})
                # check_point mdl
                if i % report_error_freq == 0:
                    sess.run(step.assign(i))
                    #
                    train_error = sess.run(fetches=loss, feed_dict={x: X_train, y_: Y_train})
                    test_error = sess.run(fetches=loss, feed_dict={x: X_test, y_: Y_test})
                    print( 'step %d, train error: %s test_error %s'%(i,train_error,test_error) )

essentiellement, il essaie de nombreux modèles en une seule exécution, mais il construit chaque modèle dans un graphique distinct et exécute chacun dans une session distincte.

Je suppose que ma principale préoccupation est que je ne sais pas comment tensorflow sous le capot alloue des ressources pour les GPU à utiliser. Par exemple, charge-t-il (une partie de) l'ensemble de données uniquement lorsqu'une session est exécutée? Lorsque je crée un graphique et un modèle, est-il introduit immédiatement dans le GPU ou quand est-il inséré dans le GPU? Dois-je effacer/libérer le GPU chaque fois qu'il essaie un nouveau modèle? En fait, je ne m'inquiète pas trop si les modèles sont exécutés en parallèle dans plusieurs GPU (ce qui peut être un ajout intéressant), mais je veux d'abord qu'il exécute tout en série sans se bloquer. Y a-t-il quelque chose de spécial que je dois faire pour que cela fonctionne?


Actuellement, je reçois une erreur qui commence comme suit:

I tensorflow/core/common_runtime/bfc_allocator.cc:702] Stats:
Limit:                   340000768
InUse:                   336114944
MaxInUse:                339954944
NumAllocs:                      78
MaxAllocSize:            335665152

W tensorflow/core/common_runtime/bfc_allocator.cc:274] ***************************************************xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx
W tensorflow/core/common_runtime/bfc_allocator.cc:275] Ran out of memory trying to allocate 160.22MiB.  See logs for memory state.
W tensorflow/core/framework/op_kernel.cc:975] Resource exhausted: OOM when allocating tensor with shape[60000,700]

et plus loin sur la ligne, il est dit:

ResourceExhaustedError (see above for traceback): OOM when allocating tensor with shape[60000,700]
         [[Node: standardNN/NNLayer1/Z1/add = Add[T=DT_FLOAT, _device="/job:localhost/replica:0/task:0/gpu:0"](standardNN/NNLayer1/Z1/MatMul, b1/read)]]

I tensorflow/core/common_runtime/gpu/gpu_device.cc:975] Creating TensorFlow device (/gpu:0) -> (device: 0, name: Tesla P100-SXM2-16GB, pci bus id: 0000:06:00.0)

cependant, plus bas dans le fichier de sortie (où il s'imprime), il semble bien imprimer les erreurs/messages qui devraient s'afficher au fur et à mesure de la formation. Est-ce à dire qu'il n'a pas manqué de ressources? Ou était-il réellement capable d'utiliser le GPU? S'il a pu utiliser le CPU au lieu du CPU, alors pourquoi est-ce une erreur qui ne se produit que lorsque le GPU est sur le point d'être utilisé?

La chose étrange est que l'ensemble de données n'est vraiment pas si grand (tous les 60K points sont 24,5M) et quand j'exécute un seul modèle localement sur mon propre ordinateur, il semble que le processus utilise moins de 5 Go. Les GPU ont au moins 8 Go et l'ordinateur avec eux a beaucoup de RAM et disque (au moins 16 Go). Ainsi, les erreurs que tensorflow me lance sont assez déroutantes. Qu'est-ce qu'il essaie à faire et pourquoi se produisent-ils? Des idées?


Après avoir lu la réponse qui suggère d'utiliser la bibliothèque multitraitement, j'ai trouvé le script suivant:

def train_mdl(args):
    train(mdl,args)

if __name__ == '__main__':
    for mdl_id in range(100):
        # train one model with some specific hyperparms (assume they are chosen randomly inside the funciton bellow or read from a config file or they could just be passed or something)
        p = Process(target=train_mdl, args=(args,))
        p.start()
        p.join()
    print('Done training all models!')

honnêtement, je ne sais pas pourquoi sa réponse suggère d'utiliser le pool, ou pourquoi il y a des supports Tuple étranges, mais c'est ce qui aurait du sens pour moi. Les ressources pour tensorflow seraient-elles réallouées chaque fois qu'un nouveau processus est créé dans la boucle ci-dessus?

21
Charlie Parker

Je pense que l'exécution de tous les modèles dans un seul script peut être une mauvaise pratique à long terme (voir ma suggestion ci-dessous pour une meilleure alternative). Cependant, si vous souhaitez le faire, voici une solution: vous pouvez encapsuler votre session TF dans un processus avec le module multiprocessing, cela garantira que TF libère la mémoire de la session une fois le processus terminé. Voici un extrait de code:

from multiprocessing import Pool
import contextlib
def my_model((param1, param2, param3)): # Note the extra (), required by the pool syntax
    < your code >

num_pool_worker=1 # can be bigger than 1, to enable parallel execution 
with contextlib.closing(Pool(num_pool_workers)) as po: # This ensures that the processes get closed once they are done
     pool_results = po.map_async(my_model,
                                    ((param1, param2, param3)
                                     for param1, param2, param3 in params_list))
     results_list = pool_results.get()

Remarque de l'OP: la valeur de départ du générateur de nombres aléatoires ne se réinitialise pas automatiquement avec la bibliothèque de multi-traitement si vous choisissez de l'utiliser. Détails ici: tilisation de python multiprocessing avec une graine aléatoire différente pour chaque processus

À propos de l'allocation des ressources TF: TF alloue généralement beaucoup plus de ressources qu'il n'en a besoin. Plusieurs fois, vous pouvez restreindre chaque processus à utiliser une fraction de la mémoire GPU totale et découvrir par essais et erreurs la fraction dont votre script a besoin.

Vous pouvez le faire avec l'extrait de code suivant

gpu_memory_fraction = 0.3 # Choose this number through trial and error
gpu_options = tf.GPUOptions(per_process_gpu_memory_fraction=gpu_memory_fraction,)
session_config = tf.ConfigProto(gpu_options=gpu_options)
sess = tf.Session(config=session_config, graph=graph)

Notez que parfois TF augmente l'utilisation de la mémoire afin d'accélérer l'exécution. Par conséquent, la réduction de l'utilisation de la mémoire peut ralentir l'exécution de votre modèle.

Réponses aux nouvelles questions dans votre édition/commentaires:

  1. Oui, Tensorflow sera réalloué à chaque fois qu'un nouveau processus est créé et effacé à la fin d'un processus.

  2. La boucle for de votre montage devrait également faire l'affaire. Je suggère d'utiliser plutôt Pool, car cela vous permettra d'exécuter plusieurs modèles simultanément sur un seul GPU. Voir mes notes sur la configuration de gpu_memory_fraction Et "le choix du nombre maximal de processus". Notez également que: (1) La carte Pool exécute la boucle pour vous, vous n'avez donc pas besoin d'une boucle for externe une fois que vous l'utilisez. (2) Dans votre exemple, vous devriez avoir quelque chose comme mdl=get_model(args) avant d'appeler train ()

  3. Parenthèse de tuple bizarre: le pool n'accepte qu'un seul argument, nous utilisons donc un tuple pour passer plusieurs arguments. Voir multiprocessing.pool.map et fonction avec deux arguments pour plus de détails. Comme suggéré dans une réponse, vous pouvez le rendre plus lisible avec

    def train_mdl(params):
        (x,y)=params
        < your code >
    
  4. Comme l'a suggéré @Seven, vous pouvez utiliser la variable d'environnement CUDA_VISIBLE_DEVICES pour choisir le GPU à utiliser pour votre processus. Vous pouvez le faire à partir de votre script python en utilisant ce qui suit au début de la fonction de processus (train_mdl).

    import os # the import can be on the top of the python script
    os.environ["CUDA_VISIBLE_DEVICES"] = "{}".format(gpu_id)
    

Une meilleure pratique pour exécuter vos expériences serait d'isoler votre code de formation/évaluation du code de recherche hyper paramètres/modèle. Par exemple. avoir un script nommé train.py, qui accepte une combinaison spécifique d'hyper paramètres et de références à vos données comme arguments, et exécute la formation pour un seul modèle.

Ensuite, pour parcourir toutes les combinaisons de paramètres possibles, vous pouvez utiliser une file d'attente de tâches (travaux) simple et soumettre toutes les combinaisons possibles d'hyper-paramètres en tant que travaux séparés. La file d'attente des tâches alimentera vos tâches une par une sur votre machine. Habituellement, vous pouvez également définir la file d'attente pour exécuter simultanément plusieurs processus (voir les détails ci-dessous).

Plus précisément, j'utilise spouleur de tâches , qui est super facile à installer et à manipuler (ne nécessite pas de privilèges d'administrateur, détails ci-dessous).

L'utilisation de base est (voir les notes ci-dessous sur l'utilisation du spouleur de tâches):

ts <your-command>

Dans la pratique, j'ai un script python séparé qui gère mes expériences, définit tous les arguments par expérience spécifique et envoie les travaux à la file d'attente ts.

Voici quelques extraits pertinents du code python de mon gestionnaire d'expériences:

run_bash Exécute une commande bash

def run_bash(cmd):
    p = subprocess.Popen(cmd, Shell=True, stdout=subprocess.PIPE, executable='/bin/bash')
    out = p.stdout.read().strip()
    return out  # This is the stdout from the Shell command

L'extrait suivant définit le nombre de processus simultanés à exécuter (voir la remarque ci-dessous sur le choix du nombre maximal de processus):

max_job_num_per_gpu = 2
run_bash('ts -S %d'%max_job_num_per_gpu)

L'extrait suivant parcourt une liste de toutes les combinaisons d'hyper params/paramètres de modèle. Chaque élément de la liste est un dictionnaire, où les clés sont les arguments de ligne de commande pour le script train.py

for combination_dict in combinations_list:

    job_cmd = 'python train.py ' + '  '.join(
            ['--{}={}'.format(flag, value) for flag, value in combination_dict.iteritems()])

    submit_cmd = "ts bash -c '%s'" % job_cmd
    run_bash(submit_cmd)

Une note sur le choix du nombre maximal de processus:

Si vous manquez de GPU, vous pouvez utiliser gpu_memory_fraction Que vous avez trouvé, pour définir le nombre de processus comme max_job_num_per_gpu=int(1/gpu_memory_fraction)

Remarques sur le spouleur de tâches (ts):

  1. Vous pouvez définir le nombre de processus simultanés à exécuter ("emplacements") avec:

    ts -S <number-of-slots>

  2. L'installation de ts ne nécessite pas de privilèges d'administrateur. Vous pouvez le télécharger et le compiler depuis la source avec un simple make, l'ajouter à votre chemin et vous avez terminé.

  3. Vous pouvez configurer plusieurs files d'attente (je l'utilise pour plusieurs GPU), avec

    TS_SOCKET=<path_to_queue_name> ts <your-command>

    par exemple.

    TS_SOCKET=/tmp/socket-ts.gpu_queue_1 ts <your-command>

    TS_SOCKET=/tmp/socket-ts.gpu_queue_2 ts <your-command>

  4. Voir ici pour un exemple d'utilisation supplémentaire

Une note sur la définition automatique des noms de chemin et des noms de fichiers: Une fois que vous aurez séparé votre code principal du gestionnaire d'expériences, vous aurez besoin d'un moyen efficace pour générer un fichier noms et noms de répertoires, compte tenu des hyper-paramètres. Je garde généralement mes paramètres hyper importants dans un dictionnaire et j'utilise la fonction suivante pour générer une seule chaîne chaînée à partir des paires clé-valeur du dictionnaire. Voici les fonctions que j'utilise pour le faire:

def build_string_from_dict(d, sep='%'):
    """
     Builds a string from a dictionary.
     Mainly used for formatting hyper-params to file names.
     Key-value pairs are sorted by the key name.

    Args:
        d: dictionary

    Returns: string
    :param d: input dictionary
    :param sep: key-value separator

    """

    return sep.join(['{}={}'.format(k, _value2str(d[k])) for k in sorted(d.keys())])


def _value2str(val):
    if isinstance(val, float): 
        # %g means: "Floating point format.
        # Uses lowercase exponential format if exponent is less than -4 or not less than precision,
        # decimal format otherwise."
        val = '%g' % val
    else:
        val = '{}'.format(val)
    val = re.sub('\.', '_', val)
    return val
17
Yuval Atzmon

Si je comprends bien, premièrement tensorflow construit un graphe symbolique et déduit les dérivées en fonction de la règle de chaîne. Alloue ensuite de la mémoire à tous les tenseurs (nécessaires), y compris certaines entrées et sorties de couches pour plus d'efficacité. Lors de l'exécution d'une session, les données seront chargées dans le graphique mais en général, l'utilisation de la mémoire ne changera plus.

L'erreur que vous avez rencontrée, je suppose, peut être causée par la construction de plusieurs modèles dans un même GPU.

Isoler votre code de formation/évaluation des paramètres hyper est un bon choix, comme l'a proposé @ user2476373. Mais j'utilise directement le script bash, pas le spouleur de tâches (c'est peut-être plus pratique), par exemple.

CUDA_VISIBLE_DEVICES=0 python train.py --lrn_rate 0.01 --weight_decay_rate 0.001 --momentum 0.9 --batch_size 8 --max_iter 60000 --snapshot 5000
CUDA_VISIBLE_DEVICES=0 python eval.py 

Ou vous pouvez écrire une boucle 'for' dans le script bash, pas nécessairement dans le script python. Notant que j'ai utilisé CUDA_VISIBLE_DEVICES=0 au début du script (l'index peut être 7 si vous avez 8 GPU sur une même machine). Parce que sur la base de mon expérience, j'ai trouvé que tensorflow utilise tous les GPU sur une seule machine si je ne spécifiais pas les opérations utiliser quel GPU avec le code comme celui-ci

with tf.device('/gpu:0'):

Si vous voulez essayer une implémentation multi-GPU, il y en a exemple .

J'espère que cela pourrait vous aider.

2
LI Xuhong

Vous ne voulez probablement pas faire ça.

Si vous exécutez des milliers et des milliers de modèles sur vos données et choisissez celui qui évalue le mieux, vous ne faites pas d'apprentissage automatique; au lieu de cela, vous mémorisez votre ensemble de données et il n'y a aucune garantie que le modèle que vous choisissez fonctionnera en dehors de cet ensemble de données.

En d'autres termes, cette approche est similaire à un modèle unique, qui a des milliers de degrés de liberté. Avoir un modèle avec un tel degré de complexité est problématique, car il pourra mieux ajuster vos données que ce qui est réellement garanti; un tel modèle est énormément capable de mémoriser tout bruit (valeurs aberrantes, erreurs de mesure, etc.) dans vos données d'entraînement, ce qui fait que le modèle fonctionne mal lorsque le bruit est même légèrement différent.

(Toutes mes excuses pour avoir posté cela comme réponse, le site ne m'a pas laissé ajouter de commentaire.)

0
Bass

Une solution simple: donnez à chaque modèle une session et un graphique uniques.

Cela fonctionne pour cette plate-forme: TensorFlow 1.12.0, Keras 2.1.6-tf, Python 3.6.7, Jupyter Notebook.

Code clé:

with session.as_default():
    with session.graph.as_default():
        # do something about an ANN model

Code complet:

import tensorflow as tf
from tensorflow import keras
import gc

def limit_memory():
    """ Release unused memory resources. Force garbage collection """
    keras.backend.clear_session()
    keras.backend.get_session().close()
    tf.reset_default_graph()
    gc.collect()
    #cfg = tf.ConfigProto()
    #cfg.gpu_options.allow_growth = True
    #keras.backend.set_session(tf.Session(config=cfg))
    keras.backend.set_session(tf.Session())
    gc.collect()


def create_and_train_ANN_model(hyper_parameter):
    print('create and train my ANN model')
    info = { 'result about this ANN model' }
    return info

for i in range(10):
    limit_memory()        
    session = tf.Session()
    keras.backend.set_session(session)
    with session.as_default():
        with session.graph.as_default():   
            hyper_parameter = { 'A set of hyper-parameters' }  
            info = create_and_train_ANN_model(hyper_parameter)      
    limit_memory()

Inspiré par ce lien: Erreur Keras (backend Tensorflow) - L'entrée Tensor_1: 0, spécifiée dans feed_devices ou fetch_devices est introuvable dans le Graphique

0
liudonggalaxy