Rspress

# #精度调优工具使用指南

由于浮点转定点过程中存在误差，当您在使用量化训练工具时，难免会碰到量化模型精度掉点问题。通常来说，造成掉点的原因有：

1. 原有浮点模型不利于量化，如存在共享 op 或共享结构。

2. QAT 网络结构或配置异常，如模型中存在没有 fuse 的 pattern，没有设置高精度输出等。

3. 某些算子对量化比较敏感，该算子的量化误差在前向传播过程中逐层累积，最终导致模型输出误差较大。

针对上述情况，量化训练工具提供了精度调优工具来帮助您快速定位并解决精度问题，主要包括如下：

- 模型结构检查：检查模型中是否存在共享 op、没有 fuse 的 pattern 或者不符合预期的量化配置。

- 使用 QuantAnalysis 类 自动比对分析两个模型：通过对比定位到量化模型中异常算子或者量化敏感 op。

- 使用ModelProfiler 类 和 HbirModelProfiler 类 获得模型中每一个 op 的数值特征信息：获取的信息包括输入输出的最大最小值等。这两个类的功能完全一致，区别在于 HbirModelProfiler 仅接受 qat hbir 模型作为输入。通常您无需手动调用该模块，可以直接通过 QuantAnalysis.run 来获得两个模型的数值信息。

## #快速上手

当碰到量化模型精度掉点问题时，我们推荐按照如下的流程使用精度调优工具。

1. 检查模型中是否存在不利于量化的结构或者异常配置。

2. 使用 QuantAnalysis 模块进行分析，具体步骤如下：

   1). 找到一个基准模型和待分析模型输出相差最大的那个输入(即bad case)作为模型的输入。

   2). 进行量化敏感度分析，目前的经验是 L1 敏感度排序前 n 个通常为量化敏感 op（不同的模型 n 的数值不一样，因此暂无自动确定的方法，需要手动尝试，如前 10 个，20 个...）。将量化敏感 op 设置高精度量化（如 int16 量化），重新进行量化流程。

   3). 或者逐层比较两个模型的输入输出等信息，检查是否存在数据范围过大或者 scale 不合理等量化异常的 op，如某些具有物理含义的 op 应设置固定 scale。

整体的流程图如下：

一个完整的例子如下：

`from horizon_plugin_profiler import QuantAnalysis import torch from torch import nn from torch.quantization import DeQuantStub, QuantStub from horizon_plugin_pytorch import set_march, qint8 from horizon_plugin_pytorch.quantization import FakeQuantState from horizon_plugin_pytorch.quantization import hbdk4 as hb4 from horizon_plugin_pytorch.quantization import ( prepare, set_fake_quantize, get_qconfig, ) from horizon_plugin_pytorch.quantization.qconfig_setter import * class Net(nn.Module): def __init__(self): super(Net, self).__init__() self.conv = nn.Conv2d(3, 3, 1) self.relu = nn.ReLU() self.quant = QuantStub() self.dequant = DeQuantStub() def forward(self, x): x = self.quant(x) x = self.conv(x) x = self.relu(x) x = torch.nn.functional.interpolate( x, scale_factor=1.3, mode="bilinear", align_corners=False ) x = self.dequant(x) return x data = torch.rand((1, 3, 32, 32)) float_net = Net() float_net(data) set_march("nash-e") ############################### 模型结构检查 ############################## # 确认提示的异常层是否符合预期 calib_net = prepare( float_net, data, qconfig_setter=QconfigSetter( get_qconfig(), [ ModuleNameTemplate({"": qint8}), ConvDtypeTemplate(), MatmulDtypeTemplate(), ], ), ) ########################################################################## # 进行 calibration set_fake_quantize(calib_net.eval(), FakeQuantState.CALIBRATION) calib_net(data) float_net.eval() # 重要！！！将 calibration 模型置为 validation 状态 set_fake_quantize(calib_net.eval(), FakeQuantState.VALIDATION) # 导出 hbir 模型 qat_hbir = hb4.export(calib_net, (data,)) ############################### quant analysis ############################ # 1. 初始化 qa = QuantAnalysis( baseline_model=float_net, analysis_model=calib_net, analysis_model_type="fake_quant", device_ids=0, # GPU index，若不指定则在 CPU 上 out_dir="./floatvsqat", ) # 也支持对比 qat 和 qat hbir # qa = QuantAnalysis( # baseline_model=calib_net, # analysis_model=qat_hbir, # analysis_model_type="export", # device_ids=0, # GPU index，若不指定则在 CPU 上 # out_dir="./qatvshbir", # ) # 2. 若有已知的 badcase，可以通过该接口直接设置此 badcase 输入 qa.set_bad_case(data) # 实际场景下推荐使用 auto_find_bad_case 在整个 dataloader 上搜索 bad case # 也支持设置 num_steps 参数来控制搜索的范围 # qa.auto_find_bad_case(your_dataloader, num_steps=100) # 3. 运行两个模型 qa.run() # 4. 两个模型逐层比较。确认 abnormal_layer_advisor.txt 提示的异常层是否符合预期 qa.compare_per_layer() # 5. 计算敏感度节点。可以将 topk 排序的敏感度节点设置高精度来尝试提升量化模型精度 qa.sensitivity()`

