单机多卡训练

飞桨框架2.0增加paddle.distributed.spawn函数来启动单机多卡训练,同时原有的paddle.distributed.launch的方式依然保留。

一、launch启动

1.1 高层API场景

当调用paddle.Model高层API来实现训练时,想要启动单机多卡训练非常简单,代码不需要做任何修改,只需要在启动时增加一下参数-m paddle.distributed.launch

  1. # 单机单卡启动,默认使用第0号卡
  2. $ python train.py
  3. # 单机多卡启动,默认使用当前可见的所有卡
  4. $ python -m paddle.distributed.launch train.py
  5. # 单机多卡启动,设置当前使用的第0号和第1号卡
  6. $ python -m paddle.distributed.launch --gpus='0,1' train.py
  7. # 单机多卡启动,设置当前使用第0号和第1号卡
  8. $ export CUDA_VISIBLE_DEVICES=0,1
  9. $ python -m paddle.distributed.launch train.py

1.2 基础API场景

如果使用基础API实现训练,想要启动单机多卡训练,需要对单机单卡的代码进行3处修改,具体如下:

  1. import paddle
  2. # 第1处改动 导入分布式训练所需的包
  3. import paddle.distributed as dist
  4. # 加载数据集
  5. train_dataset = paddle.vision.datasets.MNIST(mode='train')
  6. test_dataset = paddle.vision.datasets.MNIST(mode='test')
  7. # 第2处改动,初始化并行环境
  8. dist.init_parallel_env()
  9. # 定义网络结构
  10. mnist = paddle.nn.Sequential(
  11. paddle.nn.Flatten(1, -1),
  12. paddle.nn.Linear(784, 512),
  13. paddle.nn.ReLU(),
  14. paddle.nn.Dropout(0.2),
  15. paddle.nn.Linear(512, 10)
  16. )
  17. # 用 DataLoader 实现数据加载
  18. train_loader = paddle.io.DataLoader(train_dataset, batch_size=32, shuffle=True)
  19. # 第3处改动,增加paddle.DataParallel封装
  20. mnist = paddle.DataParallel(mnist)
  21. mnist.train()
  22. # 设置迭代次数
  23. epochs = 5
  24. # 设置优化器
  25. optim = paddle.optimizer.Adam(parameters=mnist.parameters())
  26. for epoch in range(epochs):
  27. for batch_id, data in enumerate(train_loader()):
  28. x_data = data[0] # 训练数据
  29. y_data = data[1] # 训练数据标签
  30. predicts = mnist(x_data) # 预测结果
  31. # 计算损失 等价于 prepare 中loss的设置
  32. loss = paddle.nn.functional.cross_entropy(predicts, y_data)
  33. # 计算准确率 等价于 prepare 中metrics的设置
  34. acc = paddle.metric.accuracy(predicts, y_data)
  35. # 下面的反向传播、打印训练信息、更新参数、梯度清零都被封装到 Model.fit() 中
  36. # 反向传播
  37. loss.backward()
  38. if (batch_id+1) % 1800 == 0:
  39. print("epoch: {}, batch_id: {}, loss is: {}, acc is: {}".format(epoch, batch_id, loss.numpy(), acc.numpy()))
  40. # 更新参数
  41. optim.step()
  42. # 梯度清零
  43. optim.clear_grad()

修改完后保存文件,然后使用跟高层API相同的启动方式即可。 注意: 单卡训练不支持调用init_parallel_env,请使用以下几种方式进行分布式训练。

  1. # 单机多卡启动,默认使用当前可见的所有卡
  2. $ python -m paddle.distributed.launch train.py
  3. # 单机多卡启动,设置当前使用的第0号和第1号卡
  4. $ python -m paddle.distributed.launch --gpus '0,1' train.py
  5. # 单机多卡启动,设置当前使用第0号和第1号卡
  6. $ export CUDA_VISIBLE_DEVICES=0,1
  7. $ python -m paddle.distributed.launch train.py

二、spawn启动

launch方式启动训练,以文件为单位启动多进程,需要用户在启动时调用paddle.distributed.launch,对于进程的管理要求较高。飞桨框架2.0版本增加了spawn启动方式,可以更好地控制进程,在日志打印、训练退出时更友好。使用示例如下:

  1. from __future__ import print_function
  2. import paddle
  3. import paddle.nn as nn
  4. import paddle.optimizer as opt
  5. import paddle.distributed as dist
  6. class LinearNet(nn.Layer):
  7. def __init__(self):
  8. super(LinearNet, self).__init__()
  9. self._linear1 = nn.Linear(10, 10)
  10. self._linear2 = nn.Linear(10, 1)
  11. def forward(self, x):
  12. return self._linear2(self._linear1(x))
  13. def train(print_result=False):
  14. # 1. 初始化并行训练环境
  15. dist.init_parallel_env()
  16. # 2. 创建并行训练 Layer 和 Optimizer
  17. layer = LinearNet()
  18. dp_layer = paddle.DataParallel(layer)
  19. loss_fn = nn.MSELoss()
  20. adam = opt.Adam(
  21. learning_rate=0.001, parameters=dp_layer.parameters())
  22. # 3. 运行网络
  23. inputs = paddle.randn([10, 10], 'float32')
  24. outputs = dp_layer(inputs)
  25. labels = paddle.randn([10, 1], 'float32')
  26. loss = loss_fn(outputs, labels)
  27. if print_result is True:
  28. print("loss:", loss.numpy())
  29. loss.backward()
  30. adam.step()
  31. adam.clear_grad()
  32. # 使用方式1:仅传入训练函数
  33. # 适用场景:训练函数不需要任何参数,并且需要使用所有当前可见的GPU设备并行训练
  34. if __name__ == '__main__':
  35. dist.spawn(train)
  36. # 使用方式2:传入训练函数和参数
  37. # 适用场景:训练函数需要一些参数,并且需要使用所有当前可见的GPU设备并行训练
  38. if __name__ == '__main__':
  39. dist.spawn(train, args=(True,))
  40. # 使用方式3:传入训练函数、参数并指定并行进程数
  41. # 适用场景:训练函数需要一些参数,并且仅需要使用部分可见的GPU设备并行训练,例如:
  42. # 当前机器有8张GPU卡 {0,1,2,3,4,5,6,7},此时会使用前两张卡 {0,1};
  43. # 或者当前机器通过配置环境变量 CUDA_VISIBLE_DEVICES=4,5,6,7,仅使4张
  44. # GPU卡可见,此时会使用可见的前两张卡 {4,5}
  45. if __name__ == '__main__':
  46. dist.spawn(train, args=(True,), nprocs=2)
  47. # 使用方式4:传入训练函数、参数、指定进程数并指定当前使用的卡号
  48. # 使用场景:训练函数需要一些参数,并且仅需要使用部分可见的GPU设备并行训练,但是
  49. # 可能由于权限问题,无权配置当前机器的环境变量,例如:当前机器有8张GPU卡
  50. # {0,1,2,3,4,5,6,7},但你无权配置CUDA_VISIBLE_DEVICES,此时可以通过
  51. # 指定参数 gpus 选择希望使用的卡,例如 gpus='4,5',
  52. # 可以指定使用第4号卡和第5号卡
  53. if __name__ == '__main__':
  54. dist.spawn(train, nprocs=2, gpus='4,5')