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

Api refactor #1728

Merged
merged 39 commits into from
Nov 12, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
39 commits
Select commit Hold shift + click to select a range
400e312
fix tools/nni_gpu_tool pylint
Oct 31, 2019
d0f65c8
Merge branch 'master' of https://github.com/microsoft/nni
Oct 31, 2019
5d1a115
add instrument_layer_hook && fix quantization param calc
Nov 3, 2019
cb89e16
Merge branch 'master' of https://github.com/microsoft/nni
Nov 3, 2019
493b0f3
add QAT example
Nov 4, 2019
bd809a7
remove data
Nov 4, 2019
c41a6cc
modify framework
Cjkkkk Nov 4, 2019
1b02f6a
rm irrelevant files
Cjkkkk Nov 4, 2019
61d471e
fix pylint for QAT quantizer
Cjkkkk Nov 4, 2019
3a3f3ce
resolve conflicts
Cjkkkk Nov 4, 2019
f884f2a
API refactor
Cjkkkk Nov 4, 2019
47d639f
warning for no weight parameter
Cjkkkk Nov 4, 2019
985dc43
API refactor
Cjkkkk Nov 4, 2019
c2e3871
fix pylint for QAT_torch_quantizer.py
Cjkkkk Nov 4, 2019
71c3369
init modules_to_compress to None
Cjkkkk Nov 4, 2019
2897613
modify config
Cjkkkk Nov 5, 2019
e837314
add doc string for QAT_quantizer
Cjkkkk Nov 5, 2019
e229624
rename quant_delay to quant_start_step
Cjkkkk Nov 5, 2019
bca4b51
remove EMA
Cjkkkk Nov 5, 2019
1ddba06
add docstring to explain dequantize in dequantize method
Cjkkkk Nov 5, 2019
439cdec
fix typo
Cjkkkk Nov 5, 2019
34f3b62
fix
Cjkkkk Nov 5, 2019
8ef4c4b
change to stateless
Cjkkkk Nov 5, 2019
4a1d122
update doc
Cjkkkk Nov 5, 2019
6a29346
fix
Cjkkkk Nov 6, 2019
40f9062
remove return name in docstring
Cjkkkk Nov 6, 2019
81bb549
fix test
Cjkkkk Nov 6, 2019
2c09777
update doc
Cjkkkk Nov 6, 2019
ce7bea6
fix docstring
Cjkkkk Nov 7, 2019
8102990
fix compressor doc
Cjkkkk Nov 11, 2019
762d74d
Merge branch 'master' of https://github.com/microsoft/nni into api_fa…
Cjkkkk Nov 11, 2019
43df1e1
API_refactor only
Cjkkkk Nov 11, 2019
43eab27
API_refactor only
Cjkkkk Nov 11, 2019
2231d35
rm layerinfo method
Cjkkkk Nov 11, 2019
a2e65b7
modify test
Cjkkkk Nov 11, 2019
7631a63
fix wrong return statement & restore doc
Cjkkkk Nov 11, 2019
9c619f2
fix docstring
Cjkkkk Nov 11, 2019
bec5297
fix docstring
Cjkkkk Nov 11, 2019
13b1085
change docs
Cjkkkk Nov 12, 2019
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
52 changes: 46 additions & 6 deletions docs/en_US/Compressor/Overview.md
Original file line number Diff line number Diff line change
Expand Up @@ -180,12 +180,54 @@ class YourQuantizer(nni.compression.tensorflow.Quantizer):

def quantize_weight(self, weight, config, **kwargs):
"""
weight is the target weight tensor
config is the selected dict object in config_list for this layer
kwargs contains op, op_types, and op_name
design your quantizer and return new weight
quantize should overload this method to quantize weight tensors.
This method is effectively hooked to :meth:`forward` of the model.

Parameters
----------
weight : Tensor
weight that needs to be quantized
config : dict
the configuration for weight quantization
"""

# Put your code to generate `new_weight` here

return new_weight

def quantize_output(self, output, config, **kwargs):
"""
quantize should overload this method to quantize output.
This method is effectively hooked to `:meth:`forward` of the model.

Parameters
----------
output : Tensor
output that needs to be quantized
config : dict
the configuration for output quantization
"""
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

new_output undefine?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Oh, I follow the quantize_weight format. This means user needs to return a tensor, which is the quantized output.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. At least add ...here?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. ... will make more sense.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I add a comment

# Put your code to generate `new_input` here
return new_input

I think this will be more clear.


# Put your code to generate `new_output` here

return new_output

def quantize_input(self, *inputs, config, **kwargs):
"""
quantize should overload this method to quantize input.
This method is effectively hooked to :meth:`forward` of the model.

Parameters
----------
inputs : Tensor
inputs that needs to be quantized
config : dict
the configuration for inputs quantization
"""
Cjkkkk marked this conversation as resolved.
Show resolved Hide resolved

# Put your code to generate `new_input` here

return new_input

