68 Star 388 Fork 847

Ascend / pytorch

加入 Gitee
与超过 1200万 开发者一起发现、参与优秀开源项目,私有仓库也完全免费 :)
免费加入
克隆/下载
PyTorch网络模型移植&训练指南.md 218.40 KB
一键复制 编辑 原始数据 按行查看 历史

PyTorch网络模型移植&训练指南

概述

当前阶段针对PyTorch框架实现的对接适配昇腾AI处理器的方案为在线对接方案。

方案特性及优点

昇腾AI处理器的加速实现方式是以各种算子为粒度进行调用(OP-based),即通过AscendCL调用一个或几个D亲和算子组合的形式,代替原有GPU的实现方式。其逻辑模型如图1所示。

图 1 逻辑模型

当前选择在线对接适配方案的主要原因有以下几点:

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

约束与限制

  • infershape阶段算子不支持unknowshape的推导。
  • cube计算的算子只支持float16。
  • 不支持float16类型的inf/nan数据输入输出 。
  • 出现4D以上的format时不能降维。
  • Apex当前版本的实现方式为python实现,不支持APEX中的自定义优化CUDA Kernel。
  • Apex当前版本只支持适配昇腾AI处理器的混合精度计算和多种融合优化器功能,其他功能暂未支持。
  • 集合通信约束:
    • 数据并行模式中不同device上执行的图相同。
    • 只支持1/2/4/8P粒度的分配。
    • 只支持int8,int32,float16和float32数据类型。

迁移流程

模型迁移主要指将开源社区中实现过的模型迁移到昇腾AI处理器上,主要流程如图1所示。

图 1 迁移流程

表 1 迁移流程说明

场景

说明

模型选取

选取需要迁移的模型。

模型移植评估

详情请参见模型移植评估

算子开发

详情请参见《PyTorch算子开发指南》

环境准备

详情请参见环境准备

模型迁移

详情请参见模型迁移

模型训练

详情请参见模型训练

错误分析

详情请参见《CANN 软件安装指南》中“日志参考>日志操作”章节设置日志级别《CANN 软件安装指南》中“开发工具>AI Core Error分析工具”章节。

性能调优和分析

详情请参见性能调优和分析

精度调测

详情请参见精度调测

模型保存与转换

详情请参见模型保存与转换《CANN 软件安装指南》中“ATC模型转换”章节。

应用软件开发

详情请参见《CANN 软件安装指南》中”应用开发(c++)“章节。

FAQ

主要涉及环境准备、模型迁移、模型调测和其他常见问题的解决方法。详情请参见FAQ

快速上手

简介

对ResNet50模型进行迁移,帮助用户快速了解迁移过程。

模型选取

本样例基于PyTorch官网提供的Imagenet数据集训练模型main.py脚本进行适配昇腾910 AI处理器的迁移。

模型移植评估

模型是否可以迁移成功主要取决于模型算子是否支持昇腾AI处理器。故需要对模型算子对昇腾AI处理器的支持性进行评估,一般有两种方式评估算子支持性

  • 模型迁移前,使用dump op方法获取算子信息,与《PyTorch API 支持清单》中自定义算子进行比较,确定是否支持。
  • 模型迁移后,在昇腾设备上进行运行训练脚本,若存在不支持昇腾AI设备的算子,会提示报错信息。

若存在不支持算子,可以采用修该模型用等价支持的算子替换或者参考《PyTorch算子开发指南》进行算子开发。

ResNet50模型用到的算子已经在昇腾AI处理器上支持。

环境准备

请参见《PyTorch安装指南》 进行CANN软件安装、PyTorch框架及混合精度模块安装,并配置环境变量。

参考PyTorch examples 准备模型运行所需要的Python环境及依赖。

硬件支持

aarch64架构推荐使用:Atlas800-9000+kunpeng920+Ascend910+NVMe 3.2T+750GRAM

X86_64架构推荐使用:Atlas800-9010+Intel Platinum8260+Ascend910+NVMe 3.2T+750GRAM

模型迁移

在main.py训练脚本的基础上进行修改,实现模型的单卡训练和单机多卡训练迁移。

单卡训练迁移

  1. 在main.py脚本中导入torch.npu模块。

    import torch.npu
  2. 在main.py中定义训练设备。

    CALCULATE_DEVICE = "npu:0"
  3. 修改参数以及判断选项,使其只在昇腾910 AI处理器上进行训练。

    代码位置:main.py文件中的main_worker()函数:

    def main_worker(gpu, ngpus_per_node, args):
        global best_acc1
        # 原代码为使用GPU进行训练,原代码如下:
        # args.gpu = gpu
        ############## npu modify begin #############
        args.gpu = None
        ############## npu modify end #############
        
        if args.gpu is not None:
            print("Use GPU: {} for training".format(args.gpu))
            
        if args.distributed:
            if args.dist_url == "env://" and args.rank == -1:
                args.rank = int(os.environ["RANK"])
            if args.multiprocessing_distributed:
                # For multiprocessing distributed training, rank needs to be the
                # global rank among all the processes
                args.rank = args.rank * ngpus_per_node + gpu
            dist.init_process_group(backend=args.dist_backend, init_method=args.dist_url,
                                    world_size=args.world_size, rank=args.rank)
        # create model
        if args.pretrained:
            print("=> using pre-trained model '{}'".format(args.arch))
            model = models.__dict__[args.arch](pretrained=True)
        else:
            print("=> creating model '{}'".format(args.arch))
            model = models.__dict__[args.arch]()
        # 原代码中需要判断是否在GPU上进行训练,原代码如下:
        # if not torch.cuda.is_available():
            # print('using CPU, this will be slow')
        # elif args.distributed:
        ############## npu modify begin #############
        # 迁移后为直接判断是否进行分布式训练,去掉判断是否在GPU上进行训练
        if args.distributed:
        ############## npu modify end #############
            # For multiprocessing distributed, DistributedDataParallel constructor
            # should always set the single device scope, otherwise,
            # DistributedDataParallel will use all available devices.
            if args.gpu is not None:
               ......
  4. 将模型以及损失函数迁移到昇腾910 AI处理器上进行计算。

    代码位置:main.py文件中的main_worker()函数:

    elif args.gpu is not None:
            torch.cuda.set_device(args.gpu)
            model = model.cuda(args.gpu)
        else:
            # DataParallel will divide and allocate batch_size to all available GPUs
            if args.arch.startswith('alexnet') or args.arch.startswith('vgg'):
                model.features = torch.nn.DataParallel(model.features)
                model.cuda()
            else:
                # 原代码使用torch.nn.DataParallel()类来用多个GPU加速训练
                # model = torch.nn.DataParallel(model).cuda()
            ############## npu modify begin #############
                # 将模型迁移到NPU上进行训练。
               model = model.to(CALCULATE_DEVICE)
           ############## npu modify end #############
        # 原代码中损失函数是在GPU上进行计算
        # # define loss function (criterion) and optimizer
        # criterion = nn.CrossEntropyLoss().cuda(args.gpu)
        ############## npu modify begin #############
        # 将损失函数迁移到NPU上进行计算。
        criterion = nn.CrossEntropyLoss().to(CALCULATE_DEVICE)   
        ############## npu modify end #############
  5. 将数据集目标结果target修改成int32类型解决算子报错问题;将数据集迁移到昇腾910 AI处理器上进行计算。

    • 代码位置:main.py文件中的train()函数:

         for i, (images, target) in enumerate(train_loader):
              # measure data loading time
              data_time.update(time.time() - end)
      
              if args.gpu is not None:
                  images = images.cuda(args.gpu, non_blocking=True)
              # 原代码中训练数据集在GPU上进行加载计算,原代码如下:
              # if torch.cuda.is_available():
                  # target = target.cuda(args.gpu, non_blocking=True)
              ############## npu modify begin #############
              # 将数据集迁移到NPU上进行计算并修改target数据类型,以提升性能
              if 'npu' in CALCULATE_DEVICE:     
                  target = target.to(torch.int32)                      
              images, target = images.to(CALCULATE_DEVICE, non_blocking=True), target.to(CALCULATE_DEVICE, non_blocking=True)
              ############## npu modify end #############
    • 代码位置:main.py文件中的validate()函数:

          with torch.no_grad():
              end = time.time()
              for i, (images, target) in enumerate(val_loader):
                  if args.gpu is not None:
                      images = images.cuda(args.gpu, non_blocking=True)
                  # 原代码中训练数据集在GPU上进行加载计算,原代码如下:
                  # if torch.cuda.is_available():
                      # target = target.cuda(args.gpu, non_blocking=True)
                  ############## npu modify begin #############
                  # 将数据集迁移到NPU上进行计算并修改target数据类型
                  if 'npu' in CALCULATE_DEVICE:
                      target = target.to(torch.int32)
                images, target = images.to(CALCULATE_DEVICE, non_blocking=True), target.to(CALCULATE_DEVICE, non_blocking=True)
                 ############## npu modify end #############
  6. 设置当前正在使用的device。

    代码位置:main.py文件中的主函数入口:

    if __name__ == '__main__':
        ############## npu modify begin #############
        if 'npu' in CALCULATE_DEVICE:
           torch.npu.set_device(CALCULATE_DEVICE)
        ############## npu modify begin #############
        main()

单机多卡训练修改

  1. main.py增加头文件以支持基于PyTorch框架的模型在昇腾910 AI处理器上训练及进行混合精度训练。

    import torch.npu
    from apex import amp
  2. 参数设置增加以下参数,包括指定参与训练的昇腾910 AI处理器以及进行混合精度训练需要的参数。

    parser.add_argument('--device', default='npu', type=str, help='npu or gpu')                        
    parser.add_argument('--addr', default='10.136.181.115', type=str, help='master addr')                        
    parser.add_argument('--device-list', default='0,1,2,3,4,5,6,7', type=str, help='device id list')
    parser.add_argument('--amp', default=False, action='store_true', help='use amp to train the model')                    
    parser.add_argument('--loss-scale', default=1024., type=float,
                        help='loss scale using in amp, default -1 means dynamic')
    parser.add_argument('--opt-level', default='O2', type=str,
                        help='loss scale using in amp, default -1 means dynamic')
  3. 创建由device_id到process_id的映射函数,指定device进行训练。在main.py函数中增加以下接口。

    def device_id_to_process_device_map(device_list):
        devices = device_list.split(",")
        devices = [int(x) for x in devices]
        devices.sort()
    
        process_device_map = dict()
        for process_id, device_id in enumerate(devices):
            process_device_map[process_id] = device_id
    
        return process_device_map
  4. 指定训练服务器的ip和端口。

    代码位置:main.py文件中的主函数main()(修改部分为字体加粗部分)。

    def main():
        args = parser.parse_args()
        ############## npu modify begin #############
        os.environ['MASTER_ADDR'] = args.addr 
        os.environ['MASTER_PORT'] = '29688'
        ############## npu modify end #############
  5. 创建由device_id到process_id的映射参数,获取单节点昇腾910 AI处理器数量。

    代码位置:main.py文件中的主函数main()。

    args.distributed = args.world_size > 1 or args.multiprocessing_distributed
    ############## npu modify begin #############
    args.process_device_map = device_id_to_process_device_map(args.device_list)
    if args.device == 'npu':
        ngpus_per_node = len(args.process_device_map)
    else:
        ngpus_per_node = torch.cuda.device_count()
    ############## npu modify end #############
    # 原代码如下:
    # ngpus_per_node = torch.cuda.device_count()
  6. 获取进程process_id对应的昇腾910 AI处理器编号,指定在对应的昇腾910 AI处理器上进行训练。

    代码位置:main.py文件中的main_worker()。

    def main_worker(gpu, ngpus_per_node, args):   
        global best_acc1
        ############## npu modify begin #############
        args.gpu = args.process_device_map[gpu]
        ############## npu modify end #############
        # 原代码如下:
        # args.gpu = gpu
  7. 初始化进程组,屏蔽掉初始化方式。

    代码位置:main.py文件中的main_worker()。

          ############## npu modify begin #############  
            if args.device == 'npu':
                dist.init_process_group(backend=args.dist_backend, #init_method=args.dist_url,
                                    world_size=args.world_size, rank=args.rank)
            else:
                dist.init_process_group(backend=args.dist_backend, init_method=args.dist_url,         
                                    world_size=args.world_size, rank=args.rank)
          ############## npu modify begin #############  
          # 原代码如下:
          # dist.init_process_group(backend=args.dist_backend, init_method=args.dist_url,
          #                          world_size=args.world_size, rank=args.rank)
  8. 要进行分布式训练且需要引入混合精度模块,并且需要将模型迁移到昇腾AI处理器上,因此需要屏蔽掉原始代码中判断是否为分布式训练以及模型是否在GPU上进行训练的代码部分。

    代码位置:main.py文件中的main_worker()。

        # create model
        if args.pretrained:
            print("=> using pre-trained model '{}'".format(args.arch))
            model = models.__dict__[args.arch](pretrained=True)
        else:
            print("=> creating model '{}'".format(args.arch))
            model = models.__dict__[args.arch]()
    ############## npu modify begin #############
        # 代码中添加如下内容
        # 指定训练设备为昇腾AI处理器
        loc = 'npu:{}'.format(args.gpu)
        torch.npu.set_device(loc)
        # 计算用于训练的batch_size和workers
        args.batch_size = int(args.batch_size / ngpus_per_node)
        args.workers = int((args.workers + ngpus_per_node - 1) / ngpus_per_node)
    ############## npu modify end #############
        # 原始代码如下,需屏蔽掉,已注释
        # if not torch.cuda.is_available():
        #     print('using CPU, this will be slow')
        # elif args.distributed:
        #     # For multiprocessing distributed, DistributedDataParallel constructor
        #     # should always set the single device scope, otherwise,
        #     # DistributedDataParallel will use all available devices.
        #     if args.gpu is not None:
        #         torch.cuda.set_device(args.gpu)
        #         model.cuda(args.gpu)
        #         # When using a single GPU per process and per
        #         # DistributedDataParallel, we need to divide the batch size
        #         # ourselves based on the total number of GPUs we have
        #         args.batch_size = int(args.batch_size / ngpus_per_node)
        #         args.workers = int((args.workers + ngpus_per_node - 1) / ngpus_per_node)
        #         model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.gpu])
        #     else:
        #         model.cuda()
        #         # DistributedDataParallel will divide and allocate batch_size to all
        #         # available GPUs if device_ids are not set
        #         model = torch.nn.parallel.DistributedDataParallel(model)
        # elif args.gpu is not None:
        #     torch.cuda.set_device(args.gpu)
        #     model = model.cuda(args.gpu)
        # else:
        #     # DataParallel will divide and allocate batch_size to all available GPUs
        #     if args.arch.startswith('alexnet') or args.arch.startswith('vgg'):
        #         model.features = torch.nn.DataParallel(model.features)
        #         model.cuda()
        #     else:
        #         model = torch.nn.DataParallel(model).cuda()
  9. 屏蔽掉损失函数、优化器和断点训练部分,将这部分在后面与混合精度训练结合起来。

    代码位置:main.py文件中的main_worker()。

        # 屏蔽掉原始代码,已注释
        # # define loss function (criterion) and optimizer
        # criterion = nn.CrossEntropyLoss().cuda(args.gpu)
        #
        # optimizer = torch.optim.SGD(model.parameters(), args.lr,
        #                             momentum=args.momentum,
        #                             weight_decay=args.weight_decay)
        #
        # # optionally resume from a checkpoint
        # if args.resume:
        #     if os.path.isfile(args.resume):
        #         print("=> loading checkpoint '{}'".format(args.resume))
        #         if args.gpu is None:
        #             checkpoint = torch.load(args.resume)
        #         else:
        #             # Map model to be loaded to specified single gpu.
        #             loc = 'cuda:{}'.format(args.gpu)
        #             checkpoint = torch.load(args.resume, map_location=loc)
        #         args.start_epoch = checkpoint['epoch']
        #         best_acc1 = checkpoint['best_acc1']
        #         if args.gpu is not None:
        #             # best_acc1 may be from a checkpoint from a different GPU
        #             best_acc1 = best_acc1.to(args.gpu)
        #         model.load_state_dict(checkpoint['state_dict'])
        #         optimizer.load_state_dict(checkpoint['optimizer'])
        #         print("=> loaded checkpoint '{}' (epoch {})"
        #               .format(args.resume, checkpoint['epoch']))
        #     else:
        #         print("=> no checkpoint found at '{}'".format(args.resume))
        #
        # cudnn.benchmark = True
  10. 数据加载器,结合了数据集和取样器,并且可以提供多个线程处理数据集。使用昇腾AI处理器进行训练,需要将pin_memory设置为False;由于当前仅支持固定shape下的训练,数据流中剩余的样本数可能小于batch大小,因此需要将drop_last设置为True;另外需要将验证部分数据集shuffle设置为True

    代码位置:main.py文件中的main_worker()。

        ############## npu modify begin #############
        train_loader = torch.utils.data.DataLoader(
            train_dataset, batch_size=args.batch_size, shuffle=(train_sampler is None),
            num_workers=args.workers, pin_memory=False, sampler=train_sampler, drop_last=True)
    
        val_loader = torch.utils.data.DataLoader(
            datasets.ImageFolder(valdir, transforms.Compose([
                transforms.Resize(256),
                transforms.CenterCrop(224),
                transforms.ToTensor(),
                normalize,
            ])),
            batch_size=args.batch_size, shuffle=True,
            num_workers=args.workers, pin_memory=False, drop_last=True)
            ############## npu modify end #############
  11. 进行损失函数及优化器构建,将模型、损失函数迁移到昇腾AI处理器上;将优化器、模型与混合精度模块进行结合以支持混合精度训练;将断点训练部分与混合精度模块结合以支持混合精度训练。

    代码位置:main.py文件中的main_worker()中验证数据加载后。

        val_loader = torch.utils.data.DataLoader(
            datasets.ImageFolder(valdir, transforms.Compose([
                transforms.Resize(256),
                transforms.CenterCrop(224),
                transforms.ToTensor(),
                normalize,
            ])),
            batch_size=args.batch_size, shuffle=True,
            num_workers=args.workers, pin_memory=False, drop_last=True)
    
        ############## npu modify begin #############
        model = model.to(loc)
        # define loss function (criterion) and optimizer
        criterion = nn.CrossEntropyLoss().to(loc)
        optimizer = torch.optim.SGD(model.parameters(), args.lr,
                                    momentum=args.momentum,
                                    weight_decay=args.weight_decay)
    
        if args.amp:
            model, optimizer = amp.initialize(model, optimizer, opt_level=args.opt_level, loss_scale=args.loss_scale)
        model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.gpu])
    
        # optionally resume from a checkpoint
        if args.resume:
            if os.path.isfile(args.resume):
                print("=> loading checkpoint '{}'".format(args.resume))
                checkpoint = torch.load(args.resume, map_location=loc)
                args.start_epoch = checkpoint['epoch']
                best_acc1 = checkpoint['best_acc1']
                model.load_state_dict(checkpoint['state_dict'])
                optimizer.load_state_dict(checkpoint['optimizer'])
                if args.amp:
                  amp.load_state_dict(checkpoint['amp'])
                print("=> loaded checkpoint '{}' (epoch {})"
                      .format(args.resume, checkpoint['epoch']))
            else:
                print("=> no checkpoint found at '{}'".format(args.resume))
    
        cudnn.benchmark = True
        ############## npu modify end #############
  12. 断点checkpoint保存需要与混合精度训练结合,修改如下。

    代码位置:main.py文件中的main_worker()(修改部分为字体加粗部分)。

            # remember best acc@1 and save checkpoint
            is_best = acc1 > best_acc1
            best_acc1 = max(acc1, best_acc1)
    
            if not args.multiprocessing_distributed or (args.multiprocessing_distributed
                    and args.rank % ngpus_per_node == 0):
            ############## npu modify begin #############
                if args.amp:
                    save_checkpoint({
                        'epoch': epoch + 1,
                        'arch': args.arch,
                        'state_dict': model.state_dict(),
                        'best_acc1': best_acc1,
                        'optimizer' : optimizer.state_dict(),
                        'amp': amp.state_dict(),
                    }, is_best)
                else:
                    save_checkpoint({
                        'epoch': epoch + 1,
                        'arch': args.arch,
                        'state_dict': model.state_dict(),
                        'best_acc1': best_acc1,
                        'optimizer' : optimizer.state_dict(),
                    }, is_best)
             ############## npu modify end #############
  13. 训练时,需要将数据集迁移到昇腾AI处理器上,修改如下:

    代码位置:main.py文件中的train()(修改部分为字体加粗部分)。

        for i, (images, target) in enumerate(train_loader):
            # measure data loading time
            data_time.update(time.time() - end)
            ############## npu modify begin #############
            loc = 'npu:{}'.format(args.gpu)
            target = target.to(torch.int32)
            images, target = images.to(loc, non_blocking=False), target.to(loc, non_blocking=False)
            ############## npu modify end #############
            # 原模型代码如下:
            # if args.gpu is not None:
            #     images = images.cuda(args.gpu, non_blocking=True)
            # if torch.cuda.is_available():
            #     target = target.cuda(args.gpu, non_blocking=True)
  14. 标记反向传播.backward()发生的位置,这样混合精度模块就可以进行Loss Scaling并清除每次迭代的状态,代码如下:

    代码位置:main.py文件中的train()(修改部分为字体加粗部分)。

            optimizer.zero_grad()
            ############## npu modify begin #############
            if args.amp:
                with amp.scale_loss(loss, optimizer) as scaled_loss:
                    scaled_loss.backward()
            else:
                loss.backward()
            ############## npu modify end #############
            # 原代码如下注释部分:
            # loss.backward()
            optimizer.step()
  15. 验证时,需要将验证数据集迁移到昇腾AI处理器上,修改如下:

    代码位置:main.py文件中的validate()。

        with torch.no_grad():
            end = time.time()
            for i, (images, target) in enumerate(val_loader):
            ############## npu modify begin #############
                loc = 'npu:{}'.format(args.gpu)
                target = target.to(torch.int32)
                images, target = images.to(loc, non_blocking=False), target.to(loc, non_blocking=False)
            ############## npu modify end #############
            # 原模型代码如下注释部分:
            # if args.gpu is not None:
            #     images = images.cuda(args.gpu, non_blocking=True)
            # if torch.cuda.is_available():
            #     target = target.cuda(args.gpu, non_blocking=True)

