目前,众多的深度学习算法采用PyTorch框架开发,通常是在CPU或者英伟达的GPU运行,为了使这些程序能够利用在昇腾AI处理器上运行,需要对PyTorch模型进行迁移。

PyTorch适配昇腾AI处理器

在线对接方案

PyTorch框架对接适配昇腾AI处理器的方案为在线对接方案。
当前选择在线对接适配方案的主要原因有以下几点:

  1. 最大限度的继承PyTorch框架动态图的特性。
  2. 最大限度的继承GPU在PyTorch上的使用方式,可以使用户在将模型移植到昇腾AI处理器设备进行训练时,在开发方式和代码重用方面做到最小的改动。
  3. 最大限度的继承PyTorch原生的体系结构,保留框架本身出色的特性,比如自动微分、动态分发、Debug、Profiling、Storage共享机制以及设备侧的动态内存管理等。
  4. 扩展性好。在打通流程的通路之上,对于新增的网络类型或结构,只需涉及相关计算类算子的开发和实现。框架类算子,反向图建立和实现机制等结构可保持复用。
  5. 与GPU的使用方式和风格保持一致。用户在使用在线对接方案时,只需在Python侧和device相关操作中,指定device为昇腾AI处理器,即可完成用昇腾AI处理器在PyTorch对网络的开发、训练以及调试,用户无需进一步关注昇腾AI处理器具体的底层细节。这样可以确保用户的修改最小化,迁移成本较低。

昇腾AI处理器

昇腾AI处理器在硬件体系结构上深度神经网络进行了特殊的优化,其主要的架构组成部件包括AI CPU、控制CPU、AI Core、任务调度器和数字视觉预处理模块。

  • 控制CPU用于控制处理器整体运行;而AI CPU用于承担非矩阵类复杂计算。
  • AI Core是处理器的算力担当,采用达芬奇架构实现了高通量、大算力和低功耗,特别适合处理深度学习中神将网络必须的常用计算,如矩阵乘法等。
  • 任务调度器是实现计算任务在AI Core上的高效分配和调度的专用CPU。
  • 数字视觉预处理模块(Digital Vision Pre-Processing,DVPP)实现图像的格式

DaVinci

昇腾AI处理器的计算核心主要由AI Core构成,负责执行标量、向量和张量相关的计算密集型算子,采用达芬奇架构。包含了三种基础计算资源:矩阵计算单元(Cube Unit)、向量计算单元(Vector Unit)和标量计算单元(Scalar Unit)。矩阵计算单元负责执行矩阵运算,每次可以执行完成一个fp16的16*16与16*16的矩阵乘法,涵盖matmul、conv2d、linear等操作;向量计算单元负责执行向量运算,算力低于矩阵计算单元但是灵活度高,涵盖各种基本的计算类型和许多定制的计算类型,主要包括fp16,fp32,int32,int8等;标量计算单元负责各类标量运算和程序的流程控制。

此外还有片上缓冲区、储存转换单元、控制单元等。存储单元为各个计算单元提供被转置过并符合要求的数据, 计算单元返回运算的结果给存储单元, 控制单元为计算单元和存储单元提供指令控制, 三者相互协调合作完成计算任务。

PyTorch模型迁移

目前业界较为常用的深度学习框架为PyTorch,为了使基于PyTorch开发的算法模型可以在昇腾NPU设备上运行,需要对模型进行迁移。

transfer

上图展示了模型迁移的全流程包括脚本迁移、模型训练、精度调优、性能调优、模型固化五个过程。

  1. 脚本迁移是对PyTorch训练脚本进行少量修改,使其可以在昇腾AI处理器上运行。
  2. 模型训练是通过训练数据对网络权重进行迭代优化,并在训练过程中评估模型准确度。
  3. 精度调优是对GPU和NPU模型精度进行对比,通过检查代码和超参、检查模型结构、检查输入数据、检查损失函数曲线等方法判断NPU模型的精度是否达到预期。
  4. 性能调优是对NPU模型的运行性能进行优化,常用的方法有使用自动混合精度、替换亲和接口、使用AOE工具对网络进行子图调优、算子调优与梯度优化等。

脚本迁移

将基于PyTorch的训练脚本迁移到昇腾AI处理器上进行训练,目前有以下3种方式:自动迁移(推荐)、工具迁移、手工迁移,且迁移前要保证该脚本能在GPU、CPU上运行。

自动迁移

在训练脚本中导入脚本转换库,然后运行脚本进行训练。脚本在运行时会自动将CUDA接口替换为昇腾AI处理器支持的NPU接口,整体过程为边训练边转换。(该方法仅支持PyTorch1.8及以上版本)

1
2
3
import torch
import torch_npu
from torch_npu.contrib import transfer_to_npu

工具迁移

