存在GPU时,如何在TensorFlow的单个脚本中训练多个模型?

vwoqyblh  于 2023-02-05  发布在  其他
关注(0)|答案(5)|浏览(163)

假设我可以访问一台计算机中的多个GPU(为了便于讨论,假设一台计算机上有8个GPU,每个GPU的最大内存为8 GB,并具有一定数量的RAM和磁盘)。我想在一个脚本中运行,并在一台计算机上运行一个评估多个模型的程序(比如50或200),每个都有不同的超级参数设置(例如步长、衰减率、批量、时期/迭代,在训练结束时,假设我们只是记录其准确性并丢弃模型(如果你想假设模型经常被检查点,所以扔掉模型,从头开始训练是可以的。你也可以假设一些其他的数据可能会被记录,比如特定的超参数,训练,验证,训练错误在我们训练的时候被记录等等)。
目前我有一个(伪)脚本,如下所示:

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

本质上,它在一次运行中尝试许多模型,但是它在单独的图中构建每个模型,并且在单独的会话中运行每个模型。
我想我最担心的是我不清楚tensorflow是如何为GPU分配资源的。(部分)数据集仅在会话运行时?当我创建图形和模型时,它是立即被带进GPU还是什么时候被插入GPU?每次它尝试新型号时,我需要清除/释放GPU吗?我不需要我不太关心模型是否在多个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,那么为什么只有在将要使用GPU时才会出现这种错误?
奇怪的是,数据集并不是那么大(所有的60 K点都是24. 5 M)而且当我在自己的电脑上本地运行一个单一的模型时,这个过程似乎使用了不到5GB。GPU至少有8 GB,装有它们的电脑有足够的RAM和磁盘(至少16 GB)。因此,TensorFlow抛给我的错误相当令人费解。它试图做什么,为什么会发生?有什么想法吗?
在阅读了建议使用多处理库的答案后,我得出了以下脚本:

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

老实说,我不知道为什么他的答案建议使用池,或者为什么有奇怪的元组括号,但这是什么对我来说是有意义的。是否每次在上面的循环中创建一个新进程时,都会重新分配用于tensorflow的资源?

jdgnovmf

jdgnovmf1#

我认为在一个脚本中运行所有的模型从长远来看是不好的做法(参见我下面的建议,以获得更好的替代方案)。但是,如果你想这样做,这里有一个解决方案:你可以用multiprocessing模块把你的TF会话封装到一个进程中,这样可以确保进程完成后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注解:如果您选择使用随机数生成器种子,它不会随多处理库自动重置。详细信息如下:Using python multiprocessing with different random seed for each process
关于TF资源分配:通常TF分配的资源比它需要的多得多。很多时候你可以限制每个进程使用总GPU内存的一小部分,然后通过反复试验发现你的脚本需要的那一小部分。
您可以使用以下代码段执行此操作

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会增加内存使用量以加速执行,因此,减少内存使用量可能会使模型运行速度变慢。

对编辑/评论中新问题的回答:

1.是,每次创建新进程时都会重新分配Tensorflow,并在进程结束后清除。
1.编辑中的for循环也可以完成这个工作。我建议使用Pool,因为它可以让你在一个GPU上同时运行几个模型。请参阅我关于设置gpu_memory_fraction和“选择最大进程数”的注解。还要注意:(1)PoolMap为您运行循环,因此一旦使用它,您就不需要外部for循环。(2)在您的示例中,在调用train()之前应该有类似mdl=get_model(args)的内容。
1.奇怪的元组括号:Pool只接受一个参数,因此我们使用元组来传递多个参数。multiprocessing.pool.map有关详细信息,请参阅www.example.com和function with two arguments。正如在一个答案中建议的那样,您可以使用

def train_mdl(params):
    (x,y)=params
    < your code >

1.正如@Seven所建议的,您可以使用CUDA_VISIBLE_DEVICES环境变量来选择进程使用的GPU,您可以在进程函数(train_mdl)的开头使用以下代码在python脚本中执行此操作。

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

执行实验的一个更好的做法是将训练/评估代码与超参数/模型搜索代码隔离开来。例如,有一个名为train.py的脚本,它接受超参数和数据引用的特定组合作为参数,并为单个模型执行训练。

然后,要迭代所有可能的参数组合,您可以使用简单的任务(作业)队列,并将所有可能的超参数组合作为单独的作业提交。任务队列将一次一个地将作业馈送到您的计算机。通常,您还可以设置队列以并发执行多个进程(请参阅下面的详细信息)。
具体来说,我使用的是task spooler,它非常容易安装,而且很简单(不需要管理员权限,详情如下)。
基本用法是(请参阅下面有关任务假脱机程序用法的注解):

ts <your-command>

实际上,我有一个单独的python脚本来管理我的实验,为每个特定的实验设置所有参数,并将作业发送到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)