模型训练

准备数据集

准备数据集并上传到运行环境的目录下,例如:/home/data/resnet50/imagenet。

执行命令

单卡训练:

python3 main.py /home/data/resnet50/imagenet --batch-size 128 \       # 训练批次大小
                                               --lr 0.1 \               # 学习率
                                               --epochs 90 \            # 训练迭代轮数
                                               --arch resnet50 \        # 模型架构
                                               --world-size 1 \
                                               --rank 0 \         
                                               --workers 40 \           # 加载数据进程数
                                               --momentum 0.9 \         # 动量  
                                               --weight-decay 1e-4      # 权重衰减

分布式训练:

python3 main.py /home/data/resnet50/imagenet --addr='1.1.1.1' \                # 示例IP地址,请根据实际修改
                                               --seed 49  \                      # 随机种子
                                               --workers 160 \                   # 加载数据进程数
                                               --lr 0.8 \
                                               --print-freq 1 \
                                               --arch resnet50 \                 # 模型架构
                                               --dist-url 'tcp://127.0.0.1:50000' \                   
                                               --dist-backend 'hccl' \
                                               --multiprocessing-distributed \   # 使用多卡训练
                                               --world-size 1 \
                                               --batch-size 2048 \               # 训练批次大小
                                               --epochs 90 \                     # 训练迭代轮数
                                               --rank 0 \
                                               --device-list '0,1,2,3,4,5,6,7' \
                                               --amp                             # 使用混合精度训练

说明: dist-backend需配置成hccl以支持在昇腾AI设备上进行分布式训练。

模型移植评估

  1. 在选取模型时,尽可能选取权威Pytorch模型实现仓作为标杆,包括但不限于Pytorch(example/vision等)、facebookresearch(Detectron/detectron2等)和open-mmlab(mmdetection/mmpose等)。

  2. 查看算子适配情况。将原始模型及训练脚本迁移到昇腾AI处理器上之前,可以将原始模型及训练脚本在CPU上进行训练,使用dump op方法获取当前模型算子列表并在《PyTorch API 支持清单》中查找该算子查看是否支持。dump op方法参见dump op方法,当有不支持算子时参见《PyTorch算子开发指南》进行算子开发。

    说明: 查看算子适配情况也可以先将模型及训练脚本迁移到昇腾AI处理器(迁移方法参见下文)进行训练来查看报错信息。一般会提示不能在昇腾AI处理器的backend下运行某个算子(第一个不支持的算子)。

环境准备

请参见《PyTorch安装指南》 进行PyTorch及混合精度模块安装,并配置环境变量。

模型迁移

工具迁移

Ascend平台提供了脚本转换工具使用户能通过命令行方式将训练脚本迁移到昇腾AI处理器上进行训练,命令行方式工具详细使用说明参见下文。除命令行方式外,用户也可通过MindStudio中集成的PyTorch GPU2Ascend功能进行迁移,详情请参见《MindStudio 用户指南》

功能介绍

简介

昇腾NPU是AI算力的后起之秀,但目前训练和在线推理脚本大多是基于GPU的。由于NPU与GPU的架构差异,基于GPU的训练和在线推理脚本不能直接在NPU上使用,脚本转换工具提供了将基于GPU的脚本转换为基于NPU的脚本的自动化方法,节省了人工手动进行脚本迁移的学习成本与工作量,大幅提升了迁移效率。

说明:

  • 脚本转换工具根据适配规则,对用户脚本给出修改建议并提供转换功能,大幅度提高了脚本迁移速度,降低了开发者的工作量。
  • 此脚本转换工具当前仅支持PyTorch训练脚本转换。

模型支持

目前支持模型请参考《昇腾Modelzoo社区》 ,筛选类型分类:“训练”、框架分类:“Pytorch”的Pytorch训练模型。

系统要求

脚本转换工具支持Ubuntu 18.04、CentOS 7.6或EulerOS 2.8。

环境准备

详情请参考《CANN 软件安装指南》 安装开发环境。

操作指南

参数说明

表 1 参数说明

参数

参数说明

取值示例

-i

--input

  • 要进行转换的原始脚本文件所在文件夹路径或文件路径。
  • 必选。
  • /home/username/fmktransplt
  • /home/username/fmktransplt.py

-o

--output

  • 脚本转换结果文件输出路径。会在该路径下输出带有msft后缀的文件夹。
  • 必选。

/home/username/fmktransplt_output

-r

--rule

  • 用户自定义通用转换规则的json文件路径,主要分为:函数参数修改、函数名称修改和模块名称修改三部分。
  • 可选。

/home/username/fmktransplt_rule.json

-s

--specify-device

  • 可以通过环境变量设置指定device作为高级特性,但有可能导致原本脚本中分布式功能失效。
  • 可选。

-

-sim

--similar

  • 用功能相似的API替换某些不支持的API,但有可能导致准确性和性能下降。
  • 可选。

-

distributed

将GPU单卡脚本转换为NPU多卡脚本,仅支持使用torch.utils.data.DataLoader方式加载数据的场景,指定此参数时,才可以指定以下两个参数。

  • -m,--main:训练脚本的入口python文件,必选。
  • -t,--target model:目标模型变量名,默认为'model',可选。

-

-h

--help

显示帮助信息。

-

**自定义规则文件**

自定义转换规则样例如下:

{
    "rules": {
        "ArgsModifyRule": [
            {
                "func_name": "name1",
                "arg_idx": 0,
                "arg_new": "agrs0"
            },
            {
                "func_name": "name2",
                "arg_idx": 0,
                "arg_new": "agrs0"
            }
        ],
        "FuncNameModifyRule": [
            {
                "old_name": "func",
                "new_name": "new_func"
            }
        ],
        "ModuleNameModifyRule": [
            {
                "old_name": "module",
                "new_name": "new_module",
                "parent_module":"parent_module"
            }
        ]
    }
}

表 2 参数说明

参数

说明

ArgsModifyRule

函数参数修改

func_name

函数名称

arg_idx

参数的位置

arg_new

新的参数

FuncNameModifyRule

函数名称修改

ModuleNameModifyRule

模块名称修改

old_name

旧名称

new_name

新名称

parent_module

父级模块全名

例如torch.cuda.amp,amp的父级模块全名为torch.cuda。

**执行转换**
  1. 进入脚本转换工具所在路径。

    cd Ascend-cann-toolkit安装目录/ascend-toolkit/{version}/{arch}-linux/toolkit/tools/ms_fmk_transplt
  2. 执行脚本转换工具。

    python3 ms_fmk_transplt.py -i 原始脚本路径 -o 脚本转换结果输出路径 [-r 自定义规则json文件路径] [-s] [-sim] [distributed -m 训练脚本的入口文件 -t 目标模型变量名]

    说明: distributed及其参数-m、-t在语句最后指定。

  3. 完成脚本转换。

结果解析

脚本转换完成后,进入脚本转换结果输出路径查看结果文件,以GPU单卡脚本转换为NPU多卡脚本为例。

├── xxx_msft                // 脚本转换结果输出目录,默认为原始脚本路径。xxx为原始脚本所在文件夹名称。
│   ├── 生成脚本文件                 // 与转换前的脚本文件目录结构一致。
│   ├── msFmkTranspltlog.txt                 // 脚本转换过程日志文件。
│   ├── unsupported_op.xlsx                // 不支持算子列表文件。
│   ├── change_list.csv                    // 修改记录文件。
│   ├── run_distributed_npu.sh            // 多卡启动shell脚本。

手工迁移

单P训练模型迁移

当前在线对接方案优点在于保证在昇腾AI处理器上训练与GPU的使用方式和风格保持一致。用户在使用在线对接方案时,只需在Python侧和Device相关操作中,指定device为昇腾AI处理器,即可完成用昇腾AI处理器在PyTorch对网络的开发、训练以及调试。针对单P模型训练,主要迁移改动如下。

迁移前GPU代码:

    CALCULATE_DEVICE = “gpu:0”  
    torch.cuda.set_device(CALCULATE_DEVICE)
    # 放到device的两种方法  
    model = model.cuda() # 写法1
    model = model.to(CALCULATE_DEVICE) # 写法2
    # 将输入也从host放到device
    images = images.to(CALCULATE_DEVICE)
    target = target.to(CALCULATE_DEVICE)

迁移到昇腾AI处理器上代码为:

    CALCULATE_DEVICE = “npu:0”   
    torch.npu.set_device(CALCULATE_DEVICE)   
    # 放到device的两种方法   
    model = model.npu() # 写法1
    model = model.to(CALCULATE_DEVICE) # 写法2
    # 将输入也从host放到device
    images = images.to(CALCULATE_DEVICE)
    target = target.to(CALCULATE_DEVICE)

更多迁移细节请参见单卡训练修改

多P训练模型迁移

多P训练模型迁移除了需在Python侧和Device相关操作中,指定device为昇腾AI处理器外,依然通过PyTorch的DistributedDataParallel方式来进行分布式训练,即在模型初始化阶段执行init_process_group,再将模型初始化为DistributedDataParallel模型。但须注意的是在初始化init_process_group时需要将backend配置为hccl并屏蔽掉初始化方式。

PyTorch分布式训练代码示例(部分代码省略):

import torch
import torch.distributed as dist
import torch.nn.parallel
def main():
    args = parser.parse_args()
    # 需屏蔽掉初始化方式
    dist.init_process_group(backend='hccl',# init_method=args.dist_url,
                                world_size=args.world_size, rank=args.rank)
    model = torch.nn.parallel.DistributedDataParallel(model, device_ids=[args.gpu]) # model需要下发到npu上
    train_loader = torch.utils.data.DataLoader(
        train_dataset, batch_size=args.batch_size, shuffle=(train_sampler is None),
        num_workers=args.workers, pin_memory=True, sampler=train_sampler)
    for epoch in range(args.start_epoch, args.epochs):
        acc1 = train(train_loader, model, criterion, optimizer, epoch, args,ngpus_per_node,
          lr_scheduler)

更多迁移细节请参见单机多卡训练修改

