MinMaxObserver
ObserverBaseMinMaxObserver(qscheme: QScheme = per_tensor_affine, qdtype: QDtype = quint8, eps: float = 1e-08)Per-tensor observer tracking the global running min / max of the input.
The simplest and most common calibration statistic: on every batch it folds the
tensor's overall min / max into a cumulative running range, and
calculate_qparams distills that single [min_val, max_val] into one
per-tensor (scale, zero_point) pair. It is the natural default for activations
and a serviceable choice for weights, but because the running range only ever grows,
a single spike in any calibration batch permanently widens it — wasting codes on a
range that most values never occupy.
When to pick it. Reach for MinMaxObserver when the tensor's distribution is
well-behaved (roughly bounded, light-tailed) and you want the cheapest possible
calibration. If a few calibration batches carry outliers, prefer
MovingAverageMinMaxObserver, whose EMA lets the range relax back toward
the bulk of the data; for heavy-tailed activations where clipping the tails buys real
accuracy, prefer HistogramObserver. For weights, the per-channel
PerChannelMinMaxObserver is almost always tighter.
Per batch the running range is the cumulative min/max:
seeded from , then reduced to qparams by the affine map of
lucid.quantization.calculate_qparams (range first widened to include 0):
(or the symmetric form of the base class if a symmetric qscheme is passed).
Parameters
zero_point to fit an
asymmetric range tightly (e.g. post-ReLU activations).quint8 gives the unsigned grid [0, 255].epsfloat= 1e-8scale — avoids a zero scale on a constant input
(min_val == max_val).Attributes
Notes
- Cumulative, not per-batch.
min_val/max_valare monotone: they only widen across calibration, so the final range reflects the extreme of all data seen, not any single batch. This is the source of the outlier sensitivity. - Buffer re-registration.
forwardupdates the range viaregister_buffer(same name), per theObserverBasein-place-update contract. - Symmetric vs affine. With a symmetric
qschemethe base-class math pinszero_point(0 for a signed dtype, mid-range for unsigned); with an affine schemezero_pointis free. - Uncalibrated. Before the first
forwardthe range is[+inf, -inf]; callingcalculate_qparamson it yields a meaningless (infinite) scale — always calibrate first. - Not the default in
lucid.quantization.get_default_qconfig(which usesHistogramObserverfor activations); it is the plain building block the EMA and per-channel variants specialise.
Examples
>>> import lucid
>>> import lucid.quantization as Q
>>> obs = Q.MinMaxObserver()
>>> for _ in range(4): # feed several calibration batches
... _ = obs(lucid.randn(32, 256))
>>> scale, zero_point = obs.calculate_qparams()
>>> bool(scale.item() > 0)
True
An *uncalibrated* observer still holds the [+inf, -inf] seed, so its range is
degenerate until it has seen data:
>>> fresh = Q.MinMaxObserver()
>>> import math
>>> math.isinf(fresh.min_val.item()), math.isinf(fresh.max_val.item())
(True, True)See Also
- lucid.quantization.MovingAverageMinMaxObserver—EMA variant, robust to outliers.
- lucid.quantization.PerChannelMinMaxObserver—Per-channel granularity for weights.
- lucid.quantization.HistogramObserver—Tail-clipping for heavy-tailed activations.
- lucid.quantization.calculate_qparams—The range →
(scale, zero_point)reduction. - lucid.quantization.FakeQuantize—Wraps an observer for QAT.