Framework.rst 6.98 KB
Newer Older
kvartet's avatar
kvartet committed
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
模型压缩框架概述
=======================================

.. contents::

下图展示了模型压缩框架的组件概览。


.. image:: ../../img/compressor_framework.jpg
   :target: ../../img/compressor_framework.jpg
   :alt: 


NNI 模型压缩框架中主要有三个组件/类: ``Compressor``\ , ``Pruner``  ``Quantizer`` 下面会逐个详细介绍:

Compressor
----------

Compressor  Pruner  Quantizer 的基类,提供了统一的接口,可用同样的方式使用它们。 例如,使用 Pruner

.. code-block:: python

   from nni.algorithms.compression.pytorch.pruning import LevelPruner

   # 读取预训练的模型,或在使用 Pruner 前进行训练。

   configure_list = [{
       'sparsity': 0.7,
       'op_types': ['Conv2d', 'Linear'],
   }]

   pruner = LevelPruner(model, configure_list, optimizer)
   model = pruner.compress()

   # 剪枝已准备好,开始调优模型,
   # 模型会在训练过程中自动剪枝

使用 Quantizer

.. code-block:: python

   from nni.algorithms.compression.pytorch.pruning import DoReFaQuantizer

   configure_list = [{
       'quant_types': ['weight'],
       'quant_bits': {
           'weight': 8,
       },
       'op_types':['Conv2d', 'Linear']
   }]
   optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=1e-4)
   quantizer = DoReFaQuantizer(model, configure_list, optimizer)
   quantizer.compress()

查看 :githublink:`示例代码 <examples/model_compress>` 了解更多信息。

``Compressor`` 类提供了一些工具函数:

设置包装的属性
^^^^^^^^^^^^^^^^^^^^^

有时,``calc_mask`` 需要保存一些状态数据,可以像 PyTorch  module 一样,使用 ``set_wrappers_attribute`` API 来注册属性。 这些缓存会注册到 ``module 包装`` 中。 用户可以通过 ``module 包装``来直接访问这些缓存。
在上述示例中,使用了 ``set_wrappers_attribute`` 类设置缓冲 ``if_calculated``,它用来标识某层的掩码是否已经计算过了。

 forward 时收集数据
^^^^^^^^^^^^^^^^^^^^^^^^^^^

有时,需要在 forward 方法中收集数据,例如,需要激活的平均值。 可通过向 module 中添加定制的 Collector 来做到。

.. code-block:: python

   class MyMasker(WeightMasker):
       def __init__(self, model, pruner):
           super().__init__(model, pruner)
           # 为所有包装类设置 `collected_activation` 属性
           # 保存所有层的激活值
           self.pruner.set_wrappers_attribute("collected_activation", [])
           self.activation = torch.nn.functional.relu

           def collector(wrapper, input_, output):
               # 通过每个包装的 collected_activation 属性,
               # 来评估收到的激活值
               wrapper.collected_activation.append(self.activation(output.detach().cpu()))

           self.pruner.hook_id = self.pruner.add_activation_collector(collector)

收集函数会在每次 forward 方法运行时调用。

还可这样来移除收集方法:

.. code-block:: python

   # 保存 Collector 的标识
   collector_id = self.pruner.add_activation_collector(collector)

   #  Collector 不再需要后,
   # 可以通过保存的 Collector 标识来删除
   self.pruner.remove_activation_collector(collector_id)

----

Pruner
------

kvartet's avatar
kvartet committed
105
Pruner 接收 ``模型````配置``  ``优化器`` 作为参数。一些剪枝器,比如 ``TaylorFOWeightFilter Pruner`` 通过在 ``optimizer.step()`` 上添加一个钩子,在训练循环期间根据 ``config_list`` 修剪模型。
kvartet's avatar
kvartet committed
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208

Pruner 类是 Compressor 的子类,因此它包含了 Compressor 的所有功能,并添加了剪枝所需要的组件,包括:

权重掩码
^^^^^^^^^^^^^

``权重掩码`` 是剪枝算法的实现,可将由 ``module 包装`` 所包装起来的一层根据稀疏度进行修建。

剪枝模块包装
^^^^^^^^^^^^^^^^^^^^^^

``剪枝 module 的包装`` 包含:


#. 原始的 module
#. ``calc_mask`` 使用的一些缓存
#. 新的 forward 方法,用于在运行原始的 forward 方法前应用掩码。

使用 ``module 包装`` 的原因:


#. 计算掩码所需要的 ``calc_mask`` 方法需要一些缓存,这些缓存需要注册在 ``module 包装`` 里,这样就不需要修改原始的 module
#. 新的 ``forward`` 方法用来在原始 ``forward`` 调用前,将掩码应用到权重上。

剪枝回调
^^^^^^^^^^^^

 Pruner 构造时会添加剪枝的回调,用来在 ``optimizer.step()`` 被调用时,调用 Pruner  calc_mask

----

Quantizer
---------

Quantizer 也是 ``Compressor`` 的子类,用来通过减少权重或激活值的位宽来压缩模型,这样可以减少模型推理时的计算时间。 它包含:

量化 module 包装
^^^^^^^^^^^^^^^^^^^^^^^^^^^

模型中每个要量化的模块和层,都需要量化包装,它通过提供 ``forward`` 方法来量化原始模型的权重、输入和输出。

量化回调
^^^^^^^^^^^^^^^^^

量化回调会在调用 ``optimizer.step()`` 时设置。

量化相关函数
^^^^^^^^^^^^^^^^^^^^

``Quantizer`` 类为子类提供一下方法来实现量化算法:

.. code-block:: python

   class Quantizer(Compressor):
       """
       PyTorch 的量化基类
       """
       def quantize_weight(self, weight, wrapper, **kwargs):
           """
           重载此方法实现权重的量化。
           此方法挂载于模型的 :meth:`forward`。
           参数量
           ----------
           weight : Tensor
               要被量化的权重
           wrapper : QuantizerModuleWrapper
               原始 module 的包装
           """
           raise NotImplementedError('Quantizer must overload quantize_weight()')

       def quantize_output(self, output, wrapper, **kwargs):
           """
           重载此方法实现输出的量化。
           此方法挂载于模型的 :meth:`forward`。
           参数量
           ----------
           output : Tensor
               需要被量化的输出
           wrapper : QuantizerModuleWrapper
               原始 module 的包装
           """
           raise NotImplementedError('Quantizer must overload quantize_output()')

       def quantize_input(self, *inputs, wrapper, **kwargs):
           """
           重载此方法量化输入
           此方法挂载于模型的 :meth:`forward`。
           参数量
           ----------
           inputs : Tensor
               需要被量化的张量
           wrapper : QuantizerModuleWrapper
               原始 module 的包装
           """
           raise NotImplementedError('Quantizer must overload quantize_input()')

----

 GPU 支持
-----------------

在多 GPU 训练中,缓存和参数会在每次 ``forward`` 方法被调用时,复制到多个 GPU 上。 如果缓存和参数要在 ``forward`` 更新,就需要通过 ``原地`` 更新来提高效率。
因为 ``calc_mask`` 会在 ``optimizer.step`` 方法中的调用,会在 ``forward`` 方法后才被调用,且只会发生在单 GPU 上,因此它天然的就支持多 GPU 的情况。