飞道的博客

OpenPPL PPQ量化(2):离线静态量化源码剖析

448人阅读  评论(0)

目录

模型支持

量化onnx原生模型:quantize_onnx_model

输入输出

执行流程

ONNX格式解析

后记

模型支持

openppl支持了三种模型:onnx、caffe、pytorch,其中pytorch和caffe是通过quantize_torch_model和quantize_caffe_model,先将模型转换成onnx模型,再调用quantize_onnx_model来实现量化的。


  
  1. @ empty_ppq_cache
  2. def quantize_torch_model(
  3. model: torch.nn.Module,
  4. calib_dataloader: DataLoader,
  5. calib_steps: int,
  6. input_shape: List[int],
  7. platform: TargetPlatform,
  8. input_dtype: torch.dtype = torch.float,
  9. setting: QuantizationSetting = None,
  10. collate_fn: Callable = None,
  11. inputs: List[ Any] = None,
  12. do_quantize: bool = True,
  13. onnx_export_ file: str = 'onnx.model',
  14. device: str = 'cuda',
  15. verbose: int = 0,
  16. ) - > BaseGraph:
  17. # dump pytorch model to onnx
  18. dump_torch_ to_onnx(model =model, onnx_export_ file =onnx_export_ file,
  19. input_shape = input_shape, input_dtype = input_dtype,
  20. inputs =inputs, device =device)
  21. return quantize_onnx_model(onnx_import_ file =onnx_export_ file,
  22. calib_dataloader =calib_dataloader, calib_steps =calib_steps, collate_fn =collate_fn,
  23. input_shape = input_shape, input_dtype = input_dtype, inputs =inputs, setting =setting,
  24. platform =platform, device =device, verbose =verbose, do_quantize =do_quantize)

  
  1. @ empty_ppq_cache
  2. def quantize_caffe_model(
  3. caffe_proto_ file: str,
  4. caffe_model_ file: str,
  5. calib_dataloader: DataLoader,
  6. calib_steps: int,
  7. input_shape: List[int],
  8. platform: TargetPlatform,
  9. input_dtype: torch.dtype = torch.float,
  10. setting: QuantizationSetting = None,
  11. collate_fn: Callable = None,
  12. inputs: List[ Any] = None,
  13. do_quantize: bool = True,
  14. device: str = 'cuda',
  15. verbose: int = 0,
  16. ) - > BaseGraph:
  17. if do_quantize:
  18. if calib_dataloader is None or calib_steps is None:
  19. raise TypeError( 'Quantization needs a valid calib_dataloader and calib_steps setting.')
  20. if setting is None:
  21. setting = QuantizationSettingFactory. default_setting()
  22. ppq_ir = load_graph( file_path =caffe_proto_ file,
  23. caffemodel_path =caffe_model_ file,
  24. from_framework =NetworkFramework.CAFFE)
  25. ppq_ir = format_graph(ppq_ir)
  26. ppq_ir = dispatch_graph(ppq_ir, platform,
  27. dispatcher =setting.dispatcher,
  28. dispatching_ table =setting.dispatching_ table)
  29. if inputs is None:
  30. dummy_ input = torch. zeros( size = input_shape, device =device, dtype = input_dtype)
  31. else: dummy_ input = inputs
  32. quantizer = PFL.Quantizer(platform =platform, graph =ppq_ir)
  33. executor = TorchExecutor(graph =quantizer._graph, device =device)
  34. executor.tracing_operation_meta(inputs =dummy_ input)
  35. if do_quantize:
  36. quantizer.quantize(
  37. inputs =dummy_ input,
  38. calib_dataloader =calib_dataloader,
  39. executor =executor,
  40. setting =setting,
  41. calib_steps =calib_steps,
  42. collate_fn =collate_fn
  43. )
  44. if verbose: quantizer. report()
  45. return quantizer._graph
  46. else:
  47. return quantizer._graph

所以我们接下来看看quantize_onnx_model是怎么实现的。

量化onnx原生模型:quantize_onnx_model

输入输出


  
  1. onnx_import_ file (str): 被量化的 onnx 模型文件路径 onnx model location
  2. calib_dataloader (DataLoader): 校准数据集 calibration data loader
  3. calib_steps (int): 校准步数 calibration steps
  4. collate_fn (Callable): 校准数据的预处理函数 batch collate func for preprocessing
  5. input_shape (List[int]): 模型输入尺寸,用于执行 jit.trace,对于动态尺寸的模型,输入一个模型可接受的尺寸即可。
  6. 如果模型存在多个输入,则需要使用 inputs 变量进行传参,此项设置为 None
  7. a list of ints indicating size of input, for multiple inputs, please use
  8. keyword arg inputs for direct parameter passing and this should be set to None
  9. input_dtype (torch.dtype): 模型输入数据类型,如果模型存在多个输入,则需要使用 inputs 变量进行传参,此项设置为 None
  10. the torch datatype of input, for multiple inputs, please use keyword arg inputs
  11. for direct parameter passing and this should be set to None
  12. inputs (List[ Any], optional): 对于存在多个输入的模型,在Inputs中直接指定一个输入List,从而完成模型的tracing。
  13. for multiple inputs, please give the specified inputs directly in the form of
  14. a list of arrays
  15. setting (OptimSetting): 量化配置信息,用于配置量化的各项参数,设置为 None 时加载默认参数。
  16. Quantization setting, default setting will be used when set None
  17. do_quantize (Bool, optional): 是否执行量化 whether to quantize the model, defaults to True.
  18. platform (TargetPlatform, optional): 量化的目标平台 target backend platform, defaults to TargetPlatform.DSP_INT 8.
  19. device (str, optional): 量化过程的执行设备 execution device, defaults to 'cuda'.
  20. verbose (int, optional): 是否打印详细信息 whether to print details, defaults to 0.

