Unverified Commit 91d9a797 authored by Yuge Zhang's avatar Yuge Zhang Committed by GitHub
Browse files

Adopt symbolic links in Chinese documentation (#4345)


Co-authored-by: default avatarjiahangxu <jiahangxu@microsoft.com>
parent 0e0ee86d
../../en_US/Compression/CustomizeCompressor.rst
\ No newline at end of file
滤波器剪枝的依赖感知模式
========================================
目前,我们有几种针对卷积层的滤波剪枝算法,分别为:FPGM Pruner, L1Filter Pruner, L2Filter Pruner, Activation APoZ Rank Filter Pruner, Activation Mean Rank Filter Pruner, Taylor FO On Weight Pruner。 在这些滤波器剪枝算法中,剪枝器将分别对每个卷积层进行剪枝。 在裁剪卷积层时,算法会根据一些特定的规则(如l1-norm)量化每个滤波器的重要性,并裁剪不太重要的滤波器。
就像 `dependency analysis utils <./CompressionUtils.md>`__ 显示,如果将两个卷积层(conv1,conv2)的输出通道相加,这两个卷积层之间将具有通道依赖关系(更多详细信息参见 `Compression Utils <./CompressionUtils.rst>`__\ )。 以下图为例。
.. image:: ../../img/mask_conflict.jpg
:target: ../../img/mask_conflict.jpg
:alt:
如果我们为 conv1 修剪前50%的输出通道(滤波器),并为 conv2 修剪后50%的输出通道, 虽然这两个层都删减了50%的滤波器,但加速模块仍然需要添加零来对齐输出通道。 在这种情况下,我们无法从模型剪枝中获得速度效益。
为了更好地发挥模型剪枝的速度优势,我们为滤波器剪枝添加了一个依赖感知模式。 在依赖感知模式下,剪枝器不仅根据每个滤波器的l1范数对模型进行剪枝,而且还会基于整个网络结构的拓扑结构对模型进行剪枝。
在依赖关系感知模式下(``dependency_aware`` 设置为``True``),剪枝器将尝试为彼此具有通道依赖关系的层修剪相同的输出通道,如下图所示。
.. image:: ../../img/dependency-aware.jpg
:target: ../../img/dependency-aware.jpg
:alt:
以 L1Filter Pruner 的依赖感知模式为例。 具体来说,剪枝器将为每个通道计算依赖集中所有层的L1范数和。 显然,最终可以从这个依赖集中删除的通道数量是由这个依赖集中,层的最小稀疏性决定的(记作 ``min_sparsity``)。 根据每个通道的 L1 范数和,剪枝器将对所有层修剪相同的 ``min_sparsity`` 通道。 接下来,剪枝器将根据每个卷积层自己的 L1 范数为它们额外修剪 ``sparsity`` - ``min_sparsity`` 通道。 例如,假设 ``conv1``、``conv2`` 的输出通道相加,分别配置稀疏度为0.3和0.2。 在这种情况下,``依赖感知的剪枝器`` 将会
.. code-block:: bash
- 首先,根据 `conv1` 和 `conv2` 的 L1 范数和,修剪 `conv1` 和 `conv2` 相同的20%通道。
- 其次,剪枝器将根据 `conv1` 每个通道的 L1 范数为 `conv1` 额外修剪10%的通道。
此外,对于具有多个滤波器组的卷积层,``依赖感知剪枝器`` 也将尝试为每个滤波器组修剪相同数量的通道。 总体而言,该剪枝器将根据每个滤波器的 L1 范数对模型进行剪枝,并尝试满足拓扑约束(通道依赖等),以提高加速过程后的最终速度增益。
在依赖关系感知模式下,剪枝器将为模型修剪提供更好的速度增益。
用法
-----
在本节中,我们将说明如何使用滤波剪枝器的依赖感知模式。 目前,我们有几种 One-Shot 的 Pruner,比如 FPGM Pruner, L1Filter Pruner, L2Filter Pruner, Activation APoZ Rank Filter Pruner, Activation Mean Rank Filter Pruner, Taylor FO On Weight Pruner 支持依赖感知模式。
以为 ``L1FilterPruner`` 启动依赖感知模式为例:
.. code-block:: python
from nni.algorithms.compression.pytorch.pruning import L1FilterPruner
config_list = [{ 'sparsity': 0.8, 'op_types': ['Conv2d'] }]
# dummy_input 对于依赖感知模式是必需的
dummy_input = torch.ones(1, 3, 224, 224).cuda()
pruner = L1FilterPruner(model, config_list, dependency_aware=True, dummy_input=dummy_input)
# 对于 L2FilterPruner
# pruner = L2FilterPruner(model, config_list, dependency_aware=True, dummy_input=dummy_input)
# 对于 FPGMPruner
# pruner = FPGMPruner(model, config_list, dependency_aware=True, dummy_input=dummy_input)
# 对于 ActivationAPoZRankFilterPruner
# pruner = ActivationAPoZRankFilterPruner(model, config_list, statistics_batch_num=1, , dependency_aware=True, dummy_input=dummy_input)
# 对于 ActivationMeanRankFilterPruner
# pruner = ActivationMeanRankFilterPruner(model, config_list, statistics_batch_num=1, dependency_aware=True, dummy_input=dummy_input)
# 对于 TaylorFOWeightFilterPruner
# pruner = TaylorFOWeightFilterPruner(model, config_list, statistics_batch_num=1, dependency_aware=True, dummy_input=dummy_input)
pruner.compress()
评估
----------
为了比较有无依赖感知模式的剪枝器的性能,我们使用 L1FilterPruner 分别在依赖感知模式打开和关闭下修剪 Mobilenet_v2。 为了简化实验,我们将统一剪枝,这意味着我们为模型中的所有卷积层分配相同的稀疏度。
我们在cifar10数据集上训练了Mobilenet_v2模型,并根据此预先训练的检查点对模型进行裁剪。 下图显示了不同剪枝器剪枝后模型的准确率和 FLOPs。
.. image:: ../../img/mobilev2_l1_cifar.jpg
:target: ../../img/mobilev2_l1_cifar.jpg
:alt:
在图中,``依赖感知`` 表示 L1FilterPruner 启用了依赖感知模式。 ``L1 Filter`` 是没有依赖感知模式的普通 ``L1FilterPruner``,而 ``No-Dependency`` 意味着剪枝器只修剪了与其他层没有通道依赖的层。 如图所示,当启用依赖感知模式时,剪枝器可以在相同的浮点运算率下带来更高的精度。
../../en_US/Compression/DependencyAware.rst
\ No newline at end of file
模型压缩框架概述
=======================================
.. 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
------
Pruner 接收 ``模型````配置`` ``优化器`` 作为参数。一些剪枝器,比如 ``TaylorFOWeightFilter Pruner`` 通过在 ``optimizer.step()`` 上添加一个钩子,在训练循环期间根据 ``config_list`` 修剪模型。
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 的情况。
../../en_US/Compression/Framework.rst
\ No newline at end of file
加速掩码的模型
=====================
*此功能处于测试阶段。*
介绍
------------
剪枝算法通常都用权重掩码来模拟实际的剪枝。 掩码可以用来
检查某个剪枝(或稀疏)算法的模型性能,但还没有真正加速。
模型加速才是模型剪枝的最终目标。
因此提供了此工具,来帮助基于用户提供的掩码(掩码来自于剪枝算法),
将已有模型转换成小模型。
有两种剪枝算法。 一种是细粒度的剪枝,不改变权重形状,和输入输出的张量。 稀疏内核会被用来加速细粒度剪枝的层。 另一类是粗粒度的剪枝(例如,通道),通常,权重形状,输入输出张量会有所改变。 要加速这类剪枝算法,不需要使用系数内核,只需要用更小的层来替换。 由于开源社区中对稀疏内核的支持还比较有限,当前仅支持粗粒度剪枝,会在将来再支持细粒度的剪枝算法。
设计和实现
-------------------------
为了加速模型,被剪枝的层应该被替换掉,要么为粗粒度掩码使用较小的层,要么用稀疏内核来替换细粒度的掩码。 粗粒度掩码通常会改变权重的形状,或输入输出张量,因此,应该通过形状推断,来检查是否其它未被剪枝的层由于形状变化而需要改变形状。 因此,在设计中,主要有两个步骤:第一,做形状推理,找出所有应该替换的模块;第二,替换模块。 第一步需要模型的拓扑(即连接),我们使用了 ``jit.trace`` 来获取 PyTorch 的模型图。
对于每个模块,要准备四个函数,三个用于形状推理,一个用于模块替换。 三个形状推理函数是:给定权重形状推断输入/输出形状,给定输入形状推断权重/输出形状,给定输出形状推断权重/输入形状。 模块替换功能返回一个较小的新创建的模块。
用法
-----
.. code-block:: python
from nni.compression.pytorch import ModelSpeedup
# model: 要加速的模型
# dummy_input: 模型的示例输入,传给 `jit.trace`
# masks_file: 剪枝算法创建的掩码文件
m_speedup = ModelSpeedup(model, dummy_input.to(device), masks_file)
m_speedup.speedup_model()
dummy_input = dummy_input.to(device)
start = time.time()
out = model(dummy_input)
print('elapsed time: ', time.time() - start)
完整示例参考 :githublink:`这里 <examples/model_compress/pruning/model_speedup.py>`。
注意:当前支持 PyTorch 1.3.1 或更高版本。
局限性
-----------
由于每个模块需要 4 个函数用于形状推理和模块替换,因此工作量较大,当前仅实现了示例所需的函数。 如果要加速自己的模型,但当前不支持,欢迎贡献。
对于 PyTorch,仅提供了替换模块,如果是在 ``forward`` 中的函数,当前不支持。 一种解决方案是将函数变为 PyTorch 模块。
示例的加速结果
---------------------------
实验代码在 :githublink:`这里 <examples/model_compress/pruning/model_speedup.py>`。
slim Pruner 示例
^^^^^^^^^^^^^^^^^^^
在一块 V100 GPU 上
输入张量: ``torch.randn(64, 3, 32, 32)``
.. list-table::
:header-rows: 1
:widths: auto
* - 次数
- 掩码时延
- 加速后的时延
* - 1
- 0.01197
- 0.005107
* - 2
- 0.02019
- 0.008769
* - 4
- 0.02733
- 0.014809
* - 8
- 0.04310
- 0.027441
* - 16
- 0.07731
- 0.05008
* - 32
- 0.14464
- 0.10027
fpgm Pruner 示例
^^^^^^^^^^^^^^^^^^^
在 cpu 上
输入张量: ``torch.randn(64, 1, 28, 28)``\ ,
方差较大
.. list-table::
:header-rows: 1
:widths: auto
* - 次数
- 掩码时延
- 加速后的时延
* - 1
- 0.01383
- 0.01839
* - 2
- 0.01167
- 0.003558
* - 4
- 0.01636
- 0.01088
* - 40
- 0.14412
- 0.08268
* - 40
- 1.29385
- 0.14408
* - 40
- 0.41035
- 0.46162
* - 400
- 6.29020
- 5.82143
l1filter Pruner 示例
^^^^^^^^^^^^^^^^^^^^^^^
在一块 V100 GPU 上
输入张量: ``torch.randn(64, 3, 32, 32)``
.. list-table::
:header-rows: 1
:widths: auto
* - 次数
- 掩码时延
- 加速后的时延
* - 1
- 0.01026
- 0.003677
* - 2
- 0.01657
- 0.008161
* - 4
- 0.02458
- 0.020018
* - 8
- 0.03498
- 0.025504
* - 16
- 0.06757
- 0.047523
* - 32
- 0.10487
- 0.086442
APoZ Pruner 示例
^^^^^^^^^^^^^^^^^^^
在一块 V100 GPU 上
输入张量: ``torch.randn(64, 3, 32, 32)``
.. list-table::
:header-rows: 1
:widths: auto
* - Times
- Mask Latency
- Speedup Latency
* - 1
- 0.01389
- 0.004208
* - 2
- 0.01628
- 0.008310
* - 4
- 0.02521
- 0.014008
* - 8
- 0.03386
- 0.023923
* - 16
- 0.06042
- 0.046183
* - 32
- 0.12421
- 0.087113
SimulatedAnnealing Pruner 示例
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
这个实验使用了 SimulatedAnnealing Pruner 在 cifar10 数据集上裁剪 resnet18 模型。
我们评估了剪枝模型在不同稀疏比下的延迟和精度,如下图所示。
在一块 V100 GPU 上,输入张量为 ``torch.randn(128, 3, 32, 32)``。
.. image:: ../../img/SA_latency_accuracy.png
\ No newline at end of file
../../en_US/Compression/ModelSpeedup.rst
\ No newline at end of file
.. 37577199d91c137b881450f825f38fa2
使用 NNI 进行模型压缩 使用 NNI 进行模型压缩
========================== ==========================
......
This diff is collapsed.
../../en_US/Compression/Pruner.rst
\ No newline at end of file
加速混合精度量化模型(实验性)
==========================================================
介绍
------------
深度神经网络一直是计算密集型和内存密集型的,
这增加了部署深度神经网络模型的难度。 量化是
一种广泛用于减少内存占用和加速推理过程的基础技术
。 许多框架开始支持量化,但很少有框架支持混合精度
量化并取得真正的速度提升。 像 `HAQ: Hardware-Aware Automated Quantization with Mixed Precision <https://arxiv.org/pdf/1811.08886.pdf>`__\ 这样的框架只支持模拟混合精度量化,这将
不加快推理过程。 为了获得混合精度量化的真正加速和
帮助人们从硬件中获得真实的反馈,我们设计了一个具有简单接口的通用框架,允许 NNI 量化算法连接不同的
DL 模型优化后端(例如 TensorRT、NNFusion),使用量化算法,在量化模型后为用户提供端到端体验
量化模型可以直接通过连接的优化后端加速。 在这个阶段,NNI 连接了
TensorRT,并将在未来支持更多的后端。
设计和实现
-------------------------
为了支持加速混合精度量化,我们将框架划分为两个部分,前端和后端。
前端可以是流行的训练框架,如 PyTorch、TensorFlow 等。 后端可以是
为不同硬件设计的推理框架,如 TensorRT。 目前,我们支持 PyTorch 作为前端和
TensorRT 作为后端。 为了将 PyTorch 模型转换为 TensorRT 引擎,我们利用 onnx 作为中间图
表示。 通过这种方式,我们将 PyTorch 模型转换为 onnx 模型,然后 TensorRT 解析 onnx
模型生成推理引擎。
量化感知训练结合了 NNI 量化算法 'QAT' 和 NNI 量化加速工具。
用户应该设置配置,使用 QAT 算法训练量化模型(请参考 `NNI量化算法 <https://nni.readthedocs.io/zh/stable/Compression/Quantizer.html>`__)。
经过量化感知训练,用户可以得到带有校准参数的新配置和带有量化权重的模型。 通过将新的配置和模型传递给量化加速工具,用户可以得到真正的混合精度加速引擎来进行推理。
在得到混合精度引擎后,用户可以使用输入数据进行推理。
注意
* 用户也可以直接利用 TensorRT 进行训练后的量化处理(需要提供校准数据集)。
* 并非所有OP类型都已支持。 目前,NNI 支持 Conv, Linear, Relu 和 MaxPool。 未来版本中将支持更多操作类型。
先决条件
------------
CUDA version >= 11.0
TensorRT version >= 7.2
用法
-----
量化感知训练:
.. code-block:: python
# 为QAT算法设置比特配置
configure_list = [{
'quant_types': ['weight', 'output'],
'quant_bits': {'weight':8, 'output':8},
'op_names': ['conv1']
}, {
'quant_types': ['output'],
'quant_bits': {'output':8},
'op_names': ['relu1']
}
]
quantizer = QAT_Quantizer(model, configure_list, optimizer)
quantizer.compress()
calibration_config = quantizer.export_model(model_path, calibration_path)
engine = ModelSpeedupTensorRT(model, input_shape, config=calibration_config, batchsize=batch_size)
# 建立 Tensorrt 推理引擎
engine.compress()
# 数据应该是 Pytorch Tensor
output, time = engine.inference(data)
请注意,NNI还直接支持后训练量化,请参阅完整的示例以获取详细信息。
完整的例子请参考 :githublink:`这里 <examples/model_compress/quantization/mixed_precision_speedup_mnist.py>`。
关于 'TensorRTModelSpeedUp' 类的更多参数,你可以参考 `Model Compression API Reference <https://nni.readthedocs.io/zh/stable/Compression/CompressionReference.html#quantization-speedup>`__ 。
Mnist 测试
^^^^^^^^^^^^^^^^^^^
在一块 GTX2080 GPU 上
输入张量:``torch.randn(128, 1, 28, 28)``
.. list-table::
:header-rows: 1
:widths: auto
* - 量化策略
- 延迟
- 准确率
* - 均为 32bit
- 0.001199961
- 96%
* - 混合精度(平均 bit 20.4)
- 0.000753688
- 96%
* - 均为 8bit
- 0.000229869
- 93.7%
Cifar10 resnet18 测试(训练一个 epoch)
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
在一块 GTX2080 GPU 上
输入张量: ``torch.randn(128, 3, 32, 32)``
.. list-table::
:header-rows: 1
:widths: auto
* - 量化策略
- 延迟
- 准确率
* - 均为 32bit
- 0.003286268
- 54.21%
* - 混合精度(平均 bit 11.55)
- 0.001358022
- 54.78%
* - 均为 8bit
- 0.000859139
- 52.81%
\ No newline at end of file
../../en_US/Compression/QuantizationSpeedup.rst
\ No newline at end of file
支持的量化算法
========================================
支持的量化算法列表
* `Naive Quantizer <#naive-quantizer>`__
* `QAT Quantizer <#qat-quantizer>`__
* `DoReFa Quantizer <#dorefa-quantizer>`__
* `BNN Quantizer <#bnn-quantizer>`__
* `LSQ Quantizer <#lsq-quantizer>`__
Naive Quantizer
---------------
Naive Quantizer Quantizer 权重默认设置为 8 位,可用它来测试量化算法。
用法
^^^^^
PyTorch
.. code-block:: python
model = nni.algorithms.compression.pytorch.quantization.NaiveQuantizer(model).compress()
----
QAT Quantizer
-------------
`Quantization and Training of Neural Networks for Efficient Integer-Arithmetic-Only Inference <http://openaccess.thecvf.com/content_cvpr_2018/papers/Jacob_Quantization_and_Training_CVPR_2018_paper.pdf>`__ 中,作者 Benoit Jacob Skirmantas Kligys 提出了一种算法在训练中量化模型。
..
我们提出了一种方法,在训练的前向过程中模拟量化效果。 此方法不影响反向传播,所有权重和偏差都使用了浮点数保存,因此能很容易的进行量化。 然后,前向传播通过实现浮点算法的舍入操作,来在推理引擎中模拟量化的推理。
* 权重在与输入卷积操作前进行量化。 如果在层中使用了批量归一化(参考 [17]),批量归一化参数会被在量化前被“折叠”到权重中。
* 激活操作在推理时会被量化。 例如,在激活函数被应用到卷积或全连接层输出之后,或在增加旁路连接,或连接多个层的输出之后(如:ResNet)。
用法
^^^^^
可在训练代码前将模型量化为 8 位。
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.quantization import QAT_Quantizer
model = Mnist()
config_list = [{
'quant_types': ['weight'],
'quant_bits': {
'weight': 8,
}, # 这里可以仅使用 `int`,因为所有 `quan_types` 使用了一样的位长,参考下方 `ReLu6` 配置。
'op_types':['Conv2d', 'Linear']
}, {
'quant_types': ['output'],
'quant_bits': 8,
'quant_start_step': 7000,
'op_types':['ReLU6']
}]
quantizer = QAT_Quantizer(model, config_list)
quantizer.compress()
查看示例进一步了解
QAT Quantizer 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
压缩算法的公共配置可在 `config_list 说明 <./QuickStart.rst>`__ 中找到。
此算法所需的配置:
* **quant_start_step:** int
在运行到某步骤前,对模型禁用量化。这让网络在进入更稳定的
状态后再激活量化,这样不会配除掉一些分数显著的值,默认为 0
注意
^^^^
当前不支持批处理规范化折叠。
----
LSQ Quantizer
-------------
`可训练的步长量化 <https://arxiv.org/pdf/1902.08153.pdf>`__\ 中,作者 Steven K. Esser Jeffrey L. McKinstry 提供一种算法来训练带有梯度的尺度。
..
作者介绍了一种新颖的方法来估计和缩放每个权重和激活层的量化器步长的任务损失梯度,使得它可以与其他网络参数结合使用。
用法
^^^^^
您可以在训练代码之前添加下面的代码。 必须完成三件事:
1. 配置哪一层要被量化,以及该层的哪个张量(输入/输出/权重)要被量化。
2. 构建 lsq quantizer
3. 调用 `compress` API
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.quantization import LsqQuantizer
model = Mnist()
configure_list = [{
'op_types': 'default'
'quant_bits': {
'weight': 8,
'input': 8,
},
'op_names': ['conv1']
}, {
'quant_types': ['output'],
'quant_bits': {'output': 8,},
'op_names': ['relu1']
}]
quantizer = LsqQuantizer(model, configure_list, optimizer)
quantizer.compress()
查看示例了解更多信息 :githublink:`examples/model_compress/quantization/LSQ_torch_quantizer.py <examples/model_compress/quantization/LSQ_torch_quantizer.py>`
LSQ Quantizer 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
压缩算法的公共配置可在 `config_list 说明 <./QuickStart.rst>`__ 中找到。
此算法所需的配置:
----
DoReFa Quantizer
----------------
`DoReFa-Net: Training Low Bitwidth Convolutional Neural Networks with Low Bitwidth Gradients <https://arxiv.org/abs/1606.06160>`__ 中,作者 Shuchang Zhou Yuxin Wu 提出了 DoReFa 算法在训练时量化权重,激活函数和梯度。
用法
^^^^^
要实现 DoReFa Quantizer,在训练代码前加入以下代码。
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.quantization import DoReFaQuantizer
config_list = [{
'quant_types': ['weight'],
'quant_bits': 8,
'op_types': ['default']
}]
quantizer = DoReFaQuantizer(model, config_list)
quantizer.compress()
查看示例进一步了解
DoReFa Quantizer 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
压缩算法的公共配置可在 `config_list 说明 <./QuickStart.rst>`__ 中找到。
此算法所需的配置:
----
BNN Quantizer
-------------
`Binarized Neural Networks: Training Deep Neural Networks with Weights and Activations Constrained to +1 or -1 <https://arxiv.org/abs/1602.02830>`__
..
引入了一种训练二进制神经网络(BNN)的方法 - 神经网络在运行时使用二进制权重。 在训练时,二进制权重和激活用于计算参数梯度。 forward 过程中,BNN 会大大减少内存大小和访问,并将大多数算术运算替换为按位计算,可显著提高能源效率。
用法
^^^^^
PyTorch 代码
.. code-block:: python
from nni.algorithms.compression.pytorch.quantization import BNNQuantizer
model = VGG_Cifar10(num_classes=10)
configure_list = [{
'quant_bits': 1,
'quant_types': ['weight'],
'op_types': ['Conv2d', 'Linear'],
'op_names': ['features.0', 'features.3', 'features.7', 'features.10', 'features.14', 'features.17', 'classifier.0', 'classifier.3']
}, {
'quant_bits': 1,
'quant_types': ['output'],
'op_types': ['Hardtanh'],
'op_names': ['features.6', 'features.9', 'features.13', 'features.16', 'features.20', 'classifier.2', 'classifier.5']
}]
quantizer = BNNQuantizer(model, configure_list)
model = quantizer.compress()
可以查看 :githublink:`示例 <examples/model_compress/quantization/BNN_quantizer_cifar10.py>` 了解更多信息。
BNN Quantizer 的用户配置
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
压缩算法的公共配置可在 `config_list 说明 <./QuickStart.rst>`__ 中找到。
此算法所需的配置:
实验
^^^^^^^^^^
我们实现了 `Binarized Neural Networks: Training Deep Neural Networks with Weights and Activations Constrained to +1 or -1 <https://arxiv.org/abs/1602.02830>`__ 中的一个实验,对 CIFAR-10 上的 **VGGNet** 进行了量化操作。 我们的实验结果如下:
.. list-table::
:header-rows: 1
:widths: auto
* - 模型
- 准确率
* - VGGNet
- 86.93%
实验代码在 :githublink:`examples/model_compress/BNN_quantizer_cifar10.py <examples/model_compress/quantization/BNN_quantizer_cifar10.py>`
../../en_US/Compression/Quantizer.rst
\ No newline at end of file
.. b7c7ceacdaabfcf383be1e74d067098d
快速入门 快速入门
=========== ===========
.. toctree:: .. toctree::
:hidden: :hidden:
教程 <Tutorial> Notebook Example <compression_pipeline_example>
模型压缩通常包括三个阶段:1)预训练模型,2)压缩模型,3)微调模型。 NNI 主要关注于第二阶段,并为模型压缩提供非常简单的 API 遵循本指南,快速了解如何使用 NNI 压缩模型。 NNI 主要关注于第二阶段,并为模型压缩提供非常简单的 API 恭喜! 您已经通过 NNI 压缩了您的第一个模型。 更深入地了解 NNI 中的模型压缩,请查看 `Tutorial <./Tutorial.rst>`__ 模型压缩通常包括三个阶段:1)预训练模型,2)压缩模型,3)微调模型。 NNI 主要关注于第二阶段,并为模型压缩提供非常简单的 API 遵循本指南,快速了解如何使用 NNI 压缩模型。 NNI 主要关注于第二阶段,并为模型压缩提供非常简单的 API 恭喜! 您已经通过 NNI 压缩了您的第一个模型。 更深入地了解 NNI 中的模型压缩,请查看 `Tutorial <./Tutorial.rst>`__
......
教程
========
.. contents::
在本教程中,我们将更详细地解释 NNI 中模型压缩的用法。
设定压缩目标
----------------------
指定配置
^^^^^^^^^^^^^^^^^^^^^^^^^
用户可为压缩算法指定配置 (即, ``config_list`` )。 例如,压缩模型时,用户可能希望指定稀疏率,为不同类型的操作指定不同的稀疏比例,排除某些类型的操作,或仅压缩某类操作。 配置规范可用于表达此类需求。 可将其视为一个 Python 的 ``list`` 对象,其中每个元素都是一个 ``dict`` 对象。
``list`` 中的 ``dict`` 会依次被应用,也就是说,如果一个操作出现在两个配置里,后面的 ``dict`` 会覆盖前面的配置。
``dict`` 中有不同的键值。以下是所有压缩算法都支持的:
* **op_types**:指定要压缩的操作类型。'default' 表示使用算法的默认设置。 所有在 Pytorch 中支持的模块类型都定义在 :githublink:`default_layers.py <nni/compression/pytorch/default_layers.py>` 。
* **op_names**:指定需要压缩的操作的名称。如果没有设置此字段,操作符不会通过名称筛选。
* **exclude**:默认为 False。如果此字段为 True,表示要通过类型和名称,将一些操作从压缩中排除。
其他一些键值通常是针对某个特定算法的,可参考 `剪枝算法 <./Pruner.rst>`__ 和 `量化算法 <./Quantizer.rst>`__,查看每个算法的键值。
修剪所有 ``conv2d`` 层,稀疏性0.6为,配置可以写作:
.. code-block:: python
[
'sparsity': 0.6,
'op_types': ['Conv2d']
]
为了控制特定层的稀疏度,配置可以写成:
.. code-block:: python
]
'sparsity': 0.8,
'op_types': ['default']
},
{
'sparsity': 0.6,
'op_names': ['op_name1', 'op_name2']
},
{
'exclude': True,
'op_names': ['op_name3']
}, {
其表示压缩操作的默认稀疏度为 0.8,但 ``op_name1`` 和 ``op_name2`` 会使用 0.6,且不压缩 ``op_name3``。
Quantization Specific Keys
^^^^^^^^^^^^^^^^^^^^^^^^^^
如果使用量化算法,则需要设置下面的 ``config_list``。 如果使用剪枝算法,则可以忽略这些键值。
* **quant_types** : 字符串列表。
要应用量化的类型,当前支持 "权重","输入","输出"。"权重"是指将量化操作应用到 module 的权重参数上。"输入" 是指对 module 的 forward 方法的输入应用量化操作。 "输出"是指将量化运法应用于模块 forward 方法的输出,在某些论文中,这种方法称为"激活"。
* **quant_bits**:int 或 dict {str : int}
量化的位宽,键是量化类型,值是量化位宽度,例如:
.. code-block:: bash
{
quant_bits: {
'weight': 8,
'output': 4,
},
}
当值为 int 类型时,所有量化类型使用相同的位宽。 例如:
.. code-block:: bash
{
quant_bits: 8, # 权重和输出的位宽都为 8 bits
}
下面的示例展示了一个更完整的 ``config_list``,它使用 ``op_names``(或者 ``op_types``)指定目标层以及这些层的量化位数。
.. code-block:: bash
config_list = [{
'quant_types': ['weight'],
'quant_bits': 8,
'op_names': ['conv1']
},
{
'quant_types': ['weight'],
'quant_bits': 4,
'quant_start_step': 0,
'op_names': ['conv2']
},
{
'quant_types': ['weight'],
'quant_bits': 3,
'op_names': ['fc1']
},
{
'quant_types': ['weight'],
'quant_bits': 2,
'op_names': ['fc2']
}]
在这个示例中,'op_names' 是层的名字,四个层将被量化为不同的 quant_bits。
导出压缩结果
-------------------------
导出量化后的模型
^^^^^^^^^^^^^^^^^^^^^^^
使用下列 API 可轻松将裁剪后的模型导出,稀疏模型权重的 ``state_dict`` 会保存在 ``model.pth`` 文件中,可通过 ``torch.load('model.pth')`` 加载。注意,导出的 ``model.pth`` 具有与原始模型相同的参数,只是掩码的权重为零。 ``mask_dict`` 存储剪枝算法产生的二进制值,可以进一步用来加速模型。
.. code-block:: python
# 导出模型的权重和掩码。
pruner.export_model(model_path='model.pth', mask_path='mask.pth')
# 将掩码应用到模型
from nni.compression.pytorch import apply_compression_results
apply_compression_results(model, mask_file, device)
用 ``onnx`` 格式导出模型,(需要指定\ ``input_shape`` ):
.. code-block:: python
pruner.export_model(model_path='model.pth', mask_path='mask.pth', onnx_path='model.onnx', input_shape=[1, 1, 28, 28])
导出裁剪后的模型
^^^^^^^^^^^^^^^^^^^^^^^^^^
您可以使用 ``torch.save`` api 直接导出量化模型。量化后的模型可以通过 ``torch.load`` 加载,不需要做任何额外的修改。下面的例子展示了使用 QAT quantizer 保存、加载量化模型并获取相关参数的过程。
.. code-block:: python
# 保存使用 NNI QAT 算法生成的量化模型
torch.save(model.state_dict(), "quantized_model.pth")
# 模拟模型加载过程
# 初始化新模型并在加载之前压缩它
qmodel_load = Mnist()
optimizer = torch.optim.SGD(qmodel_load.parameters(), lr=0.01, momentum=0.5)
quantizer = QAT_Quantizer(qmodel_load, config_list, optimizer)
quantizer.compress()
# 加载量化的模型
qmodel_load.load_state_dict(torch.load("quantized_model.pth"))
# 获取加载后模型的 scale, zero_point 和 conv1 的权重
conv1 = qmodel_load.conv1
scale = conv1.module.scale
zero_point = conv1.module.zero_point
weight = conv1.module.weight
模型加速
------------------
掩码实际上并不能加速模型。 应该基于导出的掩码来对模型加速,因此,NNI 提供了 API 来加速模型。在模型上调用 ``apply_compression_results`` 后,模型会变得更小,推理延迟也会减小。
.. code-block:: python
from nni.compression.pytorch import apply_compression_results, ModelSpeedup
dummy_input = torch.randn(config['input_shape']).to(device)
m_speedup = ModelSpeedup(model, dummy_input, masks_file, device)
m_speedup.speedup_model()
参考 `这里 <ModelSpeedup.rst>`__,了解详情。 模型加速的示例代码在 :githublink:`这里 <examples/model_compress/pruning/model_speedup.py>`。 知识蒸馏有效地从大型教师模型中学习小型学生模型。 用户可以通过知识蒸馏来增强模型的微调过程,提高压缩模型的性能。 示例代码在 :githublink:`这里 <examples/model_compress/pruning/finetune_kd_torch.py>`。
控制微调过程
-------------------------------
控制微调的 API
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
某些压缩算法会控制微调过程中的压缩进度(例如, `AGP <../Compression/Pruner.rst#agp-pruner>`__),一些算法需要在每个批处理步骤后执行一些逻辑。 因此,NNI 提供了两个 API:``pruner.update_epoch(epoch)`` 和 ``pruner.step()``。 `AGP <../Compression/Pruner.rst#agp-pruner>`__),一些算法需要在每个批处理步骤后执行一些逻辑。 因此,NNI 提供了两个 API:``pruner.update_epoch(epoch)`` 和 ``pruner.step()``。
``update_epoch`` 会在每个 Epoch 时调用,而 ``step`` 会在每次批处理后调用。 注意,大多数算法不需要调用这两个 API。详细情况可参考具体算法文档。对于不需要这两个 API 的算法,可以调用它们,但不会有实际作用。
强化微调过程
^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
知识蒸馏有效地从大型教师模型中学习小型学生模型。 用户可以通过知识蒸馏来增强模型的微调过程,提高压缩模型的性能。 示例代码在 :githublink:`这里 <examples/model_compress/pruning/finetune_kd_torch.py>`。
../../en_US/Compression/Tutorial.rst
\ No newline at end of file
.. acd3f66ad7c2d82b950568efcba1f175
高级用法 高级用法
============== ==============
......
.. 0f2050a973cfb2207984b4e58c4baf28
################# #################
剪枝 剪枝
################# #################
......
.. fe32a6de0be31a992afadba5cf6ffe23
################# #################
量化 量化
################# #################
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment