config.py 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479
  1. # Copyright (c) 2022 PaddlePaddle Authors. All Rights Reserved.
  2. #
  3. # Licensed under the Apache License, Version 2.0 (the "License");
  4. # you may not use this file except in compliance with the License.
  5. # You may obtain a copy of the License at
  6. #
  7. # http://www.apache.org/licenses/LICENSE-2.0
  8. #
  9. # Unless required by applicable law or agreed to in writing, software
  10. # distributed under the License is distributed on an "AS IS" BASIS,
  11. # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. # See the License for the specific language governing permissions and
  13. # limitations under the License.
  14. import copy
  15. from typing import Dict, Union
  16. import paddle
  17. from paddle import nn
  18. from paddle.nn import Layer
  19. from .factory import QuanterFactory
  20. from .wrapper import ObserveWrapper
  21. # TODO: Implement quanted layer and fill the mapping dict
  22. DEFAULT_QAT_LAYER_MAPPINGS: Dict[Layer, Layer] = {
  23. nn.quant.Stub: nn.quant.stub.QuanterStub,
  24. nn.Linear: nn.quant.qat.QuantedLinear,
  25. nn.Conv2D: nn.quant.qat.QuantedConv2D,
  26. }
  27. DEFAULT_LEAVES = [nn.ReLU, nn.AvgPool2D]
  28. class SingleLayerConfig:
  29. r"""
  30. Configure how to quantize the activations and weights of a single layer.
  31. Args:
  32. activation(QuanterFactory): The factory to create instance of quanter used to quantize activations.
  33. weight(QuanterFactory): The factory to create instance of quanter used to quantize weights.
  34. """
  35. def __init__(self, activation: QuanterFactory, weight: QuanterFactory):
  36. self._activation = activation
  37. self._weight = weight
  38. @property
  39. def activation(self):
  40. return self._activation
  41. @property
  42. def weight(self):
  43. return self._weight
  44. def __str__(self):
  45. return f"activation: {self._activation}\nweight: {self._weight}"
  46. class QuantConfig:
  47. r"""
  48. Configure how to quantize a model or a part of the model. It will map each layer to
  49. an instance of SingleLayerConfig by the settings. It provides diverse methods to set
  50. the strategies of quantization.
  51. Args:
  52. activation(QuanterFactory): The global quantizer used to quantize the activations.
  53. weight(QuanterFactory): The global quantizer used to quantize the weights.
  54. Examples:
  55. .. code-block:: python
  56. >>> from paddle.quantization import QuantConfig
  57. >>> from paddle.quantization.quanters import FakeQuanterWithAbsMaxObserver
  58. >>> quanter = FakeQuanterWithAbsMaxObserver(moving_rate=0.9)
  59. >>> q_config = QuantConfig(activation=quanter, weight=quanter)
  60. >>> print(q_config)
  61. Global config:
  62. activation: FakeQuanterWithAbsMaxObserver(name=None,moving_rate=0.9,bit_length=8,dtype=float32)
  63. weight: FakeQuanterWithAbsMaxObserver(name=None,moving_rate=0.9,bit_length=8,dtype=float32)
  64. """
  65. def __init__(self, activation: QuanterFactory, weight: QuanterFactory):
  66. if activation is None and weight is None:
  67. self._global_config = None
  68. else:
  69. self._global_config = SingleLayerConfig(activation, weight)
  70. self._layer2config = {}
  71. self._prefix2config = {}
  72. self._type2config = {}
  73. self._model = None
  74. self._qat_layer_mapping = copy.deepcopy(DEFAULT_QAT_LAYER_MAPPINGS)
  75. self._customized_qat_layer_mapping = {}
  76. self._customized_leaves = []
  77. def add_layer_config(
  78. self,
  79. layer: Union[Layer, list],
  80. activation: QuanterFactory = None,
  81. weight: QuanterFactory = None,
  82. ):
  83. r"""
  84. Set the quantization config by layer. It has the highest priority among
  85. all the setting methods.
  86. Args:
  87. layer(Union[Layer, list]): One or a list of layers.
  88. activation(QuanterFactory): Quanter used for activations.
  89. weight(QuanterFactory): Quanter used for weights.
  90. Examples:
  91. .. code-block:: python
  92. >>> import paddle
  93. >>> from paddle.nn import Linear
  94. >>> from paddle.quantization import QuantConfig
  95. >>> from paddle.quantization.quanters import FakeQuanterWithAbsMaxObserver
  96. >>> class Model(paddle.nn.Layer):
  97. ... def __init__(self):
  98. ... super().__init__()
  99. ... self.fc = Linear(576, 120)
  100. >>> model = Model()
  101. >>> quanter = FakeQuanterWithAbsMaxObserver(moving_rate=0.9)
  102. >>> q_config = QuantConfig(activation=None, weight=None)
  103. >>> q_config.add_layer_config([model.fc], activation=quanter, weight=quanter)
  104. >>> # doctest: +SKIP('random memory address')
  105. >>> print(q_config)
  106. Global config:
  107. None
  108. Layer prefix config:
  109. {'linear_0': <paddle.quantization.config.SingleLayerConfig object at 0x7fe41a680ee0>}
  110. """
  111. if isinstance(layer, list):
  112. for _element in layer:
  113. self.add_layer_config(
  114. _element, activation=activation, weight=weight
  115. )
  116. else:
  117. self.add_name_config(
  118. layer.full_name(), activation=activation, weight=weight
  119. )
  120. def add_name_config(
  121. self,
  122. layer_name: Union[str, list],
  123. activation: QuanterFactory = None,
  124. weight: QuanterFactory = None,
  125. ):
  126. r"""
  127. Set the quantization config by full name of layer. Its priority is
  128. lower than `add_layer_config`.
  129. Args:
  130. layer_name(Union[str, list]): One or a list of layers' full name.
  131. activation(QuanterFactory): Quanter used for activations.
  132. weight(QuanterFactory): Quanter used for weights.
  133. Examples:
  134. .. code-block:: python
  135. >>> import paddle
  136. >>> from paddle.nn import Linear
  137. >>> from paddle.quantization import QuantConfig
  138. >>> from paddle.quantization.quanters import FakeQuanterWithAbsMaxObserver
  139. >>> class Model(paddle.nn.Layer):
  140. ... def __init__(self):
  141. ... super().__init__()
  142. ... self.fc = Linear(576, 120)
  143. >>> model = Model()
  144. >>> quanter = FakeQuanterWithAbsMaxObserver(moving_rate=0.9)
  145. >>> q_config = QuantConfig(activation=None, weight=None)
  146. >>> q_config.add_name_config([model.fc.full_name()], activation=quanter, weight=quanter)
  147. >>> # doctest: +SKIP('random memory address')
  148. >>> print(q_config)
  149. Global config:
  150. None
  151. Layer prefix config:
  152. {'linear_0': <paddle.quantization.config.SingleLayerConfig object at 0x7fe41a680fd0>}
  153. """
  154. if isinstance(layer_name, str):
  155. config = SingleLayerConfig(activation, weight)
  156. self._prefix2config[layer_name] = config
  157. if isinstance(layer_name, list):
  158. for _element in layer_name:
  159. self.add_name_config(
  160. _element, activation=activation, weight=weight
  161. )
  162. def add_type_config(
  163. self,
  164. layer_type: Union[type, list],
  165. activation: QuanterFactory = None,
  166. weight: QuanterFactory = None,
  167. ):
  168. r"""
  169. Set the quantization config by the type of layer. The `layer_type` should be
  170. subclass of `paddle.nn.Layer`. Its priority is lower than `add_layer_config`
  171. and `add_name_config`.
  172. Args:
  173. layer_type(Union[type, list]): One or a list of layers' type. It should be subclass of
  174. `paddle.nn.Layer`. Python build-in function `type()` can be used to get the type of a layer.
  175. activation(QuanterFactory): Quanter used for activations.
  176. weight(QuanterFactory): Quanter used for weights.
  177. Examples:
  178. .. code-block:: python
  179. >>> import paddle
  180. >>> from paddle.nn import Linear
  181. >>> from paddle.quantization import QuantConfig
  182. >>> from paddle.quantization.quanters import FakeQuanterWithAbsMaxObserver
  183. >>> class Model(paddle.nn.Layer):
  184. ... def __init__(self):
  185. ... super().__init__()
  186. ... self.fc = Linear(576, 120)
  187. >>> model = Model()
  188. >>> quanter = FakeQuanterWithAbsMaxObserver(moving_rate=0.9)
  189. >>> q_config = QuantConfig(activation=None, weight=None)
  190. >>> q_config.add_type_config([Linear], activation=quanter, weight=quanter)
  191. >>> # doctest: +SKIP('random memory address')
  192. >>> print(q_config)
  193. Global config:
  194. None
  195. Layer type config:
  196. {<class 'paddle.nn.layer.common.Linear'>: <paddle.quantization.config.SingleLayerConfig object at 0x7fe41a680a60>}
  197. """
  198. if isinstance(layer_type, type) and issubclass(
  199. layer_type, paddle.nn.Layer
  200. ):
  201. config = SingleLayerConfig(activation, weight)
  202. self._type2config[layer_type] = config
  203. if isinstance(layer_type, list):
  204. for _element in layer_type:
  205. self.add_type_config(
  206. _element, activation=activation, weight=weight
  207. )
  208. def add_qat_layer_mapping(self, source: type, target: type):
  209. r"""
  210. Add rules converting layers to simulated quantization layers
  211. before quantization-aware training. It will convert layers
  212. with type `source` to layers with type `target`. `source` and
  213. `target` should be subclass of `paddle.nn.Layer`. And a default
  214. mapping is provided by property `default_qat_layer_mapping`.
  215. Args:
  216. source(type): The type of layers that will be converted.
  217. target(type): The type of layers that will be converted to.
  218. Examples:
  219. .. code-block:: python
  220. >>> from paddle.nn import Conv2D
  221. >>> from paddle.quantization import QuantConfig
  222. >>> from paddle.quantization.quanters import FakeQuanterWithAbsMaxObserver
  223. >>> quanter = FakeQuanterWithAbsMaxObserver(moving_rate=0.9)
  224. >>> q_config = QuantConfig(activation=None, weight=None)
  225. >>> class CustomizedQuantedConv2D:
  226. ... def forward(self, x):
  227. ... pass
  228. ... # add some code for quantization simulation
  229. >>> q_config.add_qat_layer_mapping(Conv2D, CustomizedQuantedConv2D)
  230. """
  231. assert isinstance(source, type) and issubclass(
  232. source, paddle.nn.Layer
  233. ), "The source layer to be placed should be a subclass of paddle.nn.Layer"
  234. assert isinstance(target, type) and issubclass(
  235. source, paddle.nn.Layer
  236. ), "The target layer should be a subclass of paddle.nn.qat.Layer"
  237. self._qat_layer_mapping[source] = target
  238. self._customized_qat_layer_mapping[source] = target
  239. def add_customized_leaf(self, layer_type: type):
  240. r"""
  241. Declare the customized layer as leaf of model for quantization.
  242. The leaf layer is quantized as one layer. The sublayers of
  243. leaf layer will not be quantized.
  244. Args:
  245. layer_type(type): The type of layer to be declared as leaf.
  246. Examples:
  247. .. code-block:: python
  248. >>> from paddle.nn import Sequential
  249. >>> from paddle.quantization import QuantConfig
  250. >>> from paddle.quantization.quanters import FakeQuanterWithAbsMaxObserver
  251. >>> q_config = QuantConfig(activation=None, weight=None)
  252. >>> q_config.add_customized_leaf(Sequential)
  253. """
  254. self._customized_leaves.append(layer_type)
  255. @property
  256. def customized_leaves(self):
  257. r"""
  258. Get all the customized leaves.
  259. """
  260. return self._customized_leaves
  261. def _need_observe(self, layer: Layer):
  262. r"""
  263. Whether the layer should be observed by observer.
  264. """
  265. return self._is_leaf(layer) and self._has_observer_config(layer)
  266. def _get_qat_layer(self, layer: Layer):
  267. q_config = self._get_config_by_layer(layer)
  268. target_type = self._customized_qat_layer_mapping.get(
  269. type(layer), self.qat_layer_mappings.get(type(layer))
  270. )
  271. return target_type(layer, q_config)
  272. def _has_observer_config(self, layer: Layer):
  273. r"""
  274. Whether the layer has been configured for activation quantization.
  275. """
  276. _config = self._get_config_by_layer(layer)
  277. return _config is not None and _config.activation is not None
  278. def _is_leaf(self, layer: Layer):
  279. return (
  280. self._is_default_leaf(layer)
  281. or self._is_real_leaf(layer)
  282. or self._is_customized_leaf(layer)
  283. )
  284. def _is_default_leaf(self, layer: Layer):
  285. return type(layer) in DEFAULT_LEAVES
  286. def _is_real_leaf(self, layer: Layer):
  287. r"""
  288. The leaf is real leaf when it has no sublayers.
  289. """
  290. return layer._sub_layers is None or len(layer._sub_layers) == 0
  291. def _is_customized_leaf(self, layer: Layer):
  292. return type(layer) in self.customized_leaves
  293. def _get_observer(self, layer: Layer):
  294. r"""
  295. Create an instance of observer or quanter according to the
  296. given layer's quantization config.
  297. """
  298. _config = self._get_config_by_layer(layer)
  299. _observer = None if _config is None else _config.activation
  300. return None if _observer is None else _observer._instance(layer)
  301. def _get_observe_wrapper(self, layer: Layer):
  302. _observer = self._get_observer(layer)
  303. return ObserveWrapper(_observer, layer)
  304. @property
  305. def qat_layer_mappings(self):
  306. return self._qat_layer_mapping
  307. @property
  308. def default_qat_layer_mapping(self):
  309. return DEFAULT_QAT_LAYER_MAPPINGS
  310. @property
  311. def global_config(self) -> SingleLayerConfig:
  312. return self._global_config
  313. def _get_config_by_layer(self, layer) -> SingleLayerConfig:
  314. return self._layer2config.get(layer, None)
  315. def _is_quantifiable(self, layer: Layer):
  316. r"""
  317. The layer is quantifiable when it configured by activation quanter/observer
  318. or weight quanter/observer.
  319. """
  320. return layer in self._layer2config
  321. def _specify(self, model: Layer):
  322. r"""
  323. Specify the quantization config of each sublayer in model.
  324. For each layer in sublayers of mode,
  325. 1. Set the config by global config
  326. 2. Overwrite the config with parents' config
  327. 3. Overwrite the config with config set by layer's type
  328. 4. Overwrite the config with config set by layer's full name
  329. 5. Overwrite the config with config set by layer
  330. Args:
  331. model(Layer): The model to be specified by the config.
  332. Examples:
  333. .. code-block:: python
  334. >>> import paddle
  335. >>> from paddle.nn import Linear, Sequential
  336. >>> from paddle.quantization import QuantConfig
  337. >>> from paddle.quantization.quanters import FakeQuanterWithAbsMaxObserver
  338. >>> class Model(paddle.nn.Layer):
  339. ... def __init__(self):
  340. ... super().__init__()
  341. ... self.fc = Sequential(Linear(576, 120),Linear(576, 120))
  342. >>> model = Model()
  343. >>> quanter = FakeQuanterWithAbsMaxObserver(moving_rate=0.9)
  344. >>> q_config = QuantConfig(activation=None, weight=None)
  345. >>> q_config.add_layer_config([model.fc], activation=quanter, weight=quanter)
  346. >>> q_config._specify(model)
  347. """
  348. self._model = model
  349. self._specify_helper(self._model)
  350. def _specify_helper(self, model: Layer):
  351. for child in model.children():
  352. layer_prefix = child.full_name()
  353. config = self._layer2config.get(model, self.global_config)
  354. config = self._type2config.get(type(child), config)
  355. config = self._prefix2config.get(layer_prefix, config)
  356. if config is not None:
  357. self._layer2config[child] = config
  358. self._specify_helper(child)
  359. return self
  360. def details(self) -> str:
  361. r"""
  362. Get the formatted details of current config.
  363. """
  364. if self._model is None:
  365. return self.__str__()
  366. return self._details_helper(self._model)
  367. def _details_helper(self, layer: Layer):
  368. sublayer_lines = []
  369. for name, sublayer in layer.named_children():
  370. sublayer_str = self._details_helper(sublayer)
  371. sublayer_str = self._addindent(sublayer_str, 2)
  372. if sublayer in self._layer2config:
  373. sublayer_lines.append(
  374. '('
  375. + name
  376. + '): '
  377. + sublayer_str
  378. + ', '
  379. + str(self._layer2config[sublayer])
  380. )
  381. final_str = layer.__class__.__name__ + '('
  382. if sublayer_lines:
  383. final_str += '\n ' + '\n '.join(sublayer_lines) + '\n'
  384. final_str += ')'
  385. return final_str
  386. def _addindent(self, string, indent):
  387. s1 = string.split('\n')
  388. if len(s1) == 1:
  389. return string
  390. s2 = []
  391. for idx, line in enumerate(s1):
  392. if idx > 0:
  393. s2.append(str((indent * ' ') + line))
  394. return s1[0] + '\n' + '\n'.join(s2)
  395. def __str__(self):
  396. result = ""
  397. result += f"Global config:\n{self._global_config}\n"
  398. if len(self._type2config) > 0:
  399. result += f"Layer type config:\n{self._type2config}\n"
  400. if len(self._prefix2config) > 0:
  401. result += f"Layer prefix config: \n{self._prefix2config}\n"
  402. return result