使用脚本迁移工具将训练脚本中的CUDA接口替换为昇腾AI处理器支持的NPU接口,并生成迁移报告(脚本转换日志、不支持算子列表、脚本修改记录),整体过程为先转换脚本再进行训练。

  1. 安装依赖

    1
    2
    3
    pip3 install pandas         #pandas版本号需大于或等于1.2.4
    pip3 install libcst #Python语法树解析器,用于解析Python文件
    pip3 install jedi #可选,用于跨文件解析,建议安装
  2. 进入迁移工具所在路径

    1
    cd Ascend-cann-toolkit安装目录/ascend-toolkit/latest/tools/ms_fmk_transplt/
  3. 进入分析迁移工具所在路径,执行脚本迁移任务。

    1
    ./pytorch_gpu2npu.sh -i 原始脚本路径 -o 脚本迁移结果输出路径 -v 原始脚本框架版本 [-r 自定义规则json文件路径] [-s] [-sim] [-a 模型名称] [-m] [distributed -t 目标模型变量名 -m 训练脚本的入口文件]

    参数说明查看工具迁移

  4. 查看结果文件。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    ├── xxx_msft/xxx_msft_multi          // 脚本迁移结果输出目录
    │ ├── 生成脚本文件 // 与迁移前的脚本文件目录结构一致
    │ ├── msFmkTranspltlog.txt // 脚本迁移过程日志文件,日志文件限制大小为1M,若超过限制将分多个文件进行存储,最多不会超过10个
    │ ├── cuda_op_list.csv // 分析出的cuda算子列表
    │ ├── unknown_api.csv // 支持情况存疑的API列表
    │ ├── unsupported_api.csv // 不支持的API列表
    │ ├── change_list.csv // 修改记录文件
    │ ├── run_distributed_npu.sh // 多卡启动shell脚本
    │ ├── ascend_function // 如果启用了Replace Unsupported APIs参数,会生成该包含等价算子的目录
    │ ├── ascend_modelarts_function
    │ │ ├── modelarts_path_manager.py // 启用ModelArts参数,会生成该路径映射适配层代码文件
    │ │ ├── path_mapping_config.py // 启用ModelArts参数,会生成该路径映射配置文件

手工迁移

手工迁移直接将脚本中与GPU相关的库、接口、配置等修改为NPU相关的库、接口、配置等。

手工迁移前,若PyTorch代码中多卡训练采用的是DP方法,需要将DP改为DDP。

  • 单卡迁移

    1. 导入NPU库

      1
      2
      import torch
      import torch_npu
    2. 指定NPU为训练设备

      1
      2
      3
      4
      5
      6
      7
      # .to(device)方式
      # device = torch.device('cuda:{}'.format(args.gpu))
      device = torch.device('npu:{}'.format(args.gpu))

      # .cuda方式
      # torch.cuda.set_device(args.gpu)
      torch_npu.npu.set_device(args.gpu)
    3. 替换CUDA接口

      1
      2
      3
      4
      5
      6
      7
      8
      9
      10
      11
      12
      13
      # 检查硬件可用性
      # torch.cuda.is_available()
      torch_npu.npu.is_available()

      # 指定数据集所在设备
      # images = images.cuda(args.gpu, non_blocking=True)
      # target = target.cuda(args.gpu, non_blocking=True)
      images = images.npu(args.gpu, non_blocking=True)
      target = target.npu(args.gpu, non_blocking=True)

      # 指定模型所在设备
      # model.cuda(args.gpu)
      model.npu(args.gpu)
  • 多卡迁移
    多卡代码迁移中,除了修改单卡中的各项外,还需要将通信方式由“nccl”切换为“hccl”。

    1
    2
    3
    # **为端口号,根据实际选择一个闲置端口填写
    # dist.init_process_group(backend='nccl', init_method = "tcp//:127.0.0.1:**", word_size=args.world.size, rank = args.rank)
    dist.init_process_group(backend='hccl', init_method = "tcp//:127.0.0.1:**", word_size=args.world.size, rank = args.rank)

在迁移过程中可能会遇到失败的情况,所以在手工迁移时可以通过打点、hook以及查看Host日志的方式查看错误信息。

混合精度

混合精度训练是在训练时混合使用单精度(float32)与半精度(float16)数据类型,将两者结合在一起,并使用相同的超参数实现了与float32几乎相同的精度。在迁移完成、训练开始之前,基于NPU芯片的架构特性,开启混合精度可以提升模型的性能。可以通过使用PyTorch1.8.1及以上版本框架内置的AMP功能模块来使能混合精度训练,代码实现如下。

  1. 引入相关模块

    1
    2
    3
    4
    5
    6
    import time

    import torch
    import torch.nn as nn
    import torch_npu
    from torch_npu.npu import amp # 导入AMP模块
  2. 在模型、优化器定义之后,定义AMP功能中的GradScaler。

    1
    2
    3
    4
    5
    model = CNN().to(device)
    train_dataloader = DataLoader(train_data, batch_size=batch_size) # 定义DataLoader
    loss_func = nn.CrossEntropyLoss().to(device) # 定义损失函数
    optimizer = torch.optim.SGD(model.parameters(), lr=0.1) # 定义优化器
    scaler = amp.GradScaler() # 在模型、优化器定义之后,定义GradScaler
  3. 在训练代码中添加AMP功能相关的代码开启AMP。

    1
    2
    3
    4
    5
    6
    7
    8
    9
    10
    11
    12
    for epo in range(epochs):
    for imgs, labels in train_dataloader:
    imgs = imgs.to(device)
    labels = labels.to(device)
    with amp.autocast():
    outputs = model(imgs) # 前向计算
    loss = loss_func(outputs, labels) # 损失函数计算
    optimizer.zero_grad()
    # 进行反向传播前后的loss缩放、参数更新
    scaler.scale(loss).backward() # loss缩放并反向传播
    scaler.step(optimizer) # 更新参数(自动unscaling)
    scaler.update() # 基于动态Loss Scale更新loss_scaling系数

相关原理可以参考自动混合精度

模型训练

迁移成功后的脚本可在昇腾AI处理器上执行单卡训练,也可以在多卡上执行分布式训练。
在分布式训练中,性能随着启动方式的不同而不同,性能从低到高依次为:

  1. python -m torch.distributed.lanunch --nproc_per_node=*--master_port=*

  2. mp.spwan

  3.    for i in $(seq 0 8)
          do
          taskset -c 0-24 python3.7 *
          done