Как организовать несколько моделей в одном script в TensorFlow при наличии графических процессоров?

Скажем, у меня есть доступ к ряду графических процессоров на одной машине (ради аргумента предполагают 8GPU с максимальной памятью 8 ГБ каждый на одной машине с некоторым объемом ОЗУ и диска). Я хотел запустить один сингл script, а на одной машине - программу, которая оценивает несколько моделей (скажем, 50 или 200) в TensorFlow, каждый из которых имеет другую настройку гиперпараметра (скажем, пошаговый, скорость распада, размер партии, эпохи/итерации и т.д.). По окончанию обучения предположим, что мы просто записываем его точность и избавляемся от модели (если вы хотите, чтобы модель проверялась каждый раз так часто, поэтому ее штраф, чтобы просто выбросить модель и начать обучение с нуля. предположим, что некоторые другие данные могут быть записаны как специальные гиперпараметры, поезд, валидация, ошибки поезда регистрируются по мере обучения и т.д.).

В настоящее время у меня есть (псевдо -) script, который выглядит следующим образом:

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) )

по существу он пробует много моделей за один прогон, но он строит каждую модель в отдельном графе и запускает каждый в отдельном сеансе.

Я думаю, что мое основное беспокойство заключается в том, что его неясно, как тензорный поток под капотом выделяет ресурсы для используемых графических процессоров. Например, загружает ли (часть) набор данных только при запуске сеанса? Когда я создаю график и модель, он сразу же появляется в графическом процессоре или когда он вставлен в графический процессор? Нужно ли мне очищать/освобождать GPU каждый раз, когда он пробует новую модель? Мне на самом деле не очень-то интересно, если модели работают параллельно в нескольких графических процессорах (что может быть приятным дополнением), но я хочу, чтобы он сначала запускал все последовательно без сбоев. Есть ли что-то особенное, что мне нужно сделать для этого?


В настоящее время я получаю сообщение об ошибке, которое начинается следующим образом:

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]

и далее вниз по строке:

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)

однако дальше по выходному файлу (там, где он печатается), кажется, печатаются мелкие ошибки/сообщения, которые должны отображаться по мере продолжения обучения. Означает ли это, что у него не осталось ресурсов? Или действительно ли он мог использовать GPU? Если бы он смог использовать CPU вместо CPU, то почему это ошибка происходит только при использовании графического процессора?

Странно то, что набор данных на самом деле не такой большой (все 60K точек 24.5M), и когда я запускаю одну модель локально на своем собственном компьютере, кажется, что процесс использует менее 5 ГБ. Графические процессоры имеют не менее 8 ГБ, а компьютер с ними имеет большое количество оперативной памяти и диска (не менее 16 ГБ). Таким образом, ошибки, которые тензорный поток бросает на меня, довольно озадачивают. Что он пытается делать и почему они происходят? Любые идеи?


После прочтения ответа, предлагающего использовать библиотеку многопроцессорности, я пришел к следующему script:

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!')

Честно говоря, я не уверен, почему его ответ предлагает использовать пул, или почему есть странные скобки, но это то, что имеет смысл для меня. Будут ли ресурсы для тензорного потока перераспределяться каждый раз, когда новый процесс создается в вышеуказанном цикле?

Ответ 1

Я думаю, что запуск всех моделей в одном script может быть плохой практикой в ​​долгосрочной перспективе (см. мое предложение ниже для лучшей альтернативы). Однако, если вы хотите это сделать, вот решение: вы можете инкапсулировать сессию TF в процесс с помощью модуля multiprocessing, это гарантирует, что TF освободит память сеанса после завершения процесса. Вот фрагмент кода:

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()

Примечание от OP: семя генератора случайных чисел не будет reset автоматически с библиотекой многопроцессорной обработки, если вы решите его использовать. Подробности здесь: Использование многопроцессорности python с различными случайными затратами для каждого процесса

О распределении ресурсов TF: Обычно TF выделяет гораздо больше ресурсов, чем нужно. Много раз вы можете ограничить каждый процесс использованием доли общей памяти GPU и обнаружить через пробную версию и ошибку необходимую вам долю script.

Вы можете сделать это со следующим фрагментом

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)

Обратите внимание, что иногда TF увеличивает использование памяти, чтобы ускорить выполнение. Поэтому сокращение использования памяти может привести к замедлению работы вашей модели.

Ответы на новые вопросы в вашем редактировании/комментариях:

  • Да, Tensorflow будет перераспределяться каждый раз при создании нового процесса и очищается после завершения процесса.

  • Для цикла в вашем редактировании также должно выполняться задание. Я предлагаю использовать Pool вместо этого, потому что он позволит вам одновременно запускать несколько моделей на одном графическом процессоре. См. Мои заметки о настройке gpu_memory_fraction и "выборе максимального количества процессов". Также обратите внимание, что: (1) Карта пула запускает цикл для вас, поэтому вам не нужен внешний цикл for-loop, как только вы его используете. (2) В вашем примере у вас должно быть что-то вроде mdl=get_model(args) перед вызовом train()

  • Скопируйте скопированные строки: пул принимает только один аргумент, поэтому мы используем кортеж для передачи нескольких аргументов. Подробнее см. multiprocessing.pool.map и функцию с двумя аргументами. Как было предложено в одном ответе, вы можете сделать его более читаемым с помощью

    def train_mdl(params):
        (x,y)=params
        < your code >
    
  • Как показано в @Seven, вы можете использовать переменную среды CUDA_VISIBLE_DEVICES, чтобы выбрать, какой графический процессор использовать для вашего процесса. Вы можете сделать это из своего python script, используя следующее в начале функции процесса (train_mdl).

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

