Framework.md 6.39 KB
Newer Older
Chi Song's avatar
Chi Song committed
1
# 模型压缩框架概述
Chi Song's avatar
Chi Song committed
2

Chi Song's avatar
Chi Song committed
3
4
5
```eval_rst
.. contents::
```
Chi Song's avatar
Chi Song committed
6

Chi Song's avatar
Chi Song committed
7
下图展示了模型压缩框架的组件概览。
Chi Song's avatar
Chi Song committed
8

Chi Song's avatar
Chi Song committed
9
![](../../img/compressor_framework.jpg)
Chi Song's avatar
Chi Song committed
10

Chi Song's avatar
Chi Song committed
11
12
13
14
15
NNI 模型压缩框架中主要有三个组件/类:`Compressor`, `Pruner``Quantizer`。 下面会逐个详细介绍:

## Compressor

Compressor 是 Pruner 和 Quantizer 的基类,提供了统一的接口,可用同样的方式使用它们。 例如,使用 Pruner:
Chi Song's avatar
Chi Song committed
16
17

```python
Chi Song's avatar
Chi Song committed
18
19
20
21
from nni.compression.torch import LevelPruner

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

Chi Song's avatar
Chi Song committed
22
23
configure_list = [{
    'sparsity': 0.7,
Chi Song's avatar
Chi Song committed
24
    'op_types': ['Conv2d', 'Linear'],
Chi Song's avatar
Chi Song committed
25
26
27
}]

optimizer = torch.optim.SGD(model.parameters(), lr=0.001, momentum=0.9, weight_decay=1e-4)
Chi Song's avatar
Chi Song committed
28
pruner = LevelPruner(model, configure_list, optimizer)
Chi Song's avatar
Chi Song committed
29
model = pruner.compress()
Chi Song's avatar
Chi Song committed
30
31
32

# 剪枝已准备好,开始调优模型,
# 模型会在训练过程中自动剪枝
Chi Song's avatar
Chi Song committed
33
34
```

Chi Song's avatar
Chi Song committed
35
36
37
使用 Quantizer:
```python
from nni.compression.torch import DoReFaQuantizer
Chi Song's avatar
Chi Song committed
38

Chi Song's avatar
Chi Song committed
39
40
41
42
43
44
45
46
47
48
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()
Chi Song's avatar
Chi Song committed
49
50

```
Chi Song's avatar
Chi Song committed
51
52
53
54
查看[示例代码](https://github.com/microsoft/nni/tree/master/examples/model_compress)了解更多信息。

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

Chi Song's avatar
Chi Song committed
55
56
### 设置包装的属性

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

### 在 forward 时收集数据
Chi Song's avatar
Chi Song committed
60
61

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

```python
Chi Song's avatar
Chi Song committed
64
65
66
67
68
69
70
71
72
73
74
75
76
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)
Chi Song's avatar
Chi Song committed
77
```
Chi Song's avatar
Chi Song committed
78

Chi Song's avatar
Chi Song committed
79
80
81
收集函数会在每次 forward 方法运行时调用。

还可这样来移除收集方法:
Chi Song's avatar
Chi Song committed
82

Chi Song's avatar
Chi Song committed
83
```python
Chi Song's avatar
Chi Song committed
84
85
86
87
88
# 保存 Collector 的标识
collector_id = self.pruner.add_activation_collector(collector)

# 当 Collector 不再需要后,可以通过保存的 Collector 标识来删除
self.pruner.remove_activation_collector(collector_id)
Chi Song's avatar
Chi Song committed
89
90
```

Chi Song's avatar
Chi Song committed
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
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
***

## Pruner

Pruner 接收 `model`, `config_list` 以及 `optimizer` 参数。 通过往 `optimizer.step()` 上增加回调,在训练过程中根据 `config_list` 来对模型剪枝。

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

### 权重掩码

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

### 剪枝模块包装

`剪枝 module 的包装` 包含:

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

使用 `module 包装`的原因:

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

### 剪枝回调

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


***

## Quantizer

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

### 量化 module 包装

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

### 量化回调

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

### 量化相关函数

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

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

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

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

```

***

## 多 GPU 支持

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