• Yolov5_lite pytorch量化


    目录

    主题

    pytorch 静态量化

    对称量化

    非对称量化

    YoloV5_lite 量化

    代码地址

    量化代码

    fuse_modules 模块提取代码

    模型网络中增加量化Module

    量化过程中出现的问题

    silu 不支持量化

    add 操作不支持量化操作

    yolov detect 回归报错

    结尾

    参考文献


    主题

    针对yolov5_lite 网络采用pytorch 进行训练后的静态量化,主要介绍量化的过程,并记录其中遇到的各种问题。

    pytorch 静态量化

    Q = \left [ \frac{X}{S} + Q_{0} \right ]_{round}

    X表示是浮点值, Q表示量化值,量化中一般会将浮点进行截断,选取【T2, T1】区间

    那么就有:            Q=\left\{\begin{matrix} max(int) & X >= T1\\ \frac{X}{S}+Q_{0}& T2<X < T1\\ min(int)&X<=T2 \end{matrix}\right.

    其中Q0 表示量化的0点, S表示量化的scale。

    \left\{\begin{matrix} S = \frac{T1 - T2}{max(int) - min(int)}\\ Q_{0} = \frac{T1 *min(int)- T2*max(int)}{T1 - T2} \end{matrix}\right.

    对称量化

    Q0=0, T1 = -T2 这个是时候就是对称量化, 所以在神经网络计算的结果如下:

    Y = \sum W*X = \sum W_{q} * S _{w} * X_{q} * S _{x} = (S _{w} * S _{x}) * \sum W_{q} * X_{q}

    当前的网络计算将大量的否点乘法,转换成了整数乘法,这样可以大量的减少计算时间。

    非对称量化

    Q0 != 0 , T1 != -T2, 这个时候是非对称量化,量化损失更小,但是计算量上更大,如下:

    Y= \sum W*X = \sum S_{w} * \left ( W_{q} - Q_{q0} \right ) * S_{x} * \left ( X_{q} - Q_{x0}\right ) = \left ( S_{w} * S_{x} \right ) \sum \left ( W_{q} * X_{q}-X_{q}* Q_{q0} - W_{q} * Q_{x0} + Q_{w0} * Q_{x0}\right )

    YoloV5_lite 量化

    代码地址

    https://github.com/ppogg/YOLOv5-Lite.git

    量化代码

    1. backend = "fbgemm"
    2. model.qconfig = torch.quantization.get_default_qconfig(backend) # 不同平台不同配置
    3. model = torch.quantization.fuse_modules(model,fuse_ops) # 合并某些层,不想合并这句也可以跳过
    4. model_fp32_prepared = torch.quantization.prepare(model)
    5. stride = int(model.stride.max())
    6. dataset = LoadImages(opt.source, img_size=opt.img_size, stride=stride)
    7. import tqdm
    8. tx = None
    9. index = 0
    10. for path, img, im0s, vid_cap in tqdm.tqdm(dataset):
    11. img = torch.from_numpy(img).to(device)
    12. img = img.float() # uint8 to fp16/32
    13. img /= 255.0 # 0 - 255 to 0.0 - 1.0
    14. if img.ndimension() == 3:
    15. img = img.unsqueeze(0)
    16. tx = img
    17. out = model_fp32_prepared(img, augment=opt.augment)
    18. index = index + 1
    19. #print(model_fp32_prepared)
    20. #model_fp32_prepared = model_fp32_prepared.to('cpu')
    21. model_int8 = torch.quantization.convert(model_fp32_prepared)
    22. #print(model_int8)
    23. for path, img, im0s, vid_cap in tqdm.tqdm(dataset):
    24. img = torch.from_numpy(img).to("cpu")
    25. img = img.float() # uint8 to fp16/32
    26. img /= 255.0 # 0 - 255 to 0.0 - 1.0
    27. if img.ndimension() == 3:
    28. img = img.unsqueeze(0)
    29. tx = img
    30. out = model_int8(img, augment=opt.augment)
    31. index = index + 1
    32. torch.save(model_int8.state_dict(), "int_8_model.pt")

    fuse_modules 模块提取代码

    fuse_modules的时候目前,只是支持conv+bn, conv+bn+relu 这两种方式的融合, 这个需要传入

    融合模块名称的list, 这里提供了查找融合模块的list的代码

    1. def is_fused_itm(module): #用来判断该模块是否是需要fuse的模块
    2. if module.__module__ in ['torch.nn.modules.conv', 'torch.nn.modules.batchnorm', 'torch.nn.modules.activation']:
    3. return True
    4. return False
    5. def get_fuse_module(model):
    6. sub_module_dict = {}
    7. for name, module in model.named_modules(): #提取每一个{name:op}的操作
    8. module_name_structs = name.split('.')
    9. if is_fused_itm(module):
    10. print(name, module)
    11. sub_module_name = ".".join(module_name_structs[0:len(module_name_structs) - 1])
    12. if not sub_module_name in sub_module_dict:
    13. sub_module_dict[sub_module_name] = [(name, module)]
    14. else:
    15. sub_module_dict[sub_module_name].append((name,module))
    16. fuse_ops = []
    17. tmp_fuse_ops = []
    18. for name in sub_module_dict.keys(): #遍历所有的module,找到每个子module 可以合并的部分
    19. vals = sub_module_dict[name]
    20. for n, m in vals:
    21. if m.__module__ == 'torch.nn.modules.conv':
    22. if len(tmp_fuse_ops) > 1:
    23. fuse_ops.append(tmp_fuse_ops)
    24. tmp_fuse_ops = [n]
    25. elif m.__module__ == 'torch.nn.modules.batchnorm':
    26. tmp_fuse_ops.append(n)
    27. elif m.__module__ == 'torch.nn.modules.activation' and m._get_name()=='ReLU':
    28. tmp_fuse_ops.append(n)
    29. if len(tmp_fuse_ops) > 1: # 处理掉当前队列中的剩余的,如果剩余大于1 则说明可以fuse, 并清空缓存
    30. fuse_ops.append(tmp_fuse_ops)
    31. tmp_fuse_ops = []
    32. return fuse_ops

    模型网络中增加量化Module

    在pytorch中实现量化需要在代码中添加量化和恢复浮点的模块如下:

    1. self.quant = torch.quantization.QuantStub()
    2. self.dequant = torch.quantization.DeQuantStub()

    这里为了尽量保持之前的代码不变的情况下可以采用如下的修改方式:

    1. class QuantModel(Model):
    2. def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None, anchors=None):
    3. super(QuantModel, self).__init__(cfg, ch, nc, anchors)
    4. def set_quant(self):
    5. logger.info("quant model need to set")
    6. self.quant = torch.quantization.QuantStub()
    7. self.dequant = torch.quantization.DeQuantStub()
    8. def forward(self, x, augment=False, profile=False):
    9. x = self.quant(x)
    10. x = super(QuantModel, self).forward(x, augment, profile)
    11. x = [self.dequant(val) for val in x]
    12. return x

    这里的Model 表示的是原来的网络,set_quant在原有的Model 定义为空,并且在构造函数的开始调用, 如下所示:

    1. class Model(nn.Module):
    2. def set_quant(self):
    3. logger.info("org model not need to set quant")
    4. def __init__(self, cfg='yolov5s.yaml', ch=3, nc=None, anchors=None): # model, input channels, number of classes
    5. super(Model, self).__init__()
    6. self.set_quant()

    这样原始的网络跟量化没有关系。

    量化过程中出现的问题

    silu 不支持量化

    1. File "/home/kylin/anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1190, in _call_impl
    2. return forward_call(*input, **kwargs)
    3. File "/home/kylin/YOLOv5-Lite/models/yolo.py", line 315, in forward
    4. x = super(QuantModel, self).forward(x, augment, profile)
    5. File "/home/kylin/YOLOv5-Lite/models/yolo.py", line 166, in forward
    6. return self.forward_once(x, profile) # single-scale inference, train
    7. File "/home/kylin/YOLOv5-Lite/models/yolo.py", line 182, in forward_once
    8. x = m(x) # run
    9. File "/home/kylin/anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1190, in _call_impl
    10. return forward_call(*input, **kwargs)
    11. File "/home/kylin/YOLOv5-Lite/models/common.py", line 171, in forward
    12. return self.act(self.bn(self.conv(x)))
    13. File "/home/kylin/anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1190, in _call_impl
    14. return forward_call(*input, **kwargs)
    15. File "/home/kylin/anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/modules/activation.py", line 395, in forward
    16. return F.silu(input, inplace=self.inplace)
    17. File "/home/kylin/anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/functional.py", line 2059, in silu
    18. return torch._C._nn.silu(input)
    19. NotImplementedError: Could not run 'aten::silu.out' with arguments from the 'QuantizedCPU' backend. This could be because the operator doesn't exist for this backend, or was omitted during the selective/custom build process (if using custom build). If you are a Facebook employee using PyTorch on mobile, please visit https://fburl.com/ptmfixes for possible resolutions. 'aten::silu.out' is only available for these backends: [CPU, CUDA, Meta, BackendSelect, Python, FuncTorchDynamicLayerBackMode, Functionalize, Named, Conjugate, Negative, ZeroTensor, ADInplaceOrView, AutogradOther, AutogradCPU, AutogradCUDA, AutogradHIP, AutogradXLA, AutogradMPS, AutogradIPU, AutogradXPU, AutogradHPU, AutogradVE, AutogradLazy, AutogradMeta, AutogradPrivateUse1, AutogradPrivateUse2, AutogradPrivateUse3, AutogradNestedTensor, Tracer, AutocastCPU, AutocastCUDA, FuncTorchBatched, FuncTorchVmapMode, Batched, VmapMode, FuncTorchGradWrapper, PythonTLSSnapshot, FuncTorchDynamicLayerFrontMode, PythonDispatcher].

    pytorch 的量化op表中不支持silu 这种activation 操作,目前的方式直接变成identity 进行重新训练,当前的自己的数据集上差别不大, 修改的位置在common.py 中的 Conv 类中, 代码如下:

    1. class Conv(nn.Module):
    2. # Standard convolution
    3. def __init__(self, c1, c2, k=1, s=1, p=None, g=1, act=True): # ch_in, ch_out, kernel, stride, padding, groups
    4. super(Conv, self).__init__()
    5. self.conv = nn.Conv2d(c1, c2, k, s, autopad(k, p), groups=g, bias=False)
    6. self.bn = nn.BatchNorm2d(c2)
    7. self.act = nn.SiLU() if act is True else (act if isinstance(act, nn.Module) else nn.Identity())

    将act=True , 改成False, 这样默认就是 采用了Identity 操作了。 注意这里修改完需要重新训练一下。

    add 操作不支持量化操作

    1. File "/home/kylin/anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1190, in _call_impl
    2. return forward_call(*input, **kwargs)
    3. File "/home/kylin/YOLOv5-Lite/models/yolo.py", line 315, in forward
    4. x = super(QuantModel, self).forward(x, augment, profile)
    5. File "/home/kylin/YOLOv5-Lite/models/yolo.py", line 166, in forward
    6. return self.forward_once(x, profile) # single-scale inference, train
    7. File "/home/kylin/YOLOv5-Lite/models/yolo.py", line 182, in forward_once
    8. x = m(x) # run
    9. File "/home/kylin/anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1190, in _call_impl
    10. return forward_call(*input, **kwargs)
    11. File "/home/kylin/YOLOv5-Lite/models/common.py", line 826, in forward
    12. return torch.add(x1, x2, alpha=self.a)
    13. NotImplementedError: Could not run 'aten::add.out' with arguments from the 'QuantizedCPU' backend. This could be because the operator doesn't exist for this backend, or was omitted during the selective/custom build process (if using custom build). If you are a Facebook employee using PyTorch on mobile, please visit https://fburl.com/ptmfixes for possible resolutions. 'aten::add.out' is only available for these backends: [CPU, CUDA, Meta, MkldnnCPU, SparseCPU, SparseCUDA, SparseCsrCPU, SparseCsrCUDA, BackendSelect, Python, FuncTorchDynamicLayerBackMode, Functionalize, Named, Conjugate, Negative, ZeroTensor, ADInplaceOrView, AutogradOther, AutogradCPU, AutogradCUDA, AutogradHIP, AutogradXLA, AutogradMPS, AutogradIPU, AutogradXPU, AutogradHPU, AutogradVE, AutogradLazy, AutogradMeta, AutogradPrivateUse1, AutogradPrivateUse2, AutogradPrivateUse3, AutogradNestedTensor, Tracer, AutocastCPU, AutocastCUDA, FuncTorchBatched, FuncTorchVmapMode, Batched, VmapMode, FuncTorchGradWrapper, PythonTLSSnapshot, FuncTorchDynamicLayerFrontMode, PythonDispatcher].

    add, cat  这些二元操作,由于两个输入的scale 不一致这样直接int 加会出现问题,所以add 这类操作需要直接采用float 运算,不能采用量化操作运行进行处理,pytorch 中提供了量化后的浮点操作,修改是在common.py 的 ADD模块中进行修改

    1. class ADD(nn.Module):
    2. # Stortcut a list of tensors along dimension
    3. def __init__(self, alpha=0.5):
    4. super(ADD, self).__init__()
    5. self.a = alpha
    6. self.ff = torch.nn.quantized.FloatFunctional()
    7. def forward(self, x):
    8. x1, x2 = x[0], x[1]
    9. #return torch.add(x1, x2, alpha=self.a)
    10. return self.ff.add(x1, self.ff.mul_scalar(x2, self.a)) #用于量化

    调用了torch.nn.quantized.FloatFunctional() 获取了浮点操作。将原始的pytorch 的 add 操作变成了

    浮点操作。可以参考torch.ao.nn.quantized.modules.functional_modules — PyTorch 1.13 documentation

    yolov detect 回归报错

    1. File "/home/kylin/anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1190, in _call_impl
    2. return forward_call(*input, **kwargs)
    3. File "/home/kylin/YOLOv5-Lite/models/yolo.py", line 315, in forward
    4. x = super(QuantModel, self).forward(x, augment, profile)
    5. File "/home/kylin/YOLOv5-Lite/models/yolo.py", line 166, in forward
    6. return self.forward_once(x, profile) # single-scale inference, train
    7. File "/home/kylin/YOLOv5-Lite/models/yolo.py", line 182, in forward_once
    8. x = m(x) # run
    9. File "/home/kylin/anaconda3/envs/torch/lib/python3.8/site-packages/torch/nn/modules/module.py", line 1190, in _call_impl
    10. return forward_call(*input, **kwargs)
    11. File "/home/kylin/YOLOv5-Lite/models/yolo.py", line 82, in forward
    12. return x if self.training else (torch.cat(z, 1), torch.cat(logits_, 1), x)
    13. RuntimeError: Tensors must have same number of dimensions: got 5 and 3

    目前这块问题的解决方案只能采用将detect 的框回归的部分从网络中抽离出来,然后在量化计算完成后再直接进行相关的框的计算。修改是在yolo.py 中的Detect 模块。

    1. def forward(self, x):
    2. # x = x.copy() # for profiling
    3. z = [] # inference output
    4. logits_ = []
    5. self.training |= self.export
    6. for i in range(self.nl):
    7. x[i] = self.m[i](x[i]) # conv
    8. # bs, _, ny, nx = x[i].shape # x(bs,255,20,20) to x(bs,3,20,20,85)
    9. # x[i] = x[i].view(bs, self.na, self.no, ny, nx).permute(0, 1, 3, 4, 2).contiguous()
    10. # if not self.training: # inference
    11. # if torch.onnx.is_in_onnx_export():
    12. # self.grid[i] = self._make_grid(nx, ny).to(x[i].device)
    13. # elif self.grid[i].shape[2:4] != x[i].shape[2:4]:
    14. # self.grid[i] = self._make_grid(nx, ny).to(x[i].device)
    15. # logits = x[i][..., 5:]
    16. # y = x[i].sigmoid()
    17. # logits_.append(y)
    18. # if not torch.onnx.is_in_onnx_export():
    19. # y[..., 0:2] = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * self.stride[i] # xy
    20. # y[..., 2:4] = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i] # wh
    21. # else:
    22. # xy = (y[..., 0:2] * 2. - 0.5 + self.grid[i]) * self.stride[i] # xy
    23. # wh = (y[..., 2:4] * 2) ** 2 * self.anchor_grid[i].data # wh
    24. # y = torch.cat((xy, wh, y[..., 4:]), -1)
    25. # z.append(y.view(bs, -1, self.no))
    26. # logits_.append(logits.view(bs, -1, self.no - 5))
    27. # return x if self.training else (torch.cat(z, 1), torch.cat(logits_, 1), x)
    28. return x

    将原有的这部分代码注释掉就可以了。训练还是用原始的代码这样,量化的时候修改

    结尾

    目前可以将yolov5 的pytorch 的静态量化跑通, 性能和精度后续再补齐。

    参考文献

    torch — PyTorch 1.13 documentation

  • 相关阅读:
    数据结构篇【5】——哈希表开散列实现(哈希桶)及封装
    单词记忆词典 python
    java计算机毕业设计高校迎新管理系统源码+数据库+系统+lw文档+部署
    C++ Reference: Standard C++ Library reference: C Library: cmath: remainder
    【第八章 Thread类中的常用方法、线程优先级】
    538页21万字数字政府大数据云平台项目建设方案
    CV计算机视觉每日开源代码Paper with code速览-2023.10.26
    FreeRTOS教程4 消息队列
    概要设计:描绘软件结构的图形工具,结构图既能表示模块间的数据信息、控制信息,也能表示选择调用和循环调用关系。
    10分钟了解BIM+GIS融合,常见BIM数据格式及特性
  • 原文地址:https://blog.csdn.net/sxk20091111/article/details/80708648