文档反馈

问题文档片段

问题文档片段包含公式时,显示为空格。

提交类型
issue

有点复杂...

找人问问吧。

PR

小问题,全程线上修改...

一键搞定!

请选择提交类型

问题类型
规范和低错类

- 规范和低错类:

- 错别字或拼写错误,标点符号使用错误、公式错误或显示异常。

- 链接错误、空单元格、格式错误。

- 英文中包含中文字符。

- 界面和描述不一致,但不影响操作。

- 表述不通顺,但不影响理解。

- 版本号不匹配:如软件包名称、界面版本号。

易用性

- 易用性:

- 关键步骤错误或缺失,无法指导用户完成任务。

- 缺少主要功能描述、关键词解释、必要前提条件、注意事项等。

- 描述内容存在歧义指代不明、上下文矛盾。

- 逻辑不清晰,该分类、分项、分步骤的没有给出。

正确性

- 正确性:

- 技术原理、功能、支持平台、参数类型、异常报错等描述和软件实现不一致。

- 原理图、架构图等存在错误。

- 命令、命令参数等错误。

- 代码片段错误。

- 命令无法完成对应功能。

- 界面错误,无法指导操作。

- 代码样例运行报错、运行结果不符。

风险提示

- 风险提示:

- 对重要数据或系统存在风险的操作,缺少安全提示。

内容合规

- 内容合规:

- 违反法律法规,涉及政治、领土主权等敏感词。

- 内容侵权。

问题描述

请勾选同意隐私声明

自适应梯度求和算法

查看源文件

概述

本教程介绍在分布式训练中,如何使用自适应梯度求和算法 Scaling Distributed Training with Adaptive Summation,提升网络训练的临界批量(critical batch size),并加快网络收敛。

传统的分布式训练中,每个计算节点计算得到loss和梯度后,会将所有节点的梯度求均值,然后进行梯度更新。

与传统的分布式训练中的梯度更新不同,自适应梯度求和考虑到梯度的方向。在网络训练初期,不同batch获得的梯度更新方向基本是平行的,但是随着训练进行,梯度更新方向趋向于正交。而且网络的不同层梯度更新的正交性差异也是比较大的。

以两个训练节点为例,梯度的更新原理如下:

w=w0α[(1g2Tg12||g1||2)g1+(1g2Tg12||g2||2)g2]=w0αAdasum(g1,g2)

其中,g1 是训练节点1的梯度,g2 是训练节点2的梯度。当训练节点拓展到 nn=2x,x=1,2,3) 个时,采用递归的方式来对问题进行分解,递归公式如下:

Adasum(g|0,n|)=Adasum(Adasum(g|0,n/2|),Adasum(g|n/2,n|))

从上述公式中可见,论文中是对梯度更新,考虑到优化器(optimizer)对梯度的操作不一定满足线性转换,因此优化为对经过optimizer后的网络权重差值(delta weights)做adasum操作。

本篇教程将在Ascend910上,以ResNet-50在ImageNet 2012数据集上的训练过程为例,介绍在Boost模式下如何实现自适应梯度求和。mindspore.boost中集合了网络训练加速的各类算法,并对外提供配置接口来开启加速算法。

需要注意的是,经实验验证,在小型分布式训练中(例如本实验中2个节点),adasum实验效果不明显,随着节点数的增加,效果也会越明显。本教程仅为了说明如何使用adasum,因此以2节点为例进行说明。

准备环节

你可以在这里下载完整的样例代码:

https://gitee.com/mindspore/docs/tree/r1.8/docs/sample_code/adasum

代码中引用到的models库链接:

https://gitee.com/mindspore/models

目录结构如下:

└─sample_code
    ├─adasum
    │      rank_table_16pcs.json
    │      resnet.py
    │      training.py
    │      run_node1.sh
    │      run_node2.sh

其中,rank_table_16pcs.jsons是多卡环境的组网信息文件,resnet.py和train.py是定义网络结构的文件,run_node1.py和run_node2.py是执行脚本。

配置分布式环境变量

本教程以2个节点,16卡环境为例,json文件的配置信息如下:

{
  "version": "1.0",
  "server_count": "2",
  "server_list": [
    {
      "server_id": "10.*.*.*",
      "device": [
        {"device_id": "0","device_ip": "192.1.27.6","rank_id": "0"},
        {"device_id": "1","device_ip": "192.2.27.6","rank_id": "1"},
        {"device_id": "2","device_ip": "192.3.27.6","rank_id": "2"},
        {"device_id": "3","device_ip": "192.4.27.6","rank_id": "3"},
        {"device_id": "4","device_ip": "192.1.27.7","rank_id": "4"},
        {"device_id": "5","device_ip": "192.2.27.7","rank_id": "5"},
        {"device_id": "6","device_ip": "192.3.27.7","rank_id": "6"},
        {"device_id": "7","device_ip": "192.4.27.7","rank_id": "7"}],
      "host_nic_ip": "reserve"
    },
    {
      "server_id": "10.*.*.*",
      "device": [
        {"device_id": "0","device_ip": "192.1.27.8","rank_id": "8"},
        {"device_id": "1","device_ip": "192.2.27.8","rank_id": "9"},
        {"device_id": "2","device_ip": "192.3.27.8","rank_id": "10"},
        {"device_id": "3","device_ip": "192.4.27.8","rank_id": "11"},
        {"device_id": "4","device_ip": "192.1.27.9","rank_id": "12"},
        {"device_id": "5","device_ip": "192.2.27.9","rank_id": "13"},
        {"device_id": "6","device_ip": "192.3.27.9","rank_id": "14"},
        {"device_id": "7","device_ip": "192.4.27.9","rank_id": "15"}],
      "host_nic_ip": "reserve"
    }
  ],
  "status": "completed"
}

