Rspress

# #常见问题

## #训练环境

### #Docker 容器无法使用 Nvidia 资源？

解决方法：建议参考文档中 环境部署 章节中 GPU Docker 部分的描述。

## #QAT 量化训练

### #Calibration 精度差是否可以说明 QAT 的精度大概率也会低？

不考虑训练的随机性，对于同一模型来说，Calibration 精度和 QAT 精度是正相关的，正相关程度因模型而异，没有明确指标。

### #如何判断 Calibration 阶段终止，可以开启 QAT 训练了？

单纯调整校准参数对于精度的提升比较小，建议校准完成后进行一次 QAT 训练：

1. 如果校准精度几乎达到目标，那么调整校准参数可能能解决问题。

2. 如果 QAT 精度比校准精度高，但比目标还差的比较多，那么留在校准阶段做精度 debug，改掉一些量化不友好的问题，直到校准精度有比较明显的提升再 QAT。

3. 如果 QAT 精度没有校准高，那么先定位 QAT pipeline 和训练策略的问题。

### #QAT 训练和 float 训练的现象是否一致？

loss，精度等表现出来的现象应该与浮点基本一致。发生不一致时建议优先排查 QAT pipeline 是否存在使用问题。

### #量化精度异常

解决方法：QAT/Quantized 精度不符合预期、出现 NAN 或 QAT 初始 loss 相对 float 明显异常。请参考 精度调优工具使用指南。

### #为什么开启多机训练后精度表现变差？

开启多机训练后 batchsize 成倍增大，此时需要同步调整 LR 等超参来进行平衡。

### #Qconfig 是否需要用户干预？

地平线提供的 `Qconfig` 定义了 activation 和 weight 如何进行量化，目前支持 `FakeQuantize`、`LSQ`、`PACT` 等量化算法。

### #如何导出各阶段的 ONNX 模型？

请参考如下代码实现：

`from horizon_plugin_pytorch.utils import onnx_helper as horizon_onnx_helper # [Optional] Export float 、qat net to ONNX # -------------------------------------------------------------------- logging.info("Export qat model to ONNX...") data = torch.rand((1, 3, 228, 228), device=device) horizon_onnx_helper.export_to_onnx(qat_net, data, "resnet_qat.onnx") # [Optional] Export quantized_model to ONNX # -------------------------------------------------------------------- horizon_onnx_helper.export_quantized_onnx( quantized_model, data, "resnet_quantized.onnx" )`

### #为什么 QAT 训练时存在 nan？

该问题的影响因素较多，建议从以下几个方面检查：

1. 检查输入数据是否含 nan。

2. 检查浮点模型是否收敛。未收敛的浮点模型针对某些微量化误差可能会导致很大波动。

3. 检查是否开启 calib。建议开启，可以给模型更好的初始系数。

4. 检查训练策略是否适合。不适合的训练策略也 会导致出现 NAN 值，例如学习率 lr 过大（可通过调低学习率或使用梯度截断方式）等。在训练策略上，默认 QAT 与浮点保持一致，如果浮点训练采用的是 OneCycle 等会影响 LR 设置的优化器，建议使用 SGD 替换。

### #配置 int16 节点或高精度输出节点无效

该现象可能是因为错误配置 module_name 导致，module_name 字段只支持 string，不支持按 index 索引进行配置。

### #如何查看某一层是否开启了高精度输出？

可以打印 qat_model 的所在层，查看该层是否有 `(activation_post_process): FakeQuantize`，若没有，则说明其为高精度输出。例如 int32 高精度 conv 打印如下：

`(1): ConvModule2d( (0): Conv2d( 64, 3, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1) (weight_fake_quant): FakeQuantize( fake_quant_enabled=tensor([1], dtype=torch.uint8), observer_enabled=tensor([1], dtype=torch.uint8), quant_min=-128, quant_max=127, dtype=qint8, qscheme=torch.per_channel_symmetric, ch_axis=0, scale=tensor([1., 1., 1.]), zero_point=tensor([0, 0, 0]) (activation_post_process): MovingAveragePerChannelMinMaxObserver(min_val=tensor([]), max_val=tensor([])) ) ) )`

