当前阶段针对PyTorch框架实现的对接适配昇腾AI处理器的方案为在线对接方案。
昇腾AI处理器的加速实现方式是以各种算子为粒度进行调用(OP-based),即通过AscendCL调用一个或几个D亲和算子组合的形式,代替原有GPU的实现方式。其逻辑模型如图1所示。
当前选择在线对接适配方案的主要原因有以下几点:
模型迁移主要指将开源社区中实现过的模型迁移到昇腾AI处理器上,主要流程如图1所示。
表 1 迁移流程说明
详情请参见模型移植评估。 |
|
详情请参见《PyTorch算子开发指南》。 |
|
详情请参见环境准备。 |
|
详情请参见模型迁移。 |
|
详情请参见模型训练。 |
|
详情请参见《CANN 软件安装指南》中“日志参考>日志操作”章节设置日志级别和《CANN 软件安装指南》中“开发工具>AI Core Error分析工具”章节。 |
|
详情请参见性能调优和分析。 |
|
详情请参见精度调测。 |
|
详情请参见模型保存与转换和《CANN 软件安装指南》中“ATC模型转换”章节。 |
|
详情请参见《CANN 软件安装指南》中”应用开发(c++)“章节。 |
|
主要涉及环境准备、模型迁移、模型调测和其他常见问题的解决方法。详情请参见FAQ。 |
对ResNet50模型进行迁移,帮助用户快速了解迁移过程。
本样例基于PyTorch官网提供的Imagenet数据集训练模型main.py脚本进行适配昇腾910 AI处理器的迁移。
模型是否可以迁移成功主要取决于模型算子是否支持昇腾AI处理器。故需要对模型算子对昇腾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训练脚本的基础上进行修改,实现模型的单卡训练和单机多卡训练迁移。
在main.py脚本中导入torch.npu模块。
import torch.npu
在main.py中定义训练设备。
CALCULATE_DEVICE = "npu:0"
修改参数以及判断选项,使其只在昇腾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:
......
将模型以及损失函数迁移到昇腾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 #############
将数据集目标结果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 #############
设置当前正在使用的device。
代码位置:main.py文件中的主函数入口:
if __name__ == '__main__':
############## npu modify begin #############
if 'npu' in CALCULATE_DEVICE:
torch.npu.set_device(CALCULATE_DEVICE)
############## npu modify begin #############
main()
main.py增加头文件以支持基于PyTorch框架的模型在昇腾910 AI处理器上训练及进行混合精度训练。
import torch.npu
from apex import amp
参数设置增加以下参数,包括指定参与训练的昇腾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')
创建由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
指定训练服务器的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 #############
创建由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()
获取进程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
初始化进程组,屏蔽掉初始化方式。
代码位置: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)
要进行分布式训练且需要引入混合精度模块,并且需要将模型迁移到昇腾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()
屏蔽掉损失函数、优化器和断点训练部分,将这部分在后面与混合精度训练结合起来。
代码位置: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
数据加载器,结合了数据集和取样器,并且可以提供多个线程处理数据集。使用昇腾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 #############
进行损失函数及优化器构建,将模型、损失函数迁移到昇腾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 #############
断点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 #############
训练时,需要将数据集迁移到昇腾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)
标记反向传播.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()
验证时,需要将验证数据集迁移到昇腾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设备上进行分布式训练。
在选取模型时,尽可能选取权威Pytorch模型实现仓作为标杆,包括但不限于Pytorch(example/vision等)、facebookresearch(Detectron/detectron2等)和open-mmlab(mmdetection/mmpose等)。
查看算子适配情况。将原始模型及训练脚本迁移到昇腾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 参数说明
自定义转换规则样例如下:
{
"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 参数说明
进入脚本转换工具所在路径。
cd Ascend-cann-toolkit安装目录/ascend-toolkit/{version}/{arch}-linux/toolkit/tools/ms_fmk_transplt
执行脚本转换工具。
python3 ms_fmk_transplt.py -i 原始脚本路径 -o 脚本转换结果输出路径 [-r 自定义规则json文件路径] [-s] [-sim] [distributed -m 训练脚本的入口文件 -t 目标模型变量名]
说明: distributed及其参数-m、-t在语句最后指定。
完成脚本转换。
脚本转换完成后,进入脚本转换结果输出路径查看结果文件,以GPU单卡脚本转换为NPU多卡脚本为例。
├── xxx_msft // 脚本转换结果输出目录,默认为原始脚本路径。xxx为原始脚本所在文件夹名称。
│ ├── 生成脚本文件 // 与转换前的脚本文件目录结构一致。
│ ├── msFmkTranspltlog.txt // 脚本转换过程日志文件。
│ ├── unsupported_op.xlsx // 不支持算子列表文件。
│ ├── change_list.csv // 修改记录文件。
│ ├── run_distributed_npu.sh // 多卡启动shell脚本。
当前在线对接方案优点在于保证在昇腾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训练模型迁移除了需在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)
更多迁移细节请参见单机多卡训练修改。
为了使昇腾AI处理器使用PyTorch框架的能力,需要对原生的PyTorch框架进行一定Device层面的适配,对外呈现是需要将跟cpu和cuda相关的接口进行切换;在进行网络迁移时,需要将某些设备相关的接口转换成跟昇腾AI处理器相关的接口,当前适配的设备相关接口请参见表1:
表 1 设备接口替换
用户在构建网络或进行网络迁移时,需要创建指定数据类型的tensor,在昇腾AI处理器上创建的tensor如下。
表 2 tensor创建接口替换
更多接口请参见《PyTorch API 支持清单》。
基于NPU芯片的架构特性,会涉及到混合精度训练,即混合使用float16和float32数据类型的应用场景。使用float16代替float32有如下好处:
但是,混合精度训练受限于float16表达的精度范围,单纯将float32转换成float16会影响训练收敛情况,为了保证部分计算使用float16来进行加速的同时能保证训练收敛,这里采用混合精度模块Apex来达到以上效果。混合精度模块Apex是一个集优化性能、精度收敛于一身的综合优化库。
适配昇腾AI处理器的混合精度模块Apex除了上述优点外,还能提升运算性能。具体如下:
混合精度模块功能和优化描述如表1所示。
表 1 混合精度模块功能
说明:
- 当前版本的实现方式主要为python实现,不支持AscendCL或者CUDA优化。
- 当前昇腾AI设备暂不支持原始Apex的FusedLayerNorm接口模块,如果模型原始脚本文件使用了FusedLayerNorm接口模块,需要在模型迁移过程中将脚本头文件“from apex.normalization import FusedLayerNorm“替换为“from torch.nn import LayerNorm“。
使用apex混合精度模块需要首先从apex库中导入amp,代码如下:
from apex import amp
导入amp模块后,需要初始化amp,使其能对模型、优化器以及PyTorch内部函数进行必要的改动,初始化代码如下:
model, optimizer = amp.initialize(model, optimizer, combine_grad=True)
标记反向传播.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安装路径。
通过训练执行结果,判断吞吐量指标是否达到预期要求。
当吞吐量指标不达标时,需要找出制约性能瓶颈的原因,主要为以下几个方面:
针对以上制约性能瓶颈的原因进行分析与优化。
当模型训练过程中吞吐量指标不达标时,可以通过采集训练过程中的profiling数据,分析哪个环节、哪个算子导致的性能消耗。Profiling数据采集分为PyTorch层面和CANN层面的采集,PyTorch层面采集的是PyTorch API的数据,CANN层面采集的是TBE算子的数据。
请参见以下方式进行profiling数据的获取,并根据实际情况选择需要的数据采集方式。
PyTorch层面Profiling数据采集。
获取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)
运行成功后会打印出profiler结果信息。
打印结果包含CPU和NPU的耗时等相关信息,详细信息参见表2 。
Name | Self CPU % | Self CPU | CPU total % | CPU total | CPU time avg | Self NPU % | Self NPU | NPU total | NPU time avg | # of Calls |
---|
查看chrome_trace文件。
chrome_trace文件可以通过以下方式打开查看:在Chrome浏览器 中输入“chrome://tracing“地址,然后将落盘文件拖到空白处即可打开文件内容,通过键盘W、A、S、D键,可以对profiler的结果进行缩放和移动。
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 Mem
、Self CPU Mem
、NPU Mem
、Self 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数据采集。
获取性能数据文件。
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上。
解析性能数据文件。
请参见《开发工具》中“Profiling工具>高级功能(所有性能调优方式和采集项)>数据解析与导出”章节。
高级用法
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执行的,通过OPInfo日志,我们可以获取实际执行时的算子及其属性。通过get_ascend_op_info.py脚本获取。
编写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)
设置环境变量,将host日志打屏。
export ASCEND_SLOG_PRINT_TO_STDOUT=1
设置日志级别为info,参考《故障管理》中“日志参考>日志操作”章节设置日志级别。
执行训练脚本,进行模型训练,训练完成后获取host侧日志,默认位置为$HOME/ascend/log/plog目录下,$HOME表示Host侧用户根目录。
解析host侧日志会在当前目录下得到OPInfo信息ascend_op_info_summary.txt。
python3 get_ascend_op_info.py --host_log_folder $HOME/ascend/log/plog
分析TaskInfo中额外的task,尤其关注transdata。
在进行PyTorch模型迁移训练时,部分网络模型会出现FPS较低、性能不达标的情况。可以考虑对服务器进行以下优化尝试提高训练性能。
提升网络性能需要在X86服务器BIOS设置中将电源策略设为高性能模式,具体操作如下。
登录iBMC界面,启动虚拟控制台,远程控制选择HTML5集成远程控制台,如图1。
在虚拟界面工具栏中,单击启动项工具,弹出启动项配置界面,如图2。
在启动项配置界面选择,选择“BIOS设置”,然后在虚拟界面工具栏中单击重启工具,重启服务器。
系统重启后进入BIOS配置界面,依次选择“Advanced”>“Socket Configuration”,如图3所示。
进入Advanced Power Mgmt. Configuration,设置Power Policy为Performance。如图4。
按下“F10”保存配置并重启服务器。
请使用root用户执行如下操作。
cat /sys/devices/system/cpu/cpu0/cpufreq/scaling_governor
执行以上命令会输出当前CPU模式,CPU模式说明请参见表1。如果当前CPU模式不是performance模式,请执行以下操作设置CPU为performance模式。否则请跳过以下步骤。
表 1 CPU模式
安装工具,使用如下命令安装。
以“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
设置CPU为performance模式。
cpupower frequency-set -g performance
再次执行步骤1查看当前CPU模式是否已设置为performance模式。
在某些对Host侧CPU要求较高的模型中,例如目标检测类模型,需要进行较为复杂的图像预处理,开启电源高性能模式能一定程度上提高性能和稳定性。ARM服务器提升网络性能需要在BIOS设置中将电源策略设为高性能模式,具体操作如下。
登录ibmc界面,启动虚拟控制台,远程控制选择HTML5集成远程控制台,如图1。
在虚拟界面工具栏中,单击启动项工具,弹出启动项配置界面,如图2。
在启动项配置界面选择,选择“BIOS设置”,然后在虚拟界面工具栏中单击重启工具,重启服务器。
系统重启后进入BIOS配置界面,依次选择“Advanced”>“ Performance Config”,如图3所示。
进入“Performance Config”,设置Power Policy为Performance。如图4。
按下“F10”保存配置并重启服务器。
安装高性能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
安装高性能pillow库。
执行如下命令卸载原生pillow。
pip3.7 uninstall -y pillow
安装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
修改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-3.4.10版本。
获取训练过程中算子信息OP_INFO,参见获取算子信息OP_INFO。
分析OP_INFO中算子的规格和调用关系,定位是否插入了多余的算子,重点关注transdata是否合理。
优化方案:通过指定部分算子初始化格式,对多余的格式转换算子进行消除。
在pytorch/torch/nn/modules/module.py中,在cast_weight中指定算子初始化格式,如下图。
格式设置原则可参考如下规则:
针对公版模型中常见的网络结构和函数,我们针对性地对其进行了优化,使得运算性能大幅度提升,同时,将其集成到Pytorch框架中,便于模型性能调优中使用。
亲和库详细说明请参见《PyTorch API 支持清单》中”亲和库“章节。
说明: 该部分调优内容会随着版本不断增强和更新,请以实际PyTorch版本中对应路径下的内容为准。
对于NPU设备,算子输入参数的信息(shape/format等)会影响算子的性能,进而影响模型整体性能。为了使模型获得更良好的性能,可以将模型中所有的算子的输入参数信息获取至本地进行分析(dump),然后将每个算子在NPU上运行,调整算子运行时的策略,确定性能最佳的策略。以上这个过程称为调优,AOE工具则实现了这样的调优功能,可以用于提升模型的性能。
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)
算子调优
设置环境变量:
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。
调优完成后,还原代码修改,运行模型,验证模型/算子性能是否提高。
目前pytorch1.8.1暂不支持。
优先在同等语义和超参下,跑一定的epoch(推荐完整epoch数的20%),使精度,loss等对齐GPU相应水平,完成后再对齐最终精度。
精度问题排查需要找出是哪一步出现的问题,主要以下几个方面:
定位思路:在网络中加入hook进行排查判断是哪个地方有较大嫌疑,然后构建单算子用例逐渐缩小错误范围,证明该算子在当前网络场景下计算有误,可以对比CPU或GPU结果证明。
规避方案:使用同等语义其他算子替代。
解决方案:改进算子精度或功能问题。
定位思路:由于Loss的特殊性和可以自定义,在判断Loss计算错误后建议dump网络中的loss的输入来测试而非随机同shape tensor,这样才能更好地复现证明。
规避方案:使用同等语义其他算子替代。
解决方案:改进算子精度或功能问题(loss也是由算子构成)。
参数更新错误。
多卡计算错误。
定位思路:在保证单卡精度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=1
、USE_NCCL=0
字段,将 USE_HCCL
、USE_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或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语句外。
将在GPU(NPU)环境dump的数据model_gpu.h5拷贝到NPU(GPU)环境。
在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语句外。
使用msaccucmp.py对算子输出数据对比。
ascend-toolkit提供了msaccucmp.py工具脚本用具精度对比。
该脚本路径为:"/user/local/Ascend/ascend-toolkit/latest/tools/operator_cmp/compare/msaccucmp.py",
路径仅供参考,请以ascend-toolkit实际安装路径为准。
也可以使用如下命令查找msaccucmp.py路径。
find / -name msaccucmp.py
执行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数据文件路径。
用户通过算子溢出检测功能检测算子是否有溢出,然后采集溢出算子的数据,从而帮助开发人员快速定位并解决算子精度问题。
# 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_path}路径下生成dump数据的.h5文件,用户可进入路径自行查看。
将采集到的.h5文件映射到TBE算子,映射方法请参见算子层级精度对比。
请将算子溢出的打印截图及映射后的TBE算子输入输出文件通过Issue附件形式反馈给华为开发人员。
将GPU/CPU上一层算子的输出作为NPU当前层算子的输入,逐层进行精度比对,以排除由于累计误差造成的影响。 前提条件:
export ACL_DUMP_DATA=0
。torch.npu.init.dump()
和torch.npu.set.dump()
接口。操作步骤:
准备好需要映射的算子.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"文件中。
配置acl.json文件。
在模型目录下创建acl dump功能所需的的配置文件acl.json
{
"dump":
{
"dump_list":[]
"dump_path":"./output_IR2TBE"# 映射结果输出路径
"dump_mode":"all"
"dump_op_switch":"on"
}
}
需将dump_path
修改为结果输出路径,其他字段不需要修改。
修改训练脚本。
在训练脚本中添加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)
模型运行。
运行一步完整的模型计算过程,在计算过程中load遇到output.h5中的数据后,自动开启acl dump功能,执行IR,并dump出IR相对应的TBE算子的输入输出数据,IR执行结束,acl dump结束。
获得映射文件。
运行成功后,在acl.json配置文件中的dump_path
路径下查看输出结果文件。
用户也可通过分析整个网络的方式来进行网络模型的精度调测。
通过对比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),则认为运算结果正常。
通过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来确定。
通过直接获取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文件所示。
通过state_dict来保存和加载模型,示例如下:
保存模型。
# 创建保存路径
PATH = "state_dict_model.pt"
# 保存模型
torch.save(net.state_dict(), PATH)
加载模型以用于在线推理,示例如下,详情请参见《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。
保存和加载模型示例如下:
保存模型。
PATH = "checkpoint.pth.tar"
torch.save({
'epoch': epoch,
'loss': loss,
'state_dict': model.state_dict(),
'optimizer' : optimizer.state_dict(),
...
}, PATH)
加载模型用于推理或恢复训练。
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层设置为推理模式。
昇腾AI处理器Pytorch模型的部署策略是基于Pytorch官方支持的ONNX模块实现的。ONNX是业内目前比较主流的模型格式,广泛用于模型交流及部署。本节主要介绍如何将Checkpoint文件通过torch.onnx.export()接口导出为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模型时需要先确定保存时的信息,有时保存的节点名称和模型定义中的节点会有差异,例如会多出前缀和后缀。在进行转换的时候,可以对节点名称进行修改。转换代码样例如下。
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()
本样例基于PyTorch官网提供的Imagenet数据集训练模型进行适配昇腾910 AI处理器的迁移改造,样例获取路径为https://github.com/pytorch/examples/tree/master/imagenet。
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 前向排查
详细说明如下:
整网排查记录表如下:
表 2 整网排查
详细说明如下:
使用计算类算子替换框架类算子。
使用buffer记录index信息到npu,消除index.to('npu') 的操作。
使用计算类算子消除非连续。
contiguous_with_gatherv2是使用aicore算子GatherV2来完成非连续转连续操作。
修改batchsize。
修改batchsize + contiguous_with_gatherv2。
由于concat算子的反向是chunk,会引起非连续问题,故自定义concat算子反向,使用Gatherv2替代chunk,将其融合成cat+shuffle+chunk,消除不连续。
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。
IndexSelectFullImplementation中涉及到了对一个5HD的tensor做两次gatherv2操作,这个时候会导致两次的5HD->4D,可以手动先做一次5HD->4D,这样就可以在gatherv2时不做transdata,从而消减一次transdata操作。
加入混合精度O1。
加入混合精度O2。
由于Axpy算子的参数校验,所有网络在参数更新时,如C不整除16则会transdata为4D进行Axpy运算,引入了大量的transdata算子,通过增加一个函数,当Axpy的input的shape一致时结束校验,从而避免了格式转换,增加了运行效率。
删除所有的流同步操作,原因是容易导致不收敛,没有采纳。
使用针对非对齐优化后的Gatherv2算子后,整体性能提速至交付水平。
使用针对ShufflenetV2场景再次优化后的Gatherv3算子后,整体性能还能继续提升。
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方法。
单算子测试用例构建,直接调用该算子即可复现错误场景。
例如构建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上报错。
基于上下文的单算子测试用例构建。
这里虽然是单算子样例,但有时候不仅仅为一个操作,而是带有上下文的场景,还有时候是一个带参数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()
当前适配昇腾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数据会在{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出现了“.”、“/”、“\”、空格时,会转换为下划线表示。
请根据实际情况,将{op_type}.{op_name}.{taskid}.{stream_id}.{timestamp}上传到安装有CANN软件包的环境。
进入解析脚本所在路径,假设CANN软件包安装目录为:/home/HwHiAiUser/Ascend。
cd /home/HwHiAiUser/Ascend/ascend-toolkit/latest/toolkit/tools/operator_cmp/compare
执行msaccucmp.pyc脚本,转换dump文件为numpy文件。举例:
python3 msaccucmp.pyc convert -d /home/HwHiAiUser/dump -out /home/HwHiAiUser/dumptonumpy -v 2
说明: -d参数支持传入单个文件,对单个dump文件进行转换,也支持传入目录,对整个path下所有的dump文件进行转换。
调用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官网介绍。
开启TASK多线程下发,绝大多数情况下,打开该功能会进一步提升整网训练性能。
export TASK_QUEUE_ENABLE=1
开启重定向日志到stdout,用于导出host日志到屏幕。
export ASCEND_SLOG_PRINT_TO_STDOUT=0
设置日志级别,日志级别设置,信息从多到少分别是 debug --> info --> warning --> error --> null,一般设置为error,调试时使用info。请参考《故障管理》中“日志参考>日志操作”章节设置日志级别设置日志级别。
export ASCEND_GLOBAL_LOG_LEVEL=3
dump图,主要用于查看图结构。
export DUMP_GE_GRAPH=2
export DUMP_GRAPH_LEVEL=3
设置Event日志开启标志。
export ASCEND_GLOBAL_EVENT_ENABLE=0
设置是否开启PTCopy。
export PTCOPY_ENABLE=1
设置是否开启combined标志。
export COMBINED_ENABLE=1
设置特殊场景是否需要重新编译,不需要修改。
export DYNAMIC_OP="ADD#MUL"
HCCL白名单开关。
export HCCL_WHITELIST_DISABLE=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"))
将改造后的训练脚本在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配合使用。
以下步骤请在root用户下执行。
下载gcc-7.3.0.tar.gz,下载地址为https://mirrors.tuna.tsinghua.edu.cn/gnu/gcc/gcc-7.3.0/gcc-7.3.0.tar.gz。
安装gcc时候会占用大量临时空间,所以先执行下面的命令清空/tmp目录:
sudo rm -rf /tmp/*
安装依赖。
centos/bclinux执行如下命令安装。
yum install bzip2
ubuntu/debian执行如下命令安装。
apt-get install bzip2
编译安装gcc。
进入gcc-7.3.0.tar.gz源码包所在目录,解压源码包,命令为:
tar -zxvf gcc-7.3.0.tar.gz
进入解压后的文件夹,执行如下命令下载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
如果上述命令校验失败,需要确保依赖包为一次性下载成功,无重复下载现象。
./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“。
配置环境变量。
当用户执行训练时,需要用到gcc升级后的编译环境,因此要在训练脚本中配置环境变量,通过如下命令配置。
export LD_LIBRARY_PATH=${install_path}/lib64:${LD_LIBRARY_PATH}
其中${install_path}为3.中配置的gcc7.3.0安装路径,本示例为“/usr/local/gcc7.3.0/“。
说明: 本步骤为用户在需要用到gcc升级后的编译环境时才配置环境变量。
以下步骤请在root用户下执行。
获取代码。
git clone https://github.com/HDFGroup/hdf5.git
切换到hdf5-1_10_7分支。
cd hdf5
git checkout remotes/origin/hdf5_1_10_7
编译hdf5。
./configure --prefix=/usr/local/hdf5 --enable-cxx
make -j72 #-j 后的数值可以根据CPU的核数设置
make check # run test suite.
make install
make check-install # verify installation.
添加环境变量。
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
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
在模型运行时遇到报错“terminate called after throwing an instance of 'c10::Error' what(): HelpACLExecute:”
在模型运行时遇到报错“terminate called after throwing an instance of 'c10::Error' what(): 0 INTERNAL ASSERT”
在模型运行时将多任务下发关闭(export TASK_QUEUE_ENABLE=0)后仍然遇到报错“HelpACLExecute.”
目前在一个线程内,只能调用一个NPU设备,当切换不同的npu device时,出现上述错误。
检查代码中在调用torch.npu.set_device(device)、tensor.to(device)或者model.to(device)时,同一个线程内前后调用时device名称不一致。对于多个线程情况(如多卡训练),每个线程同样只能调用固定的npu device。
在torch初始化时,若未通过torch.npu.device(id)指定npu设备,则默认使用device 0设备。若直接使用其他NPU设备,如指定在device 1上创建tensor,那么在运行时会出现上述错误。
在调用NPU设备之前,通过torch.npu.set_device(device)指定需要使用的NPU设备即可。
目前HelpACLExecute的报错信息无法直接找到报错位置,此处在task任务下发时报错,是由于开启了TASK多线程下发(export TASK_QUEUE_ENABLE=1),上层封装了报错信息,导致无法获取更加详细的报错日志。
可通过如下两种方式处理:
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()
目前对外发布的pytorch安装包,默认使用NPU和HCCL功能,因此在调用时需要将HCCL模块路径添加到环境变量中。根据报错信息“can not find libhccl.so”,出现上述错误原因为缺少hccl库文件。
将hccl模块的路径添加到环境变量中,一般情况下hccl库文件路径为安装包下的.../fwkacllib/python/site-packages/hccl。
根据报错信息,初步判断为npu设备初始化错误。进一步查找host日志报错信息如下:
根据日志信息定位报错原因为系统在拉起npu设备时报错。
可通过以下步骤解决该问题。
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。
脚本:
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算子的异步执行,导致报错信息滞后。
对于该报错需要根据实际的错误来定位,可参考如下步骤进行处理:
脚本:
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算子的异步执行,导致报错信息滞后。
对于该报错需要根据实际的错误来定位,可参考如下步骤进行处理:
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算子调用位置,确定该算子是否可由其他算子代替,若可由其他算子报错,暂时使用代替方案,并将算子报错信息报告华为工程师。若无替代算子,请将算子报错信息通知华为工程师解决。
模型训练过程中,查看host训练日志(路径:“/root/ascend/log/plog/“),可能出现如下报错信息。
该报错信息是由于调用某一公共API接口导致。
该报错信息不影响训练功能与性能,可忽略该报错信息。
对于NPUCachingAllocator中malloc类型的错误原因一般为NPU显存不足,所需显存大于npu上可用显存。
在模型调测中,可用通过减小batch size参数来减少NPU显存的分配,解决该问题。
目前npu设备仅支持pytorch部分算子,对于不支持的算子在使用时均会报上述错误,算子正在不断开发中。算子支持情况可参考PyTorch原生算子,持续更新。
在模型调测中,可通过减小batch size参数,来减少NPU显存的占用,解决该问题。
在模型搭建中,算子输入参数是多样的。某些算子(如MaxPoolGradWithArgmaxV1算子和max算子)在特定参数下,计算报错或者不支持,根据报错信息可以定位到具体算子。
根据报错信息定位到具体算子,解决步骤如下:
排查模型中对该算子的调用方式和参数是否正确;
根据报错算子构建单算子用例,构建报错场景;
一般算子错误无法在python侧解决,构建出报错场景。在论坛中发帖附上报错场景,求助华为工程师即可。
说明: 输入参数shape和dtype需要重点关注,一般是导致算子报错的主要原因。
前述图中,根据报错信息,定位到是MaxPoolGradWithArgmaxV1算子和max算子报错。MaxPoolGradWithArgmaxV1是在反向计算过程中报错,那么构建测试用例时需要构建对应的反向场景;而对于max算子,是正向计算时报错,构建正向场景即可。
在模型中遇到算子报错,首选是仅构建单算子测试用例,确定报错场景和原因即可;若无法在单算子中构建单算子用例,则需要构建基于上下文的单算子场景, 可以参考单算子样例编写说明编写用例。
首先确定报错位置,上述报错路径为.../code/pytorch/torch/__init__.py,而当前运行路径在.../code/pytorch下,在执行import torch时,默认首先在当前目录下查找torch文件夹,因此报错。此处应是调用在系统目录下安装的torch包,而不是当前目录下的torch。
切换到其他目录执行脚本。
npu未使用npu的流同步方法。
使用NPU的流同步方法:
stream = torch.npu.current_stream()
stream.synchronize()
未导入AICPU。
导入AICPU(以root用户安装CANN软件包,安装路径为默认路径为例):
export ASCEND_AICPU_PATH=/usr/local/Ascend/ascend-toolkit/latest
python进程残留,需要kill。
终止python进程:
pkill -9 python
PTIndexPut编译的算子和输入的shape不一致, 并有acl_dynamic_shape_op打头的日志字样,确定为动态shape报错。
PTIndexPut对应tensor[indices] = value,需要在代码中找到对应的地方将动态shape修改为固定shape。
[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代码中输入的数据类型,并修改。
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
模型训练后保存的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)
对模型进行分布式训练时,会调用集合通信模块HCCL,需要根据实际情况设置IP和端口信息。根据报错信息,确定是IP地址设置错误。
在运行脚本中设置正确的IP地址,对于单机情况,设置为本机的IP地址即可;对于多机情况,每个服务器上脚本中的IP需要设置为master节点的IP。
模型进行分布式训练时,系统防火墙可能会阻截HCCL的集合通信端口的通信。需要根据报错信息,排查通信端口的开放情况,并进行相应设置。
查询出被系统防火墙阻截的集合通信端口,并开放相应端口。
此处可能存在不合适展示的内容,页面不予展示。您可通过相关编辑功能自查并修改。
如您确认内容无涉及 不当用语 / 纯广告导流 / 暴力 / 低俗色情 / 侵权 / 盗版 / 虚假 / 无价值内容或违法国家有关法律法规的内容,可点击提交进行申诉,我们将尽快为您处理。