『MXNet』第五弹_多GPU并行程序设计

资料原文

一、概述思路

假设一台机器上有k个GPU。给定需要训练的模型,每个GPU将分别独立维护一份完整的模型参数。

在模型训练的任意一次迭代中,给定一个小批量,我们将该批量中的样本划分成k份并分给每个GPU一份。

然后,每个GPU将分别根据自己分到的训练数据样本和自己维护的模型参数计算模型参数的梯度。

接下来,我们把k个GPU上分别计算得到的梯度相加,从而得到当前的小批量梯度。

之后,每个GPU都使用这个小批量梯度分别更新自己维护的那一份完整的模型参数。

二、网络以及辅助函数

使用“卷积神经网络——从零开始”里的LeNet来作为本节的样例:

# 初始化模型参数。
scale = 0.01
W1 = nd.random.normal(scale=scale, shape=(20, 1, 3, 3))
b1 = nd.zeros(shape=20)
W2 = nd.random.normal(scale=scale, shape=(50, 20, 5, 5))
b2 = nd.zeros(shape=50)
W3 = nd.random.normal(scale=scale, shape=(800, 128))
b3 = nd.zeros(shape=128)
W4 = nd.random.normal(scale=scale, shape=(128, 10))
b4 = nd.zeros(shape=10)
params = [W1, b1, W2, b2, W3, b3, W4, b4]

# 定义模型。
def lenet(X, params):
    h1_conv = nd.Convolution(data=X, weight=params[0], bias=params[1],
                             kernel=(3, 3), num_filter=20)
    h1_activation = nd.relu(h1_conv)
    h1 = nd.Pooling(data=h1_activation, pool_type="avg", kernel=(2, 2),
                    stride=(2, 2))
    h2_conv = nd.Convolution(data=h1, weight=params[2], bias=params[3],
                             kernel=(5, 5), num_filter=50)
    h2_activation = nd.relu(h2_conv)
    h2 = nd.Pooling(data=h2_activation, pool_type="avg", kernel=(2, 2),
                    stride=(2, 2))
    h2 = nd.flatten(h2)
    h3_linear = nd.dot(h2, params[4]) + params[5]
    h3 = nd.relu(h3_linear)
    y_hat = nd.dot(h3, params[6]) + params[7]
    return y_hat

# 交叉熵损失函数。
loss = gloss.SoftmaxCrossEntropyLoss()

参数列表复制到指定设备

下面函数将模型参数[参数一,参数二,……]复制到某个特定GPU,并标记梯度求解:

def get_params(params, ctx):
    new_params = [p.copyto(ctx) for p in params]
    for p in new_params:
        p.attach_grad()
    return new_params

同一参数设备间同步

以下函数可以把各个GPU上的同一参数数据加起来,然后再广播到所有GPU上:

def allreduce(data):  # 输入为list,包含位于不同设备上的同一参数
    for i in range(1, len(data)):
        data[0][:] += data[i].copyto(data[0].context)  # 将i位复制到0位设备上,并加给0位
    for i in range(1, len(data)):
        data[0].copyto(data[i])  # 使用累计后的0位替换i位

数据划分到设备

给定一个批量的数据样本,以下函数可以划分它们并复制到各个GPU上:

def split_and_load(data, ctx):
    n, k = data.shape[0], len(ctx)
    m = n // k
    assert m * k == n, ‘# examples is not divided by # devices.‘
    return [data[i * m: (i + 1) * m].as_in_context(ctx[i]) for i in range(k)]

三、训练过程

将完整的模型参数复制到多个GPU上,并在每次迭代时对单个小批量上进行多GPU训练:

def train(num_gpus, batch_size, lr):
    train_iter, test_iter = gb.load_data_fashion_mnist(batch_size)
    ctx = [mx.gpu(i) for i in range(num_gpus)]  # 设备代号list
    print(‘running on:‘, ctx)
    # 将模型参数复制到 num_gpus 个 GPU 上。
    gpu_params = [get_params(params, c) for c in ctx]  # 每个元素为一个设备上的参数
    for epoch in range(1, 6):
        start = time()
        for X, y in train_iter:
            # 对单个小批量上进行多 GPU 训练。
            train_batch(X, y, gpu_params, ctx, lr)
        nd.waitall()
        print(‘epoch %d, time: %.1f sec‘ % (epoch, time() - start))
        # 在 GPU0 上验证模型。
        net = lambda x: lenet(x, gpu_params[0])
        test_acc = gb.evaluate_accuracy(test_iter, net, ctx[0])
        print(‘validation accuracy: %.4f‘ % test_acc)

实现单个小批量上的多GPU训练:

def train_batch(X, y, gpu_params, ctx, lr):
    # 划分小批量数据样本并复制到各个 GPU 上。
    gpu_Xs = split_and_load(X, ctx)
    gpu_ys = split_and_load(y, ctx)
    # 在各个 GPU 上计算损失。
    with autograd.record():
        ls = [loss(lenet(gpu_X, gpu_W), gpu_y)  # 不同设备上的loss对象
              for gpu_X, gpu_y, gpu_W in zip(gpu_Xs, gpu_ys, gpu_params)]
    # 在各个 GPU 上反向传播。
    for l in ls:
        l.backward()
    # 把各个 GPU 上的梯度加起来,然后再广播到所有 GPU 上。
    for i in range(len(gpu_params[0])):  # gpu_params[0]:位于设备0上的全部参数
        allreduce([gpu_params[c][i].grad for c in range(len(ctx))])  # 汇总梯度并广播
    # 在各个 GPU 上更新自己维护的那一份完整的模型参数。
    for param in gpu_params:  # 各个设备分别更新
        gb.sgd(param, lr, X.shape[0])

原文地址:https://www.cnblogs.com/hellcat/p/9091191.html

时间: 2024-08-02 15:42:50

『MXNet』第五弹_多GPU并行程序设计的相关文章

『MXNet』第七弹_分类器demo示意

解压文件命令: with zipfile.ZipFile('../data/kaggle_cifar10/' + fin, 'r') as zin: zin.extractall('../data/kaggle_cifar10/') 拷贝文件命令: shutil.copy(原文件, 目标文件) 整理数据 我们有两个文件夹'../data/kaggle_cifar10/train'和'../data/kaggle_cifar10/test',一个记录了文件名和类别的索引文件 我们的目的是在新的文件

『MXNet』第六弹_数据处理API(待续)

一.Gluon数据加载 图片数据(含标签)加载函数:gluon.data.vision.ImageFolderDataset 给出ImageFolderDataset类的描述, Init signature: mxnet.gluon.data.vision.datasets.ImageFolderDataset(root, flag=1, transform=None) Source: class ImageFolderDataset(dataset.Dataset): """

『MXNet』第八弹_物体检测之SSD

预.API介绍 mxnet.metric from mxnet import metric cls_metric = metric.Accuracy() box_metric = metric.MAE() cls_metric.update([cls_target], [class_preds.transpose((0,2,1))]) box_metric.update([box_target], [box_preds * box_mask]) cls_metric.get() box_metr

『MXNet』第十一弹_符号式编程专题

一.符号分类 符号对我们想要进行的计算进行了描述, 下图展示了符号如何对计算进行描述. 我们定义了符号变量A, 符号变量B, 生成了符号变量C, 其中, A, B为参数节点, C为内部节点! mxnet.symbol.Variable可以生成参数节点, 用于表示计算时的输入. 二.常用符号方法 一个Symbol具有的属性和方法如下图所示: 关联节点查看 list_argument()用来检查计算图的输入参数; list_output()返回此Symbol的所有输出,输出的自动命名遵循一定的规则

『PyTorch』第五弹_深入理解Tensor对象_中上:索引

一.普通索引 示例 a = t.Tensor(4,5) print(a) print(a[0:1,:2]) print(a[0,:2]) # 注意和前一种索引出来的值相同,shape不同 print(a[[1,2]]) # 容器索引 3.3845e+15 0.0000e+00 3.3846e+15 0.0000e+00 3.3845e+15 0.0000e+00 3.3845e+15 0.0000e+00 3.3418e+15 0.0000e+00 3.3845e+15 0.0000e+00 3

『PyTorch』第五弹_深入理解Tensor对象_中下:数学计算以及numpy比较

一.简单数学操作 1.逐元素操作 t.clamp(a,min=2,max=4)近似于tf.clip_by_value(A, min, max),修剪值域. a = t.arange(0,6).view(2,3) print("a:",a) print("t.cos(a):",t.cos(a)) print("a % 3:",a % 3) # t.fmod(a, 3) print("a ** 2:",a ** 2) # t.po

『PyTorch』第五弹_深入理解Tensor对象_下:从内存看Tensor

Tensor存储结构如下, 如图所示,实际上很可能多个信息区对应于同一个存储区,也就是上一节我们说到的,初始化或者普通索引时经常会有这种情况. 一.几种共享内存的情况 view a = t.arange(0,6) print(a.storage()) b = a.view(2,3) print(b.storage()) print(id(a.storage())==id(b.storage())) a[1] = 10 print(b) 上面代码,我们通过.storage()可以查询到Tensor

『PyTorch』第五弹_深入理解autograd_上:Variable

一.Variable类源码简介 class Variable(_C._VariableBase): """ Attributes: data: 任意类型的封装好的张量. grad: 保存与data类型和位置相匹配的梯度,此属性难以分配并且不能重新分配. requires_grad: 标记变量是否已经由一个需要调用到此变量的子图创建的bool值.只能在叶子变量上进行修改. volatile: 标记变量是否能在推理模式下应用(如不保存历史记录)的bool值.只能在叶变量上更改.

『PyTorch』第五弹_深入理解autograd_下:Variable梯度探究

查看非叶节点梯度的两种方法 在反向传播过程中非叶子节点的导数计算完之后即被清空.若想查看这些变量的梯度,有两种方法: 使用autograd.grad函数 使用hook autograd.grad和hook方法都是很强大的工具,更详细的用法参考官方api文档,这里举例说明基础的使用.推荐使用hook方法,但是在实际使用中应尽量避免修改grad的值. 求z对y的导数 x = V(t.ones(3)) w = V(t.rand(3),requires_grad=True) y = w.mul(x) z