int8 低精度 conv 打印如下：

`(0): ConvModule2d( (0): ConvReLU2d( 64, 64, kernel_size=(3, 3), stride=(1, 1), padding=(1, 1) (weight_fake_quant): FakeQuantize( fake_quant_enabled=tensor([1], dtype=torch.uint8), observer_enabled=tensor([1], dtype=torch.uint8), quant_min=-128, quant_max=127, dtype=qint8, qscheme=torch.per_channel_symmetric, ch_axis=0, scale=tensor([1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1., 1.]), zero_point=tensor([0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0]) (activation_post_process): MovingAveragePerChannelMinMaxObserver(min_val=tensor([]), max_val=tensor([])) ) (activation_post_process): FakeQuantize( fake_quant_enabled=tensor([1], dtype=torch.uint8), observer_enabled=tensor([1], dtype=torch.uint8), quant_min=-128, quant_max=127, dtype=qint8, qscheme=torch.per_tensor_symmetric, ch_axis=-1, scale=tensor([1.]), zero_point=tensor([0]) (activation_post_process): MovingAverageMinMaxObserver(min_val=tensor([]), max_val=tensor([])) ) ) )`

### #辅助分支能否插入伪量化节点？

建议仅对部署上板的模型部分插入伪量化节点。由于 QAT 训练为全局训练，辅助分支的存在会导致训练难度增加，若辅助分支处的数据分布与其他分支差异较大还会加大精度风险，建议去除。

### #如何将地平线 gridsample 算子改写为 torch 公版实现？

horizon_plugin_pytorch 中的 gridsample 算子非公版实现，其 grid 输入（输入 2）是 int16 类型的绝对坐标，而 torch 公版是 float32 类型的归一化坐标，范围是 `[-1, 1]`。

因此，从 `torch.nn.functional.grid_sample` 路径导入 grid_sample 算子后，可以通过如下方式归一化 grid：

`def norm_grid(x, grid): n = grid.size(0) h = grid.size(1) w = grid.size(2) base_coord_y = ( torch.arange(h, dtype=grid.dtype, device=grid.device) .unsqueeze(-1) .unsqueeze(0) .expand(n, h, w) ) base_coord_x = ( torch.arange(w, dtype=grid.dtype, device=grid.device) .unsqueeze(0) .unsqueeze(0) .expand(n, h, w) ) absolute_grid_x = grid[:, :, :, 0] + base_coord_x absolute_grid_y = grid[:, :, :, 1] + base_coord_y norm_grid_x = absolute_grid_x * 2 / (x.size(3) - 1) - 1 norm_grid_y = absolute_grid_y * 2 / (x.size(2) - 1) - 1 norm_grid = torch.stack((norm_grid_x, norm_grid_y), dim=-1) return norm_grid`

### #load calibration 后的 QAT 训练为什么权重参数不更新？

可以依次检查如下：

1. prepare 是否在 optimizer 定义之前。因为 prepare 会进行算子融合，导致模型结构发生变化。

2. fake_quant_enabled 和 observe_enabled 是否为 1。

3. module 中的 training 变量是否为 True。

## #设置类错误

### #无需量化的模块设置了非 None 的 qconfig，例如 前后处理，loss function 等。

解决方法：只对需要量化的模块设置 qconfig。

### #没有正确设置 march，这样可能导致模型编译失败或部署精度不一致。

解决方法：根据要部署的处理器选择正确的 BPU 架构。

`horizon.march.set_march("nash-e")`

### #模型输出节点没有设置成高精度输出，导致量化精度不符合预期。

错误示例如下：

假设模型定义如下：

`class ToyNet(nn.Module): def __init__(self): self.conv0 = nn.Conv2d(4,4,3,3) self.relu0 = nn.ReLU() self.classifier = nn.Conv2d(4,4,3,3) def forward(self, x): out = self.conv0(x) out = self.relu(out) out = self.classifier(out) return out # 错误的设置 qconfig 示例： float_model = ToyNet() # 整网设置成 int8 量化 float_model.qconfig = default_qat_8bit_fake_quant_qconfig qat_model = prepare(float_model, example_input)`