# note for pytorch version, there is no sess in input arguments
def update_epoch(self, epoch_num, sess):
Expand All @@ -200,8 +242,6 @@ class YourQuantizer(nni.compression.tensorflow.Quantizer):
pass
```

__[TODO]__ Will add another member function `quantize_layer_output`, as some quantization algorithms also quantize layers' output.

### Usage of user customized compression algorithm

__[TODO]__ ...
112 changes: 93 additions & 19 deletions src/sdk/pynni/nni/compression/torch/compressor.py
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,23 @@ def __init__(self, model, config_list):
"""
self.bound_model = model
self.config_list = config_list
self.modules_to_compress = []
self.modules_to_compress = None

def detect_modules_to_compress(self):
"""
detect all modules should be compressed, and save the result in `self.modules_to_compress`.

The model will be instrumented and user should never edit it after calling this method.
"""
if self.modules_to_compress is None:
self.modules_to_compress = []
for name, module in self.bound_model.named_modules():
layer = LayerInfo(name, module)
config = self.select_config(layer)
if config is not None:
self.modules_to_compress.append((layer, config))
return self.modules_to_compress


def compress(self):
"""
Expand All @@ -41,12 +57,9 @@ def compress(self):
The model will be instrumented and user should never edit it after calling this method.
`self.modules_to_compress` records all the to-be-compressed layers
"""
for name, module in self.bound_model.named_modules():
layer = LayerInfo(name, module)
config = self.select_config(layer)
if config is not None:
self._instrument_layer(layer, config)
self.modules_to_compress.append((layer, config))
modules_to_compress = self.detect_modules_to_compress()
for layer, config in modules_to_compress:
self._instrument_layer(layer, config)
return self.bound_model

def get_modules_to_compress(self):
Expand All @@ -55,7 +68,7 @@ def get_modules_to_compress(self):

Returns
-------
self.modules_to_compress : list
list
a list of the layers, each of which is a tuple (`layer`, `config`),
`layer` is `LayerInfo`, `config` is a `dict`
"""
Expand All @@ -72,7 +85,7 @@ def select_config(self, layer):

Returns
-------
ret : config or None
config or None
the retrieved configuration for this layer, if None, this layer should
not be compressed
"""
Expand Down Expand Up @@ -238,26 +251,87 @@ class Quantizer(Compressor):
"""

def quantize_weight(self, weight, config, op, op_type, op_name):
"""user should know where dequantize goes and implement it in quantize method
we now do not provide dequantize method
"""
quantize should overload this method to quantize weight.
This method is effectively hooked to :meth:`forward` of the model.

Parameters
----------
weight : Tensor
weight that needs to be quantized
config : dict
the configuration for weight quantization
"""
raise NotImplementedError("Quantizer must overload quantize_weight()")

def quantize_output(self, output, config, op, op_type, op_name):
"""
quantize should overload this method to quantize output.
This method is effectively hooked to :meth:`forward` of the model.

Parameters
----------
output : Tensor
output that needs to be quantized
config : dict
the configuration for output quantization
"""
raise NotImplementedError("Quantizer must overload quantize_output()")

def quantize_input(self, *inputs, config, op, op_type, op_name):
"""
quantize should overload this method to quantize input.
This method is effectively hooked to :meth:`forward` of the model.

Parameters
----------
inputs : Tensor
inputs that needs to be quantized
config : dict
the configuration for inputs quantization
"""
raise NotImplementedError("Quantizer must overload quantize_input()")


def _instrument_layer(self, layer, config):
"""
Create a wrapper forward function to replace the original one.

Parameters
----------
layer : LayerInfo
the layer to instrument the mask
config : dict
the configuration for quantization
"""
assert layer._forward is None, 'Each model can only be compressed once'
if not _check_weight(layer.module):
_logger.warning('Module %s does not have parameter "weight"', layer.name)
return
assert "quant_types" in config, 'must provide quant_types in config'
assert isinstance(config["quant_types"], list), 'quant_types must be list type'
Cjkkkk marked this conversation as resolved.
Show resolved Hide resolved

if 'weight' in config["quant_types"]:
if not _check_weight(layer.module):
_logger.warning('Module %s does not have parameter "weight"', layer.name)
layer._forward = layer.module.forward

def new_forward(*inputs):
weight = layer.module.weight.data
new_weight = self.quantize_weight(weight, config, op=layer.module, op_type=layer.type, op_name=layer.name)
layer.module.weight.data = new_weight
return layer._forward(*inputs)
if 'input' in config["quant_types"]:
inputs = self.quantize_input(inputs, config=config, op=layer.module, op_type=layer.type, op_name=layer.name)

if 'weight' in config["quant_types"] and _check_weight(layer.module):
weight = layer.module.weight.data
new_weight = self.quantize_weight(weight, config, op=layer.module, op_type=layer.type, op_name=layer.name)
layer.module.weight.data = new_weight
result = layer._forward(*inputs)
layer.module.weight.data = weight
else:
result = layer._forward(*inputs)

layer.module.forward = new_forward
if 'output' in config["quant_types"]:
result = self.quantize_output(result, config, op=layer.module, op_type=layer.type, op_name=layer.name)

return result

layer.module.forward = new_forward

def _check_weight(module):
try:
Expand Down
9 changes: 8 additions & 1 deletion src/sdk/pynni/tests/test_compressor.py
Original file line number Diff line number Diff line change
Expand Up @@ -114,7 +114,14 @@ def test_torch_pruner(self):

def test_torch_quantizer(self):
model = TorchMnist()
torch_compressor.NaiveQuantizer(model, [{'op_types': ['default']}]).compress()
configure_list = [{
'quant_types': ['weight'],
'quant_bits': {
'weight': 8,
},
'op_types':['Conv2d', 'Linear']
}]
torch_compressor.NaiveQuantizer(model, configure_list).compress()


if __name__ == '__main__':
Expand Down