PyTorch接口替换

  1. 为了使昇腾AI处理器使用PyTorch框架的能力,需要对原生的PyTorch框架进行一定Device层面的适配,对外呈现是需要将跟cpu和cuda相关的接口进行切换;在进行网络迁移时,需要将某些设备相关的接口转换成跟昇腾AI处理器相关的接口,当前适配的设备相关接口请参见表1

    表 1 设备接口替换

    PyTorch原始接口

    适配昇腾AI处理器后的接口

    说明

    torch.cuda.is_available()

    torch.npu.is_available()

    判断当前环境上设备是否可用(不代表最后结果)。

    torch.cuda.current_device()

    torch.npu.current_device()

    获取当前正在使用的device。

    torch.cuda.device_count()

    torch.npu.device_count()

    获取当前环境上的设备数量。

    torch.cuda.set_device()

    torch.npu.set_device()

    设置当前正在使用的device。

    torch.tensor([1,2,3]).is_cuda

    torch.tensor([1,2,3]).is_npu

    判断某个tensor是否是cuda/npu设备上的格式。

    torch.tensor([1,2,3]).cuda()

    torch.tensor([1,2,3]).npu()

    将某个tensor转换成cuda/npu格式。

    torch.tensor([1,2,3]).to("cuda")

    torch.tensor([1,2,3]).to('npu')

    将某个tensor转换成cuda/npu格式。

    torch.cuda.synchronize()

    torch.npu.synchronize()

    同步等待事件完成。

    torch.cuda.device

    torch.npu.device

    生成一个device类,可以执行device相关操作。

    torch.cuda.Stream(device)

    torch.npu.Stream(device)

    生成一个stream对象。

    torch.cuda.stream(Stream)

    torch.npu.stream(Stream)

    多用于作用域限定。

    torch.cuda.current_stream()

    torch.npu.current_stream()

    获取当前stream。

    torch.cuda.default_stream()

    torch.npu.default_stream()

    获取默认stream。

    device = torch.device("cuda:0")

    device = torch.device("npu:0")

    指定一个设备。

    torch.autograd.profiler.profile

    (use_cuda=True)

    torch.autograd.profiler.profile

    (use_npu=True)

    指定执行profiler过程中使用cuda/npu。

    torch.cuda.Event()

    torch.npu.Event()

    返回某个设备上的事件。

  2. 用户在构建网络或进行网络迁移时,需要创建指定数据类型的tensor,在昇腾AI处理器上创建的tensor如下。

    表 2 tensor创建接口替换

    GPU tensor

    适配昇腾AI处理器后的接口

    torch.tensor([1,2,3],dtype=torch.long,device='cuda')

    torch.tensor([1,2,3],dtype=torch.long,device='npu')

    torch.tensor([1,2,3],dtype=torch.int,device='cuda')

    torch.tensor([1,2,3],dtype=torch.int,device='npu')

    torch.tensor([1,2,3],dtype=torch.half,device='cuda')

    torch.tensor([1,2,3],dtype=torch.half,device='npu')

    torch.tensor([1,2,3],dtype=torch.float,device='cuda')

    torch.tensor([1,2,3],dtype=torch.float,device='npu')

    torch.tensor([1,2,3],dtype=torch.bool,device='cuda')

    torch.tensor([1,2,3],dtype=torch.bool,device='npu')

    torch.cuda.BoolTensor([1,2,3])

    torch.npu.BoolTensor([1,2,3])

    torch.cuda.FloatTensor([1,2,3])

    torch.npu.FloatTensor([1,2,3])

    torch.cuda.IntTensor([1,2,3])

    torch.npu.IntTensor([1,2,3])

    torch.cuda.LongTensor([1,2,3])

    torch.npu.LongTensor([1,2,3])

    torch.cuda.HalfTensor([1,2,3])

    torch.npu.HalfTensor([1,2,3])

更多接口请参见《PyTorch API 支持清单》

混合精度

概述

基于NPU芯片的架构特性,会涉及到混合精度训练,即混合使用float16和float32数据类型的应用场景。使用float16代替float32有如下好处:

  • 对于中间变量的内存占用更少,节省内存的使用。
  • 因内存使用会减少,所以数据传出的时间也会相应减少。
  • float16的计算单元可以提供更快的计算性能。

但是,混合精度训练受限于float16表达的精度范围,单纯将float32转换成float16会影响训练收敛情况,为了保证部分计算使用float16来进行加速的同时能保证训练收敛,这里采用混合精度模块Apex来达到以上效果。混合精度模块Apex是一个集优化性能、精度收敛于一身的综合优化库。

适配昇腾AI处理器的混合精度模块Apex除了上述优点外,还能提升运算性能。具体如下:

  • Apex在混合精度运算过程中,会对模型的grad进行运算。开启combine_grad开关,可以加速这些运算。具体为将amp.initialize()接口参数combine_grad设置为True;
  • 适配后的Apex针对adadelta/adam/sgd/lamb做了昇腾AI处理器亲和性优化,得到的NPU融合优化器与原生算法保持一致,但运算速度更快。使用时只需将原有优化器替换为apex.optimizers.*(“*”为优化器名称,例如NpuFusedSGD)。
  • 适配后的Apex针对数据并行场景做了昇腾AI处理器亲和性优化,支持利用融合grad进行加速,同时保持计算逻辑一致性。通过开启combine_ddp开关,也就是将amp.initialize()接口参数combine_ddp设置为True并关闭DistributedDataParallel,即可开启该功能。

特性支持

混合精度模块功能和优化描述如表1所示。

表 1 混合精度模块功能

功能

描述

O1配置模式

Conv,Matmul等使用float16计算,其他如Softmax、BN使用float32。

O2配置模式

除了BN使用float32外,其他绝大部分使用float16。

静态Loss Scale功能

静态设置参数确保混合精度训练收敛。

动态Loss Scale功能

动态计算loss Scale值并判断是否溢出。

说明:

  • 当前版本的实现方式主要为python实现,不支持AscendCL或者CUDA优化。
  • 当前昇腾AI设备暂不支持原始Apex的FusedLayerNorm接口模块,如果模型原始脚本文件使用了FusedLayerNorm接口模块,需要在模型迁移过程中将脚本头文件“from apex.normalization import FusedLayerNorm“替换为“from torch.nn import LayerNorm“。

将混合精度模块集成到PyTorch模型中

  1. 使用apex混合精度模块需要首先从apex库中导入amp,代码如下:

    from apex import amp
  2. 导入amp模块后,需要初始化amp,使其能对模型、优化器以及PyTorch内部函数进行必要的改动,初始化代码如下:

    model, optimizer = amp.initialize(model, optimizer, combine_grad=True)
  3. 标记反向传播.backward()发生的位置,这样Amp就可以进行Loss Scaling并清除每次迭代的状态,代码如下:

    原始代码:

    loss = criterion(…) 
    loss.backward() 
    optimizer.step()

    修改以支持loss scaling后的代码:

    loss = criterion(…) 
    with amp.scale_loss(loss, optimizer) as scaled_loss:     
        scaled_loss.backward() 
    optimizer.step()

模型训练

训练脚本迁移完成后,需要参见《PyTorch安装指南》 运行环境变量章节配置环境变量,然后执行python3 _xxx_进行模型训练。具体样例请参考命令执行

说明: 执行“python3 xxx“命令时,须将python3软链接到与当前pytorch适配版本的python安装路径。

性能调优和分析

前提条件

  1. 参见模型调优样例改造开源代码,使模型能够正常运行,包括数据预处理,前向计算,loss计算,混合精度,反向计算,参数更新等。
  2. 模型迁移阶段优先关注模型是否能跑通,现有算子是否能满足,如果遇到不满足的算子需参见《PyTorch算子开发指南》进行算子适配开发。
  3. 优先打通单卡功能,再打通多卡功能。

调测过程

总体思路

  1. 通过训练执行结果,判断吞吐量指标是否达到预期要求。

  2. 当吞吐量指标不达标时,需要找出制约性能瓶颈的原因,主要为以下几个方面:

    • 算子瓶颈,在某个算子上执行过慢。
    • copy瓶颈,非连续转连续时进行copy带来的瓶颈。
    • 框架瓶颈,由于算子格式转换带来了额外操作。
    • 编译瓶颈,由于shape或属性来回变化造成反复编译。
  3. 针对以上制约性能瓶颈的原因进行分析与优化。

采集训练过程相关数据

Profiling数据采集

当模型训练过程中吞吐量指标不达标时,可以通过采集训练过程中的profiling数据,分析哪个环节、哪个算子导致的性能消耗。Profiling数据采集分为PyTorch层面和CANN层面的采集,PyTorch层面采集的是PyTorch API的数据,CANN层面采集的是TBE算子的数据。

