Overview.md 12.1 KB
Newer Older
Chi Song's avatar
Chi Song committed
1
2
3
4
5
6
7
# Compressor

我们很高兴的宣布,基于 NNI 的模型压缩工具发布了 Alpha 版本。该版本仍处于试验阶段,根据用户反馈会进行改进。 诚挚邀请您使用、反馈,或更多贡献。

NNI 提供了易于使用的工具包来帮助用户设计并使用压缩算法。 其使用了统一的接口来支持 TensorFlow 和 PyTorch。 只需要添加几行代码即可压缩模型。 NNI 中也内置了一些流程的模型压缩算法。 用户还可以通过 NNI 强大的自动调参功能来找到最好的压缩后的模型,详见[自动模型压缩](./AutoCompression.md)。 另外,用户还能使用 NNI 的接口,轻松定制新的压缩算法,详见[教程](#customize-new-compression-algorithms)

## 支持的算法
Chi Song's avatar
Chi Song committed
8

Chi Song's avatar
Chi Song committed
9
10
11
12
13
14
15
16
17
18
19
20
21
22
NNI 提供了几种压缩算法,包括剪枝和量化算法:

**剪枝**

| 名称                                              | 算法简介                                                                                                                                   |
| ----------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------- |
| [Level Pruner](./Pruner.md#level-pruner)        | 根据权重的绝对值,来按比例修剪权重。                                                                                                                     |
| [AGP Pruner](./Pruner.md#agp-pruner)            | 自动的逐步剪枝(是否剪枝的判断:基于对模型剪枝的效果)[参考论文](https://arxiv.org/abs/1710.01878)                                                                    |
| [L1Filter Pruner](./Pruner.md#l1filter-pruner)  | 剪除卷积层中最不重要的过滤器 (PRUNING FILTERS FOR EFFICIENT CONVNETS)[参考论文](https://arxiv.org/abs/1608.08710)                                        |
| [Slim Pruner](./Pruner.md#slim-pruner)          | 通过修剪 BN 层中的缩放因子来修剪卷积层中的通道 (Learning Efficient Convolutional Networks through Network Slimming)[参考论文](https://arxiv.org/abs/1708.06519) |
| [Lottery Ticket Pruner](./Pruner.md#agp-pruner) | "The Lottery Ticket Hypothesis: Finding Sparse, Trainable Neural Networks" 提出的剪枝过程。 它会反复修剪模型。 [参考论文](https://arxiv.org/abs/1803.03635) |
| [FPGM Pruner](./Pruner.md#fpgm-pruner)          | Filter Pruning via Geometric Median for Deep Convolutional Neural Networks Acceleration [参考论文](https://arxiv.org/pdf/1811.00250.pdf)   |

**量化**
Chi Song's avatar
Chi Song committed
23
24
25
26
27
28
29
30
31
32
33

| 名称                                                  | 算法简介                                                                                                                                                                       |
| --------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| [Naive Quantizer](./Quantizer.md#naive-quantizer)   | 默认将权重量化为 8 位                                                                                                                                                               |
| [QAT Quantizer](./Quantizer.md#qat-quantizer)       | 为 Efficient Integer-Arithmetic-Only Inference 量化并训练神经网络。 [参考论文](http://openaccess.thecvf.com/content_cvpr_2018/papers/Jacob_Quantization_and_Training_CVPR_2018_paper.pdf) |
| [DoReFa Quantizer](./Quantizer.md#dorefa-quantizer) | DoReFa-Net: 通过低位宽的梯度算法来训练低位宽的卷积神经网络。 [参考论文](https://arxiv.org/abs/1606.06160)                                                                                              |

## 内置压缩算法的用法

通过简单的示例来展示如何修改 Trial 代码来使用压缩算法。 比如,需要通过 Level Pruner 来将权重剪枝 80%,首先在代码中训练模型前,添加以下内容([完整代码](https://github.com/microsoft/nni/tree/master/examples/model_compress))。

Chi Song's avatar
Chi Song committed
34
PyTorch 代码
Chi Song's avatar
Chi Song committed
35

Chi Song's avatar
Chi Song committed
36
```python
Chi Song's avatar
Chi Song committed
37
from nni.compression.torch import LevelPruner
Chi Song's avatar
Chi Song committed
38
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
Chi Song's avatar
Chi Song committed
39
pruner = LevelPruner(model, config_list)
Chi Song's avatar
Chi Song committed
40
pruner.compress()
Chi Song's avatar
Chi Song committed
41
42
```

Chi Song's avatar
Chi Song committed
43
TensorFlow 代码
Chi Song's avatar
Chi Song committed
44

Chi Song's avatar
Chi Song committed
45
```python
Chi Song's avatar
Chi Song committed
46
from nni.compression.tensorflow import LevelPruner
Chi Song's avatar
Chi Song committed
47
config_list = [{ 'sparsity': 0.8, 'op_types': ['default'] }]
Chi Song's avatar
Chi Song committed
48
pruner = LevelPruner(tf.get_default_graph(), config_list)
Chi Song's avatar
Chi Song committed
49
pruner.compress()
Chi Song's avatar
Chi Song committed
50
51
```

Chi Song's avatar
Chi Song committed
52
53

可使用 `nni.compression` 中的其它压缩算法。 此算法分别在 `nni.compression.torch``nni.compression.tensorflow` 中实现,支持 PyTorch 和 TensorFlow。 参考 [Pruner](./Pruner.md)[Quantizer](./Quantizer.md) 进一步了解支持的算法。 此外,如果要使用知识蒸馏算法,可参考 [KD 示例](../TrialExample/KDExample.md)
Chi Song's avatar
Chi Song committed
54

Chi Song's avatar
Chi Song committed
55
函数调用 `pruner.compress()` 来修改用户定义的模型(在 Tensorflow 中,通过 `tf.get_default_graph()` 来获得模型,而 PyTorch 中 model 是定义的模型类),并修改模型来插入 mask。 然后运行模型时,这些 mask 即会生效。 mask 可在运行时通过算法来调整。
Chi Song's avatar
Chi Song committed
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71

实例化压缩算法时,会传入 `config_list`。 配置说明如下。

### 压缩算法中的用户配置

压缩模型时,用户可能希望指定稀疏率,为不同类型的操作指定不同的比例,排除某些类型的操作,或仅压缩某类操作。 配置规范可用于表达此类需求。 可将其视为一个 Python 的 `list` 对象,其中每个元素都是一个 `dict` 对象。 在每个 `dict` 中,有一些 NNI 压缩算法支持的键值:

* __op_types__:指定要压缩的操作类型。 'default' 表示使用算法的默认设置。
* __op_names__:指定需要压缩的操作的名称。 如果没有设置此字段,操作符不会通过名称筛选。
* __exclude__:默认为 False。 如果此字段为 True,表示要通过类型和名称,将一些操作从压缩中排除。

`dict` 还有一些其它键值,由特定的压缩算法所使用。 例如:

`list` 中的 `dict` 会依次被应用,也就是说,如果一个操作出现在两个配置里,后面的 `dict` 会覆盖前面的配置。

配置的简单示例如下:
Chi Song's avatar
Chi Song committed
72

Chi Song's avatar
Chi Song committed
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
```python
[
    {
        'sparsity': 0.8,
        'op_types': ['default']
    },
    {
        'sparsity': 0.6,
        'op_names': ['op_name1', 'op_name2']
    },
    {
        'exclude': True,
        'op_names': ['op_name3']
    }
]
```
Chi Song's avatar
Chi Song committed
89

Chi Song's avatar
Chi Song committed
90
91
92
93
94
95
96
其表示压缩操作的默认稀疏度为 0.8,但`op_name1``op_name2` 会使用 0.6,且不压缩 `op_name3`

### 其它 API

一些压缩算法使用 Epoch 来控制压缩进度(如[AGP](./Pruner.md#agp-pruner)),一些算法需要在每个批处理步骤后执行一些逻辑。 因此提供了另外两个 API。 一个是 `update_epoch`,可参考下例使用:

TensorFlow 代码
Chi Song's avatar
Chi Song committed
97

Chi Song's avatar
Chi Song committed
98
99
100
```python
pruner.update_epoch(epoch, sess)
```
Chi Song's avatar
Chi Song committed
101

Chi Song's avatar
Chi Song committed
102
PyTorch 代码
Chi Song's avatar
Chi Song committed
103

Chi Song's avatar
Chi Song committed
104
105
106
107
108
109
```python
pruner.update_epoch(epoch)
```

另一个是 `step`,可在每个批处理后调用 `pruner.step()`。 注意,并不是所有的算法都需要这两个 API,对于不需要它们的算法,调用它们不会有影响。

Chi Song's avatar
Chi Song committed
110
111
112
113
114
115
116
117
118
119
120
使用下列 API 可轻松将压缩后的模型导出,稀疏模型的 `state_dict` 会保存在 `model.pth` 文件中,可通过 `torch.load('model.pth')` 加载。

```
pruner.export_model(model_path='model.pth')
```

`mask_dict``onnx` 格式的剪枝模型(需要指定 `input_shape`)可这样导出:

```python
pruner.export_model(model_path='model.pth', mask_path='mask.pth', onnx_path='model.onnx', input_shape=[1, 1, 28, 28])
```
Chi Song's avatar
Chi Song committed
121
122
123
124
125
126
127
128
129
130
131

## 定制新的压缩算法

为了简化压缩算法的编写,NNI 设计了简单且灵活的接口。 对于 Pruner 和 Quantizer 分别有相应的接口。

### 剪枝算法

要实现新的剪枝算法,根据使用的框架,添加继承于 `nni.compression.tensorflow.Pruner``nni.compression.torch.Pruner` 的类。 然后,根据算法逻辑来重写成员函数。

```python
# TensorFlow 中定制 Pruner。
Chi Song's avatar
Chi Song committed
132
# PyTorch 的 Pruner,只需将
Chi Song's avatar
Chi Song committed
133
134
135
# nni.compression.tensorflow.Pruner 替换为
# nni.compression.torch.Pruner
class YourPruner(nni.compression.tensorflow.Pruner):
Chi Song's avatar
Chi Song committed
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
    def __init__(self, model, config_list):
        """
        建议使用 NNI 定义的规范来配置
        """
        super().__init__(model, config_list)

    def calc_mask(self, layer, config):
        """
        Pruner 需要重载此方法来为权重提供掩码
        掩码必须与权重有相同的形状和类型。
        将对权重执行 ``mul()`` 操作。
        此方法会挂载到模型的 ``forward()`` 方法上。

        Parameters
        ----------
        layer: LayerInfo
            为 ``layer`` 的权重计算掩码
        config: dict
            生成权重所需要的掩码
        """
Chi Song's avatar
Chi Song committed
156
157
        return your_mask

Chi Song's avatar
Chi Song committed
158
    #  PyTorch 版本不需要 sess 参数
Chi Song's avatar
Chi Song committed
159
160
161
    def update_epoch(self, epoch_num, sess):
        pass

Chi Song's avatar
Chi Song committed
162
    #  PyTorch 版本不需要 sess 参数
Chi Song's avatar
Chi Song committed
163
    def step(self, sess):
Chi Song's avatar
Chi Song committed
164
165
166
        """
        根据需要可基于 bind_model 方法中的模型或权重进行操作
        """
Chi Song's avatar
Chi Song committed
167
168
169
        pass
```

Chi Song's avatar
Chi Song committed
170
对于最简单的算法,只需要重写 `calc_mask` 函数。 它会接收需要压缩的层以及其压缩配置。 可在此函数中为此权重生成 mask 并返回。 NNI 会应用此 mask。
Chi Song's avatar
Chi Song committed
171

Chi Song's avatar
Chi Song committed
172
一些算法根据训练进度来生成 mask,如 Epoch 数量。 Pruner 可使用 `update_epoch` 来了解训练进度。 应在每个 Epoch 之前调用它。
Chi Song's avatar
Chi Song committed
173

Chi Song's avatar
Chi Song committed
174
一些算法可能需要全局的信息来生成 mask,例如模型的所有权重(用于生成统计信息). 可在 Pruner 类中通过 `self.bound_model` 来访问权重。 如果需要优化器的信息(如在 Pytorch 中),可重载 `__init__` 来接收优化器等参数。 然后 `step` 可以根据算法来处理或更新信息。 可参考[内置算法的源码](https://github.com/microsoft/nni/tree/master/src/sdk/pynni/nni/compressors)作为示例。
Chi Song's avatar
Chi Song committed
175
176
177
178
179
180
181

### 量化算法

定制量化算法的接口与剪枝算法类似。 唯一的不同是使用 `quantize_weight` 替换了 `calc_mask``quantize_weight` 直接返回量化后的权重,而不是 mask。这是因为对于量化算法,量化后的权重不能通过应用 mask 来获得。

```python
# TensorFlow 中定制 Quantizer。
Chi Song's avatar
Chi Song committed
182
# PyTorch 的 Quantizer,只需将
Chi Song's avatar
Chi Song committed
183
184
185
# nni.compression.tensorflow.Quantizer 替换为
# nni.compression.torch.Quantizer
class YourQuantizer(nni.compression.tensorflow.Quantizer):
Chi Song's avatar
Chi Song committed
186
187
188
189
190
    def __init__(self, model, config_list):
        """
        建议使用 NNI 定义的规范来配置
        """
        super().__init__(model, config_list)
Chi Song's avatar
Chi Song committed
191
192

    def quantize_weight(self, weight, config, **kwargs):
Chi Song's avatar
Chi Song committed
193
194
195
196
197
198
199
200
201
202
203
204
205
206
        """
        quantize 需要重载此方法来为权重提供掩码
        此方法挂载于模型的 :meth:`forward`。

        Parameters
        ----------
        weight : Tensor
            要被量化的权重
        config : dict
            权重量化的配置
        """

        # 此处逻辑生成 `new_weight`

Chi Song's avatar
Chi Song committed
207
208
        return new_weight

Chi Song's avatar
Chi Song committed
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
    def quantize_output(self, output, config, **kwargs):
        """
        重载此方法输出量化
        此方法挂载于模型的 `:meth:`forward`。

        Parameters
        ----------
        output : Tensor
            需要被量化的输出
        config : dict
            输出量化的配置
        """

        # 实现生成 `new_output`

        return new_output

    def quantize_input(self, *inputs, config, **kwargs):
        """
        重载此方法量化输入
        此方法挂载于模型的 :meth:`forward`。

        Parameters
        ----------
        inputs : Tensor
            需要量化的输入
        config : dict
            输入量化用的配置
        """

        # 实现生成 `new_input`

        return new_input

    # Pytorch 版本不需要 sess 参数
Chi Song's avatar
Chi Song committed
244
245
246
    def update_epoch(self, epoch_num, sess):
        pass

Chi Song's avatar
Chi Song committed
247
    # Pytorch 版本不需要 sess 参数
Chi Song's avatar
Chi Song committed
248
    def step(self, sess):
Chi Song's avatar
Chi Song committed
249
250
251
        """
       根据需要可基于 bind_model 方法中的模型或权重进行操作
        """
Chi Song's avatar
Chi Song committed
252
253
254
255
256
257
        pass
```

### 使用用户自定义的压缩算法

__[TODO]__ ...