Repository: open-mmlab/mmrazor (language: python)

Issue:
[Docs] update Cream readme
Thanks for your contribution and we appreciate it a lot. The following instructions would make your pull request more healthy and more easily get feedback. If you do not understand some items, don't worry, just make the pull request and seek help from maintainers.

## Motivation

Please describe the motivation of this PR and the goal you want to achieve through this PR.

## Modification

Please briefly describe what modification is made in this PR.

## BC-breaking (Optional)

Does the modification introduce changes that break the backward compatibility of the downstream repositories?
If so, please describe how it breaks the compatibility and how the downstream projects should modify their code to keep compatibility with this PR.

## Use cases (Optional)

If this PR introduces a new feature, it is better to list some use cases here and update the documentation.

## Checklist

**Before PR**:

- [ ] Pre-commit or other linting tools are used to fix the potential lint issues.
- [ ] Bug fixes are fully covered by unit tests, the case that causes the bug should be added in the unit tests.
- [ ] The modification is covered by complete unit tests. If not, please add more unit tests to ensure the correctness.
- [ ] The documentation has been modified accordingly, like docstring or example tutorials.

**After PR**:

- [ ] If the modification has potential influence on downstream or other related projects, this PR should be tested with those projects, like MMDet or MMSeg.
- [ ] CLA has been signed and all committers have signed the CLA in this PR.



Discussion / hints:

Fix patch:
diff --git a/mmrazor/models/observers/__init__.py b/mmrazor/models/observers/__init__.py
index eac6371e2..004345625 100644
--- a/mmrazor/models/observers/__init__.py
+++ b/mmrazor/models/observers/__init__.py
@@ -1,6 +1,10 @@
 # Copyright (c) OpenMMLab. All rights reserved.
 from .lsq_observer import LSQObserver
 from .minmax import EMAMinMaxObserver, MinMaxObserver
+from .minmaxfloor_observer import MinMaxFloorObserver
 from .mse import MSEObserver
 
-__all__ = ['MinMaxObserver', 'MSEObserver', 'EMAMinMaxObserver', 'LSQObserver']
+__all__ = [
+    'MinMaxObserver', 'MSEObserver', 'EMAMinMaxObserver', 'LSQObserver',
+    'MinMaxFloorObserver'
+]
diff --git a/mmrazor/models/observers/minmaxfloor_observer.py b/mmrazor/models/observers/minmaxfloor_observer.py
new file mode 100644
index 000000000..231149ad4
--- /dev/null
+++ b/mmrazor/models/observers/minmaxfloor_observer.py
@@ -0,0 +1,88 @@
+# Copyright (c) OpenMMLab. All rights reserved.
+from typing import Tuple
+
+import torch
+from torch.ao.quantization.observer import UniformQuantizationObserverBase
+
+from mmrazor.registry import MODELS
+from ..utils import _is_float_qparams, _is_symmetric_quant, sync_tensor
+
+
+@MODELS.register_module()
+class MinMaxFloorObserver(UniformQuantizationObserverBase):
+    """Calculate minmax of whole calibration dataset with floor but round.
+
+    Args:
+        dtype: Quantized data type.
+        qscheme: Quantization scheme to be used.
+        reduce_range: Reduces the range of the quantized data type by 1 bit.
+                      This is sometimes required to avoid instruction overflow.
+        quant_min: Minimum quantization value. If unspecified,
+            it will follow the 8-bit setup.
+        quant_max: Maximum quantization value. If unspecified,
+            it will follow the 8-bit setup.
+        eps: Epsilon value for float32, Defaults to
+            `torch.finfo(torch.float32).eps`.
+    """
+    min_val: torch.Tensor
+    max_val: torch.Tensor
+
+    def __init__(self,
+                 dtype=torch.quint8,
+                 qscheme=torch.per_tensor_affine,
+                 reduce_range=False,
+                 quant_min=None,
+                 quant_max=None,
+                 factory_kwargs=None,
+                 eps=torch.finfo(torch.float32).eps) -> None:
+        super().__init__(dtype, qscheme, reduce_range, quant_min, quant_max,
+                         factory_kwargs, eps)
+        factory_kwargs = torch.nn.factory_kwargs(factory_kwargs)
+        self.register_buffer('min_val',
+                             torch.tensor(float('inf'), **factory_kwargs))
+        self.register_buffer('max_val',
+                             torch.tensor(float('-inf'), **factory_kwargs))
+        if (self.qscheme == torch.per_tensor_symmetric and self.reduce_range
+                and self.dtype == torch.quint8):
+            raise NotImplementedError('Cannot reduce range for symmetric \
+                                       quantization for quint8')
+
+    def forward(self, x_orig: torch.Tensor) -> torch.Tensor:
+        """Records the running minimum and maximum of ``x``."""
+        if x_orig.numel() == 0:
+            return x_orig
+        x = x_orig.detach()  # avoid keeping autograd tape
+        x = x.to(self.min_val.dtype)
+        min_val_cur, max_val_cur = torch.aminmax(x)
+        min_val = torch.min(min_val_cur, self.min_val)
+        max_val = torch.max(max_val_cur, self.max_val)
+        self.min_val.copy_(min_val)
+        self.max_val.copy_(max_val)
+        return x_orig
+
+    @torch.jit.export
+    def calculate_qparams(self) -> Tuple[torch.Tensor, torch.Tensor]:
+        """Calculates the quantization parameters."""
+        scale, zero_point = self._calculate_qparams(self.min_val, self.max_val)
+        if not _is_symmetric_quant(self.qscheme) and not _is_float_qparams(
+                self.qscheme):
+            scale = (self.max_val - self.min_val) / float(self.quant_max -
+                                                          self.quant_min)
+            scale = torch.max(scale, self.eps)
+            zero_point = self.quant_min - torch.floor(self.min_val / scale).to(
+                torch.int)
+            zero_point = torch.clamp(zero_point, self.quant_min,
+                                     self.quant_max)
+        sync_tensor(scale)
+        sync_tensor(zero_point)
+        return scale, zero_point
+
+    @torch.jit.export
+    def extra_repr(self) -> str:
+        return 'min_val={}, max_val={}'.format(self.min_val, self.max_val)
+
+    @torch.jit.export
+    def reset_min_max_vals(self) -> None:
+        """Resets the min/max values."""
+        self.min_val.copy_(torch.tensor(float('inf')))
+        self.max_val.copy_(torch.tensor(float('-inf')))
diff --git a/mmrazor/models/quantizers/trt_quantizer.py b/mmrazor/models/quantizers/trt_quantizer.py
index f2c46844a..26c44b665 100644
--- a/mmrazor/models/quantizers/trt_quantizer.py
+++ b/mmrazor/models/quantizers/trt_quantizer.py
@@ -31,7 +31,7 @@ def __init__(self,
                          convert_custom_config_dict, equalization_qconfig_dict,
                          _remove_qconfig, init_cfg)
 