## #分析统计信息

在 QuantAnalysis 的 run() 接口被调用后，baseline_model 和 analysis_model 会使用 badcase 进行推理，并将模型每一层的输入输出保存在输出路径的 op_infos 文件夹下。

`import torch import matplotlib.pyplot as plt # 加载 opinfo opinfo = torch.load("xxx.opinfo") input = opinfo.input output = opinfo.output # 画数值分布图 plt.hist(output[0].as_subclass(torch.Tensor).flatten().cpu().numpy(), bins=30, color='blue') plt.title("Output Distribution") plt.xlabel("Value") plt.ylabel("Frequency") plt.grid(True) plt.savefig("output.png")`

使用 torch.load 加载 opinfo 文件，可以得到算子的输入输出。使用 matplotlib 可以绘制对应的数值分布图。

在分析单算子误差时，可以构造只包含一个算子的模型，将 opinfo 中的输入送入模型，对比模型输出和 opinfo 输出的差异，绘制散点图或直方图。

## #多机多卡

QuantAnalysis 工具已支持多机多卡。若您已有 torch.nn.parallel.DistributedDataParallel 构建的训练环境，您只需将 train 函数替换成示例中的 quant_analysis 函数即可。

`import os import sys import time import torch import torch.distributed as dist import torch.nn as nn import torch.optim as optim import torch.multiprocessing as mp from torch.nn.parallel import DistributedDataParallel as DDP from torch.utils.data import Dataset, DataLoader from torch.utils.data.distributed import DistributedSampler from horizon_plugin_pytorch.quantization import ( QuantStub, prepare, QconfigSetter, set_fake_quantize, FakeQuantState, get_qconfig, observer_v2, ) from horizon_plugin_pytorch.quantization.qconfig_setter import ModuleNameTemplate from horizon_plugin_pytorch.march import set_march from horizon_plugin_pytorch.dtype import qint8 from torch.ao.quantization import DeQuantStub from horizon_plugin_profiler import QuantAnalysis from horizon_plugin_profiler.utils.entities import Metric # 示例数据集，debug 时请使用评测数据集 class ExampleDataset(Dataset): def __init__(self, size=10000, input_size=32): self.size = size self.data = torch.randn(size, 3, input_size, input_size) def __len__(self): return self.size def __getitem__(self, idx): return self.data[idx] # 模型定义 class ConvNet(nn.Module): def __init__(self, num_classes=10): super(ConvNet, self).__init__() self.layer1 = nn.Sequential( nn.Conv2d(3, 16, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(16), nn.ReLU(), nn.MaxPool2d(kernel_size=2, stride=2), ) self.layer2 = nn.Sequential( nn.Conv2d(16, 32, kernel_size=3, stride=1, padding=1), nn.BatchNorm2d(32), nn.ReLU(), nn.MaxPool2d(kernel_size=2, stride=2), ) self.fc = nn.Linear(8 * 8 * 32, num_classes) self.quant = QuantStub() self.dequant = DeQuantStub() def forward(self, x): x = self.quant(x) x = self.layer1(x) x = self.layer2(x) x = x.reshape(x.size(0), -1) x = self.fc(x) x = self.dequant(x) return x def setup(rank, world_size, master_addr, master_port): """初始化分布式环境""" os.environ["MASTER_ADDR"] = master_addr os.environ["MASTER_PORT"] = str(master_port) # 初始化进程组 dist.init_process_group(backend="nccl", rank=rank, world_size=world_size) torch.cuda.set_device(rank) # 设置当前GPU def cleanup(): """清理分布式训练环境""" dist.destroy_process_group() def quant_analysis(rank, world_size, config): setup(rank, world_size, config["master_addr"], config["master_port"]) # 设置目标硬件平台代号，注意此处的 march 区分 nash-e/m/p set_march("nash-e") float_net = ConvNet() # 准备数据，需要使用 DistributedSampler dataset = ExampleDataset(size=10000) sampler = DistributedSampler( dataset, num_replicas=world_size, rank=rank, shuffle=False, ) dataloader = DataLoader( dataset, batch_size=1, # 建议 debug 时设置 batch = 1 sampler=sampler, num_workers=config["num_workers"], pin_memory=True, ) # 加载训练好的浮点权重，注意此处的参数需要替换为您实际的模型权重路径 float_ckpt = torch.load("/your_float_ckpt_path") float_net.load_state_dict(float_ckpt) # 将浮点模型转 calib 模型 example_inputs = torch.rand((1, 3, 32, 32)) calib_net = prepare( float_net, example_inputs, QconfigSetter( reference_qconfig=get_qconfig(observer=observer_v2.MSEObserver), templates=[ ModuleNameTemplate({"": qint8}), ], ), ) # 加载 calibration 权重，注意此处的参数需要替换为您实际的模型权重路径 calib_ckpt = torch.load("/your_calib_ckpt_path") calib_net.load_state_dict(calib_ckpt) float_net.eval() # 重要！！！将 calibration 模型置为 validation 状态 set_fake_quantize(calib_net.eval(), FakeQuantState.VALIDATION) # 进行 debug qa = QuantAnalysis( baseline_model=float_net, analysis_model=calib_net, analysis_model_type="fake_quant", device_ids=rank, # 将 device_ids 设置为当前 rank out_dir=config["output_dir"], ) qa.auto_find_bad_case( data_generator=dataloader, num_steps=100, metric=Metric.ATOL, ) qa.run() qa.compare_per_layer() qa.sensitivity(metric=Metric.ATOL) # 清理分布式环境 cleanup() def main(): import argparse parser = argparse.ArgumentParser(description="Multi-Node QuantAnalysis") parser.add_argument( "--nnodes", type=int, default=1, help="number of nodes in the cluster" ) parser.add_argument( "--nproc_per_node", type=int, default=torch.cuda.device_count(), help="number of GPUs per node", ) parser.add_argument( "--node_rank", type=int, default=0, help="rank of this node in the cluster (0-indexed)", ) parser.add_argument( "--master_addr", type=str, default="localhost", help="IP address of the master node (node with rank 0)", ) parser.add_argument( "--master_port", type=int, default=29500, help="port on master node for communication", ) parser.add_argument( "--num_workers", type=int, default=4, help="number of data loader workers per GPU", ) parser.add_argument( "--output_dir", type=str, default="./quant_analysis_output", help="directory to save debug outputs", ) args = parser.parse_args() os.makedirs(args.output_dir, exist_ok=True) config = { "master_addr": args.master_addr, "master_port": args.master_port, "num_workers": args.num_workers, "output_dir": args.output_dir, } # 计算全局world size world_size = args.nnodes * args.nproc_per_node # 打印配置信息 if args.node_rank == 0: print(f"Starting multi-node QuantAnalysis with:") print(f" Nodes: {args.nnodes}") print(f" GPUs per node: {args.nproc_per_node}") print(f" Total GPUs: {world_size}") print(f" Master address: {args.master_addr}:{args.master_port}") # 启动分布式 debug mp.spawn( quant_analysis, args=(world_size, config), nprocs=args.nproc_per_node, join=True ) if __name__ == "__main__": main()`

