加速掩码的模型
=====================

*此功能处于测试阶段。*

介绍
------------

剪枝算法通常都用权重掩码来模拟实际的剪枝。 掩码可以用来
检查某个剪枝（或稀疏）算法的模型性能，但还没有真正加速。
模型加速才是模型剪枝的最终目标。
因此提供了此工具，来帮助基于用户提供的掩码（掩码来自于剪枝算法），
将已有模型转换成小模型。

有两种剪枝算法。 一种是细粒度的剪枝，不改变权重形状，和输入输出的张量。 稀疏内核会被用来加速细粒度剪枝的层。 另一类是粗粒度的剪枝（例如，通道），通常，权重形状，输入输出张量会有所改变。 要加速这类剪枝算法，不需要使用系数内核，只需要用更小的层来替换。 由于开源社区中对稀疏内核的支持还比较有限，当前仅支持粗粒度剪枝，会在将来再支持细粒度的剪枝算法。

设计和实现
-------------------------

为了加速模型，被剪枝的层应该被替换掉，要么为粗粒度掩码使用较小的层，要么用稀疏内核来替换细粒度的掩码。 粗粒度掩码通常会改变权重的形状，或输入输出张量，因此，应该通过形状推断，来检查是否其它未被剪枝的层由于形状变化而需要改变形状。 因此，在设计中，主要有两个步骤：第一，做形状推理，找出所有应该替换的模块；第二，替换模块。 第一步需要模型的拓扑（即连接），我们使用了 ``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