请参见以下方式进行profiling数据的获取,并根据实际情况选择需要的数据采集方式。

  • PyTorch层面Profiling数据采集。

    1. 获取chrome_trace文件。

      使用profile接口对原始代码的loss计算和优化过程进行改造。

      # 使用ascend-pytorch适配的profile接口,即可获得,推荐只运行一个step
      with torch.autograd.profiler.profile(use_npu=True) as prof:
          out = model(input_tensor)
      	     loss=loss_func(out)
          loss.backward()
          optimizer.zero_grad()
          optimizer.step()
      # 打印profiler结果信息
      print(prof)
      # 导出chrome_trace文件到指定路径
      output_path = '/home/HwHiAiUser/profile_data.json'
      prof.export_chrome_trace(output_path)
    2. 运行成功后会打印出profiler结果信息。

      打印结果包含CPU和NPU的耗时等相关信息,详细信息参见表2 。

      表2 profiler结果字段表

      Name Self CPU % Self CPU CPU total % CPU total CPU time avg Self NPU % Self NPU NPU total NPU time avg # of Calls
    3. 查看chrome_trace文件。

      chrome_trace文件可以通过以下方式打开查看:在Chrome浏览器 中输入“chrome://tracing“地址,然后将落盘文件拖到空白处即可打开文件内容,通过键盘W、A、S、D键,可以对profiler的结果进行缩放和移动。

    4. profiler其他功能。

      • 获取算子输入tensor的shape信息。

        # 添加record_shapes参数,获取算子输入tensor的shape信息
        with torch.autograd.profiler.profile(use_npu=True, record_shapes=True) as prof:
            # 添加模型计算过程
        print(prof)

        打印结果中增加了每个算子的Input Shape信息。

      • 获取使用NPU的内存信息。

        # 添加profile参数,获取算子内存占用信息
        with torch.autograd.profiler.profile(use_npu=True, profile_memory=True) as prof:
            # 添加模型计算过程
        print(prof)

        打印结果中增加了每个算子的CPU MemSelf CPU MemNPU MemSelf NPU Mem信息。

        说明:

        该功能仅支持PyTorch1.8版本以上。

      • 获取简洁的算子性能信息。

        该功能只打印每个算子栈最底层的算子信息,使分析结果更简洁。

        # 添加use_npu_simple参数,获取简洁的算子信息
        with torch.autograd.profiler.profile(use_npu=True, use_npu_simple=True) as prof:
            # 添加模型计算过程
        # 导出chrome_trace文件到指定路径
        output_path = '/home/HwHiAiUser/profile_data.json'
        prof.export_chrome_trace(output_path)

        在Chrome浏览器中打开chrome_trace结果文件,可查看简洁的算子性能信息。

  • CANN层面Profiling数据采集。

    1. 获取性能数据文件。

      profiler_result_path  = "/home/profiling_data"     # profiling 数据保存的文件夹,请根据实际指定。
      with torch.npu.profile(profiler_result_path, config):  # 一般只需要执行1个step即可,config可默认
          out = model(input_tensor)
          loss=loss_func(out,target)
          loss.backward()
          optimizer.zero_grad()
          optimizer.step()

      其中config参数用于配置需要获取CANN的性能数据种类,设置方法见[E2E prof高级设置](#E2E prof高级设置)中的config参数说明。

      说明: 获取性能数据文件时,model、input_tensor、target需要下发到npu上。

    2. 解析性能数据文件。

      请参见《开发工具》中“Profiling工具>高级功能(所有性能调优方式和采集项)>数据解析与导出”章节。

    3. 高级用法

    pytorch框架是单算子运行方式,本身无法区分step信息,若在with语句内执行了多个step,那么profiling得到的数据则是的多个step连在一起,从prof图上无法区分某个step的数据,因此为了区分step信息,提供高级接口,使用示例如下:

    for i in range(steps):
        if i >=10 && i <= 100:  ## 表示获取第10到第100个step之间的性能数据
            if i == 10:  ## 在第10个step时,开始使能该功能
                torch.npu.prof_init(profiler_result_path) ## profiler_result_path 与前述profiler_result_path参数作用一致
                torch.npu.prof_start(config) ## config与前述config参数作用一致,可以默认
            torch.npu.iteration_start()  ## 进入每个step时打上开始标记
            train_one_step()
            torch.npu.iteration_end()    ## 每个step结束时打上开始标记
            if i == 110:   ## 在第100个step时,关闭该功能
                torch.npu.prof_stop()
                torch.npu.prof_finalize()

获取算子信息OP_INFO

网络模型最终是以OP执行的,通过OPInfo日志,我们可以获取实际执行时的算子及其属性。通过get_ascend_op_info.py脚本获取。

  1. 编写get_ascend_op_info.py脚本获取算子信息,脚本内容如下。

    # -*- coding: utf-8 -*-
    """用于导出OPINFO
    """
    import os
    import sys
    import argparse
    
    def func(host_log_folder):
        """
        :param host_log_folder: where host_log_folder addr is.
        :return:
        """
        host_log_files = os.listdir(host_log_folder)
        result = {}
    
        for host_log in host_log_files:
            if not host_log.endswith('.log') or host_log.endswith('.out'):
                continue
            with open(os.path.join(host_log_folder, host_log), 'r')as f:
                host_log_lines = f.readlines()
                for line in host_log_lines:
                    if line.startswith('[INFO] ASCENDCL') and "aclopCompile::aclOp" in line:
                        op_info = line.split('OpType: ')[1][:-2]
                        op_type = op_info.split(',')[0]
                        op_param = op_info[len(op_type) + 2:]
                        if op_type not in result.keys():
                            result[op_type] = [op_param]
                        else:
                            result[op_type].append(op_param)
    
        with open('ascend_op_info_summary.txt', 'w')as f:
            for k, v in result.items():
                v_set = set(v)
                for info in v_set:
                    f.write(k + " " + info + "\n")
    
    if __name__ == "__main__":
        parser = argparse.ArgumentParser(description='trans the log')
        parser.add_argument('--host_log_folder', default="./",
                            help="input the dir name, trans the current dir with default")
        ags = parser.parse_args()
        func(ags.host_log_folder)
  2. 设置环境变量,将host日志打屏。

    export ASCEND_SLOG_PRINT_TO_STDOUT=1
  3. 设置日志级别为info,参考《故障管理》中“日志参考>日志操作”章节设置日志级别。

  4. 执行训练脚本,进行模型训练,训练完成后获取host侧日志,默认位置为$HOME/ascend/log/plog目录下,$HOME表示Host侧用户根目录。

  5. 解析host侧日志会在当前目录下得到OPInfo信息ascend_op_info_summary.txt。

    python3 get_ascend_op_info.py --host_log_folder $HOME/ascend/log/plog
  6. 分析TaskInfo中额外的task,尤其关注transdata。

host侧性能优化

概述

在进行PyTorch模型迁移训练时,部分网络模型会出现FPS较低、性能不达标的情况。可以考虑对服务器进行以下优化尝试提高训练性能。

  • 修改CPU性能模式。
  • 安装高性能pillow库。
  • (可选)安装指定版本OpenCV库。
修改CPU性能模式(X86服务器)

设置电源策略为高性能模式

提升网络性能需要在X86服务器BIOS设置中将电源策略设为高性能模式,具体操作如下。

  1. 登录iBMC界面,启动虚拟控制台,远程控制选择HTML5集成远程控制台,如图1

    图 1 远程登录控制台

  2. 在虚拟界面工具栏中,单击启动项工具,弹出启动项配置界面,如图2

    图 2 启动项工具

  3. 在启动项配置界面选择,选择“BIOS设置”,然后在虚拟界面工具栏中单击重启工具,重启服务器。

  4. 系统重启后进入BIOS配置界面,依次选择“Advanced”>“Socket Configuration”,如图3所示。

    图 3 Socket Configuration

  5. 进入Advanced Power Mgmt. Configuration,设置Power Policy为Performance。如图4

    图 4 设置电源策略

  6. 按下“F10”保存配置并重启服务器。

将CPU设置为performance模式

请使用root用户执行如下操作。

  1. 使用如下命令查看当前CPU模式。

    cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor

    执行以上命令会输出当前CPU模式,CPU模式说明请参见表1。如果当前CPU模式不是performance模式,请执行以下操作设置CPU为performance模式。否则请跳过以下步骤。

    表 1 CPU模式

    调速器

    描述

    performance

    运行于最大频率。

    powersave

    运行于最小频率。

    userspace

    运行于用户指定的频率。

    ondemand

    按需快速动态调整CPU频率,一有CPU计算量的任务,就会立即达到最大频率运行,空闲时间增加就降低频率。

    conservative

    按需快速动态调整CPU频率,比ondemand的调整更保守。

    schedutil

    基于调度程序调整 CPU 频率。

  2. 安装工具,使用如下命令安装。

    • 以“ubuntu/debian“系统为例。

      apt-get install linux-tools-$(uname -r)
    • 以“centos/bclinux/euler“系统为例:

      yum install kernel-tools -y
      systemctl daemon-reload 
      systemctl enable cpupower 
      systemctl start cpupower
  3. 设置CPU为performance模式。

    cpupower frequency-set -g performance
  4. 再次执行步骤1查看当前CPU模式是否已设置为performance模式。

**修改CPU性能模式ARM服务器 **

设置电源策略为高性能模式

在某些对Host侧CPU要求较高的模型中,例如目标检测类模型,需要进行较为复杂的图像预处理,开启电源高性能模式能一定程度上提高性能和稳定性。ARM服务器提升网络性能需要在BIOS设置中将电源策略设为高性能模式,具体操作如下。

  1. 登录ibmc界面,启动虚拟控制台,远程控制选择HTML5集成远程控制台,如图1

    图 1 远程登录控制台

  2. 在虚拟界面工具栏中,单击启动项工具,弹出启动项配置界面,如图2

    图 2 启动项工具

  3. 在启动项配置界面选择,选择“BIOS设置”,然后在虚拟界面工具栏中单击重启工具,重启服务器。

  4. 系统重启后进入BIOS配置界面,依次选择“Advanced”>“ Performance Config”,如图3所示。

    图 3 Performance Config

  5. 进入“Performance Config”,设置Power Policy为Performance。如图4

    图 4 设置电源策略

  6. 按下“F10”保存配置并重启服务器。

安装高性能pillow库(X86服务器)
  1. 安装高性能pillow库相关依赖,命令如下。

    ubuntu/debian:

    apt-get install libtiff5-dev libjpeg8-dev libopenjp2-7-dev zlib1g-dev libfreetype6-dev liblcms2-dev libwebp-dev tcl8.6-dev tk8.6-dev python3-tk libharfbuzz-dev libfribidi-dev libxcb1-dev

    centos/bclinux/euler:

    yum install libtiff-devel libjpeg-devel openjpeg2-devel zlib-devel freetype-devel lcms2-devel libwebp-devel tcl-devel tk-devel harfbuzz-devel fribidi-devel libraqm-devel libimagequant-devel libxcb-devel
  2. 安装高性能pillow库。

    1. 执行如下命令卸载原生pillow。

      pip3.7 uninstall -y pillow
    2. 安装SSE4版本pillow-simd。

      使用root用户安装,执行如下命令,若使用非root用户安装,需在命令结尾加上--user。

      pip3.7 install pillow-simd

      说明: 如果CPU支持AVX2指令集,可安装AVX2版本pillow-simd,命令如下:

      CC="cc -mavx2" pip3.7 install -U --force-reinstall pillow-simd
  3. 修改torchvision代码解决pillow-simd缺少PILLOW_VERSION问题。

    将/usr/local/python3.x.x/lib/python3.x/site-packages/torchvision/transforms/functional.py第5行代码修改如下:

    try:
        from PIL import Image, ImageOps, ImageEnhance,PILLOW_VERSION
    except:
        from PIL import Image, ImageOps, ImageEnhance
        PILLOW_VERSION="7.0.0"
(可选)安装指定版本OpenCV库

如模型依赖OpenCV,基于训练性能考虑,建议安装OpenCV-3.4.10版本。

  1. 获取源码:获取地址
  2. 安装指导:获取地址

训练过程性能优化

算子瓶颈优化

  1. 获取训练过程中的Profiling数据,参见Profiling数据采集
  2. 分析Profiling数据,得到耗时较大的算子。
  3. 参见单算子样例编写说明构建耗时较大算子的单算子样例,通过与CPU或GPU运行单算子样例时间进行对比,若发现性能不足,则有以下两种方案解决。
    • 规避方案:使用同等语义其他高效算子替代。
    • 解决方案:改进算子性能。

copy瓶颈优化

  1. 获取训练过程中的Profiling数据,参见Profiling数据采集
  2. 分析Profiling数据分析整网中的D2DCopywithStreamSynchronize/PTCopy/format_contiguous的耗时。
  3. 若发现耗时较大,则需参照以下两种方案解决。
    • 方案一:(规避方案)PyTorch中View类型框架类算子会导致非连续转连续操作。优化思路为尽量使用计算类算子代替View类框架算子,常见的View类框架算子如View、Permute、Transpose等。更多View类框架算子可参考https://pytorch.org/docs/stable/tensor_view.html
    • 方案二:(解决方案)加速转连续操作。

框架瓶颈优化

  1. 获取训练过程中算子信息OP_INFO,参见获取算子信息OP_INFO

  2. 分析OP_INFO中算子的规格和调用关系,定位是否插入了多余的算子,重点关注transdata是否合理。

  3. 优化方案:通过指定部分算子初始化格式,对多余的格式转换算子进行消除。

  4. 在pytorch/torch/nn/modules/module.py中,在cast_weight中指定算子初始化格式,如下图。

    格式设置原则可参考如下规则:

    • Conv2D相关:Weight 可设置为FZ格式,如第424行。
    • Linear相关的参数,可设置为NZ格式,如第409行。

编译瓶颈优化

  1. 获取训练过程中算子信息OP_INFO,参见获取算子信息OP_INFO
  2. 查看INFO日志,观察第一个step以后的aclopCompile::aclOp关键字,如果后续接了Match op inputs/type failed或To compile op则说明该算子存在动态编译,需要优化。
  3. 需参照以下两种方案解决。
    • 规避方案:在理解模型语义和相关API基础上,使用固定Shape的方式代替动态Shape。
    • 解决方案:减少编译或不需要编译该算子。
    • 优化算子编译配置请参见编译选项设置

端到端性能工具(E2E prof)使用说明

亲和库

来源介绍

针对公版模型中常见的网络结构和函数,我们针对性地对其进行了优化,使得运算性能大幅度提升,同时,将其集成到Pytorch框架中,便于模型性能调优中使用。

功能介绍

亲和库详细说明请参见《PyTorch API 支持清单》中”亲和库“章节。

说明: 该部分调优内容会随着版本不断增强和更新,请以实际PyTorch版本中对应路径下的内容为准。

AOE调优工具使用说明

AOE调优工具介绍

对于NPU设备,算子输入参数的信息(shape/format等)会影响算子的性能,进而影响模型整体性能。为了使模型获得更良好的性能,可以将模型中所有的算子的输入参数信息获取至本地进行分析(dump),然后将每个算子在NPU上运行,调整算子运行时的策略,确定性能最佳的策略。以上这个过程称为调优,AOE工具则实现了这样的调优功能,可以用于提升模型的性能。

AOE调优工具使用

  1. dump算子信息至本地。

    在模型脚本中添加使能代码,将算子信息dump至本地。

    def train_model():
       torch.npu.set_aoe(dump_path) #使能接口,dump_path为设置保存dump出算子信息的路径,为必须项,不能为空;当设置的路径不存在时,会尝试创建,且支持多级目录创建。
       train_model_one_step()       #模型训练过程样例,一般仅需执行一个step即可,请根据代码实际情况修改。

    以resnet50模型为实际样例,修改如下。

    #line 427~437
    model.train()
    optimizer.zero_grad()
    end = time.time()
    torch.npu.set_aoe(dump_path)    #使能接口
    for i, (images, target) in enumerate(train_loader):
        if i > 0:             #仅需要运行一个step
            exit()
        if i > 100:
            pass
        # measure data loading time
        data_time.update(time.time() - end)
    
        if args.gpu is not None:
            images = images.cuda(args.gpu, non_blocking=True)

    参考链接:https://gitee.com/ascend/ModelZoo-PyTorch/blob/master/PyTorch/built-in/cv/classification/ResNet50_for_PyTorch/pytorch_resnet50_apex.py

  2. 算子调优

    • 设置环境变量:

      source /usr/local/Ascend/ascend-toolkit/set_env.sh  #该环境变量默认不设置TUNE_BANK_PATH
    • 调优:

      aoe --job_type=2 --model_path=./dump_path

      调优过程中,目前仅支持部分算子调优,因此会出现算子调优失败或AI Core error,属于已知问题。

    • 调优结果:

      调优完成后,结果会保存在TUNE_BANK_PATH环境变量中指定的/<soc_version>/目录,若不设置则默认保存在/{HOME}/Ascend/latest/data/aoe/custom/op/<soc_version>目录下;root用户则保存在/root/Ascend/latest/data/aoe/custom/op/<soc_version>。soc_version表示芯片类型,如Ascend910A。

注意事项

  1. 目前仅支持静态算子,动态算子暂不支持。
  2. dump算子信息时,目前无法对算子信息去重,且仅需执行一个step,否则会导致调优时间过长。
  3. 建议使用1P脚本进行dump图,多P会存在dump覆盖的问题。
  4. 使用前需关闭profiling工具,否则会影响模型性能。

性能验证

调优完成后,还原代码修改,运行模型,验证模型/算子性能是否提高。

精度调测

前提条件

目前pytorch1.8.1暂不支持。

优先在同等语义和超参下,跑一定的epoch(推荐完整epoch数的20%),使精度,loss等对齐GPU相应水平,完成后再对齐最终精度。

调测过程

总体思路

精度问题排查需要找出是哪一步出现的问题,主要以下几个方面:

  1. 模型网络计算错误。

    • 定位思路:在网络中加入hook进行排查判断是哪个地方有较大嫌疑,然后构建单算子用例逐渐缩小错误范围,证明该算子在当前网络场景下计算有误,可以对比CPU或GPU结果证明。

    • 规避方案:使用同等语义其他算子替代。

    • 解决方案:改进算子精度或功能问题。

  2. loss计算错误。

    • 定位思路:由于Loss的特殊性和可以自定义,在判断Loss计算错误后建议dump网络中的loss的输入来测试而非随机同shape tensor,这样才能更好地复现证明。

    • 规避方案:使用同等语义其他算子替代。

    • 解决方案:改进算子精度或功能问题(loss也是由算子构成)。

  3. 参数更新错误。

    • 定位思路:在每个optim.step()前对网络中的参数逐个打印其grad进行排查判断是哪个地方有较大嫌疑,然后构建单算子用例逐渐缩小错误范围,证明该算子在当前网络场景下梯度计算有误,可以对比CPU或GPU结果证明。该项优先级应低于1.2.,因为1与2的错误同样可以造成grad异常。

    • 规避方案:使用同等语义其他算子替代。

    • 解决方案:改进计算grad的算子精度或功能问题。

  4. 多卡计算错误。

    • 定位思路:在保证单卡精度OK的前提下,稳定复现多卡不收敛。

    • 解决方案:建议联系华为方支撑人员,提供稳定复现的单P和多P脚本。

精度调优方法

模型出现精度问题一般有:因算子溢出导致的训练loss不收敛或者精度不达标问题,整个网络训练引起的性能不达标问题。用户可通过单算子溢出检测和整网调测适度解决模型精度不达标问题。

环境准备
  • 安装hdf5工具以支持算子dump功能,安装详情请参见编译安装hdf5

    若使用模型算子精度对比功能,需要同时在NPU和GPU环境安装hdf5。否则,仅在NPU环境安装hdf5即可。

  • 安装支持dump功能的Ascend PyTorch框架,编译前请修改build.sh脚本,其余操作请参见《PyTorch安装指南》

    • 在NPU环境PyTorch安装

      编译前修改build.sh脚本,在脚本中增加USE_DUMP=1字段。

      DEBUG=0 USE_DISTRIBUTED=1 USE_HCCL=1 USE_MKLDNN=0 USE_CUDA=0 USE_NPU=1 BUILD_TEST=0 USE_NNPACK=0 USE_DUMP=1 python"${PY_VERSION}" setup.py build bdist_wheel
    • (可选)在GPU环境PyTorch安装,若对模型算子精度对比,请执行此操作,否则请忽略。

      编译前修改build.sh,在脚本中增加USE_DUMP=1USE_NCCL=0字段,将 USE_HCCLUSE_NPU字段的值修改为0,将USE_CUDA字段的值修改为1。

      DEBUG=0 USE_DISTRIBUTED=1 USE_HCCL=0 USE_NCCL=0 USE_MKLDNN=0 USE_CUDA=1 USE_NPU=0 BUILD_TEST=0 USE_NNPACK=0 USE_DUMP=1 python"${PY_VERSION}" setup.py build bdist_wheel
模型算子精度对比

用户使用精度对比工具,将GPU/CPU每层算子的输入输出与NPU对应层算子输入输出进行对比,识别存在精度误差的算子层,帮助开发者实现算子精度问题定位。

约束说明:

  • 建议使用小batchsize,一般设置为8及以下。

    由于每个算子输入、输出数据会存储在硬盘中,会占用较大空间,故建议使用小batchsize节省硬盘空间。

  • 建议仅dump一个step的数据进行精度对比。

  • 目前支持精度为fp32、O1或O2训练过程的算子精度对比。

对比模式:

  • GPU的输入和输出为已知数据,将GPU的输入数据加载到NPU上执行得到输出数据,NPU与GPU输出数据对比。
  • NPU的输入和输出为已知数据,将NPU的输入数据加载到GPU上执行得到输出数据,NPU与GPU输出数据对比。

操作步骤:

  1. 在GPU或NPU环境,使用dumper工具获取GPU或NPU的模型输入和算子输出数据。

    修改训练代码,增加数据dump功能。在模型训练代码的正向、反向计算位置使用with语句增加torch.utils.dumper()方法dump数据。例如,在GPU环境下修改示例:

    for i, data in enumerate(dataloader):
        with torch.utils.dumper(use_dump=True, dump_path="./model_gpu.h5") as dump:
            # 模型训练代码
            xxx # forward code 
            xxx # backward code
        exit()
        xxx # optimizer code 

    dump_path参数为dump数据保存文件路径及名称。建议仅dump一个step的数据用于精度对比,同时参数更新代码放在with语句外。

  2. 将在GPU(NPU)环境dump的数据model_gpu.h5拷贝到NPU(GPU)环境。

  3. 在NPU或NPU环境,使用dumper工具加载已经dump出的数据,并获取算子输出数据。

    修改训练代码,增加数据load、dump功能。在模型训练代码的正向、反向计算位置使用with语句增加torch.utils.dumper()方法load、dump数据。例如,在NPU环境下修改示例:

    for i, data in enumerate(dataloader):
        with torch.utils.dumper(use_dump=True, load_file_path="./model_gpu.h5", dump_path="./model_npu.h5") as dump:
            # 模型训练代码
            xxx # forward code 
            xxx # backward code
        exit()
        xxx # optimizer code

    load_file_path参数为从GPU或NPU获取的dump数据路径,dump_path参数为dump数据保存文件路径及名称。建议仅dump一个step的数据用于精度对比,同时参数更新代码放在with语句外。

  4. 使用msaccucmp.py对算子输出数据对比。

    1. ascend-toolkit提供了msaccucmp.py工具脚本用具精度对比。

      • 该脚本路径为:"/user/local/Ascend/ascend-toolkit/latest/tools/operator_cmp/compare/msaccucmp.py",

        路径仅供参考,请以ascend-toolkit实际安装路径为准。

      • 也可以使用如下命令查找msaccucmp.py路径。

        find / -name msaccucmp.py
    2. 执行msaccucmp.py脚本,进行精度对比。

      python3 /user/local/Ascend/ascend-toolkit/latest/tools/operator_cmp/compare/msaccucmp.py compare -m ./model_npu.h5 -g ./model_gpu.h5

      参数说明:

      -g参数传入使用GPU获得的dump数据文件路径。

      -m参数传入使用NPU获得的dump数据文件路径。

单算子溢出检测

用户通过算子溢出检测功能检测算子是否有溢出,然后采集溢出算子的数据,从而帮助开发人员快速定位并解决算子精度问题。

约束说明:

  • 本功能只提供IR级别的算子溢出检测,且只支持AICORE,不支持Atomic。
  • 使用单算子溢出检测功能时,请不要同时开启apex的动态loss scale模式和使用tensor融合功能,使用单P对模型进行训练,不使用分布式。

采集溢出算子数据:

# check_overflow为溢出检测控制开关
# dump_path为dump文件保存路径
with torch.utils.dumper(check_overflow=check_overflow, dump_path=dump_path, load_file_path='') as dump:   
    # 需要检测算子溢出的代码片段

运行一个step,模型运行过程中,如果有算子溢出,会打印出相应IR的名字。

查看Dump数据:

如果训练过程中采集到了Dump数据,则会在{dump_path}路径下生成dump数据的.h5文件,用户可进入路径自行查看。

解决方法:

  1. 将采集到的.h5文件映射到TBE算子,映射方法请参见算子层级精度对比

  2. 请将算子溢出的打印截图及映射后的TBE算子输入输出文件通过Issue附件形式反馈给华为开发人员。

算子层级精度对比

将GPU/CPU上一层算子的输出作为NPU当前层算子的输入,逐层进行精度比对,以排除由于累计误差造成的影响。 前提条件:

  • 设置环境变量export ACL_DUMP_DATA=0
  • 在脚本中避免使用torch.npu.init.dump()torch.npu.set.dump()接口。

操作步骤:

  1. 准备好需要映射的算子.h5文件。

    • 算子溢出检测场景下,单算子溢出检测已生成需要映射的算子.h5文件。

    • 精度对比场景下,需根据精度对比结果,参照下面命令提取需要映射的算子.h5文件。

      h5copy -pv -i "./input.h5" -o "./output.h5" -s "/op1/seqid/" -d "/op1/seqid/"

      -i 为输入精度对比文件

      -o 为输出需要映射的算子.h5文件路径

      -s 为需要提取的源算子名称及seqid

      -d 为需要提取的目的算子名称及seqid

      若需要提取多个算子,则修改-s、-d参数,多次执行该命令,可以把多算子追加提取到output.h5中。

      该命令需-s和-d参数相同。

      示例:

      h5copy -pv -i "./dump_npu.h5" -o "./output.h5" -s "/numpy_T/1/" -d "/numpy_T/1/"

      该示例表示从“./dump_npu.h5”中抽取seqid为1的numpy_T算子的输入、输出数据到"./output.h5"文件中。

  2. 配置acl.json文件。

    在模型目录下创建acl dump功能所需的的配置文件acl.json

    {
        "dump":
    	    {
                "dump_list":[]
                "dump_path":"./output_IR2TBE"# 映射结果输出路径
                "dump_mode":"all"
                "dump_op_switch":"on"
    	    }
    
    }

    需将dump_path修改为结果输出路径,其他字段不需要修改。

  3. 修改训练脚本。

    在训练脚本中添加with语句开启IR映射TBE功能。

    with torch.utils.dumper(use_load=True, dump_path="./",load_file_path="./output.h5", load_with_acl_dump=True) as dump:
        # 模型计算代码,需用户自己添加
        # x = model(input_data)
  4. 模型运行。

    运行一步完整的模型计算过程,在计算过程中load遇到output.h5中的数据后,自动开启acl dump功能,执行IR,并dump出IR相对应的TBE算子的输入输出数据,IR执行结束,acl dump结束。

  5. 获得映射文件。

    运行成功后,在acl.json配置文件中的dump_path路径下查看输出结果文件。

整网精度调测

用户也可通过分析整个网络的方式来进行网络模型的精度调测。

  1. 通过对比CPU和昇腾AI处理器的结果,判断在昇腾AI处理器上计算是否正确。

    代码样例(本样例只体现基本方法,禁止直接复制)如下:

    # 固定入参,保证模型与输入数据在CPU和昇腾AI处理器上相同
    input_tensor_cpu = torch.Tensor()
    model_cpu = build_model()
    # 将输入数据迁移到昇腾AI处理器上
    input_tensor_npu = input_tensor_cpu.npu()
    # 将模型迁移到昇腾AI处理器上
    model_npu = model_cpu.npu()
    
    # 运算结果对比
    output_cpu = model_cpu(input_tensor_cpu)
    output_npu = model_npu(input_tensor_npu)
    compute_result = (output_cpu - output_npu).abs().mean())
    print(compute_result)

    因昇腾AI处理器硬件架构与cpu不同,计算结果会略有不同。若运算结果较为接近(一般不高于1e-4),则认为运算结果正常。

  2. 通过Pytorch的hook机制来打印正向反向传播中module的输入和输出来分析。

    代码样例(本样例只体现基本方法,禁止直接复制)如下:

    # 设置hook func
    def hook_func(name, module):
        def hook_function(module, inputs, outputs):
            print(name+' inputs', inputs)
            print(name+' outputs', outputs)
        return hook_function
    
    # 注册正反向hook
    for name, module in model.named_modules():
        module.register_forward_hook(hook_func('[forward]: '+name, module))
        module.register_backward_hook(hook_func('[backward]: '+name, module))
    
    # 运行
    model(input_tensor)

    通过分析打印正向反向传播中的inputs, outputs来确定。

  3. 通过直接获取module的grad, running_mean, running_var等参数来分析更新量。

    代码样例(本样例只体现基本方法,禁止直接复制)如下:

    # 例如获取梯度和BN的均值方法来排查
    for name, module in model.named_modules():
        if isinstance(module, nn._BatchNorm):
            print("[BN_buffer]: "+name, module.running_mean, module.running_var)
        print("[grad]: "+name, module.grad)