## #API Reference

### #模型结构检查

`# from horizon_plugin_pytorch.utils.check_model import check_qat_model def check_qat_model( model: torch.nn.Module, example_inputs: Any, save_results: bool = False, out_dir: Optional[str] = None, ):`

检查 calibration/qat 模型中是否存在不利于量化的结构以及量化 qconfig 配置是否符合预期。

参数

- model: 待检查模型。

- example_inputs: 模型输入。

- save_results: 是否将检查结果保存到 txt 文件。默认 False。

- out_dir: 结果文件 'model_check_result.txt' 的保存路径。默认空，保存到 .model_check_results/ 目录下。

输出

- 屏幕输出：检查出的异常层。

- model_check_result.txt：在 save_results = True 时生成。主要由 5 部分组成：

  1). 未 fuse 的 pattern。

  2). 每个 module 的调用次数。正常每个 op 仅调用 1 次，0 表示未被调用，超过 1 次则表示被共享了多次。未调用或者共享多次的会有异常提示。

  3). 每个 op 输出的 qconfig 配置。

  4). 每个 op weight（如果有的话）的 qconfig 配置。

  5). 异常 qconfig 提示（如果有的话）。

`Fusable modules are listed below: name type ------ ----------------------------------------------------- conv <class 'horizon_plugin_pytorch.nn.qat.conv2d.Conv2d'> relu <class 'horizon_plugin_pytorch.nn.qat.relu.ReLU'> All modules in the model run exactly once. Each layer out qconfig: +---------------+-----------------------------------------------------------+---------------+---------------+----------------+-----------------------------+ | Module Name | Module Type | Input dtype | out dtype | ch_axis | observer | |---------------+-----------------------------------------------------------+---------------+---------------+----------------+-----------------------------| | quant | <class 'horizon_plugin_pytorch.nn.qat.stubs.QuantStub'> | torch.float32 | qint8 | -1 | MovingAverageMinMaxObserver | | conv | <class 'horizon_plugin_pytorch.nn.qat.conv2d.Conv2d'> | qint8 | qint8 | -1 | MovingAverageMinMaxObserver | | relu | <class 'horizon_plugin_pytorch.nn.qat.relu.ReLU'> | qint8 | qint8 | qconfig = None | | | dequant | <class 'horizon_plugin_pytorch.nn.qat.stubs.DeQuantStub'> | qint8 | torch.float32 | qconfig = None | | +---------------+-----------------------------------------------------------+---------------+---------------+----------------+-----------------------------+ Weight qconfig: +---------------+-------------------------------------------------------+----------------+-----------+---------------------------------------+ | Module Name | Module Type | weight dtype | ch_axis | observer | |---------------+-------------------------------------------------------+----------------+-----------+---------------------------------------| | conv | <class 'horizon_plugin_pytorch.nn.qat.conv2d.Conv2d'> | qint8 | 0 | MovingAveragePerChannelMinMaxObserver | +---------------+-------------------------------------------------------+----------------+-----------+---------------------------------------+`
注解