Лучшая практика для выполнения ваших экспериментов заключалась бы в том, чтобы изолировать ваш учебный/оценочный код от гипер-параметров/кода поиска модели. Например. имеют script с именем train.py, который принимает определенную комбинацию гиперпараметров и ссылок на ваши данные в качестве аргументов и выполняет обучение для одной модели.

Затем для повторения всех возможных комбинаций параметров вы можете использовать простую задачу (задания) и представить все возможные комбинации гиперпараметров в качестве отдельных заданий. Очередь задач будет подавать ваши задания по одному на ваш компьютер. Как правило, вы также можете установить очередь для одновременного выполнения нескольких процессов (см. Подробности ниже).

В частности, я использую диспетчер задач, который очень прост в установке и имеет небольшой размер (не требует привилегий администратора, подробности ниже).

Основное использование (см. примечания ниже о загрузке спулера задачи):

ts <your-command>

На практике у меня есть отдельный python script, который управляет моими экспериментами, задает все аргументы для конкретного эксперимента и отправляет задания в очередь ts.

Вот некоторые соответствующие фрагменты кода python из моего менеджера экспериментов:

run_bash выполняет команду 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

Следующий фрагмент устанавливает количество одновременных процессов, которые будут выполняться (см. примечание ниже о выборе максимального количества процессов):

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

Следующий фрагмент повторяется через список всех комбинаций параметров hyper params/model. Каждый элемент списка - это словарь, где ключи являются аргументами командной строки для train.py script

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)

Заметка о выборе максимального количества процессов:

Если вам не хватает графических процессоров, вы можете использовать gpu_memory_fraction, который вы нашли, чтобы установить количество процессов как max_job_num_per_gpu=int(1/gpu_memory_fraction)

Примечания о диспетчере очереди задач (ts):

  • Вы можете установить количество одновременных процессов для запуска ( "слоты" ) с помощью:

    ts -S <number-of-slots>

  • Установка ts не требует прав администратора. Вы можете загрузить и скомпилировать его из источника с помощью простого make, добавить его в свой путь, и все готово.

  • Вы можете настроить несколько очередей (я использую их для нескольких графических процессоров),

    TS_SOCKET=<path_to_queue_name> ts <your-command>

    например.

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

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

  • Смотрите здесь для дальнейшего использования

Заметка об автоматической установке имен путей и имен файлов: После того как вы отделите свой основной код от менеджера эксперимента, вам понадобится эффективный способ генерации имен файлов и имен каталогов, учитывая гиперпараметры. Обычно я сохраняю свои важные гиперпараметры в словаре и использую следующую функцию для генерации одной цепочки из пар слов-словаря. Вот функции, которые я использую для этого:

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

Ответ 2

Как я понимаю, во-первых, тензорный поток создает символический граф и выводит производные на основе правила цепочки. Затем выделяет память для всех (необходимых) тензоров, включая некоторые входы и выходы слоев для повышения эффективности. При запуске сеанса данные будут загружены в график, но в целом использование памяти больше не изменится.

Ошибка, с которой вы столкнулись, может быть вызвана построением нескольких моделей на одном графическом процессоре.

Выделение вашего кода обучения/оценки из гиперпараметров является хорошим выбором, так как предлагается @user2476373. Но я использую bash script напрямую, а не диспетчер очереди (может быть, это более удобно), например.

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 

Или вы можете написать цикл 'for' в bash script, не обязательно в python script. Отметив, что я использовал CUDA_VISIBLE_DEVICES=0 в начале script (индекс может быть 7, если у вас 8 графических процессоров на одной машине). Поскольку, основываясь на моем опыте, я обнаружил, что tenorflow использует все графические процессоры на одном компьютере, если я не указывал, какие операции используют графический процессор с таким кодом, как этот

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

Если вы хотите попробовать реализацию с несколькими GPU, есть пример.

Надеюсь, это поможет вам.

Ответ 3

Вероятно, вы не хотите этого делать.

Если вы запускаете тысячи и тысячи моделей своих данных и выбираете тот, который лучше всего оценивает, вы не занимаетесь машинным обучением; вместо этого вы запомните свой набор данных, и нет никакой гарантии, что выбранная вами модель будет выполнять все за пределами этого набора данных.

Другими словами, этот подход похож на наличие единой модели, которая имеет тысячи степеней свободы. Наличие модели с таким высоким уровнем сложности проблематично, поскольку она сможет лучше соответствовать вашим данным, чем на самом деле; такая модель досадно умеет запоминать любые шумы (выбросы, ошибки измерений и т.д.) в ваших данных обучения, что приводит к плохому функционированию модели, когда шум даже немного отличается.

(Извинения за отправку этого ответа, сайт не позволит мне добавить комментарий.)

Ответ 4

Простое решение: дать каждой модели уникальный сеанс и график.

Он работает для этой платформы: TensorFlow 1.12.0, Keras 2.1.6-tf, Python 3.6.7, Jupyter Notebook.

Ключевой код:

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

Полный код:

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()

Вдохновлен этой ссылкой: Ошибка Keras (серверная часть Tensorflow) - Тензор input_1: 0, указанный в feed_devices или fetch_devices, не найден в Графике