模型保存与转换

简介

模型训练完成后,通过Pytorch提供的接口保存模型文件并导出ONNX模型,然后通过ATC工具将其转换为适配昇腾AI处理器的.om文件用于离线推理。

本章主要介绍如何将训练好的pth文件pth.tar文件转换为ONNX模型,将ONNX模型转换为适配昇腾AI处理器的.om文件流程请参考《CANN 软件安装指南》手册中“ATC模型转换”章节。

如果想使用Auto Tune优化功能,请参考《开发工具》手册中“Auto Tune工具”章节。

离线推理应用构建请参考《推理应用开发》手册中“应用开发(c++)“章节。整体流程如下:

模型保存

Pytorch在训练过程中,通常使用torch.save()来保存Checkpoint文件,根据模型文件的后续用途会保存为两种格式的模型文件:

  • .pth或.pt扩展名的文件:用于在线推理或导出ONNX格式模型,仅保存模型参数,不保存模型结构,以便压缩文件的体积,可以用Netron等可视化工具打开,一般如图1 .pth文件所示。

    图 1 .pth文件

    通过state_dict来保存和加载模型,示例如下:

    1. 保存模型。

      # 创建保存路径
      PATH = "state_dict_model.pt"
      # 保存模型
      torch.save(net.state_dict(), PATH)
    2. 加载模型以用于在线推理,示例如下,详情请参见《PyTorch在线推理指南》

      # 模型文件保存路径
      PATH = "state_dict_model.pt"
      model = TheModelClass(*args, **kwargs)
      # 加载模型
      model.load_state_dict(torch.load(PATH))
      model.eval()

    须知: 保存.pth或.pt文件扩展名的文件时要提供模型定义文件,否则无法部署。

  • .pth.tar扩展名的文件:可用于在线推理或重新加载后继续训练。保存多个组件,以字典形式保存,常见的组件包括模型和优化器的state_dict、停止时的epoch、最新记录的训练损失以及外部的torch.nn.Embedding层等。如果仅用于部署推理模型,推荐只在.pth.tar扩展名的文件中保存权重信息即模型的state_dict。

    保存和加载模型示例如下:

    1. 保存模型。

      PATH = "checkpoint.pth.tar"
      torch.save({
          'epoch': epoch,
          'loss': loss,
          'state_dict': model.state_dict(),
          'optimizer' : optimizer.state_dict(),
          ...
      }, PATH)
    2. 加载模型用于推理或恢复训练。

      model = TheModelClass(*args, **kwargs)
      optimizer = TheOptimizerClass(*args, **kwargs)
      
      checkpoint = torch.load(PATH)
      model.load_state_dict(checkpoint['model_state_dict'])
      optimizer.load_state_dict(checkpoint['optimizer_state_dict'])
      epoch = checkpoint['epoch']
      loss = checkpoint['loss']
      
      model.eval()
      # - or -
      model.train()

须知: 通常情况下,训练图和推理图中对同一个算子处理方式不同(例如BatchNorm和dropout等算子),在输入格式上也有差别,因此在运行推理或导出ONNX模型之前,必须调用model.eval() 来将dropout和batch normalization层设置为推理模式。

导出ONNX模型

简介

昇腾AI处理器Pytorch模型的部署策略是基于Pytorch官方支持的ONNX模块实现的。ONNX是业内目前比较主流的模型格式,广泛用于模型交流及部署。本节主要介绍如何将Checkpoint文件通过torch.onnx.export()接口导出为ONNX模型。

.pth或.pt文件导出ONNX模型

保存的.pth或.pt文件可以通过Pytorch构建模型再加载权重的方法恢复,然后导出ONNX模型,样例如下。

import torch
import torch.onnx
import torchvision.models as models
# 设置使用CPU导出模型
device = torch.device("cpu") 
 
def convert():
    # 模型定义来自于torchvision,样例生成的模型文件是基于resnet50模型
    model = models.resnet50(pretrained = False)  
    resnet50_model = torch.load('resnet50.pth', map_location='cpu')
    model.load_state_dict(resnet50_model) 
 
    batch_size = 1  #批处理大小
    input_shape = (3, 224, 224)   #输入数据,改成自己的输入shape

    # 模型设置为推理模式
    model.eval()

    dummy_input = torch.randn(batch_size, *input_shape) #  定义输入shape
    torch.onnx.export(model, 
                      dummy_input, 
                      "resnet50_official.onnx", 
                      input_names = ["input"],   # 构造输入名
                      output_names = ["output"],    # 构造输出名
                      opset_version=11,    # ATC工具目前仅支持opset_version=11
                      dynamic_axes={"input":{0:"batch_size"}, "output":{0:"batch_size"}})  #支持输出动态轴
                      ) 
     
if __name__ == "__main__":
    convert()

说明:

  • 在导出ONNX模型之前,必须调用model.eval() 来将dropout和batch normalization层设置为推理模式。
  • 样例脚本中的model来自于torchvision模块中的定义,用户使用自己的模型时需自行指定。
  • 构造输入输出需要对应训练时的输入输出,否则无法正常推理。

.pth.tar文件导出ONNX模型

.pth.tar在导出ONNX模型时需要先确定保存时的信息,有时保存的节点名称和模型定义中的节点会有差异,例如会多出前缀和后缀。在进行转换的时候,可以对节点名称进行修改。转换代码样例如下。

import torch
import torch.onnx
from collections import OrderedDict
import mobilenet

# 本样例中的pth.tar文件保存时节点名加了前缀module,通过遍历删除
def proc_nodes_module(checkpoint, AttrName):
    new_state_dict = OrderedDict()
    for key, value in checkpoint[AttrName].items():
        if key == "module.features.0.0.weight":
            print(value)
        if(key[0:7] == "module."):
            name = key[7:]
        else:
            name = key[0:]

        new_state_dict[name] = value
    return new_state_dict

def convert():
    checkpoint = torch.load("./mobilenet_cpu.pth.tar", map_location=torch.device('cpu'))
    checkpoint['state_dict'] = proc_nodes_module(checkpoint,'state_dict')
    model = mobilenet.mobilenet_v2(pretrained = False)
    model.load_state_dict(checkpoint['state_dict'])
    model.eval()
    input_names = ["actual_input_1"]
    output_names = ["output1"]
    dummy_input = torch.randn(1, 3, 224, 224)
    torch.onnx.export(model, dummy_input, "mobilenetV2_npu.onnx", input_names = input_names, output_names = output_names, opset_version=11)

if __name__ == "__main__":
    convert()

模型调优样例

ShuffleNet模型调优示例

样例获取

  1. 本样例基于PyTorch官网提供的Imagenet数据集训练模型进行适配昇腾910 AI处理器的迁移改造,样例获取路径为https://github.com/pytorch/examples/tree/master/imagenet

  2. ShuffleNet模型参考PyTorch官网模型ShuffleNet V2,实际使用在脚本执行中直接指定参数arch为shufflenet_v2_x1_0。

    --arch shufflenet_v2_x1_0

    说明: ShuffleNet为PyTorch内置模型,了解更多内置模型请前往Pytorch官网

目录结构

主要文件目录结构如下所示:

├──main.py 

模型评估

模型评估主要关注算子适配情况,使用dump op方法获取ShuffleNet网络算子信息,与《PyTorch API 支持清单》算子进行对比,若是发现某个算子当前暂不支持,对于简单场景我们可以考虑先暂时替换成类似的算子或者把该算子单独放到cpu上执行两种方式规避,复杂场景不支持算子需要参见《PyTorch算子开发指南》进行算子开发。

网络迁移

训练脚本迁移请参见单卡训练修改单机多卡训练修改。脚本执行时注意选择参数--arch shufflenet_v2_x1_0。

网络调测

网络调测具体方法请参见调测过程。经排查ShuffleNet运行时相关算子耗时过大,以下给出耗时数据及解决方法。

前向排查

前向排查记录表如下:

表 1 前向排查

序号

time(ms)

batch_size

detail

1

1100

512

channel_shuffle操作使用channel_shuffle_index_select替代。

2

600

512

使用两个channel_shuffle_index_select操作消减chunk带来的不连续。

3

300

512

通过框架层,指定concat输出格式为NCHW以消除过多的transdata。

4

285

512

修复了未初始化weight格式。

5

275

512

修复了DWCONV没有指定输出格式为5HD的问题。

详细说明如下:

  • 由于原生实现的torch.transpose(x, 1, 2).contiguous()是使用了View类框架算子transpose,造成了非连续场景,如copy瓶颈优化所描述Copy瓶颈,使用channel_shuffle_index_select,在语义相同的情况下使用计算类算子替换框架类算子,从而减少耗时。
  • 由于shufflenetv2中含有大量的chunk操作,而chunk操作在Pytorch中为框架类算子,其结果会将一个tensor分割为几个等长的非连续的tensor,而非连续转连续这个操作目前耗时较长,故使用计算类算子消除非连续,如copy瓶颈优化所描述Copy瓶颈。
  • 适配层在适配算子时默认指定输出格式为输入格式,但是concat不支持C轴非16整数倍的5HD的格式,会转为4D进行处理,又由于concat后面接的是gatherv2算子,也是仅支持4D格式的算子,所以导致数据格式转换过程为5HD->4D->concat->5HD->4D->gatherv2->5HD,解决方法是修改concat输出格式,当非16整数倍时指定输出格式为4D,优化后数据格式转换过程为5HD->4D->concat->gatherv2->5HD,当前针对ShuffleNet的做法具体可参考pytorch/aten/src/ATen/native/npu/CatKernelNpu.cpp 第121行。
  • 设置weight初始化格式避免计算过程中反复的transdata,如copy瓶颈优化所描述框架瓶颈。
  • 修复了DWCONV weight输出格式指定,避免一些不必要5HD->4D。

整网排查

整网排查记录表如下:

表 2 整网排查

序号

time(ms)

batch_size

detail

1

5500

512

通过框架层,index tocpu成index_add操作

2

4000

512

通过自定义算子,预生成index,不再tocpu

3

1800

512

通过自定义算子,融合index_add和chunk

4

885

512

添加contiguous_with_gatherv2

5

3480

1024

修改batchsize

6

1650

1024

修改batchsize + contiguous_with_gatherv2

7

1424

1024

通过自定义算子,融合cat+shuffle+chunk,消除不连续

8

1360

1024

通过框架层,修改relugrad传入的grad格式

9

1300

1024

修改IndexSelectFullImplementation的bp传入格式

10

920

1024

修改amp O1

11

860

1024

修改amp O2

12

830

1024

消除BN参数更新时AXPY引入的过多的transdata

13

800

1024

消除所有fp bp parm_updata间的流同步

14

461

1024

针对非32对齐场景,改进GatherV2算子

15

429

1024

针对ShufflenetV2场景再次优化GatherV2算子-->GatherV3

详细说明如下:

  1. 使用计算类算子替换框架类算子。

  2. 使用buffer记录index信息到npu,消除index.to('npu') 的操作。

  3. 使用计算类算子消除非连续。

  4. contiguous_with_gatherv2是使用aicore算子GatherV2来完成非连续转连续操作。

  5. 修改batchsize。

  6. 修改batchsize + contiguous_with_gatherv2。

  7. 由于concat算子的反向是chunk,会引起非连续问题,故自定义concat算子反向,使用Gatherv2替代chunk,将其融合成cat+shuffle+chunk,消除不连续。

  8. ReluGrad算子有两个输入:grad_output(反向的输入),self(正向的输出),在shufflenet中有时会出现4D + 5HD的场景,而FE的格式对齐往往对齐第一个tensor的format,结果就会导致(4D, 5HD)->(4D, 4D)->ReluGrad->4D->5HD。由于正向的输出格式基本就是输入格式,而relu往往是配合在Conv+BN+Relu这样使用,所以可以认为,在这个场景下,输出5HD是更合适的选择。于是手动插入npu_format_cast,(4D, 5HD)->(5HD, 5HD)->ReluGrad->5HD。

  9. IndexSelectFullImplementation中涉及到了对一个5HD的tensor做两次gatherv2操作,这个时候会导致两次的5HD->4D,可以手动先做一次5HD->4D,这样就可以在gatherv2时不做transdata,从而消减一次transdata操作。

  10. 加入混合精度O1。

  11. 加入混合精度O2。

  12. 由于Axpy算子的参数校验,所有网络在参数更新时,如C不整除16则会transdata为4D进行Axpy运算,引入了大量的transdata算子,通过增加一个函数,当Axpy的input的shape一致时结束校验,从而避免了格式转换,增加了运行效率。

  13. 删除所有的流同步操作,原因是容易导致不收敛,没有采纳。

  14. 使用针对非对齐优化后的Gatherv2算子后,整体性能提速至交付水平。

  15. 使用针对ShufflenetV2场景再次优化后的Gatherv3算子后,整体性能还能继续提升。

