模型量化工具

本节主要介绍如何使用模型量化工具生成量化的模型以及使用量化好的模型进行推理。

量化可以加快网络运行速度,降低占用的位宽。神经网络具有鲁棒性,如果训练的网络鲁棒,那么量化之后一般不会降低多少精度,有的甚至会提高精度。寒武纪软件栈针对卷积、全连接算子等必须要进行量化后才能运行;而其他如激活算子、BN算子等不需要量化,直接使用浮点型计算。

调用量化接口,会将需要量化的算子替换,Cambricon Pytorch会对以下列表中的算子进行替换。更多算子替换信息,参考 torch_mlu/core/quantized/default_mappings.py

原生的PyTorch算子

替换后的MLU算子

nn.Linear

cnq.MLULinear

nn.Conv3d

cnq.MLUConv3d

nn.Conv2d

cnq.MLUConv2d

nn.Conv1d

cnq.MLUConv1d

nn.ConvTranspose3d

cnq.MLUConvTranspose3d

nn.ConvTranspose2d

cnq.MLUConvTranspose2d

nn.LocalResponseNorm

cnq.MLULocalResponseNorm

nn.LSTM

cnq.MLULSTM

nn.GRU

cnq.MLUGRU

nn.InstanceNorm2d

cnq.MLUInstanceNorm2d

nn.MaxUnpool2d

cnq.MLUMaxUnpool2d

注意

  • nntorch.nn 的简写。

  • cnqtorch_mlu.core.quantized.modules 的简写。

使用量化接口

Catch集成的量化接口用于生成量化模型和运行量化模型。

量化接口原型如下:

quantized_model = torch_mlu.core.mlu_quantize.quantize_dynamic_mlu(model, qconfig_spec=None, dtype=None, mapping=None, inplace=False, gen_quant=False, mixed_layer=None)

参数

  • model:待量化的模型。在生成量化模型时,model必须先加载原始权重,再调用该接口。在运行量化模型时,调用完该接口后,model再加载量化后的模型。

  • qconfig_spec:配置量化的字典。

    默认为 {'use_avg': False, 'data_scale': 1.0, 'firstconv': False, 'mean': None, 'std': None, 'per_channel': False}

    • use_avg:设置是否使用最值的平均值用于量化。默认值为False,即不使用。该参数为超参数,调节该参数以获得一个比较好的输入张量的取值区间,进而得到合适的缩放尺度。

    • data_scale:设置是否对图片的最值进行缩放。默认值为1.0,即不进行缩放。该参数为超参数,调节该参数以获得一个比较好的输入张量的取值区间,进而得到合适的缩放尺度。

    • firstconv:设置是否使用firstconv。默认值为False,即不使用。当开启firstconv时,需要同时设置mean和std参数。关于firstconv的更多使用信息,参见 高阶用法

    • mean:设置数据集的均值。默认值为None,取值范围为[0, 1),实际计算时会乘以255。

    • std:设置数据集的方差。默认值为None,取值范围为(0, 1),实际计算时会乘以255。

    • per_channel:设置是否使用分通道量化。默认值为False,即不使用分通道量化。

    • method:设置量化方法。值设置为eqnm时,表示使用IGEQNM量化。

    • grids:IGEQNM量化超参。默认值为2048。当method设置eqnm时,该参数才会出现。

    • ratio:IGEQNM量化超参。默认值为1.0。当method设置为eqnm时,该参数才会出现。

  • dtype:设置量化的模式。当前支持‘int8’和‘int16’模式,使用字符串类型传入。

  • mapping:设置待量化算子。不指定该参数时,量化接口会按照以上表格进行算子替换;如果调用自定义算子,必须指定该参数。

  • inplace:设置量化接口是否在原模型的基础上做原位操作。默认为False,表示先进行深拷贝再进行更改,否则就是在原模型的基础上做量化相关更改。

  • gen_quant:设置是否生成量化模型。默认为False,表示不生成量化模型。在生成量化模型时,需设置为True;在运行量化模型时,需设置为False。

  • mixed_layer:混合量化参数。取值为字典类型,默认值为None。例如,mixed_layer={"models.1, models.7": "int8"},即指定网络中“models.1”和“models.7”算子按“in8”方式量化,其余算子按指定dtype值进行量化。“models.1”和“models.7”是根据打印模型得知的,为对应的算子名称。

调用该量化接口后:

  • 如果是生成量化模型,quantized_model中的可量化算子,比如Conv2d,会替换为MLUConv2d,且均加入了量化Observer和hook等信息,用于计算scale。

  • 如果是运行量化模型,quantized_model中的可量化算子,比如Conv2d,会替换为MLUConv2d(量化信息在生成量化模型时已保存在权重中)。

