Skip to content
This repository has been archived by the owner on Sep 18, 2024. It is now read-only.

Auto pruners #2490

Merged
merged 107 commits into from
Jun 30, 2020
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
107 commits
Select commit Hold shift + click to select a range
80165e9
init sapruner
suiguoxin Apr 21, 2020
3fcebef
seperate sapruners from other one-shot pruners
suiguoxin Apr 22, 2020
b4be2d0
update
suiguoxin Apr 22, 2020
805c32c
fix model params issue
suiguoxin Apr 23, 2020
4c33432
make the process runnable
suiguoxin Apr 23, 2020
489f7b6
show evaluation result in example
suiguoxin Apr 23, 2020
00dbddf
sort the sparsities and scale it
suiguoxin Apr 23, 2020
e1f9654
fix rescale issue
suiguoxin Apr 23, 2020
4b5ea0d
fix scale issue; add pruning history
suiguoxin Apr 24, 2020
6120b70
record the actual total sparsity
suiguoxin Apr 24, 2020
a6114f7
fix sparsity 0/1 problem
suiguoxin Apr 26, 2020
2e928ae
revert useless modif
suiguoxin Apr 26, 2020
546ca73
revert useless modif
suiguoxin Apr 26, 2020
1dc4713
fix 0 pruning weights problem
suiguoxin Apr 27, 2020
d1a5646
save pruning history in csv file
suiguoxin Apr 28, 2020
75a53da
fix typo
suiguoxin Apr 28, 2020
e8900f8
remove check perm in Makefile
suiguoxin Apr 28, 2020
9c5ba41
use os path
suiguoxin Apr 29, 2020
9a60501
save config list in json format
suiguoxin Apr 29, 2020
951c60f
update analyze py; update docker
suiguoxin Apr 30, 2020
c784790
update
suiguoxin Apr 30, 2020
836e74b
update analyze
suiguoxin May 4, 2020
70aca26
update log info in compressor
suiguoxin May 4, 2020
efa0637
init NetAdapt Pruner
suiguoxin May 4, 2020
8695564
refine examples
suiguoxin May 6, 2020
8fdad96
Merge remote-tracking branch 'msft/master' into sapruner
suiguoxin May 6, 2020
db3074e
update
suiguoxin May 7, 2020
78ee01a
fine tune
suiguoxin May 7, 2020
3e40c4a
update
suiguoxin May 7, 2020
2560050
fix quote issue
suiguoxin May 7, 2020
d6e4101
add code for imagenet integrity
suiguoxin May 8, 2020
65f8e2b
update
suiguoxin May 8, 2020
d27ac7d
use datasets.ImageNet
suiguoxin May 8, 2020
f47260f
update
suiguoxin May 8, 2020
358921c
update
suiguoxin May 9, 2020
f50e947
add channel pruning in SAPruner; refine example
suiguoxin May 11, 2020
ea07c00
update net_adapt pruner; add dependency constraint in sapruner(beta)
suiguoxin May 11, 2020
7d73050
update
suiguoxin May 12, 2020
220e4a3
update
suiguoxin May 12, 2020
e692eb1
update
suiguoxin May 12, 2020
fc389d0
fix zero division problem
suiguoxin May 12, 2020
a69da67
fix typo
suiguoxin May 12, 2020
e0ab4bc
update
suiguoxin May 12, 2020
7724104
fix naive issue of NetAdaptPruner
suiguoxin May 12, 2020
f9f4a61
fix data issue for no-dependency modules
suiguoxin May 13, 2020
93698ac
add cifar10 vgg16 examplel
suiguoxin May 14, 2020
7d7f36d
update
suiguoxin May 14, 2020
9fc1029
update
suiguoxin May 14, 2020
9d506b1
fix folder creation issue; change lr for vgg exp
suiguoxin May 15, 2020
6ca5b27
update
suiguoxin May 15, 2020
fe9c1bf
add save model arg
suiguoxin May 15, 2020
1ec68a4
fix model copy issue
suiguoxin May 15, 2020
c99e4a3
init related weights calc
suiguoxin May 15, 2020
b6ce773
update analyze file
suiguoxin May 15, 2020
559c631
NetAdaptPruner: use fine-tuned weights after each iteration; fix modu…
suiguoxin May 18, 2020
2bd5a80
Merge remote-tracking branch 'msft/master' into sapruner
suiguoxin May 18, 2020
5ebea45
consider channel/filter cross pruning
suiguoxin May 18, 2020
f74324c
NetAdapt: consider previous op when calc total sparsity
suiguoxin May 18, 2020
27ad5f7
update
suiguoxin May 18, 2020
7f607ce
use customized vgg
suiguoxin May 19, 2020
6137373
add performances comparison plt
suiguoxin May 19, 2020
b9222c7
fix netadaptPruner mask copy issue
suiguoxin May 19, 2020
71e3651
add resnet18 example
suiguoxin May 19, 2020
045f114
fix example issue
suiguoxin May 19, 2020
e7b0410
Merge remote-tracking branch 'msft/master' into sapruner
suiguoxin May 19, 2020
98c5cb4
update experiment data
suiguoxin May 20, 2020
c220f84
fix bool arg parsing issue
suiguoxin May 20, 2020
5a1728e
update
suiguoxin May 20, 2020
b1a4058
init ADMMPruner
suiguoxin May 21, 2020
b36a170
ADMMPruner: update
suiguoxin May 21, 2020
fd6f3a6
ADMMPruner: finish v1.0
suiguoxin May 22, 2020
0b8840f
ADMMPruner: refine
suiguoxin May 22, 2020
7f1c319
update
suiguoxin May 22, 2020
87b090c
AutoCompress init
suiguoxin May 25, 2020
6c82d6c
AutoCompress: update
suiguoxin May 25, 2020
efd8f10
AutoCompressPruner: fix issues:
suiguoxin May 26, 2020
85a4483
add test for auto pruners
suiguoxin May 26, 2020
180a709
add doc for auto pruners
suiguoxin May 26, 2020
e87122c
fix link in md
suiguoxin May 26, 2020
955a6ee
remove irrelevant files
suiguoxin May 26, 2020
51e004e
Clean code
suiguoxin May 26, 2020
4eeb65e
code clean
suiguoxin May 26, 2020
f8ebc19
fix pylint issue
suiguoxin May 26, 2020
e241708
fix pylint issue
suiguoxin May 26, 2020
0edddeb
rename admm & autoCompress param
suiguoxin May 26, 2020
c93e0eb
use abs link in doc
suiguoxin May 26, 2020
e88e4d7
merge from master % resolve conflict
suiguoxin May 28, 2020
67c41d5
reorder import to fix import issue: autocompress relies on speedup
suiguoxin May 28, 2020
c057307
refine doc
suiguoxin Jun 4, 2020
7f3de4e
NetAdaptPruner: decay pruning step
suiguoxin Jun 11, 2020
55e705e
take changes from testing branch
suiguoxin Jun 29, 2020
e1775b3
merge from master
suiguoxin Jun 29, 2020
840213d
refine
suiguoxin Jun 29, 2020
d4b80bc
fix typo
suiguoxin Jun 29, 2020
c9fffe0
ADMMPruenr: check base_algo together with config schema
suiguoxin Jun 29, 2020
87f3232
fix broken link
suiguoxin Jun 29, 2020
16b1c95
doc refine
suiguoxin Jun 29, 2020
6bff198
ADMM:refine
suiguoxin Jun 29, 2020
d86fad4
refine doc
suiguoxin Jun 30, 2020
c29f758
resolve conflict
suiguoxin Jun 30, 2020
32d14d9
refine doc
suiguoxin Jun 30, 2020
5950bec
refince doc
ultmaster Jun 29, 2020
8a11b45
resolve conflict
suiguoxin Jun 30, 2020
be782bc
refine doc
suiguoxin Jun 30, 2020
d449e6a
refine doc
suiguoxin Jun 30, 2020
cb6376b
refine doc
suiguoxin Jun 30, 2020
cee3fdd
refine doc
suiguoxin Jun 30, 2020
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -144,6 +144,10 @@ Within the following table, we summarized the current NNI capabilities, we are g
<li><a href="docs/en_US/Compressor/Pruner.md#agp-pruner">AGP Pruner</a></li>
<li><a href="docs/en_US/Compressor/Pruner.md#slim-pruner">Slim Pruner</a></li>
<li><a href="docs/en_US/Compressor/Pruner.md#fpgm-pruner">FPGM Pruner</a></li>
<li><a href="docs/en_US/Compressor/Pruner.md#netadapt-pruner">NetAdapt Pruner</a></li>
<li><a href="docs/en_US/Compressor/Pruner.md#simulatedannealing-pruner">SimulatedAnnealing Pruner</a></li>
<li><a href="docs/en_US/Compressor/Pruner.md#admm-pruner">ADMM Pruner</a></li>
<li><a href="docs/en_US/Compressor/Pruner.md#autocompress-pruner">AutoCompress Pruner</a></li>
</ul>
<b>Quantization</b>
<ul>
Expand Down
4 changes: 4 additions & 0 deletions docs/en_US/Compressor/Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,10 @@ Pruning algorithms compress the original network by removing redundant weights o
| [ActivationMeanRankFilterPruner](https://nni.readthedocs.io/en/latest/Compressor/Pruner.html#activationmeanrankfilterpruner) | Pruning filters based on the metric that calculates the smallest mean value of output activations |
| [Slim Pruner](https://nni.readthedocs.io/en/latest/Compressor/Pruner.html#slim-pruner) | Pruning channels in convolution layers by pruning scaling factors in BN layers(Learning Efficient Convolutional Networks through Network Slimming) [Reference Paper](https://arxiv.org/abs/1708.06519) |
| [TaylorFO Pruner](https://nni.readthedocs.io/en/latest/Compressor/Pruner.html#taylorfoweightfilterpruner) | Pruning filters based on the first order taylor expansion on weights(Importance Estimation for Neural Network Pruning) [Reference Paper](http://jankautz.com/publications/Importance4NNPruning_CVPR19.pdf) |
| [ADMM Pruner](https://nni.readthedocs.io/en/latest/Compressor/Pruner.html#admm-pruner) | Pruning based on ADMM optimization technique [Reference Paper](https://arxiv.org/abs/1804.03294) |
| [NetAdapt Pruner](https://nni.readthedocs.io/en/latest/Compressor/Pruner.html#netadapt-pruner) | Automatically simplify a pretrained network to meet the resource budget by iterative pruning [Reference Paper](https://arxiv.org/abs/1804.03230) |
| [SimulatedAnnealing Pruner](https://nni.readthedocs.io/en/latest/Compressor/Pruner.html#simulatedannealing-pruner) | Automatic pruning with a guided heuristic search method, Simulated Annealing algorithm [Reference Paper](https://arxiv.org/abs/1907.03141) |
| [AutoCompress Pruner](https://nni.readthedocs.io/en/latest/Compressor/Pruner.html#autocompress-pruner) | Automatic pruning by iteratively call SimulatedAnnealing Pruner and ADMM Pruner [Reference Paper](https://arxiv.org/abs/1907.03141) |


### Quantization Algorithms
Expand Down
292 changes: 288 additions & 4 deletions docs/en_US/Compressor/Pruner.md

Large diffs are not rendered by default.

Binary file added docs/img/algo_NetAdapt.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
394 changes: 394 additions & 0 deletions examples/model_compress/auto_pruners_torch.py

Large diffs are not rendered by default.

29 changes: 29 additions & 0 deletions examples/model_compress/models/mnist/lenet.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
import torch
import torch.nn as nn
import torch.nn.functional as F


class LeNet(nn.Module):
def __init__(self):
super(LeNet, self).__init__()
self.conv1 = nn.Conv2d(1, 32, 3, 1)
self.conv2 = nn.Conv2d(32, 64, 3, 1)
self.dropout1 = nn.Dropout2d(0.25)
self.dropout2 = nn.Dropout2d(0.5)
self.fc1 = nn.Linear(9216, 128)
self.fc2 = nn.Linear(128, 10)

def forward(self, x):
x = self.conv1(x)
x = F.relu(x)
x = self.conv2(x)
x = F.relu(x)
x = F.max_pool2d(x, 2)
x = self.dropout1(x)
x = torch.flatten(x, 1)
x = self.fc1(x)
x = F.relu(x)
x = self.dropout2(x)
x = self.fc2(x)
output = F.log_softmax(x, dim=1)
return output
2 changes: 1 addition & 1 deletion src/sdk/pynni/nni/compression/torch/__init__.py
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

from .speedup import ModelSpeedup
from .pruning import *
from .quantization import *
from .compressor import Compressor, Pruner, Quantizer
from .speedup import ModelSpeedup
4 changes: 2 additions & 2 deletions src/sdk/pynni/nni/compression/torch/compressor.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ def _wrap_modules(self, layer, config):
config : dict
the configuration for generating the mask
"""
_logger.info("compressing module %s.", layer.name)
_logger.info("Module detected to compress : %s.", layer.name)
wrapper = PrunerModuleWrapper(layer.module, layer.name, layer.type, config, self)
assert hasattr(layer.module, 'weight'), "module %s does not have 'weight' attribute" % layer.name
# move newly registered buffers to the same device of weight
Expand Down Expand Up @@ -381,7 +381,7 @@ def export_model(self, model_path, mask_path=None, onnx_path=None, input_shape=N
if weight_mask is not None:
mask_sum = weight_mask.sum().item()
mask_num = weight_mask.numel()
_logger.info('Layer: %s Sparsity: %.2f', wrapper.name, 1 - mask_sum / mask_num)
_logger.info('Layer: %s Sparsity: %.4f', wrapper.name, 1 - mask_sum / mask_num)
wrapper.module.weight.data = wrapper.module.weight.data.mul(weight_mask)
if bias_mask is not None:
wrapper.module.bias.data = wrapper.module.bias.data.mul(bias_mask)
Expand Down
4 changes: 4 additions & 0 deletions src/sdk/pynni/nni/compression/torch/pruning/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,3 +7,7 @@
from .one_shot import *
from .agp import *
from .lottery_ticket import LotteryTicketPruner
from .simulated_annealing_pruner import SimulatedAnnealingPruner
from .net_adapt_pruner import NetAdaptPruner
from .admm_pruner import ADMMPruner
from .auto_compress_pruner import AutoCompressPruner
198 changes: 198 additions & 0 deletions src/sdk/pynni/nni/compression/torch/pruning/admm_pruner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,198 @@
# Copyright (c) Microsoft Corporation.
# Licensed under the MIT license.

import logging
import torch
from schema import And, Optional

from ..utils.config_validation import CompressorSchema
from .constants import MASKER_DICT
from .one_shot import OneshotPruner


_logger = logging.getLogger(__name__)


class ADMMPruner(OneshotPruner):
"""
This is a Pytorch implementation of ADMM Pruner algorithm.

Alternating Direction Method of Multipliers (ADMM) is a mathematical optimization technique,
by decomposing the original nonconvex problem into two subproblems that can be solved iteratively.
In weight pruning problem, these two subproblems are solved via 1) gradient descent algorithm and 2) Euclidean projection respectively.
This solution framework applies both to non-structured and different variations of structured pruning schemes.

For more details, please refer to the paper: https://arxiv.org/abs/1804.03294.
"""

def __init__(self, model, config_list, trainer, num_iterations=30, training_epochs=5, row=1e-4, base_algo='l1'):
"""
Parameters
----------
model : torch.nn.module
Model to be pruned
config_list : list
List on pruning configs
trainer : function
Function used for the first subproblem.
Users should write this function as a normal function to train the Pytorch model
and include `model, optimizer, criterion, epoch, callback` as function arguments.
Here `callback` acts as an L2 regulizer as presented in the formula (7) of the original paper.
The logic of `callback` is implemented inside the Pruner,
users are just required to insert `callback()` between `loss.backward()` and `optimizer.step()`.
Example::
```
>>> def trainer(model, criterion, optimizer, epoch, callback):
>>> device = torch.device("cuda" if torch.cuda.is_available() else "cpu")
>>> train_loader = ...
>>> model.train()
>>> for batch_idx, (data, target) in enumerate(train_loader):
>>> data, target = data.to(device), target.to(device)
>>> optimizer.zero_grad()
>>> output = model(data)
>>> loss = criterion(output, target)
>>> loss.backward()
>>> # callback should be inserted between loss.backward() and optimizer.step()
>>> if callback:
>>> callback()
>>> optimizer.step()
```
num_iterations : int
Total number of iterations.
training_epochs : int
Training epochs of the first subproblem.
row : float
Penalty parameters for ADMM training.
base_algo : str
Base pruning algorithm. `level`, `l1` or `l2`, by default `l1`. Given the sparsity distribution among the ops,
the assigned `base_algo` is used to decide which filters/channels/weights to prune.
"""
self._base_algo = base_algo

super().__init__(model, config_list)

self._trainer = trainer
self._num_iterations = num_iterations
self._training_epochs = training_epochs
self._row = row

self.set_wrappers_attribute("if_calculated", False)
self.masker = MASKER_DICT[self._base_algo](self.bound_model, self)

def validate_config(self, model, config_list):
"""
Parameters
----------
model : torch.nn.module
Model to be pruned
config_list : list
List on pruning configs
"""

if self._base_algo == 'level':
schema = CompressorSchema([{
'sparsity': And(float, lambda n: 0 < n < 1),
Optional('op_types'): [str],
Optional('op_names'): [str],
}], model, _logger)
elif self._base_algo in ['l1', 'l2']:
schema = CompressorSchema([{
'sparsity': And(float, lambda n: 0 < n < 1),
'op_types': ['Conv2d'],
Optional('op_names'): [str]
}], model, _logger)

schema.validate(config_list)

def _projection(self, weight, sparsity):
'''
Return the Euclidean projection of the weight matrix according to the pruning mode.

Parameters
----------
weight : tensor
original matrix
sparsity : float
the ratio of parameters which need to be set to zero

Returns
-------
tensor
the projected matrix
'''
w_abs = weight.abs()
if self._base_algo == 'level':
k = int(weight.numel() * sparsity)
if k == 0:
mask_weight = torch.ones(weight.shape).type_as(weight)
else:
threshold = torch.topk(w_abs.view(-1), k, largest=False)[0].max()
mask_weight = torch.gt(w_abs, threshold).type_as(weight)
elif self._base_algo in ['l1', 'l2']:
filters = weight.size(0)
num_prune = int(filters * sparsity)
if filters < 2 or num_prune < 1:
mask_weight = torch.ones(weight.size()).type_as(weight).detach()
else:
w_abs_structured = w_abs.view(filters, -1).sum(dim=1)
threshold = torch.topk(w_abs_structured.view(-1), num_prune, largest=False)[0].max()
mask_weight = torch.gt(w_abs_structured, threshold)[:, None, None, None].expand_as(weight).type_as(weight)

return weight.data.mul(mask_weight)

def compress(self):
"""
Compress the model with ADMM.

Returns
-------
torch.nn.Module
model with specified modules compressed.
"""
_logger.info('Starting ADMM Compression...')

# initiaze Z, U
# Z_i^0 = W_i^0
# U_i^0 = 0
Z = []
U = []
for wrapper in self.get_modules_wrapper():
z = wrapper.module.weight.data
Z.append(z)
U.append(torch.zeros_like(z))

optimizer = torch.optim.Adam(
self.bound_model.parameters(), lr=1e-3, weight_decay=5e-5)

# Loss = cross_entropy + l2 regulization + \Sum_{i=1}^N \row_i ||W_i - Z_i^k + U_i^k||^2
criterion = torch.nn.CrossEntropyLoss()

# callback function to do additonal optimization, refer to the deriatives of Formula (7)
def callback():
for i, wrapper in enumerate(self.get_modules_wrapper()):
wrapper.module.weight.data -= self._row * \
(wrapper.module.weight.data - Z[i] + U[i])

# optimization iteration
for k in range(self._num_iterations):
_logger.info('ADMM iteration : %d', k)

# step 1: optimize W with AdamOptimizer
for epoch in range(self._training_epochs):
self._trainer(self.bound_model, optimizer=optimizer,
criterion=criterion, epoch=epoch, callback=callback)

# step 2: update Z, U
# Z_i^{k+1} = projection(W_i^{k+1} + U_i^k)
# U_i^{k+1} = U^k + W_i^{k+1} - Z_i^{k+1}
for i, wrapper in enumerate(self.get_modules_wrapper()):
z = wrapper.module.weight.data + U[i]
Z[i] = self._projection(z, wrapper.config['sparsity'])
U[i] = U[i] + wrapper.module.weight.data - Z[i]

# apply prune
self.update_mask()

_logger.info('Compression finished.')

return self.bound_model
Loading