Python侧优化细节

Python侧优化主要是通过一些同等语义的修改,使网络在NPU上边的更加亲和。当前非连续转连续容易成为性能瓶颈,而ShufflenetV2中的channel_shuffle操作就涉及了permute后转连续的操作,导致整网性能在NPU上较差。通过对channel_shuffle操作进行同等语义的修改,加上和concat操作的融合,使得整网性能得到飞升。采用的是torchvision版本参见开源链接

  • 框架原始channel_shuffle操作。

    def channel_shuffle(x, groups):
        # type: (torch.Tensor, int) -> torch.Tensor
        batchsize, num_channels, height, width = x.data.size()
        channels_per_group = num_channels // groups
        # reshape
        x = x.view(batchsize, groups,
                   channels_per_group, height, width)
        x = torch.transpose(x, 1, 2).contiguous()
        # flatten
        x = x.view(batchsize, -1, height, width)
        return x
    
    class InvertedResidual(nn.Module):
        def __init__(self, inp, oup, stride):
            super(InvertedResidual, self).__init__()
            if not (1 <= stride <= 3):
                raise ValueError('illegal stride value')
            self.stride = stride
            branch_features = oup // 2
            assert (self.stride != 1) or (inp == branch_features << 1)
            if self.stride > 1:
                self.branch1 = nn.Sequential(
                    self.depthwise_conv(inp, inp, kernel_size=3, stride=self.stride, padding=1),
                    nn.BatchNorm2d(inp),
                    nn.Conv2d(inp, branch_features, kernel_size=1, stride=1, padding=0, bias=False),
                    nn.BatchNorm2d(branch_features),
                    nn.ReLU(inplace=True),
                )
            else:
                self.branch1 = nn.Sequential()
    
            self.branch2 = nn.Sequential(
                nn.Conv2d(inp if (self.stride > 1) else branch_features,
                          branch_features, kernel_size=1, stride=1, padding=0, bias=False),
                nn.BatchNorm2d(branch_features),
                nn.ReLU(inplace=True),
                self.depthwise_conv(branch_features, branch_features, kernel_size=3, stride=self.stride, padding=1),
                nn.BatchNorm2d(branch_features),
                nn.Conv2d(branch_features, branch_features, kernel_size=1, stride=1, padding=0, bias=False),
                nn.BatchNorm2d(branch_features),
                nn.ReLU(inplace=True),
            )
    
        @staticmethod
        def depthwise_conv(i, o, kernel_size, stride=1, padding=0, bias=False):
            return nn.Conv2d(i, o, kernel_size, stride, padding, bias=bias, groups=i)
    
        def forward(self, x):
            if self.stride == 1:
                x1, x2 = x.chunk(2, dim=1)
                out = torch.cat((x1, self.branch2(x2)), dim=1)
            else:
                out = torch.cat((self.branch1(x), self.branch2(x)), dim=1)
    
            out = channel_shuffle(out, 2)
    
            return out
  • 同等语义改写。

    def channel_shuffle_index_select(x, groups=2):
        N, C, H, W = x.shape
        inp = C
        # channel_shuffle操作是对C维按一定规则的重排的工作,可以被表达为一次简单的重排
        group_len = inp // groups
        index = torch.from_numpy(np.array(list(range(inp))).reshape(groups, group_len).transpose(1, 0).flatten()).long()
    
        x = x.index_select(1, index)
        return x
    
    # 对两个操作进行结果对比,可以看到语义是相等的
    x = torch.randn(2, 232, 14, 14)
    for group in [2, 4, 8]:
        out1 = channel_shuffle(x, group)
        out2 = channel_shuffle_index_select(x, group)
        print((out1 - out2).sum())
  • 昇腾AI处理器亲和写法。

    # 对应 out = channel_shuffle(torch.cat((self.branch1(x), self.branch2(x)), dim=1)) 的情形
    # 使用channel_shuffle_index_select替代channel_shuffle
    # 自定义OP,融合channel_shuffle_index_select和cat,使用计算类算子来消减非连续
    class IndexSelectFullImplementation(torch.autograd.Function):
        @staticmethod
        def forward(ctx, x1, x2, fp_index, bp_index1, bp_index2):
            # 强制流同步,仅稳定训练作用
            stream = torch.npu.current_stream()
            stream.synchronize()
    
            # 对ctx注册bp_index1, bp_index2使反向时可以使用
            ctx.bp_index1 = bp_index1
            ctx.bp_index2 = bp_index2
    
            x = torch.cat([x1, x2], dim=1)
    
            # 使用index_select替代channel_shuffle操作,这里是后面不接chunk算子的场景
            result = x.index_select(1, fp_index)
    
            return result
    
        @staticmethod
        def backward(ctx, grad_output):
            # 强制流同步,仅稳定训练作用
            stream = torch.npu.current_stream()
            stream.synchronize()
    
            # 由于index_select不支持5HD格式,将格式转换为NCHW来减少额外的transdata
            grad_output.data = grad_output.data.npu_format_cast(0)
    
            # 依据正向推导得到的反向的表达式,使用index_select同时完成对index_select和cat的反向
            out1 = grad_output.index_select(1, ctx.bp_index1)
            out2 = grad_output.index_select(1, ctx.bp_index2)
            return out1, out2, None, None, None, None
    
    
    class IndexSelectHalfImplementation(torch.autograd.Function):
        @staticmethod
        def forward(ctx, x1, x2, fp_index1, fp_index2, bp_index1, bp_index2):
            ctx.bp_index1 = bp_index1
            ctx.bp_index2 = bp_index2
            x = torch.cat([x1, x2], dim=1)
    
            # 使用index_select替代channel_shuffle操作,这里是后面接chunk算子的场景
            return x.index_select(1, fp_index1), x.index_select(1, fp_index2)
    
        @staticmethod
        def backward(ctx, grad_output1, grad_output2):
            grad_output = torch.cat([grad_output1, grad_output2], 1)
    
            out1 = grad_output.index_select(1, ctx.bp_index1)
            out2 = grad_output.index_select(1, ctx.bp_index2)
            return out1, out2, None, None, None, None
    
    
    class Channel_Shuffle(nn.Module):
        def __init__(self, inp, groups=2, split_shuffle=True):
            super(Channel_Shuffle, self).__init__()
    
            self.split_shuffle = split_shuffle
            self.group_len = inp // groups
    
            # 初始化channel_shuffle_index_select中需要使用的fp_index
            self.out = np.array(list(range(inp))).reshape(groups, self.group_len).transpose(1, 0).flatten().tolist()
    
            # 将初始化的fp_index按需注册为module的buffer,在to.device的时候顺路带到设备,减少h2dcopy的耗时
            # 此处仅展示常用的group=2的场景下的使用方式,其他情形请自行拓展
            if self.split_shuffle:
                self.register_buffer('fp_index1', torch.tensor(self.out[:self.group_len], dtype=torch.int32))
                self.register_buffer('fp_index2', torch.tensor(self.out[self.group_len:], dtype=torch.int32))
            else:
                self.register_buffer('fp_index', torch.tensor(self.out, dtype=torch.int32))
    
            # 将对应的bp_index按需注册为module的buffer,在to.device的时候顺路带到设备,减少h2dcopy的耗时
            self.register_buffer('bp_index1', torch.tensor(list(range(0, inp, 2)), dtype=torch.int32))
            self.register_buffer('bp_index2', torch.tensor(list(range(1, inp, 2)), dtype=torch.int32))
    
        def forward(self, x1, x2):
            if self.split_shuffle:
                return IndexSelectHalfImplementation.apply(x1, x2, self.fp_index1, self.fp_index2, self.bp_index1,
                                                           self.bp_index2)
            else:
                return IndexSelectFullImplementation.apply(x1, x2, self.fp_index, self.bp_index1, self.bp_index2)
    
    
    class InvertedResidual(nn.Module):
        def __init__(self, inp, oup, stride, split_shuffle=True):
            super(InvertedResidual, self).__init__()
    
            if not (1 <= stride <= 3):
                raise ValueError('illegal stride value')
            self.stride = stride
    
            branch_features = oup // 2
            assert (self.stride != 1) or (inp == branch_features << 1)
    
            if self.stride > 1:
                self.branch1 = nn.Sequential(
                    self.depthwise_conv(inp, inp, kernel_size=3, stride=self.stride, padding=1),
                    nn.BatchNorm2d(inp),
                    nn.Conv2d(inp, branch_features, kernel_size=1, stride=1, padding=0, bias=False),
                    nn.BatchNorm2d(branch_features),
                    nn.ReLU(inplace=True),
                )
            else:
                self.branch1 = nn.Sequential()
    
            self.branch2 = nn.Sequential(
                nn.Conv2d(inp if (self.stride > 1) else branch_features,
                          branch_features, kernel_size=1, stride=1, padding=0, bias=False),
                nn.BatchNorm2d(branch_features),
                nn.ReLU(inplace=True),
                self.depthwise_conv(branch_features, branch_features, kernel_size=3, stride=self.stride, padding=1),
                nn.BatchNorm2d(branch_features),
                nn.Conv2d(branch_features, branch_features, kernel_size=1, stride=1, padding=0, bias=False),
                nn.BatchNorm2d(branch_features),
                nn.ReLU(inplace=True),
            )
    
            if self.stride > 1:
                self.channel_shuffle = Channel_Shuffle(inp=branch_features + branch_features, groups=2,
                                                       split_shuffle=split_shuffle)
            else:
                self.channel_shuffle = Channel_Shuffle(inp=inp, groups=2, split_shuffle=split_shuffle)
    
        @staticmethod
        def depthwise_conv(i, o, kernel_size, stride=1, padding=0, bias=False):
            return nn.Conv2d(i, o, kernel_size, stride, padding, bias=bias, groups=i)
    
        def forward(self, x):
    
            # 删除concat和chunk操作,融合进self.channel_shuffle内处理
            if self.stride == 1:
                x1, x2 = x
                x2 = self.branch2(x2)
            else:
                x1 = self.branch1(x)
                x2 = self.branch2(x)
    
            out = self.channel_shuffle(x1, x2)
    
            return out

参考信息

单算子样例编写说明

在模型中遇到问题时,使用整网复现问题成本较大,可以构建测试用例来复现精度或性能问题,便于定位解决。构建测试用例一般有如下两种方式。单算子dump方法请参见单算子dump方法

  1. 单算子测试用例构建,直接调用该算子即可复现错误场景。

    例如构建max算子的单算子样例如下:

    import torch
    import copy
    from torch.testing._internal.common_utils import TestCase, run_tests 
    class TestMax(TestCase):    
        def cpu_op_exec(self, input1):
            # 调用算子 
            output = torch.max(input1)
            output = output.to('cpu')
            output = output.numpy()
            return output
    
        def npu_op_exec(self, input1):
            # 调用对应npu算子 
            output = torch.max(input1)
            return output
    
        def test_max(self):
            input = torch.randn(10,20))
            input = input.to(torch.int64)   # 数据dtype转换
            input_cpu = copy.deepcopy(input)
            input_npu = copy.deepcopy(input).npu()
    
            output_cpu = self.cpu_op_exec(input_cpu)
            output_npu = self.npu_op_exec(input_npu)
    
            # 比较cpu和npu的计算结果,prec为允许误差
            self.assertEqual(output_cpu, output_npu, prec = 1e-4) 
    
    if __name__ == '__main__':
        run_tests()

    说明:

    • 运行上述代码,如果发现报错信息与模型中的max算子报错信息相同,则说明单算子测试用例构建成功。
    • 假设注释掉输入数据dtype转换代码,发现测试用例无报错,则可以说明在输入参数为torch.int64时,max算子在npu上报错。
  2. 基于上下文的单算子测试用例构建。

    这里虽然是单算子样例,但有时候不仅仅为一个操作,而是带有上下文的场景,还有时候是一个带参数Module,Module的方式是更通用的方法。此处构建一个包含两个OP的Module,构建样例如下:

    import torch
    import copy
    from torch.testing._internal.common_utils import TestCase, run_tests 
    
    class Model(nn.Module):
        def __init__(self, in_channels=1, hooks=False):
            super(Model, self).__init__()
            self.conv = nn.Conv2d(in_channels, in_channels*2, kernel_size=64)
            if hooks:
                self.conv.weight.register_hook(lambda grad: print(grad))
        def forward(self, x):
            out = self.conv(x)
            return out
    
    class TestConv2d(TestCase): 
        def test_conv2d(self):
    
            model = Model(in_channels=16)
    
            # 若需要获取反向计算结果,则加入hooks获取反向即可
            # model = Model(in_channels=16, hooks=True)
            # 创建输入tensor
            input_tensor = torch.randn(4,16,64,64)
    
            input_tensor_cpu= copy.deepcopy(input_tensor)
            out = model(input_tensor_cpu)
            loss = out.sum()
            loss.backward()
            cpuout = out
    
            # 3 to NPU 运行,将model和input_tensor放到NPU运行
            torch.npu.set_device("npu:0") # 一般先set_device设定运行卡
            model_npu = Model(in_channels=16).npu()
            input_tensor_npu= copy.deepcopy(input_tensor).npu()
            out = model_npu(input_tensor_npu)
            loss = out.sum()
            loss.backward()
            npuout = out
            #根据结果,确定是否为报错场景
            self.assertEqual(cpuout, npuout, prec = 1e-4)
    
    if __name__ == '__main__':
        run_tests()

单算子dump方法

采集Dump数据

当前适配昇腾AI处理器的PyTorch通过torch.npu中的init_dump()、set_dump()和finalize_dump接口来进行算子dump数据的采集。首先init_dump()会进行初始化dump配置,然后通过set_dump()接口通过传入配置文件来配置dump参数,最后通过finalize_dump来结束dump。以下以add_算子为例,介绍算子dump数据采集方法。

import torch
torch.npu.set_device("npu:0")
torch.npu.init_dump()
torch.npu.set_dump("/home/HwHiAiUser/dump.json")   # "/home/HwHiAiUser/dump.json"为配置文件路径,用户自行配置
a = torch.tensor([2, 2]).to("npu:0")
a.add_(1)
torch.npu.finalize_dump()

其中dump.json配置方法如下。