rank_table可以使用models下面的hccl_tools.py生成,merge_hccl.py可将多个rank_table文件进行拼接。脚本使用方法可见README.md

数据集准备

使用的数据集:ImageNet 2012

  • 数据集大小:共1000个类、224*224彩色图像

    • 训练集:共1,281,167张图像

    • 测试集:共50,000张图像

  • 数据格式:JPEG

  • 下载数据集,目录结构如下:

└─dataset
    ├─train                 # 训练数据集
    └─validation_preprocess # 评估数据集

运行模式配置

通过MindSpore提供的context接口指定运行模式、运行卡号、并行模式等,通过init初始化HCCL通信。

set_context(mode=GRAPH_MODE, device_target="Ascend")
device_id = int(os.getenv('DEVICE_ID'))
set_context(device_id=device_id)
set_auto_parallel_context(device_num=16, parallel_mode=ParallelMode.DATA_PARALLEL, gradients_mean=True)
init()

数据并行模式加载数据集

分布式训练时,数据以数据并行的方式导入。利用MindSpore提供图片加载接口ImageFolderDataset加载ImageNet 2012数据集,同时通过MindSpore提供的数据增强接口对数据集进行处理,此部分代码由models中resnet目录下的dataset.py导入。

# define train dataset
train_data_path = os.path.join(args.data_path, "train")
ds_train = create_dataset(dataset_path=train_data_path, do_train=True, batch_size=256, train_image_size=224,
                          eval_image_size=224, target="Ascend", distribute=True)
step_size = ds_train.get_dataset_size()

定义网络

ResNet-50网络的构建代码由resnet.py导入。

# define net
net = resnet(num_classes=1001)
init_weight(net=net)

定义训练模型

在定义网络的时候,我们需要将boost_level设置为”O2”来开启adasum算法。

# define loss
loss = CrossEntropySmooth(sparse=True, reduction="mean", smooth_factor=0.1, num_classes=1001)
loss_scale = FixedLossScaleManager(1024, drop_overflow_update=False)

# define optimizer
group_params = init_group_params(net)
lr = get_lr(lr_init=0, lr_end=0.0, lr_max=0.8, warmup_epochs=5, total_epochs=90, steps_per_epoch=step_size,
            lr_decay_mode="linear")
lr = Tensor(lr)
opt = Momentum(group_params, lr, 0.9, loss_scale=1024)

# define model
model = Model(net, loss_fn=loss, optimizer=opt, loss_scale_manager=loss_scale, amp_level="O2", boost_level="O2",
              keep_batchnorm_fp32=False)
# define eval_network
dist_eval_network = ClassifyCorrectCell(net)

值得注意的是,”O2”模式包含了其他的加速算法,如果我们只想开启adasum,我们可以通过配置boost_config_dict来实现。

# define boost config dictionary
boost_dict = {
    "boost": {
        "mode": "manual",
        "less_bn": False,
        "grad_freeze": False,
        "adasum": True,
        "grad_accumulation": False,
        "dim_reduce": False
    }
}

# define model
model = Model(net, loss_fn=loss, optimizer=opt, loss_scale_manager=loss_scale, amp_level="O2", boost_level="O2",
              keep_batchnorm_fp32=False, boost_config_dict=boost_dict, eval_network=dist_eval_network)

训练模型

训练开始前,定义回调函数callback,添加训练时间信息输出,loss信息输出。

# define callback
cb = [TimeMonitor(data_size=step_size), LossMonitor()]

print("============== Starting Training ==============")
model.train(90, ds_train, callbacks=cb, sink_size=step_size, dataset_sink_mode=True)

运行脚本

2机16卡训练模型,在机器1上运行脚本run_node1.sh,在机器2上运行脚本run_node2.sh。

bash run_node{i}.sh ./imagenet

运行脚本的核心配置如下,当运行机器扩增时,需要进行修改。其中RANK_TABLE_FILE是卡环境配置文件,RANK_SIZE是总的卡数,DEVICE_NUM是每台机器的卡数,SERVER_ID是当前机器的序号。

export RANK_TABLE_FILE=${EXEC_PATH}/rank_table_16pcs.json
export RANK_SIZE=16
export DEVICE_NUM=8

export SERVER_ID=0
rank_start=$((DEVICE_NUM * SERVER_ID))

输出如下,可以看到loss值随着训练逐步降低:

============== Starting Training ==============
epoch: 1 step: 312 loss is  5.5303826
...
epoch: 10 step: 312 loss is  3.3762435
...
...
...