MovingAveragePerChannelMinMaxObserver
PerChannelMinMaxObserverMovingAveragePerChannelMinMaxObserver(ch_axis: int = 0, qscheme: QScheme = per_channel_symmetric, qdtype: QDtype = qint8, averaging_constant: float = 0.01, eps: float = 1e-08)Per-channel observer with an EMA of the per-channel min / max.
The per-channel analogue of MovingAverageMinMaxObserver, and the
outlier-tolerant sibling of PerChannelMinMaxObserver. It keeps an
independent range per channel along ch_axis (reducing over every other axis) but
updates each channel's [min, max] with an exponential moving average instead of a
hard cumulative min/max — so a spike in one calibration batch nudges that channel's
range only slightly and is relaxed back by later batches. calculate_qparams
still returns a length-C vector of (scale, zero_point).
When to pick it. Use it for weights when weight calibration is noisy (e.g.
running statistics observed during QAT rather than on static weights) and you want
per-channel tightness and per-batch robustness. For static weights the plain
PerChannelMinMaxObserver is usually enough; for activations, use the
per-tensor MovingAverageMinMaxObserver or HistogramObserver.
With the values of channel in the current batch, the first batch seeds each channel directly and every later batch applies the per-channel EMA:
where is averaging_constant. The reduction to per-channel qparams is
the (default symmetric, signed) map inherited from
PerChannelMinMaxObserver, pinning each z_c = 0.
Parameters
ch_axisint= 0[-128, 127] grid).averaging_constantfloat= 0.01epsfloat= 1e-8scale.Attributes
Notes
- First-batch seeding is explicit. Because the scalar
±infseed cannot be broadcast into an elementwisewhereagainst a length-Cbatch range,forwardspecial-cases the first observation and copies the per-channel range in directly; the EMA recurrence runs only from the second batch on. - Not cumulative — each channel's range can shrink between batches (the whole
point of the EMA), unlike
PerChannelMinMaxObserver. - Buffer re-registration carries both the scalar-seed →
(C,)shape change and every later in-place EMA update, per theObserverBasecontract. - Symmetric vs affine behaves as in the per-channel base class; the default pins
each
zero_pointto 0.
Examples
>>> import lucid
>>> import lucid.quantization as Q
>>> obs = Q.MovingAveragePerChannelMinMaxObserver(ch_axis=0, averaging_constant=0.1)
>>> for _ in range(8):
... _ = obs(lucid.randn(12, 32)) # 12 output channels
>>> scale, zero_point = obs.calculate_qparams()
>>> scale.shape
(12,)
>>> bool((zero_point == 0).all().item()) # per-channel symmetric ⇒ z = 0
TrueSee Also
- lucid.quantization.PerChannelMinMaxObserver—Hard per-channel min/max (no smoothing).
- lucid.quantization.MovingAverageMinMaxObserver—Per-tensor EMA counterpart.
- lucid.quantization.get_default_qat_qconfig—QAT recipe using per-channel weights.
- lucid.quantization.calculate_qparams—The per-channel range → qparams reduction.
- lucid.quantization.FakeQuantize—Wraps an observer for QAT.