[quantization] _qparams_locked mechanism for AffineObserverBase - #866
Conversation
This commit introduces _qparams_locked mechanism for AffineObserverBase TICO-DCO-1.0-Signed-off-by: Evgenii Maltsev <e.maltsev@samsung.com>
|
Could you explain when this feature is needed? Because when |
Yes, but after |
|
Ah, I got it. Good catch. I agree with the issue raised here. In the current implementation, A separate class AffineObserverBase(ObserverBase):
def __init__(self, ...):
super().__init__(...)
...
self._qparams_locked = False
self.reset()
def reset(self) -> None:
...
self._cached_scale = self._cached_scale.new_empty((0,))
self._cached_zp = self._cached_zp.new_empty((0,), dtype=torch.int)
self._qparams_locked = False
def load_qparams(
self,
scale: torch.Tensor,
zero_point: torch.Tensor,
*,
lock: bool = True,
) -> None:
"""Load externally computed quantization parameters.
When ``lock`` is enabled, subsequent calls to ``compute_qparams()``
preserve the loaded values instead of recomputing them from collected
statistics.
"""
self._cached_scale = scale.detach().clone()
self._cached_zp = zero_point.detach().to(dtype=torch.int).clone()
self._qparams_locked = bool(lock)
if lock:
self.enabled = False
def compute_qparams(self) -> tuple[torch.Tensor, torch.Tensor]:
"""Return locked qparams or compute them from collected statistics."""
if self._qparams_locked:
if not self.has_qparams:
raise RuntimeError(
"The observer is locked but does not contain valid qparams."
)
return self._cached_scale, self._cached_zp
# Existing min/max-based qparam computation.
...With this behavior:
I would also add tests covering both the observer-level semantics and the full GPTQ conversion flow. def test_locked_qparams_survive_compute_qparams():
observer = create_test_observer()
observer.collect(
torch.tensor(
[
[-2.0, 1.0],
[-1.0, 3.0],
]
)
)
injected_scale = torch.tensor([0.1, 0.2])
injected_zero_point = torch.tensor([7, 8], dtype=torch.int)
observer.load_qparams(
injected_scale,
injected_zero_point,
lock=True,
)
scale, zero_point = observer.compute_qparams()
torch.testing.assert_close(scale, injected_scale)
torch.testing.assert_close(zero_point, injected_zero_point)The unlocked case should verify that loaded values may be replaced by values computed from the collected statistics: def test_unlocked_qparams_can_be_recomputed():
observer = create_test_observer()
observer.collect(
torch.tensor(
[
[-2.0, 1.0],
[-1.0, 3.0],
]
)
)
injected_scale = torch.tensor([123.0, 456.0])
injected_zero_point = torch.tensor([7, 8], dtype=torch.int)
observer.load_qparams(
injected_scale,
injected_zero_point,
lock=False,
)
scale, zero_point = observer.compute_qparams()
assert not torch.equal(scale, injected_scale)
assert not torch.equal(zero_point, injected_zero_point)It would also be useful to verify that def test_reset_clears_qparam_lock():
observer = create_test_observer()
observer.load_qparams(
torch.tensor([0.1]),
torch.tensor([0], dtype=torch.int),
lock=True,
)
observer.reset()
assert not observer._qparams_locked
assert not observer.has_qparams |
|
Actually, |
|
I believe GPTQ should own the final weight qparams in the current GPTQ → PTQ In The Hessian-based error compensation modifies only the remaining not-yet-quantized weights. It does not recompute the quantization grid after the final Therefore, It is possible for the observed min/max range of the final Recomputing qparams from the observed min/max of the final GPTQ weight creates a different quantization grid. During PTQ fake quantization, the already quantized GPTQ weight is then quantized again using that new grid. This second quantization is not part of the Hessian-aware GPTQ optimization and may alter the weight values and increase the reconstruction error. I suggest keeping |
|
I agree with you, changed my PR.
That's what I wanted to highlight.
Maybe we can keep this technique by introducing argument to YAML file. This technique can produce a more precise quantization grid, which may have advantages in some cases. We can conduct some tests. |
This PR introduces
_qparams_lockedmechanism for AffineObserverBaseImplemented the
_qparams_lockedmechanism inaffine_base.pywith a YAML-controlled gate that defaults to OFF (preserving original behavior). When enabled vialock_gptq_qparams: truein the PTQ stage config, GPTQ-injected scales and zero-points are preserved throughconvert()instead of being silently replaced by PTQ min/max statistics.TICO-DCO-1.0-Signed-off-by: Evgenii Maltsev e.maltsev@samsung.com