执行流程

我们首先要加载计算图:

    ppq_ir = load_onnx_graph(onnx_import_file=onnx_import_file)

此处加载的计算图是原始的,尚未被调度,也就是所有算子都被认为是可量化的。

然后我们需要执行图的切分与调度,不同算子会被执行不同的调度:


  
  1. ppq_ir = dispatch_graph(graph =ppq_ir, platform =platform,
  2. dispatcher =setting.dispatcher,
  3. dispatching_ table =setting.dispatching_ table)

所有对计算图执行的操作,最后都会返回BaseGraph类,这个类是PPQ内部专门为模型量化准备的计算图,除了保存一般计算图的必要信息之外,还存储了所有量化信息。后面在写博客解析这个量化计算图的设计。

然后根据指定的平台platform确定指定的量化类:

    quantizer = PFL.Quantizer(platform, ppq_ir)

所有的平台类型写在ppq/lib/common.py文件中:

这些具体量化方法写在quantizer文件夹中,传入量化计算图是因为这些量化类需要计算图进行初始化:

因为我们已经初始化了量化类,所以后面表示计算图不再使用ppq_ir,直接用quantizer._graph表示。我们继续要用量化图初始化执行引擎,这个引擎TorchExecutor能执行onnx的推理,由于不同平台的推理细节是不同的,所以这里的实现有点复杂,大致的流程如下:

详细的解析后面专门再写博客讲吧。

好了继续回到我们的主逻辑中,最后一步是执行量化,返回量化后的量化计算图,搞定~


  
  1. if do_quantize:
  2. quantizer.quantize(
  3. inputs =dummy_ input,
  4. calib_dataloader =calib_dataloader,
  5. executor =executor,
  6. setting =setting,
  7. calib_steps =calib_steps,
  8. collate_fn =collate_fn
  9. )
  10. if verbose: quantizer. report()
  11. return quantizer._graph
  12. else:
  13. executor = TorchExecutor(graph =ppq_ir, device =device)
  14. executor.tracing_operation_meta(inputs =

最后注意这里如果不需要执行量化,我们用没有原始载入的计算图执行一遍推理,然后返回即可。

ONNX格式解析

如果不了解ONNX格式,前面从ONNX解析出计算图部分会比较难理解,有一篇写的很棒的博客,我摘抄了一部分帮助理解:ONNX学习笔记 - 知乎

这一节我们来分析一下ONNX的组织格式,上面提到ONNX中最核心的部分就是onnx.protohttps://github.com/onnx/onnx/blob/master/onnx/onnx.proto)这个文件了,它定义了ONNX这个数据协议的规则和一些其它信息。现在是2021年1月,这个文件有700多行,我们没有必要把这个文件里面的每一行都贴出来,我们只要搞清楚里面的核心部分即可。在这个文件里面以message关键字开头的对象是我们需要关心的。我们列一下最核心的几个对象并解释一下它们之间的关系。

  • ModelProto
  • GraphProto
  • NodeProto
  • ValueInfoProto
  • TensorProto
  • AttributeProto

当我们加载了一个ONNX之后,我们获得的就是一个ModelProto,它包含了一些版本信息,生产者信息和一个GraphProto。在GraphProto里面又包含了四个repeated数组,它们分别是node(NodeProto类型),input(ValueInfoProto类型),output(ValueInfoProto类型)和initializer(TensorProto类型),其中node中存放了模型中所有的计算节点,input存放了模型的输入节点,output存放了模型中所有的输出节点,initializer存放了模型的所有权重参数。

我们知道要完整的表达一个神经网络,不仅仅要知道网络的各个节点信息,还要知道它们的拓扑关系。这个拓扑关系在ONNX中是如何表示的呢?ONNX的每个计算节点都会有inputoutput两个数组,这两个数组是string类型,通过inputoutput的指向关系,我们就可以利用上述信息快速构建出一个深度学习模型的拓扑图。这里要注意一下,GraphProto中的input数组不仅包含我们一般理解中的图片输入的那个节点,还包含了模型中所有的权重。例如,Conv层里面的W权重实体是保存在initializer中的,那么相应的会有一个同名的输入在input中,其背后的逻辑应该是把权重也看成模型的输入,并通过initializer中的权重实体来对这个输入做初始化,即一个赋值的过程。

最后,每个计算节点中还包含了一个AttributeProto数组,用来描述该节点的属性,比如Conv节点或者说卷积层的属性包含grouppadstrides等等,每一个计算节点的属性,输入输出信息都详细记录在https://github.com/onnx/onnx/blob/master/docs/Operators.md

后记

关于如何做量化校准?如果使用校准数据?如何配置量化设置?具体的量化过程是如何?如何选择需要量化的算子?……

还有很多问题没有讲明白,这个系列很长,我们一一探索!!


转载:https://blog.csdn.net/qq_41895747/article/details/128793776
查看评论
* 以上用户言论只代表其个人观点,不代表本网站的观点或立场