`prepare`接口已集成该检查。请您关注此接口输出的检查结果，并根据检查结果对模型做针对性的调整。

### #QuantAnalysis 类

QuantAnalysis 类可以自动寻找两个模型输出最大的 bad case，并以此作为输入，逐层比较两个模型的输出。此外，QuantAnalysis 类还提供计算敏感度功能，您可以尝试将敏感度排名 topk 的节点设置高精度，如 int16 量化，来提升量化模型精度。

`class QuantAnalysis(object): def __init__( self, baseline_model: Union[torch.nn.Module, HbirModule], analysis_model: Union[torch.nn.Module, HbirModule], analysis_model_type: str, device_ids: Union[List[int], int] = None, post_process: Optional[Callable] = None, out_dir: Optional[str] = None, )`

参数

- baseline_model: 基准模型（高精度）。

- analysis_model：待分析的模型（精度掉点）。

- analysis_model_type: 待分析的模型类型。支持输入：

  - fake_quant：待分析的模型可以是精度掉点的 calibration 模型，此时基准模型可以是原始浮点模型或者一个精度达标的 int8/int16 混合配置的 calibration 模型。

  - pre_export：待分析的模型是查表转定点的伪量化模型，基准模型是对应的原始伪量化模型。

  - export：待分析的模型是伪量化 hbir 模型，基准模型是对应的查表转定点的伪量化模型。

  - convert：待分析的模型是定点 hbir 模型，基准模型是对应的查表转定点的伪量化模型。

- device_ids：对比分析时模型运行的 GPU 设备 index。

- post_process：模型后处理。

- out_dir：指定比较结果的输出目录。

注意

由于 QAT 训练会改变模型 weight 分布，通常情况下，我们不建议您将浮点或 calibration 模型和 qat 模型做对比。

该类中各个 method 如下：

#### #auto_find_bad_case

`def auto_find_bad_case( self, data_generator: Iterable, num_steps: int = sys.maxsize, metric: Union[str, _Metric] = Metric.ATOL, device: Optional[Union[torch.device, str, int, List[int]]] = None, custom_metric_func: Optional[Callable] = None, custom_metric_order_seq: Optional[str] = None, cached_attrs: Optional[Tuple[str, ...]] = None, dump_in_run: bool = False, ):`

自动寻找导致两个模型输出最差的 badcase。

参数

- data_generator：dataloader 或者一个自定义的迭代器，每次迭代产生一个数据。

- num_steps：迭代 steps 次数。

- metric：指定何种 metric 作为 badcase 的 metric。默认使用 ATOL 最差的结果。支持 COSINE/L1/ATOL。

- device： 该参数已废弃。通过 QuantAnalysis 初始化时的 `device_ids` 指定 GPU。

- custom_metric_func：该参数已废弃。不再支持自定义 metric。

- custom_metric_order_seq：该参数已废弃。不再支持自定义 metric。

- cached_attrs：作为模型输入的某些属性。通常在时序模型中使用，如运行第二帧时，将第一帧的某些结果作为输入。

- dump_in_run: 是否在运行过程中保存 badcase。

注解

`auto_find_bad_case`函数遍历传入的 data_generator，运行基准模型和待分析模型，计算每个输出在 COSINE/L1/ATOL 3 种 metric 上的比较结果，并找到在各个 metric 上比较结果最差的 badcase 输入。