解决方法：为了提高模型精度，模型输出节点设置成高精度，示例如下：

`qat_model = horizon.quantization.prepare( float_model, example_input, # 使用默认模板，自动为输出设置高精度 qconfig_setter = horizon.quantization.qconfig_template.default_qat_qconfig_setter, )`

## #方法类错误

### #Calibration 过程使用多卡。

由于底层限制，Calibration 目前不支持多卡，请使用单卡进行 Calibration 操作。

### #模型输入图像数据采用数据格式为 RGB 等非 centered YUV444 格式，这样可能导致模型部署精度不一致。

由于 Horizon 硬件支持的图像格式为 centered YUV444，因此建议您从模型训练开始就直接使用 YUV444 格式作为网络输入进行训练。

### #量化感知训练中使用 qat 模型进行模型精测评测和监控，导致不能及时发现部署时精度异常的问题。

导致 QAT 与 Quantized 误差的原因是 QAT 阶段不能完全模拟 Quantized 中纯定点计算逻辑，建议使用 quantized 模型进行模型精度评测和监控。

`quantized_hbir_model = hbdk4.compiler.convert(qat_hbir_model) acc = evaluate(quantized_hbir_model, eval_data_loader)`

## #网络类错误

### #多次调用同一个通过`FloatFunctional()` 定义的成员。

错误示例如下：

`class ToyNet(nn.Module): def __init__(self): self.add = FloatFunctional() def forward(self, x, y, z) out = self.add(x, y) return self.add(out, z)`

解决方法：禁止在 forward 中多次调用同一个通过 `FloatFunctional()` 定义的变量。

`class ToyNet(nn.Module): def __init__(self): self.add0 = FloatFunctional() self.add1 = FloatFunctional() def forward(self, x, y, z) out = self.add0.add(x, y) return self.add1.add(out, z)`

## #算子类错误

### #Quantized 模型中部分算子没有经过前期的 calibration 或 QAT，如某后处理算子想要在 BPU 上加速，但是没有经过量化阶段，这时候会导致量化 Inference 失败或部署时的精度异常。

Quantized 阶段并非完全不能直接添加算子，如颜色空间转换算子等，具体添加指南详见文档。但是并非所有算子都可以直接添加，比如 cat，这种算子必须在 calibration 或 QAT 阶段统计获得的真实量化参数才能不影响最终精度，有类似需求需要调整网络结构，可以咨询框架研发。

## #模型类错误

### #浮点模型过拟合。

模型过拟合常见判定方法：

- 对输入数据稍加变换之后，输出结果变化较大。

- 模型参数赋值较大。

- 模型 activation 较大。

解决方法：自行解决浮点模型过拟合问题。

## #模型导出

### #模型导出为什么会导致精度损失？

QAT 训练阶段的查表是前后加了伪量化的浮点算子，导出时才会真正变成查表，可以通过如下代码验证是否查表算子导致了精度损失：

`from horizon_plugin_pytorch.nn.qat.segment_lut import QuantizedQATSegmentLUT # 只有查表转定点，其他为qat torch。 QuantizedQATSegmentLUT.convert_segment_lut(self.qat_model) # 查表转定点后模型的推理结果，可以验证此时精度/可视化是否损失。 qat_lut_ret = self.qat_model(self.example_input)`

如果仍不能解决问题，请反馈地平线技术支持人员。

### #导出和训练代码不一致，如何调整对代码的侵入性更小？

如果模型只包含部署逻辑，就没有导出和训练代码不一致的问题，否则需要通过下面的方法处理。

`# 通过 module 的 training 变量和自定义的部署链路标志控制。 def forward(self, x): if self.training ... elif self.deploy: ... else: ... # 定义不同的 forward，不推荐这种方法，三个 forward 都要适配，从维护代码的角度来说也不如上一种方法。 def train_forward(self, x): ... def val_forward(self, x): ... def deploy_forward(self, x): ...`
页面目录