下一个代码片段遍历所有超参数/模型参数组合的列表,列表中的每个元素都是一个字典,其中的键是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)

关于选择最大进程数的注意事项:

如果GPU不足,可以使用找到的gpu_memory_fraction将进程数设置为max_job_num_per_gpu=int(1/gpu_memory_fraction)

关于任务假脱机程序(ts)的说明:

1.您可以设置要运行的并发进程数(“插槽”):
x1米11米1x
1.安装ts不需要管理员权限,你可以用一个简单的make从源代码下载并编译它,然后把它添加到你的路径中,你就完成了。
1.您可以设置多个队列(我将其用于多个GPU),使用
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>
1.更多使用示例,请参见here

**关于自动设置路径名和文件名的注意事项:**一旦你把你的主代码从实验管理器中分离出来,你将需要一种有效的方法来生成文件名和目录名,给定超参数。我通常把我重要的超参数保存在字典中,并使用下面的函数从字典的键-值对中生成一个单链字符串。下面是我用来做这件事的函数:

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
bihw5rsg

bihw5rsg2#

据我所知,首先tensorflow 构造一个符号图,并基于链式规则推导导数。然后为所有(必要的)Tensor分配内存,包括一些层的输入和输出,以提高效率。当运行会话时,数据将被加载到图中,但一般情况下,内存使用不会改变。
您遇到的错误,我猜,可能是由于在一个GPU中构建多个模型造成的。
正如@user2476373所建议的,将你的训练/评估代码从超参数中分离出来是一个不错的选择,但是我直接使用bash脚本,而不是任务假脱机程序(可能它更方便),例如:

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

或者你可以在bash脚本中写一个“for”循环,不一定要在python脚本中写。注意我在脚本的开头使用了CUDA_VISIBLE_DEVICES=0(如果你在一台机器上有8个GPU,索引可能是7)。因为根据我的经验,我发现如果我没有在下面的代码中指定操作使用哪个GPU,tensorflow会使用一台机器上的所有GPU

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

如果你想尝试多GPU实现,这里有一些例子。
希望这能帮到你。

8i9zcol2

8i9zcol23#

简单的解决方案:为每个模型指定唯一的会话和图形。
它适用于此平台:tensorflow 1.12.0,Keras 2.1.6-tf,Python 3.6.7,木星笔记本。

    • 密钥代码:**
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()
omvjsjqw

omvjsjqw4#

我也有同样的问题,我的解决方案是从另一个脚本运行,执行下面的操作,次数和超参数配置都可以。

cmd = "python3 ./model_train.py hyperparameters"
os.system(cmd)
wpcxdonn

wpcxdonn5#

你可能不想这么做。
如果你对你的数据运行成千上万的模型,然后选择一个评估最好的,你就不是在做机器学习;相反,您是在记忆数据集,并且不能保证您选择的模型在数据集之外也能执行。
换句话说,这种方法类似于拥有一个具有数千自由度的单一模型,拥有一个如此高度复杂的模型是有问题的,因为它能够比实际保证的更好地拟合您的数据;这样的模型非常烦人,它能够记住训练数据中的任何噪声(异常值、测量误差等),这使得即使噪声有细微的差别,模型也会表现不佳。
(很抱歉把这个作为答案发布,网站不让我添加评论。)

相关问题