输出

- badcase.txt：包含 3 部分内容

  - 各个输出在不同 metric 下找到的 badcase 输入。（在 data_generator 中的索引值）。

  - 各个输出在不同 metric 下找到的 badcase 输入对应的 metric 最差值。

  - 相同 metric 指标下，输出最差的 badcase 输入。

  `The bad case input index of each output: Name/Index COSINE L1 ATOL ------------ -------- ---- ------ box0 16 0 0 box1 77 32 32 box2 66 46 56 scores 61 96 100 centerness 0 0 0 yawness 17 76 18 The metric results of each badcase: Name/Index COSINE L1 ATOL ------------ --------- --------- --------- box0 0.906329 84.7319 2.97311 box1 0.968623 38.602 2.98777 box2 0.799405 237.053 4.37895 scores 0.388762 0.0080612 0.0395061 centerness 0.904206 0.0456813 0.178009 yawness -0.325684 4.87353 1.27329 The bad case input index of the worst output: metric dataloader index -------- ------------------ COSINE 17 L1 46 ATOL 56`

- badcase.pt：保存的输入数据，为参数 `metric` 指定的 metric 指标最差的输入。作为 `run` 函数的默认输入。

#### #set_bad_case

`def set_bad_case( self, data: Any, baseline_model_cached_attr: Optional[Dict] = None, analysis_model_cached_attr: Optional[Dict] = None, ):`

手动设置 badcase。

注意

通常情况下，我们建议您通过 `auto_find_bad_case`函数寻找 badcase。若手动设置的 badcase 非真正的 badcase，分析工具很难找出量化敏感层。

参数

- data: badcase 输入。

- baseline_model_cached_attr：基准模型的 cached_attrs。

- analysis_model_cached_attr：待分析模型的 cached_attrs。

#### #load_bad_case

`def load_bad_case(self, filename: Optional[str] = None)`

从指定的文件中加载 badcase。

参数

- filename：指定的文件路径。默认从初始化时指定的 `out_dir` 目录中加载 `auto_find_bad_case` 函数保存的 badcase 相关文件。

#### #save_bad_case

`def save_bad_case(self)`

将 badcase 保存到 {self.out_dir}/badcase.pt 文件。

注意

和 `set_bad_case`搭配使用。通常情况下，您无需手动调用此函数。

#### #set_model_profiler_dir

`def set_model_profiler_dir( self, baseline_model_profiler_path: str, analysis_model_profiler_path: str, ):`

手动指定 model_profiler 的输出保存路径。

某些情况下，在 QuantAnalysis 初始化之前，ModelProfiler 就已定义并运行，此时可以直接指定已有的 ModelProfiler 路径，跳过 QuantAnalysis 的 run 步骤，直接比较两个模型的输出。

参数

- baseline_model_profiler_path：基准模型的 profiler 路径。

- analysis_model_profiler_path：待分析模型的 profiler 路径。

#### #run

`def run( self, device: Optional[Union[torch.device, str, int]] = None, index: Optional[int] = None, )`

运行两个模型并分别保存模型中每一层的结果。

参数

- device：该参数已废弃。通过 QuantAnalysis 初始化时的 `device_ids` 指定 GPU。

- index：输入数据在 data_generator 中的 index。

注意

仅支持 `auto_find_bad_case` 函数找到并在 `badcase.txt`中显示的 index 作为参数输入。

#### #compare_per_layer

`def compare_per_layer( self, prefixes: Tuple[str, ...] = None, types: Tuple[Type, ...] = None, ):`

比较两个模型中每一层的结果。

参数

- prefixes：指定 op 名字的前缀。

- types：op 类型。

注解

通常您无需指定 `prefixes` 和 `types`参数。若您基于一些先验经验，想跳过某些确定的、量化影响较小 op 的比较，或想节省时间，您可以通过两个参数，指定比较某些 op 或者某类 op。

输出

- abnormal_layer_advisor.txt: 所有异常层，包括数据范围过大/包含 inf 或 NaN/输出没有高精度等情况。