-    def prepare_for_mmdeploy(self, model, dummy_input=None, checkpoint=None)
+    def prepare_for_mmdeploy(self, model, dummy_input=None, checkpoint=None):
 
         graph = self.tracer.trace(model)
         graph_module = build_graphmodule(model, graph)


Test patch:
diff --git a/tests/test_models/test_observers/test_observer.py b/tests/test_models/test_observers/test_observer.py
index ca39ecfbd..5b99cb0fc 100644
--- a/tests/test_models/test_observers/test_observer.py
+++ b/tests/test_models/test_observers/test_observer.py
@@ -1,7 +1,15 @@
 # Copyright (c) OpenMMLab. All rights reserved.
+import copy
+import unittest
 from unittest import TestCase
 
+import torch
 import torch.nn as nn
+from torch.ao.quantization import QConfig
+from torch.ao.quantization.quantize_fx import convert_fx, prepare_fx
+from torchvision.models import resnet18
+
+from mmrazor.models.observers import MinMaxFloorObserver
 
 
 class ToyModel(nn.Module):
@@ -36,3 +44,78 @@ def test_forward(self):
 
 class TestLSQObserver(TestMinMaxObserver):
     pass
+
+
+class TestMinMaxFloorObserver(TestMinMaxObserver):
+
+    def setUp(self) -> None:
+        self.model_fp = resnet18()
+        self.w_qscheme = dict(
+            dtype=torch.qint8, qscheme=torch.per_tensor_affine)
+        self.a_qscheme = dict(
+            dtype=torch.quint8, qscheme=torch.per_tensor_affine)
+
+    def test_init(self) -> None:
+        with self.assertRaises(NotImplementedError):
+            _ = MinMaxFloorObserver(
+                dtype=torch.quint8,
+                qscheme=torch.per_tensor_symmetric,
+                reduce_range=True)
+
+    def test_prepare(self) -> None:
+        flag = False
+        model_to_quantize = copy.deepcopy(self.model_fp)
+        model_to_quantize.eval()
+        qconfig_dict = {
+            '':
+            QConfig(
+                activation=MinMaxFloorObserver.with_args(**self.a_qscheme),
+                weight=MinMaxFloorObserver.with_args(**self.w_qscheme))
+        }
+        prepared_model = prepare_fx(model_to_quantize, qconfig_dict)
+        for m in prepared_model.modules():
+            if isinstance(m, MinMaxFloorObserver):
+                flag = True
+                break
+        self.assertTrue(flag)
+
+    def test_convert(self) -> None:
+        flag = True
+        model_to_quantize = copy.deepcopy(self.model_fp)
+        model_to_quantize.eval()
+        qconfig_dict = {
+            '':
+            QConfig(
+                activation=MinMaxFloorObserver.with_args(**self.a_qscheme),
+                weight=MinMaxFloorObserver.with_args(**self.w_qscheme))
+        }
+        prepared_model = prepare_fx(model_to_quantize, qconfig_dict)
+        prepared_model(torch.randn(1, 3, 224, 224))
+        quantized_model = convert_fx(prepared_model)
+        for m in quantized_model.modules():
+            if isinstance(m, MinMaxFloorObserver):
+                flag = False
+                break
+        self.assertTrue(flag)
+
+    def test_states(self) -> None:
+        test_input = torch.Tensor([6., -8.])
+        observer = MinMaxFloorObserver(**self.w_qscheme)
+        self.assertEqual(
+            [observer.min_val, observer.max_val],
+            [torch.tensor(float('inf')),
+             torch.tensor(float('-inf'))])
+        observer.forward(test_input)
+        # per_tensor_affine
+        scale, zero_point = observer.calculate_qparams()
+        self.assertEqual(zero_point.item(), 18)
+
+    def test_forward(self) -> None:
+        test_input = torch.Tensor([1., -1.])
+        observer = MinMaxFloorObserver(**self.w_qscheme)
+        test_output = observer.forward(test_input)
+        self.assertIs(test_input, test_output)
+
+
+if __name__ == '__main__':
+    unittest.main()