生成量化模型

本节以生成ResNet50量化模型为例介绍生成量化模型的步骤。

  1. 获取ResNet50网络文件。

    net = resnet50()
    
  2. 加载原始权重文件。

    state_dict = torch.load("path_origin_resnet50.pth")
    net.load_state_dict(state_dict)
    
  3. 调用量化接口。

    quantized_model = torch_mlu.core.mlu_quantize.quantize_dynamic_mlu(net, dtype='int8', gen_quant=True)
    
  4. 在CPU上运行推理,生成量化值。

    quantized_model(input_tensor)
    
  5. 保存量化模型。

    torch.save(quantized_model.state_dict(), "path_quantize_resnet50.pth")
    

运行量化模型

本节以运行ResNet50量化模型为例介绍运行量化模型的步骤。

  1. 获取ResNet50网络文件。

    net = resnet50()
    
  2. 调用量化接口。

    quantized_model = torch_mlu.core.mlu_quantize.quantize_dynamic_mlu(net)
    
  3. 加载量化权重文件。

    state_dict = torch.load("path_quantize_resnet50.pth")
    quantized_model.load_state_dict(state_dict)
    
  4. 在MLU上运行推理。

    quantized_model_mlu = quantized_model.to(ct.mlu_device())
    quantized_model_mlu(img.to(ct.mlu_device())
    

量化原理

本小节以int8量化为例,简要说明量化原理。

  • 生成量化模型过程

    量化模型是float数值向int数值映射的过程。

    以int8为例,要将浮点输入映射成定点,需要先统计输入的数值范围,得到其绝对值的最大值,记为absmax;然后将absmax映射到127(int8下的最大值),得到映射的缩放尺度scale为 scale = 127 / absmax 。该scale即浮点输入映射为定点的缩放尺度。同理,可以计算出权值的缩放尺度。将待量化算子的输入和权重的缩放尺度保存在模型的参数里,调用 torch.save 将其存储为 pth 文件,用于后续在MLU上运行定点计算。

  • 运行量化模型过程

    以卷积算子为例,首先对卷积算子的浮点输入进行量化,使用量化模型中的scale值,根据量化公式 qx = clamp(round(x * scale)) 计算得到整型输入。同理,对该算子的权值进行量化,得到整型的权值;然后进行整型的卷积运算输出整型的卷积结果,根据反量化公式 y = qy / scale1 / scale2 ,最终得到浮点的卷积输出。

    运行量化模型过程如下图所示:

    ../_images/quantization.png

    运行量化模型过程

高阶用法

如果要提升网络精度,可以考虑使用int16和分通道量化方式。如果精度仍达不到CPU精度,参见 :ref: 精度调试工具 排查问题。

如果要提升网络性能,可以考虑开启firstconv、使用算子融合、使用半浮点输入等方法。

firstconv

firstconv主要用于加速输入数据拷贝到MLU这一过程。对于输入拷贝时间占比高的情况,通过开启firstconv可提升整体速度。而对于大的网络、输入占比很低的情形,使用firstconv效果并不明显。

针对大部分网络的输入为uint8类型的图片数据,使用firstconv可以实现两个方面的加速:

  • 直接将uint8拷贝到MLU,比原来拷贝float的图片数据快4倍左右,可降低IO传输时间。

  • 将减均值和除方差操作放在片上进行,进一步提高性能。

在使用firstconv时,注意以下内容:

  • firstconv支持单通道、三通道(会自动补成四通道)和四通道的输入。

  • 在生成量化模型时,如果开启firstconv,需同时传入mean和std到qconfig中。

  • firstconv是根据model初始化的第一个卷积来确定的,因此需要确保前向时的第一层卷积最先进行初始化。

  • 在运行量化模型时,会从权重pth中读取firstconv的信息。如果是开启firstconv的权重,在前向时不要进行归一化操作,直接将uint8的数据作为输入传入。

分通道量化

正常量化是对整个filter计算absmax,进而确定一个scale。分通道量化则是对于filter的每一个通道计算出对应的absmax,确定scale,即filter有多少个通道就会计算出多少个scale。分通道量化粒度更细,一般来说,精度也更高。

在线量化

设置环境变量 TORCH_ONLINE_QUANT 使用在线量化功能。使用此功能时仍然需要将权重文件按照普通量化方式进行量化。 目前支持在线量化的算子有Conv1d,Conv2d,ConvTranspose2d,Linear,Reorg算子。

算子融合

算子融合作为量化工具的补充,将多个连续算子融合成一个MLU大算子以获得更高性能。算子融合一般需在调用量化接口之前进行。

以下为具体使用方法:

from torch_mlu.core.quantized.fuse_modules import fuse_modules

class ConvBnReLU3dModel(nn.Module):
    def __init__(self, in_channel, out_channel, kernel_size,
        stride, padding, dilation, groups):
    super(ConvBnReLU3dModel, self).__init__()
    self.conv = nn.Conv3d(in_channel, out_channel, kernel_size,
                         stride=stride, padding=padding,
                         dilation=dilation, groups=groups)
    self.bn = nn.BatchNorm3d(out_channel)
    self.relu = nn.ReLU()

    def forward(self, x):
    x = self.conv(x)
    x = self.bn(x)
    x = self.relu(x)
    return x

model = ConvBnReLU3dModel(3, 16, 3, 1, 1, 1, 1)
# 完成conv、bn、relu三个算子的融合,将三个算子替换为一个大算子
fuse_modules(model, ['conv','bn','relu'], inplace=True)

# 对融合后模型量化
...

注意

目前只支持Conv3d+BatchNorm3d+ReLU以及ConvTranspose3d+BatchNorm3d+ReLU两种大算子。

量化不支持情况

目前不支持自定义算子量化。

例如:

class IConv(torch.nn.Conv2d):
   def __init__(self):
     super(IConv, self).__init__()
   ...

这种属于自定义了IConv继承torch.nn.Conv2d,目前无法支持该种类型的量化,自定义的IConv会被识别为非量化函数。支持量化的class类必须是继承torch.nn.Module。

精度调试工具

使用Cambricon软件栈出现的精度问题一般有两种:

  • MLU逐层比CPU的精度低。

  • MLU融合比MLU逐层的精度低。

注意

如果MLU融合精度比CPU精度低,则需要按以上两个问题进行调试。

MLU逐层比CPU的精度低

可能原因有:未正确使用量化工具、Cambricon软件栈中的算子问题、量化方法本身导致的精度差异。

对于该问题,精度调试一般流程如下:

../_images/acc_debug.png

精度调试流程图

执行以下步骤进行精度调试:

  1. 检查生成的量化模型。

    加载量化好的模型,打印keys,检查是否存在scale(没有该key,说明没有量化成功)、quantized_mode是否对应qconfig中的参数、firstconv是否如期开关等。

  2. 如果上述都没有问题,运行 模拟MLU量化推理工具

    • 如果模拟结果和MLU精度一致,说明是该量化方式固有的误差,请尝试更换量化参数,比如将int8改为int16、打开分通道量化。

    • 如果模拟结果比MLU准确,跟CPU上的一致,说明是Cambricon软件栈中的算子问题,此时使用 逐层dump工具 定位造成误差扩大的算子。

MLU融合比MLU逐层的精度低

可能原因是融合不支持可变的输入、融合会进行优化(可能会导致些微误差)。

调试方法为使用二分法修改模型(即先注释掉后半段模型、一步一步注释定位到算子)去定位,找到产生误差的算子。

模拟MLU量化推理工具

模拟MLU量化推理工具就是脱离MLU,加载生成的量化模型,针对需要整型计算的算子如conv、linear等,对input进行量化、对weight进行量化、然后使用PyTorch的卷积函数进行计算,对得到的结果进行反量化。详细代码,参见 torch_mlu/core/utils/sim_quant_utils.py

以下为使用举例:

from torch_mlu.core.utils import sim_quant_utils
# 定义模型
model = create_model()
# 注册量化的hook
sim_quant_utils.register_quant_hook(model)
# 加载量化好的权重
model.load_state_dict(torch.load("int8.pth"), strict=False)
# 前向推理,得到的output就是模拟量化推理的结果
output = model(input)

注意

必须按以下顺序执行:先定义模型,再注册钩子,然后加载量化好的权重。

逐层dump工具

逐层dump工具通过PyTorch的hook机制将dump函数注册到model的每个module上,在前向传播时,每个module计算完forward之后,就会调用该dump函数将输入和输出保存下来。 详细代码,参见 torch_mlu/core/utils/dump_utils.py

调用接口

  1. 调用注册函数。

    register_dump_hook(model, layer=None, start=None, end=None)
    
    • model:传入定义后的模型。

    • layer:可选择某些层进行dump。传入的层名称必须与模型的命名规则一致。格式为字符串,不同层以逗号隔开。

    • start:dump网络片段的开始层,传入的层名称必须与模型的命名规则一致。

    • end:dump网络片段的结束层,传入的层名称必须与模型的命名规则一致。

    注意

    layerstartend 参数不能同时取值。dump网络片段时,start和end传入层名,layer为None;dump单层算子时,layer传入层名,start和end为None。
  2. 保存数据。

    save_data(file_path, device, file_mode)
    
    • file_path:保存输出数据文件夹路径。

    • device:设备类型。可选值为“cpu”、“mlu”。

    • file_mode:保存输出数据文件类型。支持“pth”和“txt”。默认为”pth”。

  3. 比对数据。

    diff_data(file1, file2, mode)
    
    • file1:CPU数据保存文件路径。

    • file2:MLU数据保存文件路径。

    • mode:选择比对算法。可选值为“MSE”和“COS”。默认为“MSE”。

以下为保存CPU和MLU模型逐层数据并进行逐层比对的示例代码:

from torch_mlu.core.utils import dump_utils

########### 保存CPU逐层数据 ###########
# 定义模型
model = create_model()
# 注册保存数据的hook
dump_utils.register_dump_hook(model)
# 前向推理
out = model(input)
# 保存逐层数据在"output/dump_cpu_data.pth"
dump_utils.save_data("output/", "cpu")

########### 保存MLU逐层数据 ###########
# 定义模型
model = create_model()
# 注册保存数据的hook
dump_utils.register_dump_hook(model)
# 前向推理
out = model(input)
# 保存逐层数据在"output/dump_mlu_data.pth"
dump_utils.save_data("output/", "mlu")

######## 比对CPU和MLU的逐层数据 ########
dump_utils.diff_data("output/dump_cpu_data.pth", "output/dump_mlu_data.pth")

注意

对于firstconv层目前还不能dump,因此使用时,不能指定该层。

修改官方权重工具

除量化工具外,Cambricon PyTorch对支持的网络也提供了相应的未量化权重以及量化后的权重。需要注意的是,为方便使用,Cambricon PyTorch针对一些网络的权重做了优化。因此,这些网络的量化工具仅适用于Cambricon PyTorch提供的权重。如要自行量化,还需对权重进行如下方面的改动:

YOLOv2,YOLOv3

  • 优化说明

    YOLOv2和YOLOv3是由原生的darknet网络转为PyTorch网络,其权重的数据格式也是来自darknet,以.weights为后缀。在YOLOv2和YOLOv3的网络结构整合入torchvision时,Cambricon PyTorch将权重转为.pth格式。

  • 改动说明

    • Cambricon PyTorch首先使用网络中原始加载权重的方式将yolov.weights格式的权重文件加载到Module中,然后通过调用 torch.save() 函数将权重保存为.pth格式的文件,后续Cambricon PyTorch会使用.pth格式的权重文件进行量化。

    • 在生成新的.pth格式的权重文件后,原始YOLOv2和YOLOv3网络中加载和存储权重的函数将不再使用,而改用 torch.load()torch.save() 来完成.pth格式权重的加载和保存。

    • 对于原始YOLOv3网络的后处理部分的逻辑,Cambricon PyTorch直接使用一个大的BANG C算子(yolov3_detection_out)完成后处理,这个地方需要对原生的PyTorch网络进行修改,将后处理部分的整体计算换成BANG C算子的调用。

MTCNN

  • 优化说明

    MTCNN由pnet、rnet、onet三个子网络构成,官方权重是基于Caffe框架,Cambricon PyTorch将其转换为.pth格式。

  • 改动说明

    Cambricon PyTorch中使用原始加载权重的方式将Caffe格式的权重文件加载到Module中,然后通过调用 torch.save() 函数将权重保存为.pth格式的文件,后续Cambricon PyTorch会使用.pth格式的权重文件进行量化。

SSD MobilenetV1

  • 优化说明

    SSD_MobilenetV1官方网络有一些类并不是继承自torch.nn.Module,Cambricon PyTorch优化了这些类,因此权重中对应的权重key值也需要相应更新。

  • 改动说明

    • 读取官方.pb权重文件,将其保存为.pth格式的权重文件。

    • 把网络中更新的类对应在权重中的key值进行更新。如将(“depthwise/BatchNorm”, BatchNorm2d(inp))更改为(“depthwise/BatchNorm”, nn.BatchNorm2d(inp))。

MobilenetV1

  • 优化说明

    MobilenetV1官方权重包含训练以及优化器的数据和信息,Cambricon PyTorch优化掉了推理无关的权重信息。

  • 改动说明

    读取官方权重后,仅保留其权重中键值为state_dict的权重信息。

上述五个网络的原始权重可通过工具 pytorch/catch/examples/tools/convert_weight/convert_weight.py 转换成Cambricon PyTorch提供的权重。 在运行convert_weight.py脚本时,需指定以下参数:

  • -original_path:原始权重的路径。

  • -original_name:原始权重名。

  • -save_model_path:权重存储路径,默认为当前目录。

  • -save_name:生成权重存储名。

  • -cfg_file:YOLOv2和YOLOv3配置文件,其他网络无需设置。

  • -model_name:待转换权重的网络名称。例如,YOLOv2。

以下为各网络参数设置示例:

YOLOv2

python convert_weight.py -original_path official_weight/ -original_name yolo-voc.weights -model_name yolov2 -save_name yolov2.pth -cfg_file cfg/yolov2.cfg

YOLOv3

python convert_weight.py -original_path official_weight/ -original_name yolov3.weights -model_name yolov3 -save_name yolov3.pth -cfg_file cfg/yolov3.cfg

MTCNN

python convert_weight.py -original_path official_weight/ -original_name pnet.npy rnet.npy onet.npy -model_name mtcnn -save_name pnet.pth rnet.pth onet.pth

SSD MobilenetV1

python convert_weight.py -original_path official_weight/ -original_name frozen_inference_graph.pb -model_name ssd_mobilenet_v1 -save_name ssd_mobilnet_v1.pth

MobilenetV1

python convert_weight.py -original_path official_weight/ -original_name mobilenet.pth -model_name mobilenetv1 -save_name mobilenet_sgd.pth

性能分析工具Profiler

Profiler是PyTorch中自带的性能分析工具,用于统计算子时间,分析性能瓶颈,进行有针对性的性能优化。在原生Profiler基础上,寒武纪针对MLU硬件特点,有效扩展了Profiler的功能,使其在MLU设备上统计CPU/MLU算子硬件计算时间;查看算子、网络调用层次;统计网络中算子调用、整体硬件时间等情况。

import torch
import torch_mlu.core.mlu_model as ct

#设置默认底层库为CNNL
ct.set_cnml_enabled(False)

def test_profiler(self):
    # 创建输入,并放上MLU
    x = torch.randn(30, 40, 10, 10, requires_grad=True).to(ct.mlu_device())
    # 根据输入形状构建全1Tensor,并放上MLU
    grad = torch.ones(x.shape).to(ct.mlu_device())

    # 进入Profile环境
    with torch.autograd.profiler.profile(use_mlu=True) as prof:
        y = x * 2 + 4
        y.backward(grad)

    print(prof)

上述示例将数据放上MLU,并在profile中显式使用 use_mlu=True ,由此进入profile环境。以下为性能分析结果。

../_images/profiler_1.png

性能分析结果

其中,第一列是算子的名称,第2-5列分别为算子的CPU时间占总时间比例、CPU花费时间,第6-11列分别为MLU的CNNL算子的时间占总时间的比例、算子硬件计算时间、同一算子的平均调用时间、算子调用次数,以及算子处理的tensor的形状。

从第2行开始为代码中先后调用的算子,跑完前向后,继续跑反向求梯度。有个异常情况是,MulBackward0算子调用了mul的算子实现,两个算子属于前者调用后者的关系,所以两者的硬件计算时间相同,同时在计算总时间时,这个时间被计算了两次,相应的百分比因此产生了一些变化,这个属于profiler的原生设计,无法修改,只是在查看算子时间时,需要了解这一点情况。

同时,统计到的MLU硬件计算时间,与使用CNRT工具统计到的硬件时间基本一致,最大误差约为1 μs,同时因为跑程序时不是独占服务器跑,因此不同时刻统计到的硬件时间会有略微差异,最大误差约为1 μs,即在跑同样程序时,同一算子花费的硬件时间误差在1 μs内,基本就可以认为profiler工具工作正常。

此外,Profiler工具还提供以下功能和选项:

  • record_shapes

    如果需要查看算子处理的tensor的形状,则可以在torch.autograad.profiler.profile(use_mlu=True)中增加一个属性参数 record_shapes=True ,则打印出的信息会增加shape信息。

    ../_images/profiler_2.png

    record_shapes打开后的效果图

    使用这个参数时,内部处理会增加保存形状等操作,因此得到的CPU时间会有增加,MLU时间基本无变化。

  • export_chrome_trace(path)

    输出一个EventList对象,作为Chrome追踪工作的输入文件。 prof.export_chrome_trace(“./chrom_trace”) 在本地得到一个chrom_trace的文件,然后在谷歌浏览器中输入Chrome:://Tracing,将该文件拖入,得到如下时间开销图:

    ../_images/chrom_json.png

    chrom_trace加载JSON文件结果

    从该图中可以看到,在左上方 Process CPU functions 中可以看到在CPU上跑的算子的调用情况,点击相应算子,可以显示在CPU上的计算时间,在 Process MLU functions 中可以看到3个小块,其中是三个放在MLU上计算的算子。

    将加载的JSON图按W键放大,可以得到如下放大图,可以看到有三个算子放在了MLU上计算,点击相应算子,可以在屏幕左下方显示硬件计算时间。

    ../_images/chrom_json_2.png

    chrom_trace加载JSON文件放大图

  • table显示

    使用table显示打印的prof信息,并按照指定列名进行排序。

    print(prof) 改成 print(prof.table(sort_by="self_cpu_time_total",row_limit=10, header="TEST")) ,则对Prof数据按照 self_cpu_time_total 进行排序,设定row_limit为10单位,表格名称设为 TEST

    ../_images/profiler_4.png

    table效果图

  • key_averages():

    对所有函数输出其平均时间。

    ../_images/profiler_5.png

    key_averages效果图

  • total_average()

    计算所有时间的平均值。

    <FunctionEventAvg key=Total self_cpu_time=7.011ms cpu_time=994.866μs mlu_time=17.900μs input_shapes=None> self_cpu_time是key_averages()表格中 Self CPU total 列之和, cpu_time是key_averages()表格中 Self CPU total 列的总和除以总的 Number of Calls 次数,mlu_time是 MLU total 列的总和除以 Number of Calls 总和,得到的平均值。

  • 添加label

    在Python代码块中增加一个标签,方便后续进行代码追踪。示例如下:

    def test_record_function(self):
        x = torch.randn(10, 10).to(ct.mlu_device())
    
        with profile(use_mlu=True) as p:
            x = x + 1.5
            with record_function("label"):
                y = x * 2 + 4
    

    结果如下图所示。

    ../_images/profiler_6.png

    label效果图

    从结果中可以看到,label标签在add算子与mul算子之间,它相当于把 y = x * 2 + 4 这行代码整合成一个代码块,用 label 进行标记,同时它的MLU硬件时间是mul算子与add算子的MLU硬件时间之和,方便将这行代码作为一个整体进行处理。

离线模型生成工具

本小节主要介绍如何使用PyTorch生成离线模型工具。关于离线模型的使用方法请参考《寒武纪CNRT开发者手册》和 离线推理

普通离线模型

在Cambricon Catch中新增 torch_mlu.core.mlu_model.save_as_cambricon(model_name) 接口。当调用该接口时,会在进行jit.trace时自动生成离线模型。生成的离线模型一般是以model_name.cambricon命名的离线模型文件,其中包含一个名为model_name的模型。模型中包含若干个可以在MLU上运行的子网络,它们通过subnet0、subnet1、subnet2…… 来使用。如下为使用Python接口生成离线模型示例:

torch_mlu.core.mlu_model.set_core_number(core_number)
torch_mlu.core.mlu_model.set_input_format(input_format)
torch_mlu.core.mlu_model.save_as_cambricon(model_name)
net.eval().float().to(ct.mlu_device())
example_mlu = torch.randn(batch_size, 3, in_h, in_w, dtype=torch.float).to(ct.mlu_device())
net = torch.jit.trace(net, example_mlu, check_trace=False)
net(example_mlu)
torch_mlu.core.mlu_model.save_as_cambricon("")

在生成离线模型过程中,可对模型的相关参数进行设置。set_core_number(core_number) 表示对模型设置调用芯片核心数量,torch.randn(batch_size, 3, in_h, in_w) 指定推理时的batch size大小,使用 torch_mlu.core.mlu_model.save_as_cambricon(model_name) 生成离线模型。CNRT会根据参数配置,在推理时选择最优的软硬件资源配置,以达到性能最优。model_name为生成离线模型文件名:model_name.cambricon。具体使用方法可参考示例脚本genoff.py。

在Catch离线示例中提供脚本genoff.py用于生成指定的离线模型,脚本位于 catch/examples/offline/genoff/。该脚本需要使用的torchvision中包含所使用的模型,并指定模型文件.pth存放路径,该路径指定需要设置环境变量 export TORCH_HOME="${model_zoo}",实际使用时将${model_zoo}替换为存放对应模型权重的路径。如下为该脚本使用示例:

export TORCH_HOME="${model_zoo}"
python catch/examples/offline/genoff/genoff.py -model model_name -mname mname -mcore core_type -core_number corenums -batch_size batchsize -half_input 1

脚本参数使用说明:

  • -model:指定网络名称。

  • -mname:指定生成离线模型文件名,默认为offline。

  • -mcore:指定硬件架构版本,如设置设备名:MLU270。

  • -core_number:指定离线模型使用的芯片核心数量,MLU270最大值为16。

  • -batch_size:指定使用的batch size大小。

  • -half_input:指定input tensor类型,half或者float。

自动检索最优离线模型

底层依赖库有多个与性能优化相关的环境变量,这些环境变量的取值组合会影响网络整体性能和精度。自动检索最优离线模型生成工具,会在保证精度的前提下,通过遍历搜寻与性能优化相关的环境变量组合,生成一份能使离线模型达到最佳性能的环境变量配置文件,以及与之对应的离线模型。

运行以下命令生成名为config.ini的环境变量配置文件,model_name.cambricon的离线模型文件以及model_name.cambricon_twins的离线模型描述文件。

export TORCH_HOME="${model_zoo}"
python catch/examples/offline/genoff/genoff.py  -fake_device 0 \
                                                -autotune 1 \
                                                -autotune_config_path config.ini \
                                                -autotune_time_limit 120 \
                                                -model model_name \
                                                -mname mname \
                                                -mcore core_type \
                                                -core_number corenums \
                                                -batch_size batchsize \
                                                -half_input 1

脚本参数使用说明:

  • -fake_device:用于设置是否不使用mlu设备生成离线模型文件,使用自动检索最优模型必须使用mlu设备,此参数必须设置为0。

  • -autotune:用于设置是否开启自动检索生成最优离线模型选项,0表示关闭,1表示开启,默认值为0。

  • -autotune_config_path:用于设置运行自动检索优化工具时生成的环境变量配置文件路径,默认为”config.ini”。

  • -autotune_time_limit:指定运行自动检索优化工具的时间上限,默认值是120分钟,时间越长,优化效果越好,建议此数值设置在20分钟以上。

  • -model:指定网络名称。

  • -mname:指定生成离线模型文件名,默认为offline。

  • -mcore:指定硬件架构版本,当前支持MLU270。

  • -core_number:指定离线模型使用的芯片核心数量,MLU270最大值为16。

  • -batch_size:指定使用的batch size大小。

  • -half_input:指定input tensor类型,half或者float。

自动检索最优离线模型生成有以下两种方法:

  • 直接生成最优性能离线模型

    执行自动检索最优离线模型命令后,会直接生成一个对应的最优离线模型。

  • 使用环境变量配置文件生成

    执行以上命令生成最优离线模型的同时,还会生成最优性能环境变量配置文件 config.ini

    注意

    • 每一个batchsize和core_number组合生成的环境变量配置文件是不同的,不可以混用。

    • 在不同环境上运行自动检索最优离线模型命令生成的环境变量配置文件可能不完全相同,但这些差异通常不影响性能。

    • 由于自动检索过程会排列组合不同的环境变量,耗时较长。在模型参数设置不变的情况下,建议直接利用配置文件生成最优离线模型。

    执行以下命令将生成的环境变量配置文件中的变量导入:

    export CNML_OPTIMIZE="USE_CONFIG:config.ini"
    

    然后根据 普通离线模型 中生成离线模型的方法,生成最优性能离线模型。

PyTorch、Catch、Vision的wheel包生成、安装和使用

Cambricon PyTorch提供了wheel包的生成使用机制,即在使用PyToch和Catch时不需要额外编译,通过直接安装wheel包来安装和使用PyToch和Catch。

以下分别介绍PyToch wheel包、Catch wheel包和Vision wheel包的生成、安装和使用。

PyTorch、Catch、Vision的wheel包生成

  1. 确保已经安装CNToolkit软件包和CNML、CNNL、CNPlugin、CNCL等组件。

    具体安装步骤,参见《寒武纪CNToolkit软件包安装升级使用手册》和相应的寒武纪用户手册。

    有关Cambricon PyTorch的第三方依赖,参见 catch/script/release/build.property 文件。

  2. 导入编译Cambricon PyTorch与运行测试脚本所需的环境变量,调用脚本完成全部配置。

    #设置环境变量,HOME为release压缩包解压后的根目录
    cd $HOME/script/release
    source env_pytorch.sh
    
  3. 进入Cambricon Catch源码目录,安装并激活虚拟环境,然后安装依赖的第三方依赖包。

    第三方依赖包列表可在PyTorch源码主目录下的requirements.txt中查询。

    cd ${CATCH_HOME}
    virtualenv venv/pytorch #安装virtualenv
    source venv/pytorch/bin/activate #激活虚拟环境
    
    cd ${PYTORCH_HOME}
    pip install -r requirements.txt #安装第三方包
    
  4. 设置环境变量,将Catch中的patch打到PyTorch源码中,然后生成PyTorch wheel包。

    export PYTORCH_HOME=your pytorch path
    
    cd ${CATCH_HOME}/script
    ./apply_patches_to_pytorch.sh
    
    cd ${PYTORCH_HOME}
    python setup.py clean
    python setup.py bdist_wheel --universal  # 生成PyTorch wheel包
    

    执行完该步骤会在 pytorch/dist 目录下生成一个 torch-XXX.whl 的Cambricon PyTorch wheel包文件。

  5. 如果要生成Catch wheel包,需要先设置环境变量、安装PyTorch wheel包和第三方包,然后执行命令生成Catch wheel包。

    export NEUWARE_HOME = your neuware path
    
    cd ${PYTORCH_HOME}
    pip install dist/torch-*.whl
    
    cd ${CATCH_HOME}
    pip install -r requirements.txt
    python setup.py clean
    python setup.py bdist_wheel  # 生成Catch wheel包
    

    执行完以上操作会在 catch/dist 目录下生成一个 torch_mlu-XXX.whl 的Cambricon Catch wheel包文件。

  6. 生成Vision wheel包。

    cd ${VISION_HOME}
    python setup.py clean
    python setup.py bdist_wheel  # 生成Vision wheel包
    

    执行完以上操作会在 vision/dist 目录下生成一个 torchvision-XXX.whl 的Cambricon Vision wheel包文件。

PyTorch、Catch、Vision的wheel包安装与使用

在得到PyTorch wheel包之后,可以通过安装wheel包的方式来使用Cambricon PyTorch。

  1. 设置 NEUWARE_HOME 环境变量。

    export NEUWARE_HOME = your neuware path
    
  2. 安装Virtualenv,并激活虚拟环境。

    virtualenv venv/pytorch #安装virtualenv
    source venv/pytorch/bin/activate #激活虚拟环境
    
  3. 安装PyToch wheel包、Catch wheel包、Vision wheel包以及各种依赖库文件。

    cd ${PYTORCH_HOME}
    pip install -r requirements.txt
    pip install dist/torch-*.whl
    pip install Cython==0.27.3
    pip install pycocotools==2.0.0
    
    # for ubuntu18 you should:
    pip install scipy==1.1.0
    
    cd ${CATCH_HOME}
    pip install -r requirements.txt
    pip install dist/torch_mlu*.whl
    
    cd ${VISION_HOME}
    pip install dist/torchvision*.whl
    
  4. 设置以下环境变量。

    export TORCH_HOME= "YOUR MODELZOO PATH"
    export LD_LIBRARY_PATH= your neuware path/lib64
    
  5. 运行Catch支持的脚本。

    运行方式和 模型推理 章节一致,同时int8和int16转换工具等都可以正常运行。 对于离线推理部分,由于离线推理是单独分开的,因此需要在使用之前进行编译,可按照以下方法进行编译:

    cd ${CATCH_HOME}/examples/offline/
    ./scripts/build_offline.sh
    

    离线编译脚本可以通过环境变量 USE_ABI 来选择编译时是否使用ABI编译。USE_ABI=1 为设置编译选项 -D_GLIBCXX_USE_CXX11_ABI=1,表示使用C++11新特性;USE_ABI=0 为设置编译选项 -D_GLIBCXX_USE_CXX11_ABI=0,表示不使用C++11新特性;其他情况均使用GCC默认设置。 例如,CentOS7 GCC默认版本是4.8.5,如果将默认的GCC 4.8.5替换成GCC5以上版本,而底层依赖如glog,gflag等没有替换,依然使用默认GCC 4.8.5版本,此时编译将无法兼容old ABI,需设置环境变量 export USE_ABI=0

注意

  • 目前Cambricon PyTorch wheel包与Catch wheel在不同系统(Ubuntu 16、Ubuntu 18、CentOS)和不同Python环境(Python 2和Python 3)下,需重新生成不同的wheel包。不同环境下的wheel包目前无法通用。

  • GCC版本需≥5.4。