- compare_per_layer_out.txt: 以表格的形式展示模型中每层 layer 的具体信息，包括各种指标、数据范围、量化 dtype 等。从左到右每一列分别表示：

  - Index：op index。

  - mod_name：该 op 名字，若 op 为 module 类型，则显示该 module 在模型中的 prefix name，若为 function 类型，则不显示。

  - base_op_type：基准模型中该 op 的 type，可能是 module 类型或者 function 名称。

  - analy_op_type：待分析模型中该 op 的 type，可能是 module 类型或者 function 名称。

  - Shape：该 op 输出的 shape。

  - quant_dtype：该 op 输出的量化类型。

  - Qscale：该 op 输出的量化 scale。

  - Cosine：该 op 在两个模型中输出的余弦相似度。

  - L1：该 op 在两个模型中输出的 L1 距离。

  - Atol：该 op 在两个模型中输出的绝对误差。

  - max_qscale_diff：该 op 在两个模型中输出最大相差了几个 scale。

  - base_model_min：基准模型中该 op 输出的最小值。

  - analy_model_min：待分析模型中该 op 输出的最小值。

  - base_model_max：基准模型中该 op 输出的最大值。

  - analy_model_max：待分析模型中该 op 输出的最大值。

  - base_model_mean：基准模型中该 op 输出的平均值。

  - analy_model_mean：待分析模型中该 op 输出的平均值。

    `+----+------------+--------------------------------------------------------------------+--------------------------------------------------------------------+----------------------------+---------------+-----------+-----------+-----------+-----------+-------------------+------------------+-------------------+------------------+-------------------+-------------------+--------------------+ | | mod_name | base_op_type | analy_op_type | shape | quant_dtype | qscale | Cosine | L1 | Atol | max_qscale_diff | base_model_min | analy_model_min | base_model_max | analy_model_max | base_model_mean | analy_model_mean | |----+------------+--------------------------------------------------------------------+--------------------------------------------------------------------+----------------------------+---------------+-----------+-----------+-----------+-----------+-------------------+------------------+-------------------+------------------+-------------------+-------------------+--------------------| | 0 | quant | torch.ao.quantization.stubs.QuantStub | horizon_plugin_pytorch.nn.qat.stubs.QuantStub | torch.Size([1, 3, 32, 32]) | qint8 | 0.0078404 | 0.9999922 | 0.0019772 | 0.0039202 | 0.5000016 | 0.0002798 | 0.0000000 | 0.9996471 | 0.9957269 | 0.4986397 | 0.4986700 | | 1 | conv | torch.nn.modules.conv.Conv2d | horizon_plugin_pytorch.nn.qat.conv2d.Conv2d | torch.Size([1, 3, 32, 32]) | qint8 | 0.0056401 | 0.9999791 | 0.0020876 | 0.0092935 | 1.6477517 | -0.7193903 | -0.7162931 | 0.5436335 | 0.5414499 | -0.0423445 | -0.0413149 | | 2 | relu | torch.nn.modules.activation.ReLU | torch.nn.modules.activation.ReLU | torch.Size([1, 3, 32, 32]) | qint8 | 0.0056401 | 0.9999741 | 0.0009586 | 0.0088447 | 1.5681799 | 0.0000000 | 0.0000000 | 0.5436335 | 0.5414499 | 0.1555644 | 0.1557564 | | 3 | | horizon_plugin_pytorch.nn.interpolate.autocasted_interpolate_outer | horizon_plugin_pytorch.nn.interpolate.autocasted_interpolate_outer | torch.Size([1, 3, 41, 41]) | qint8 | 0.0056401 | 0.9924216 | 0.0160291 | 0.2094204 | 37.1305954 | 0.0000000 | 0.0000000 | 0.5149657 | 0.5301697 | 0.1550578 | 0.1559310 | | 4 | dequant | torch.ao.quantization.stubs.DeQuantStub | horizon_plugin_pytorch.nn.qat.stubs.DeQuantStub | torch.Size([1, 3, 41, 41]) | torch.float32 | | 0.9924216 | 0.0160291 | 0.2094204 | | 0.0000000 | 0.0000000 | 0.5149657 | 0.5301697 | 0.1550578 | 0.1559310 | +----+------------+--------------------------------------------------------------------+--------------------------------------------------------------------+----------------------------+---------------+-----------+-----------+-----------+-----------+-------------------+------------------+-------------------+------------------+-------------------+-------------------+--------------------+`

- compare_per_layer_out.csv: 以 csv 的格式展示每层的具体信息。内容和 compare_per_layer_out.txt 完全一致，csv 文件的存储格式方便您通过 excel 等软件打开分析。

#### #sensitivity

`def sensitivity( self, device: Optional[torch.device] = None, metric: Union[str, _Metric] = Metric.ATOL, reserve: bool = False ):`

模型中各个节点的敏感度排序。适用于 float 转 calibration 的精度掉点问题。

参数

- device：该参数已废弃。通过 QuantAnalysis 初始化时的 `device_ids` 指定 GPU。

- metric：相似度排序的 metric，默认 ATOL，支持 COSINE/L1/ATOL。

- reserve：是否反序打印敏感度节点，以支持将某些 int16 算子退回 int8 来提升上板性能。

