存在GPU的情况下,如何在TensorFlow中的单个脚本中训练多个模型?
machine-learning
neural-network
python
tensorflow
6
0

假设我可以在一台计算机上访问多个GPU(出于争论的考虑,假设一台计算机中有8个GPU的最大内存为8GB,并且具有一定数量的RAM和磁盘)。我想在一个脚本和一台机器上运行一个程序,该程序可以评估TensorFlow中的多个模型(例如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) )

本质上,它一次运行会尝试许多模型,但会在单独的图形中构建每个模型,并在单独的会话中运行每个模型。

我想我主要担心的是,我尚不清楚引擎盖下的张量流如何为要使用的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时才出现此错误?

奇怪的是,数据集确实不是那么大(所有60K点都是24.5M),当我在自己的计算机上本地运行单个模型时,该进程似乎使用的内存不足5GB。 GPU至少有8GB,而装有GPU的计算机则有足够的RAM和磁盘(至少16GB)。因此,张量流给我带来的错误非常令人困惑。它想做什么,为什么会发生?有任何想法吗?


阅读建议使用多处理库的答案后,我想到了以下脚本:

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的资源?

参考资料:
Stack Overflow
收藏
评论
共 2 个回答
高赞 时间 活跃

我认为从长远来看,在一个脚本中运行所有模型可能是一个坏习惯(请参阅下面的建议,以获得更好的选择)。但是,如果您愿意这样做,这是一个解决方案:您可以使用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中的注意事项:如果选择使用随机数生成器种子,则它不会随多重处理库自动重置。此处的详细信息: 对每个进程使用具有不同随机种子的python多处理

关于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,并在流程结束后将其清除。

  2. 编辑中的for循环也应该起作用。我建议改用Pool,因为它将使您能够在单个GPU上同时运行多个模型。请参阅有关设置gpu_memory_fraction和“选择最大进程数”的说明。还要注意:(1)Pool映射为您运行循环,因此一旦使用它就不需要外部的for循环。 (2)在您的示例中,在调用train()之前,应该有mdl=get_model(args)类的东西

  3. 奇怪的元组括号:Pool仅接受单个参数,因此我们使用元组传递多个参数。有关更多详细信息,请参见multiprocessing.pool.map和具有两个参数的函数 。正如一个答案中所建议的那样,您可以通过以下方式使它更具可读性:

     def train_mdl(params): (x,y)=params < your code > 
  4. 如@Seven所建议,您可以使用CUDA_VISIBLE_DEVICES环境变量来选择要用于进程的GPU。您可以在python脚本中使用以下代码(在process函数的train_mdl )( train_mdl )进行操作。

     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. 您可以使用以下命令设置要运行的并发进程数(“插槽”):

    ts -S <number-of-slots>

  2. 安装ts不需要管理员权限。您可以使用简单的make从源代码下载并编译它,然后将其添加到路径中,即可完成操作。

  3. 您可以设置多个队列(我将其用于多个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>

  4. 有关更多用法示例,请参见此处

关于自动设置路径名和文件名的注意事项:一旦将主代码与实验管理器分开,您将需要一种有效的方式来生成文件名和目录名(给定超参数)。我通常将重要的超级参数保存在字典中,并使用以下函数从字典键值对生成单个链接的字符串。这是我用来执行此操作的功能:

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
收藏
评论

据我了解,首先,张量流构造一个符号图并根据链式规则推导导数。然后为所有(必要的)张量分配内存,包括某些层的输入和输出以提高效率。在运行会话时,数据将被加载到图形中,但是通常,内存使用不会再改变。

我猜您遇到的错误可能是由在一个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实施,则有一些示例

希望这可以对您有所帮助。

收藏
评论
新手导航
  • 社区规范
  • 提出问题
  • 进行投票
  • 个人资料
  • 优化问题
  • 回答问题

关于我们

常见问题

内容许可

联系我们

@2020 AskGo
京ICP备20001863号