常见问题

训练环境

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 如何进行量化,目前支持 FakeQuantizeLSQPACT 等量化算法。

如何导出各阶段的 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 架构,如 S100 需要使用 Nash:

horizon.march.set_march(horizon.march.March.NASH)

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

错误示例如下:

假设模型定义如下:

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): ...
页面目录