输出

- sensitive_ops.txt。文件中按照量化敏感度从高到低的顺序排列 op。从左到右每一列分别表示：

  - op_name：op 名字。

  - sensitive_type：计算量化敏感的类型，包括：

    - activation：仅量化该 op 输出的量化敏感度。

    - weight：仅量化该 op 权重的量化敏感度。

    - input-{n}：仅量化该 op 第 n 个输入的量化敏感度。

  - op_type：op 类型。

  - metric：计算敏感度的指标。按照敏感度从高到低的顺序排序。支持以下几种指标。默认使用 L1。

    - L1：取值范围 [0, +∞+\infty+∞]，数值越大则该 op 对量化越敏感（从大到小排序）。

    - Cosine：取值范围 [0,1]，越接近 0 则该 op 对量化越敏感（从小到大排序）。

    - ATOL：取值范围 [0, +∞+\infty+∞]，数值越大则该 op 对量化越敏感（从大到小排序）。

  - quant_dtype：该 op 输出的量化 dtype。通常为 qint8/qint16。

  - flops：该 op 的计算量以及在整个模型计算量中的占比。

- sensitive_ops.pt。使用 torch.save 保存的敏感度排序的列表，方便您后续加载使用。若存在多个输出，则每个输出都会生成一个敏感度表。列表格式见返回值部分说明。

返回值

敏感度 List，List 中每个元素都是记录一个 op 敏感度信息的子 list。子 List 中从左到右每一项分别为 `[op_name, sensitive_type, op_type, metric, quant_dtype, flops]`。

整个 List 示例如下：

`[ [op1, "activation", op1_type, L1, qint8, flops1], [op2, "activation", op2_type, L1, qint8, flops2], [op3, "activation", op3_type, L1, qint8, flops3], [op1, "weight", op1_type, L1, qint8, flops4], ... ]`

您可以将量化敏感度排名前 n 的 op 配置高精度（如 int16）来尝试提升量化模型精度。

`op_name sensitive_type op_type L1 quant_dtype flops --------- ---------------- ------------------------------------------------------- ----------- ------------- ------------- conv weight <class 'horizon_plugin_pytorch.nn.qat.conv2d.Conv2d'> 0.000553844 qint8 9216(100.00%) conv activation <class 'horizon_plugin_pytorch.nn.qat.conv2d.Conv2d'> 0.000472854 qint8 9216(100.00%) quant activation <class 'horizon_plugin_pytorch.nn.qat.stubs.QuantStub'> 0.000249175 qint8 0(0%)`

#### #clean

`def clean(self)`

清除中间结果。仅保留比较结果等文件。

### #ModelProfiler 类

统计模型 forward 过程中，每一层算子的输入输出等信息。

`# from horizon_plugin_profiler import ModelProfiler class ModelProfiler(object): def __init__( self, model: torch.nn.Module, out_dir: str, )`

参数

- model: 需要统计的模型。

- out_dir: 相关文件保存的路径。

注解

该类仅支持通过 with 语句的方式使用。

`with ModelProfiler(net, "./profiler_dir") as p: net(data) p.get_info_manager().table() p.get_info_manager().tensorboard()`

该类中其中各个 method 如下：

#### #get_info_manager

`def get_info_manager(self)`

获得管理每个 op 信息的结构体。

返回值

管理存储的每个 op 信息的结构体 `OpRunningInfoManager`。其中两个重要的接口如下：

##### #table

`class OpRunningInfoManager: def table( self, out_dir: str = None, prefixes: Tuple[str, ...] = None, types: Tuple[Type, ...] = None, with_stack: bool = False, )`

在一个表格中展示单个模型统计量。存储到 statistic.txt 文件中。

参数

- out_dir：statistic.txt 文件的存储路径，默认 None，存储到 self.out_dir。

- prefixes：需要统计的模型中 op 的 prefixes。默认统计所有 op。

- types：需要统计的模型中 op 的 type。默认统计所有 op。

- with_stack: 是否显示每个 op 在代码中对应的位置。

输出

statistic.txt 文件，从左到右每一列分别为：

- Index：op index。

- Op Name：op type，module 类名或者 function 名。

- Mod Name：若是 module 类，则显示该 module 在模型中的 prefix name；若是 function 类型，则显示该 function 所在的 module prefix name。

- Attr：input/output/weight/bias。

- Dtype：tensor 的数据类型。

- Scale：tensor 的 scale。

- Min：当前 tensor 的最小值。

- Max：当前 tensor 的最大值。

- Mean：当前 tensor 的平均值。

- Var：当前 tensor 中数值的方差。

- Shape：tensor shape。