{
 "dump":
 {
         "dump_list":[],
         "dump_path":"/home/HwHiAiUser/dump/output",
         "dump_mode":"all",
         "dump_op_switch":"on"
 }

dump.json字段解释如下。

字段名

说明

dump_list

待dump数据的算子模型。为空,无需配置。

dump_path

dump数据文件存储到运行环境的目录,支持配置绝对路径或相对路径:

  • 绝对路径配置以“/”开头,例如:/home/HwHiAiUser/output。
  • 相对路径配置直接以目录名开始,例如:output。

例如:dump_path配置为/home/HwHiAiUser/output,则dump数据文件存储到运行环境的/home/HwHiAiUser/output目录下。

dump_mode

dump数据模式,配置如下。

  • output:dump算子的输出数据,默认取值output。
  • input:dump算子的输入数据。
  • all:dump算子的输入、输出数据。

dump_op_switch

单算子模型dump数据开关,配置如下。

  • off:关闭单算子模型dump,默认取值off。
  • on:开启单算子模型dump。

查看溢出数据

采集的dump数据会在{dump_path}/{time}/{deviceid}/{model_id}/{data_index}目录下生成例如:“/home/HwHiAiUser/output/20200808163566/0/0”目录。

存放路径及文件命名规则:

  • dump_path:用户配置的溢出数据存放路径,例如/home/HwHiAiUser/output。

  • time:时间戳,例如20200808163566。

  • deviceid:Device设备ID号。

  • model_id:子图ID。

  • dump文件:命名规则如{op_type}.{op_name}.{taskid}.{stream_id}.{timestamp},如果op_type、op_name出现了“.”、“/”、“\”、空格时,会转换为下划线表示。

解析溢出算子的dump文件

  1. 请根据实际情况,将{op_type}.{op_name}.{taskid}.{stream_id}.{timestamp}上传到安装有CANN软件包的环境。

  2. 进入解析脚本所在路径,假设CANN软件包安装目录为:/home/HwHiAiUser/Ascend。

    cd /home/HwHiAiUser/Ascend/ascend-toolkit/latest/toolkit/tools/operator_cmp/compare

  3. 执行msaccucmp.pyc脚本,转换dump文件为numpy文件。举例:

    python3 msaccucmp.pyc convert -d /home/HwHiAiUser/dump -out /home/HwHiAiUser/dumptonumpy -v 2

    说明: -d参数支持传入单个文件,对单个dump文件进行转换,也支持传入目录,对整个path下所有的dump文件进行转换。

  4. 调用Python,转换numpy文件为txt文件。举例:

    $ python3

    >>> import numpy as np

    >>> a = np.load("/home/HwHiAiUser/dumptonumpy/Pooling.pool1.1147.1589195081588018.output.0.npy")

    >>> b = a.flatten()

    >>> np.savetxt("/home/HwHiAiUser/dumptonumpy/Pooling.pool1.1147.1589195081588018.output.0.txt", b)

    转换为.txt格式文件后,维度信息、Dtype均不存在。详细的使用方法请参考numpy官网介绍。

常用环境变量说明

  1. 开启TASK多线程下发,绝大多数情况下,打开该功能会进一步提升整网训练性能。

    export TASK_QUEUE_ENABLE=1

  2. 开启重定向日志到stdout,用于导出host日志到屏幕。

    export ASCEND_SLOG_PRINT_TO_STDOUT=0

  3. 设置日志级别,日志级别设置,信息从多到少分别是 debug --> info --> warning --> error --> null,一般设置为error,调试时使用info。请参考《故障管理》中“日志参考>日志操作”章节设置日志级别设置日志级别。

    export ASCEND_GLOBAL_LOG_LEVEL=3

  4. dump图,主要用于查看图结构。

    export DUMP_GE_GRAPH=2

    export DUMP_GRAPH_LEVEL=3

  5. 设置Event日志开启标志。

    export ASCEND_GLOBAL_EVENT_ENABLE=0

  6. 设置是否开启PTCopy。

    export PTCOPY_ENABLE=1

  7. 设置是否开启combined标志。

    export COMBINED_ENABLE=1

  8. 设置特殊场景是否需要重新编译,不需要修改。

    export DYNAMIC_OP="ADD#MUL"

  9. HCCL白名单开关。

    export HCCL_WHITELIST_DISABLE=1

dump op方法

  1. 使用profile接口对原始代码训练脚本的loss计算和优化过程进行改造,打印算子信息。代码样例如下:

    with torch.autograd.profiler.profile() as prof:
        out = model(input_tensor)
        loss = out.sum()
        loss.backward()
    # 也可导出文件    
    print(prof.key_averages().table(sort_by="self_cpu_time_total"))
  2. 将改造后的训练脚本在CPU上进行训练,屏幕会打印相关算子信息。

编译选项设置

用于配置算子编译过程中的属性,可用于提升性能,通过ACL接口实现。用法及解释如下:

import torch
option = {key: val}
torch.npu.set_option(option) # 以dict方式进行设置

其中key可选值和对应的含义如下:
ACL_OP_SELECT_IMPL_MODE,      //选择算子是高精度实现还是高性能实现
ACL_OPTYPELIST_FOR_IMPLMODE,  //列举算子类型的列表,该列表中的算子使用ACL_OP_SELECT_IMPL_MODE指定的模式
ACL_OP_DEBUG_LEVEL,           //TBE算子编译debug功能开关
ACL_DEBUG_DIR,                //保存模型转换、网络迁移过程中算子编译生成的调试相关过程文件的路径,包括算子.o/.json/.cce等文件。路径必须已经存在。
ACL_OP_COMPILER_CACHE_MODE,   //算子编译磁盘缓存模式
ACL_OP_COMPILER_CACHE_DIR,    //算子编译磁盘缓存的路径,路径必须已经存在。

key对应的val值解释和可设置值如下:
ACL_OP_SELECT_IMPL_MODE: 用于选择算子是高精度实现还是高性能实现。如果不配置该编译选项,默认采用high_precision。
    high_precision:表示网络模型中所有算子选择高精度实现。
    high_performance:表示网络模型中所有算子选择高性能实现。

ACL_OPTYPELIST_FOR_IMPLMODE:设置optype列表中算子的实现方式,该参数当前仅支持设置某个具体算子的实现方式,不支持设置多个算子。当前仅支持配置的算子为Pooling、SoftmaxV2、LRN、ROIAlign。算子类型的列表中的算子使用ACL_OP_SELECT_IMPL_MODE指定的模式。

ACL_OP_DEBUG_LEVEL:用于配置TBE算子编译debug功能开关。
    0:不开启算子debug功能。在执行atc命令当前路径算子编译生成的kernel_meta文件夹中不保留.o(算子二进制文件)和.json文件(算子描述文件)。
    1:开启算子debug功能,在执行atc命令当前路径算子编译生成的kernel_meta文件夹中生成TBE指令映射文件(算子cce文件*.cce和python-cce映射文件*_loc.json),用于后续工具进行AICore Error问题定位。
    2:开启算子debug功能,在执行atc命令当前路径算子编译生成的kernel_meta文件夹中生成TBE指令映射文件(算子cce文件*.cce和python-cce映射文件*_loc.json),并关闭编译优化开关并且开启ccec调试功能(ccec编译器选项设置为-O0-g),用于后续工具进行AICore Error问题定位。
    3:不开启算子debug功能,在执行atc命令当前路径算子编译生成的kernel_meta文件夹中保留.o(算子二进制文件)和.json文件(算子描述文件)。
    4:不开启算子debug功能,在执行atc命令当前路径算子编译生成的kernel_meta文件夹中保留.o(算子二进制文件)和.json文件(算子描述文件),生成TBE指令映射文件(算子cce文件*.cce)和UB融合计算描述文件({$kernel_name}_compute.json)。

ACL_DEBUG_DIR:用于配置保存模型转换、网络迁移过程中算子编译生成的调试相关过程文件的路径,包括算子.o/.json/.cce等文件。

ACL_OP_COMPILER_CACHE_MODE:用于配置算子编译磁盘缓存模式。该编译选项需要与ACL_OP_COMPILER_CACHE_DIR配合使用。
    enable:表示启用算子编译缓存。
    disable:表示禁用算子编译缓存。
    force:表示强制刷新缓存,即先删除已有缓存,再重新编译并加入缓存。当用户的python或者依赖库等发生变化时,需要指定为force用于清理已有的缓存。

ACL_OP_COMPILER_CACHE_DIR:用于配置算子编译磁盘缓存的目录。该编译选项需要与ACL_OP_COMPILER_CACHE_MODE配合使用。

安装7.3.0版本gcc

以下步骤请在root用户下执行。

  1. 下载gcc-7.3.0.tar.gz,下载地址为https://mirrors.tuna.tsinghua.edu.cn/gnu/gcc/gcc-7.3.0/gcc-7.3.0.tar.gz

  2. 安装gcc时候会占用大量临时空间,所以先执行下面的命令清空/tmp目录:

    sudo rm -rf /tmp/*
  3. 安装依赖。

    centos/bclinux执行如下命令安装。

    yum install bzip2    

    ubuntu/debian执行如下命令安装。

    apt-get install bzip2    
  4. 编译安装gcc。

    1. 进入gcc-7.3.0.tar.gz源码包所在目录,解压源码包,命令为:

      tar -zxvf gcc-7.3.0.tar.gz
    2. 进入解压后的文件夹,执行如下命令下载gcc依赖包:

      cd gcc-7.3.0
      ./contrib/download_prerequisites

      如果执行上述命令报错,需要执行如下命令在“gcc-7.3.0/“文件夹下下载依赖包:

      wget http://gcc.gnu.org/pub/gcc/infrastructure/gmp-6.1.0.tar.bz2
      wget http://gcc.gnu.org/pub/gcc/infrastructure/mpfr-3.1.4.tar.bz2
      wget http://gcc.gnu.org/pub/gcc/infrastructure/mpc-1.0.3.tar.gz
      wget http://gcc.gnu.org/pub/gcc/infrastructure/isl-0.16.1.tar.bz2

      下载好上述依赖包后,重新执行以下命令:

      ./contrib/download_prerequisites

      如果上述命令校验失败,需要确保依赖包为一次性下载成功,无重复下载现象。

    3. 执行配置、编译和安装命令:

      ./configure --enable-languages=c,c++ --disable-multilib --with-system-zlib --prefix=/usr/local/linux_gcc7.3.0
      make -j15    # 通过grep -w processor /proc/cpuinfo|wc -l查看cpu数,示例为15,用户可自行设置相应参数。
      make install    

      注意: 其中“--prefix“参数用于指定linux_gcc7.3.0安装路径,用户可自行配置,但注意不要配置为“/usr/local“及“/usr“,因为会与系统使用软件源默认安装的gcc相冲突,导致系统原始gcc编译环境被破坏。示例指定为“/usr/local/linux_gcc7.3.0“。

  5. 配置环境变量。

    当用户执行训练时,需要用到gcc升级后的编译环境,因此要在训练脚本中配置环境变量,通过如下命令配置。

    export LD_LIBRARY_PATH=${install_path}/lib64:${LD_LIBRARY_PATH}

    其中${install_path}为3.中配置的gcc7.3.0安装路径,本示例为“/usr/local/gcc7.3.0/“。

    说明: 本步骤为用户在需要用到gcc升级后的编译环境时才配置环境变量。

编译安装hdf5

以下步骤请在root用户下执行。

  1. 获取代码。

    git clone https://github.com/HDFGroup/hdf5.git 
  2. 切换到hdf5-1_10_7分支。

    cd hdf5
    git checkout remotes/origin/hdf5_1_10_7 
  3. 编译hdf5。

    ./configure --prefix=/usr/local/hdf5 --enable-cxx
    make -j72                 #-j 后的数值可以根据CPU的核数设置
    make check                # run test suite.
    make install
    make check-install        # verify installation. 
  4. 添加环境变量。

    export PATH=/usr/local/hdf5/bin:$PATH
    export LD_LIBRARY_PATH=/usr/local/hdf5/lib:$LD_LIBRARY_PATH
    export LIBRARY_PATH=/usr/local/hdf5/lib:$LIBRARY_PATH
    export CPATH=/usr/local/hdf5/include:$CPATH 

FAQ

软件安装常见问题

pip3.7 install Pillow==5.3.0安装失败

现象描述

pip3.7 install pillow==5.3.0安装失败。

可能原因

缺少必要的依赖,如:libjpeg、python-devel、 zlib-devel 、libjpeg-turbo-devel等等。

处理方法

安装相关依赖,通过如下命令安装:

  • CentOS/EulerOS/Tlinux/BClinux/Suse

    yum install libjpeg python-devel zlib-devel libjpeg-turbo-devel

  • Ubuntu/Debian/UOS

    apt-get install libjpeg python-devel zlib-devel libjpeg-turbo-devel

模型和算子运行常见问题

在模型运行或者算子运行时遇到报错“RuntimeError: ExchangeDevice:”

现象描述

可能原因

目前在一个线程内,只能调用一个NPU设备,当切换不同的npu device时,出现上述错误。

处理方法

检查代码中在调用torch.npu.set_device(device)、tensor.to(device)或者model.to(device)时,同一个线程内前后调用时device名称不一致。对于多个线程情况(如多卡训练),每个线程同样只能调用固定的npu device。

在模型运行或者算子运行时遇到报错“Error in atexit.\_run\_exitfuncs:”

现象描述

可能原因

在torch初始化时,若未通过torch.npu.device(id)指定npu设备,则默认使用device 0设备。若直接使用其他NPU设备,如指定在device 1上创建tensor,那么在运行时会出现上述错误。

处理方法

在调用NPU设备之前,通过torch.npu.set_device(device)指定需要使用的NPU设备即可。

在模型运行时遇到报错“terminate called after throwing an instance of 'c10::Error' what\(\): HelpACLExecute:”

现象描述

可能原因

目前HelpACLExecute的报错信息无法直接找到报错位置,此处在task任务下发时报错,是由于开启了TASK多线程下发(export TASK_QUEUE_ENABLE=1),上层封装了报错信息,导致无法获取更加详细的报错日志。

处理方法

可通过如下两种方式处理:

  • 查看具体的host报错日志信息。日志默认路径为/var/log/npu/slog/host-0/,根据时间标识查找以host-0为前缀的日志文件,打开日志文件,搜索“ERROR”,查询具体的报错信息。
  • 关闭多线程下发(export TASK_QUEUE_ENABLE=0),再次运行代码,一般可根据终端报错信息定位错误原因。

在模型运行时遇到报错“terminate called after throwing an instance of 'c10::Error' what\(\): 0 INTERNAL ASSERT”

现象描述

import torch

npu = "npu"

def test_cpu():
    input = torch.randn(2000, 1000).detach().requires_grad_()
    output = torch.sum(input)
    output.backward(torch.ones_like(output))

def test_npu():
    input = torch.randn(2000, 1000).detach().requires_grad_().npu()
    output = torch.sum(input)
    output.backward(torch.ones_like(output))

if __name__ == "__main__":
    test_cpu()
    torch.npu.set_device(f"{npu}:1")
    test_npu()

执行代码后出现如下报错。

可能原因

在运行backward运算后,通过set_decice()方法手动设置device设备,导致报错。在运行backward运算时,若没有设置device,程序会自动默认初始化device为0,相当于执行了set_device("npu:0")。由于目前不支持切换device进行计算,若再通过set_decice()方法手动设置device设备,则可能出现该错误。

处理方法

在运行backward运算前,通过set_decice()方法手动设置device。修改如下。

if __name__ == "__main__":
    torch.npu.set_device(f"{npu}:1")
    test_cpu()
    test_npu()

在模型运行时遇到报错“ImportError: libhccl.so.”

现象描述

可能原因

目前对外发布的pytorch安装包,默认使用NPU和HCCL功能,因此在调用时需要将HCCL模块路径添加到环境变量中。根据报错信息“can not find libhccl.so”,出现上述错误原因为缺少hccl库文件。

处理方法

将hccl模块的路径添加到环境变量中,一般情况下hccl库文件路径为安装包下的.../fwkacllib/python/site-packages/hccl。

在模型运行时遇到报错“RuntimeError: Initialize.”

现象描述

可能原因

根据报错信息,初步判断为npu设备初始化错误。进一步查找host日志报错信息如下:

根据日志信息定位报错原因为系统在拉起npu设备时报错。

处理方法

可通过以下步骤解决该问题。

  1. 重启服务器和所有npu device。

    如果问题解决,处理完毕。

    如果问题未解决,请执行2

  2. 检查安装的driver和firmware版本是否匹配。

    如果不匹配,请执行3

    如果匹配,请执行4

  3. 更换正确版本的driver和firmware。

    如果问题解决,处理完毕。

    如果问题未解决,执行步骤四

  4. 联系华为工程师。

在模型运行时遇到报错“TVM/te/cce error.”

现象描述

可能原因

pytorch内调用npu类型算子时,强依赖于te、cce、tvm组件,pytorch、CANN/nnae和te版本需要一致。在更新CANN/nnae后,te等组件不会自动更新,当版本不匹配时,则会出现该报错。

处理方法

更新te等组件版本,具体需要更新te-*.whl和topi-*.whl安装包。在安装的CANN或者nnae的lib64子目录下(以root安装用户为例:默认安装路径在/usr/local/Ascend/ascend-toolkit/latest/lib64目录下,更新安装包即可。在该目录下有安装包topi-0.4.0-py3-none-any.whl和te-0.4.0-py3-none-any.whl,分别执行pip3 install --upgrade topi-0.4.0-py3-none-any.whl,pip install --upgrade te-0.4.0-py3-none-any.whl。

在模型运行时遇到报错“MemCopySync:drvMemcpy failed.”

现象描述

脚本:

    import torch

    def test_sum():
        xs_shape = [22400, 8]
        ys_shape = [22400, 8]
        gt_bboxes_shape = [22400, 8,4]
        xs = torch.rand(xs_shape).npu()
        ys = torch.rand(ys_shape).npu()
        gt_bboxes = torch.rand(gt_bboxes_shape).npu().half()
        left = xs - gt_bboxes[..., 0]
        right = gt_bboxes[..., 2] - xs
        top = ys - gt_bboxes[..., 1]
        bottom = gt_bboxes[..., 3] - ys
        # stream = torch.npu.current_stream()
        # stream.synchronize()
        # left, top 结果是fp32,  right, bottom 结果是fp16,
        # print(left.dtype, top.dtype, right.dtype, bottom.dtype)
        bbox_targets = torch.stack((left, top, right, bottom), -1)  #报错位置在这里
        # stream.synchronize()

        bbox_targets = torch.sum(bbox_targets)

shell报错信息:

    RuntimeError: Run:/usr1/workspace/PyTorch_Apex_Daily_c20tr5/CODE/aten/src/ATen/native/npu/utils/OpParamMaker.h:280 NPU error,NPU error code is:500002
    [ERROR] RUNTIME(160809)kernel task happen error, retCode=0x28, [aicpu timeout].
    [ERROR] RUNTIME(160809)aicpu kernel execute failed, device_id=0, stream_id=512, task_id=24, fault so_name=, fault kernel_name=, extend_info=.
    Error in atexit._run_exitfuncs:
    Traceback (most recent call last):
    File "/usr/local/python3.7.5/lib/python3.7/site-packages/torch/__init__.py", line 429, in _npu_shutdown
        torch._C._npu_shutdown()
    RuntimeError: npuSynchronizeDevice:/usr1/workspace/PyTorch_Apex_Daily_c20tr5/CODE/c10/npu/NPUStream.cpp:806 NPU error, error code is 0

日志信息:

    [ERROR] RUNTIME(12731,python3.7):2021-02-02-22:23:56.475.679 [../../../../../../runtime/feature/src/npu_driver.cc:1408]12828 MemCopySync:drvMemcpy failed: dst=0x108040288000, destMax=1240, src=0x7fe7649556d0, size=1240, kind=1, drvRetCode=17!
    [ERROR] RUNTIME(12731,python3.7):2021-02-02-22:23:56.475.698 [../../../../../../runtime/feature/src/logger.cc:113]12828 KernelLaunch:launch kernel failed, kernel=140631803535760/ArgMinWithValue_tvmbin, dim=32, stream=0x55b22b3def50
    [ERROR] RUNTIME(12731,python3.7):2021-02-02-22:23:56.475.717 [../../../../../../runtime/feature/src/api_c.cc:224]12828 rtKernelLaunch:ErrCode=207001, desc=[module new memory error], InnerCode=0x70a0002

可能原因

根据shell和日志报错信息,两者报错信息不匹配。

shell报错是在同步操作中和AI CPU错误,而日志报错信息却是在min算子(内部调用ArgMinWithValue_tvmbin),二者报错信息不对应。一般这类问题出现的原因是由于日志生成的报错信息滞后。

报错信息滞后可能是由于AI CPU算子的异步执行,导致报错信息滞后。

处理方法

对于该报错需要根据实际的错误来定位,可参考如下步骤进行处理:

  1. 通过关闭多任务算子下发后发现结果不变,推断在shell脚本报错位置和日志报错算子之前就已出现错误。
  2. 根据报错加上stream同步操作,缩小错误范围,定位错误算子。stream同步操作的作用在于其要求代码所运行到的位置之前的所有计算必须为完成状态,从而定位错误位置。
  3. 通过在代码中加上stream同步操作,确定报错算子为stack。
  4. 打印stack所有参数的shape、dtype、npu_format,通过构造单算子用例复现问题。定位到问题原因为减法计算输入参数数据类型不同,导致a-b和b-a结果的数据类型不一致,最终在stack算子中报错。
  5. 将stack入参数据类型转换为一致即可临时规避问题。

在模型运行时遇到报错“MemCopySync:drvMemcpy failed.”

现象描述

脚本:

    import torch

    def test_sum():
        xs_shape = [22400, 8]
        ys_shape = [22400, 8]
        gt_bboxes_shape = [22400, 8,4]
        xs = torch.rand(xs_shape).npu()
        ys = torch.rand(ys_shape).npu()
        gt_bboxes = torch.rand(gt_bboxes_shape).npu().half()
        left = xs - gt_bboxes[..., 0]
        right = gt_bboxes[..., 2] - xs
        top = ys - gt_bboxes[..., 1]
        bottom = gt_bboxes[..., 3] - ys
        # stream = torch.npu.current_stream()
        # stream.synchronize()
        # left, top 结果是fp32,  right, bottom 结果是fp16,
        # print(left.dtype, top.dtype, right.dtype, bottom.dtype)
        bbox_targets = torch.stack((left, top, right, bottom), -1)  #报错位置在这里
        # stream.synchronize()

        bbox_targets = torch.sum(bbox_targets)

shell报错信息:

    RuntimeError: Run:/usr1/workspace/PyTorch_Apex_Daily_c20tr5/CODE/aten/src/ATen/native/npu/utils/OpParamMaker.h:280 NPU error,NPU error code is:500002
    [ERROR] RUNTIME(160809)kernel task happen error, retCode=0x28, [aicpu timeout].
    [ERROR] RUNTIME(160809)aicpu kernel execute failed, device_id=0, stream_id=512, task_id=24, fault so_name=, fault kernel_name=, extend_info=.
    Error in atexit._run_exitfuncs:
    Traceback (most recent call last):
    File "/usr/local/python3.7.5/lib/python3.7/site-packages/torch/__init__.py", line 429, in _npu_shutdown
        torch._C._npu_shutdown()
    RuntimeError: npuSynchronizeDevice:/usr1/workspace/PyTorch_Apex_Daily_c20tr5/CODE/c10/npu/NPUStream.cpp:806 NPU error, error code is 0

日志信息:

    [ERROR] RUNTIME(12731,python3.7):2021-02-02-22:23:56.475.679 [../../../../../../runtime/feature/src/npu_driver.cc:1408]12828 MemCopySync:drvMemcpy failed: dst=0x108040288000, destMax=1240, src=0x7fe7649556d0, size=1240, kind=1, drvRetCode=17!
    [ERROR] RUNTIME(12731,python3.7):2021-02-02-22:23:56.475.698 [../../../../../../runtime/feature/src/logger.cc:113]12828 KernelLaunch:launch kernel failed, kernel=140631803535760/ArgMinWithValue_tvmbin, dim=32, stream=0x55b22b3def50
    [ERROR] RUNTIME(12731,python3.7):2021-02-02-22:23:56.475.717 [../../../../../../runtime/feature/src/api_c.cc:224]12828 rtKernelLaunch:ErrCode=207001, desc=[module new memory error], InnerCode=0x70a0002

可能原因

根据shell和日志报错信息,两者报错信息不匹配。

shell报错是在同步操作中和ai cpu错误,而日志报错信息却是在min算子(内部调用ArgMinWithValue_tvmbin),二者报错信息不对应。一般这类问题出现的原因是由于日志生成的报错信息滞后。

报错信息滞后可能是由于AI cpu算子的异步执行,导致报错信息滞后。

处理方法

对于该报错需要根据实际的错误来定位,可参考如下步骤进行处理:

  1. 通过关闭多任务算子下发后发现结果不变,推断在shell脚本报错位置和日志报错算子之前就已出现错误。
  2. 根据报错加上stream同步操作,缩小错误范围,定位错误算子。stream同步操作的作用在于其要求代码所运行到的位置之前的所有计算必须为完成状态,从而定位错误位置。
  3. 通过在代码中加上stream同步操作,确定报错算子为stack。
  4. 打印stack所有参数的shape、dtype、npu_format,通过构造单算子用例复现问题。定位到问题原因为减法计算输入参数数据类型不同,导致a-b和b-a结果的数据类型不一致,最终在stack算子中报错。
  5. 将stack入参数据类型转换为一致即可临时规避问题。

在模型运行时将多任务下发关闭\(export TASK\_QUEUE\_ENABLE=0\)后仍然遇到报错“HelpACLExecute.”

现象描述

可能原因

pytorch算子在npu上运行,通过ACL接口调用底层经过优化的算子。由于在上层报错信息显示为HelpACLExecute. 时,内部也正在对报错信息与日志进行完善,导致部分算子发生错误时,报错信息获取异常。

处理方法

查看host日志,确定报错算子和位置,日志默认路径为/var/log/npu/slog/host-0。查找对应时间的log文件,搜索ERROR字段,查找错误信息。如对上述的错误,查询日志中的ERROR字段为:

从日志信息EEROR部分可以发现,报错算子为topKD,报错原因为“The number of attrs in op desc and op store does not match. ”,定位到错误原因为topk算子参数不匹配。

在模型代码中查找topk算子调用位置,确定该算子是否可由其他算子代替,若可由其他算子报错,暂时使用代替方案,并将算子报错信息报告华为工程师。若无替代算子,请将算子报错信息通知华为工程师解决。

在模型运行时遇到报错“55056 GetInputConstDataOut: ErrorNo: -1\(failed\)”

现象描述

模型训练过程中,查看host训练日志(路径:“/root/ascend/log/plog/“),可能出现如下报错信息。

可能原因

该报错信息是由于调用某一公共API接口导致。

处理方法

该报错信息不影响训练功能与性能,可忽略该报错信息。

模型调测常见问题

在模型调测时遇到报错“RuntimeError: malloc:/..../pytorch/c10/npu/NPUCachingAllocator.cpp:293 NPU error, error code is 500000.”

现象描述

可能原因

对于NPUCachingAllocator中malloc类型的错误原因一般为NPU显存不足,所需显存大于npu上可用显存。

处理方法

在模型调测中,可用通过减小batch size参数来减少NPU显存的分配,解决该问题。

在模型调测时遇到报错“RuntimeError: Could not run 'aten::trunc.out' with arguments from the 'NPUTensorId' backend.”

现象描述

可能原因

目前npu设备仅支持pytorch部分算子,对于不支持的算子在使用时均会报上述错误,算子正在不断开发中。算子支持情况可参考PyTorch原生算子,持续更新。

处理方法

在模型调测中,可通过减小batch size参数,来减少NPU显存的占用,解决该问题。

在模型调测时遇到如MaxPoolGradWithArgmaxV1算子和max算子报错

现象描述

可能原因

在模型搭建中,算子输入参数是多样的。某些算子(如MaxPoolGradWithArgmaxV1算子和max算子)在特定参数下,计算报错或者不支持,根据报错信息可以定位到具体算子。

处理方法

根据报错信息定位到具体算子,解决步骤如下:

  1. 排查模型中对该算子的调用方式和参数是否正确;

  2. 根据报错算子构建单算子用例,构建报错场景;

  3. 一般算子错误无法在python侧解决,构建出报错场景。在论坛中发帖附上报错场景,求助华为工程师即可。

    说明: 输入参数shape和dtype需要重点关注,一般是导致算子报错的主要原因。

前述图中,根据报错信息,定位到是MaxPoolGradWithArgmaxV1算子和max算子报错。MaxPoolGradWithArgmaxV1是在反向计算过程中报错,那么构建测试用例时需要构建对应的反向场景;而对于max算子,是正向计算时报错,构建正向场景即可。

在模型中遇到算子报错,首选是仅构建单算子测试用例,确定报错场景和原因即可;若无法在单算子中构建单算子用例,则需要构建基于上下文的单算子场景, 可以参考单算子样例编写说明编写用例。

在调用torch时遇到报错“ModuleNotFoundError: No module named 'torch.\_C'”

现象描述

可能原因

首先确定报错位置,上述报错路径为.../code/pytorch/torch/__init__.py,而当前运行路径在.../code/pytorch下,在执行import torch时,默认首先在当前目录下查找torch文件夹,因此报错。此处应是调用在系统目录下安装的torch包,而不是当前目录下的torch。

处理方法

切换到其他目录执行脚本。

其他操作相关问题

cuda流同步操作报错

现象描述

可能原因

npu未使用npu的流同步方法。

处理方法

使用NPU的流同步方法:

stream = torch.npu.current_stream()
stream.synchronize()

aicpu\_kernels/libpt\_kernels.so不存在

现象描述

可能原因

未导入AICPU。

处理方法

导入AICPU(以root用户安装CANN软件包,安装路径为默认路径为例):

export ASCEND_AICPU_PATH=/usr/local/Ascend/ascend-toolkit/latest

使用npu-smi info查看显存时发现python进程残留

现象描述

可能原因

python进程残留,需要kill。

处理方法

终止python进程:

pkill -9 python

动态shape报错“match op inputs failed”

现象描述

可能原因

PTIndexPut编译的算子和输入的shape不一致, 并有acl_dynamic_shape_op打头的日志字样,确定为动态shape报错。

处理方法

PTIndexPut对应tensor[indices] = value,需要在代码中找到对应的地方将动态shape修改为固定shape。

Op type SigmoidCrossEntropyWithLogitsV2 of ops kernel AIcoreEngine is unsupported

现象描述

[ERROR] GE(24836,python3.7):2021-01-27-18:27:51.562.111 [../../../../../../graphengine/ge/engine_manager/dnnengine_manager.cc:266]25155 GetDNNEngineName: ErrorNo: 1343242282(assign engine failed) GetDNNEngineName:Op type SigmoidCrossEntropyWithLogitsV2 of ops kernel AIcoreEngine is unsupported, reason:Op SigmoidCrossEntropyWithLogitsV2 not supported reason: The type of this op is not found in op store, check whether the op store has this type of op. Op store name is tbe-custom.
The dtype, format or shape of input in op desc is not supported in op store, check the dtype, format or shape of input between the op store and the graph. Op store name is tbe-builtin.

可能原因

SigmoidCrossEntropyWithLogitsV2算子输入了不支持的数据类型,可能是输入int64类型导致的错误。

处理方法

检查对应python代码中输入的数据类型,并修改。

Hook失败

现象描述

Traceback (most recent call last):
  File "tools/train.py", line 227, in <module>
    main()
  File "tools/train.py", line 221, in main
    meta=meta)
  File "/root/YoloV3/mmdetection/mmdet/apis/train.py", line 192, in train_detector
    runner.run(data_loaders, cfg.workflow, cfg.total_epochs)
  File "/usr/local/python3.7.5/lib/python3.7/site-packages/mmcv/runner/epoch_based_runner.py", line 166, in run
    epoch_runner(data_loaders[i], **kwargs)
  File "/usr/local/python3.7.5/lib/python3.7/site-packages/mmcv/runner/epoch_based_runner.py", line 50, in train
    self.run_iter(data_batch, train_mode=True)
  File "/usr/local/python3.7.5/lib/python3.7/site-packages/mmcv/runner/epoch_based_runner.py", line 30, in run_iter
    outputs = self.model.train_step(data_batch, self.optimizer, **kwargs)
  File "/usr/local/python3.7.5/lib/python3.7/site-packages/mmcv/parallel/data_parallel.py", line 100, in train_step
    return self.module.train_step(*inputs[0], **kwargs[0])
  File "/root/YoloV3/mmdetection/mmdet/models/detectors/base.py", line 251, in train_step
    losses = self(**data)
  File "/usr/local/python3.7.5/lib/python3.7/site-packages/torch/nn/modules/module.py", line 660, in __call__
    var = next((v for v in var.values() if isinstance(v, torch.Tensor)))
StopIteration

可能原因

mmdet的loss部分结构触发了pytorch原生hook的bug,导致死循环。

处理方法

解决方案是在/usr/local/python3.7.5/lib/python3.7/site-packages/torch/nn/modules/module.py这个文件的658行加上try跳过:

if len(self._backward_hooks) > 0:
    var = result
    try:
        while not isinstance(var, torch.Tensor):
            if isinstance(var, dict):
                var = next((v for v in var.values() if isinstance(v, torch.Tensor)))
            else:
                var = var[0]
        grad_fn = var.grad_fn
        if grad_fn is not None:
            for hook in self._backward_hooks.values():
                wrapper = functools.partial(hook, self)
                functools.update_wrapper(wrapper, hook)
                grad_fn.register_hook(wrapper)
    except Exception as e:
        print('hook failed..')
        print(str(e))
return result

加载权重时遇到报错“load state\_dict error.”

现象描述

可能原因

模型训练后保存的state_dict的key值与加载时state_dict的key值不一致,保存时会在每个key的最前面多一个module前缀。

处理方法

加载权重时先遍历state_dict字典,修改key值,并使用新建的字典,具体用例参考demo.py。

脚本如下:

   ckpt = torch.load("checkpoint.pth", map_location=loc)
   # model.load_state_dict(ckpt['state_dict'])
   state_dict_old = ckpt['state_dict']
   state_dict = {}
   for key, value in state_dict_old.items():
       key = key[7:]
       state_dict[key] = value
   model.load_state_dict(state_dict)

模型分布式训练常见问题

在进行模型分布式训练时遇到报错“host not found.”

现象描述

可能原因

对模型进行分布式训练时,会调用集合通信模块HCCL,需要根据实际情况设置IP和端口信息。根据报错信息,确定是IP地址设置错误。

处理方法

在运行脚本中设置正确的IP地址,对于单机情况,设置为本机的IP地址即可;对于多机情况,每个服务器上脚本中的IP需要设置为master节点的IP。

在进行模型分布式训练时遇到报错“RuntimeError:connect\(\) timed out.”

现象描述

可能原因

模型进行分布式训练时,系统防火墙可能会阻截HCCL的集合通信端口的通信。需要根据报错信息,排查通信端口的开放情况,并进行相应设置。

处理方法

查询出被系统防火墙阻截的集合通信端口,并开放相应端口。

马建仓 AI 助手
尝试更多
代码解读
代码找茬
代码优化
Python
1
https://gitee.com/ascend/pytorch.git
git@gitee.com:ascend/pytorch.git
ascend
pytorch
pytorch
v1.5.0-3.0.rc2

搜索帮助