`+---------+--------------------------------------------------------------------+------------+--------+---------------+-----------+------------+-----------+------------+-----------+----------------------------+ | Index | Op Name | Mod Name | Attr | Dtype | Scale | Min | Max | Mean | Var | Shape | |---------+--------------------------------------------------------------------+------------+--------+---------------+-----------+------------+-----------+------------+-----------+----------------------------| | 0 | horizon_plugin_pytorch.nn.qat.stubs.QuantStub | quant | input | torch.float32 | | 0.0003164 | 0.9990171 | 0.5015678 | 0.0846284 | torch.Size([1, 3, 32, 32]) | | 0 | horizon_plugin_pytorch.nn.qat.stubs.QuantStub | quant | output | qint8 | 0.0078354 | 0.0000000 | 0.9950994 | 0.5014852 | 0.0846521 | torch.Size([1, 3, 32, 32]) | | 1 | horizon_plugin_pytorch.nn.qat.conv2d.Conv2d | conv | input | qint8 | 0.0078354 | 0.0000000 | 0.9950994 | 0.5014852 | 0.0846521 | torch.Size([1, 3, 32, 32]) | | 1 | horizon_plugin_pytorch.nn.qat.conv2d.Conv2d | conv | weight | torch.float32 | | -0.5315086 | 0.5750652 | 0.0269936 | 0.1615299 | torch.Size([3, 3, 1, 1]) | | 1 | horizon_plugin_pytorch.nn.qat.conv2d.Conv2d | conv | bias | torch.float32 | | -0.4963555 | 0.4448483 | -0.0851902 | 0.2320642 | torch.Size([3]) | | 1 | horizon_plugin_pytorch.nn.qat.conv2d.Conv2d | conv | output | qint8 | 0.0060428 | -0.7674332 | 0.4652941 | -0.0412943 | 0.0422743 | torch.Size([1, 3, 32, 32]) | | 2 | horizon_plugin_pytorch.nn.qat.relu.ReLU | relu | input | qint8 | 0.0060428 | -0.7674332 | 0.4652941 | -0.0412943 | 0.0422743 | torch.Size([1, 3, 32, 32]) | | 2 | horizon_plugin_pytorch.nn.qat.relu.ReLU | relu | output | qint8 | 0.0060428 | 0.0000000 | 0.4652941 | 0.0639115 | 0.0089839 | torch.Size([1, 3, 32, 32]) | | 3 | horizon_plugin_pytorch.nn.interpolate.autocasted_interpolate_outer | | input | qint8 | 0.0060428 | 0.0000000 | 0.4652941 | 0.0639115 | 0.0089839 | torch.Size([1, 3, 32, 32]) | | 3 | horizon_plugin_pytorch.nn.interpolate.autocasted_interpolate_outer | | output | qint8 | 0.0060428 | 0.0000000 | 0.3504813 | 0.0639483 | 0.0043366 | torch.Size([1, 3, 41, 41]) | | 4 | horizon_plugin_pytorch.nn.qat.stubs.DeQuantStub | dequant | input | qint8 | 0.0060428 | 0.0000000 | 0.3504813 | 0.0639483 | 0.0043366 | torch.Size([1, 3, 41, 41]) | | 4 | horizon_plugin_pytorch.nn.qat.stubs.DeQuantStub | dequant | output | torch.float32 | | 0.0000000 | 0.3504813 | 0.0639483 | 0.0043366 | torch.Size([1, 3, 41, 41]) | +---------+--------------------------------------------------------------------+------------+--------+---------------+-----------+------------+-----------+------------+-----------+----------------------------+`

##### #tensorboard

`class OpRunningInfoManager: def tensorboard( self, out_dir: str = None, prefixes: Tuple[str, ...] = None, types: Tuple[Type, ...] = None, force_per_channel: bool = False, ):`

在 tensorboard 中显示每一层输入输出直方图。

参数

- out_dir：tensorboard 相关文件保目录。默认保存到 self.out_dir/tensorboard 目录下。

- prefixes：需要统计的模型中 op 的 prefixes。默认统计所有。

- types：需要统计的模型中 op 的 type。默认统计所有。

- force_per_channel：是否以 per_channel 量化的方式展示直方图。

输出

tensorboard 文件，打开后截图如下：

### #HbirModelProfiler 类

该类的功能和使用方式与 ModelProfiler 类完全一致。请参考 ModelProfiler 类 进行使用。

注意

由于 hbir 模型的特殊格式，qat hbir 模型在 forward 时需添加索引 0。

`with HbirModelProfiler(qat_hbir, "./hbir_dir") as p: qat_hbir[0](data) p.get_info_manager().table() p.get_info_manager().tensorboard()`
页面目录