quantize_dynamic(model: nn.Module, qconfig_spec: set[type] | None = None, dtype: QDtype = qint8, inplace: bool = False)Dynamically quantize a model — int8 weights, runtime-quantized activations.
quantize_dynamic is the whole dynamic-PTQ workflow in one call: unlike
the static prepare → calibrate → convert flow it needs no
calibration data and no observers. Every module whose type is in
qconfig_spec (default {Linear, LSTM}) is swapped in place for its
dynamic counterpart, which stores its weight as int8 codes but measures the
activation range and quantizes activations per forward, at runtime. That
makes it the go-to for models where activation statistics vary widely between
inputs and a fixed calibrated range would be a poor fit — most notably the
Linear-heavy stacks of Transformers and RNN language models, where the weight
memory dominates and per-call activation quantization costs little.
Because activation qparams are recomputed each call, dynamic quantization
captures the 4x weight-memory win without an offline calibration pass, at
the cost of a small per-forward quantize overhead. Prefer static PTQ
(prepare / convert) when you can calibrate and want the
lowest inference latency; prefer this when calibration is impractical or the
stack is Linear-only.
When lucid.backends.quantized.use_mlx() is true, the Linear family is
routed to the real weight-only quantized_matmul MLX GEMM (the genuine
speed + memory win, W8A16 numerics), falling back to the dequantize path for
layers whose in_features is not group-size-divisible.
Parameters
modelnn.Modulenn.Linear) is handled too — it has no parent to swap
it into, so the quantized module is returned directly.qconfig_specset of module types= NoneNone (default) uses the mapping's
keys — {nn.Linear, nn.LSTM}.bits also selects the MLX
fast-path bit width when that backend is active).inplacebool= FalseTrue mutate and return model itself; if False (default)
quantize a copy.deepcopy and leave the original untouched.Returns
nn.ModuleThe dynamically-quantized, eval-mode model (or the single quantized
module when a bare target was passed).
Notes
- No calibration and no observers are inserted — the model is ready to run immediately after this call.
- Only the targeted types are swapped; everything else (activations, norms, attention softmax) stays float, so this is a partial quantization aimed at the weight-heavy layers.
- If no module of a targeted type is found, a
UserWarningis raised and the model is returned unchanged. - The model is switched to
evalmode before swapping.
Examples
>>> import lucid, lucid.nn as nn
>>> import lucid.quantization as Q
>>> mlp = nn.Sequential(nn.Linear(512, 512), nn.ReLU(), nn.Linear(512, 10))
>>> qmlp = Q.quantize_dynamic(mlp) # no calibration needed
>>> type(qmlp[0]).__name__
'Linear'
>>> qmlp(lucid.randn(1, 512)).shape
(1, 10)
A model with no targeted layers warns and comes back unchanged — e.g. a
conv-only stack, since Conv2d is not a dynamic-quant target:
>>> conv = nn.Sequential(nn.Conv2d(3, 8, 3))
>>> Q.quantize_dynamic(conv) is not None
UserWarning: quantize_dynamic(): no module of a targeted type (Linear, LSTM) found ...See Also
- lucid.quantization.prepare—Static-PTQ observer insertion (needs calibration).
- lucid.quantization.convert—Static-PTQ bake step.
- lucid.quantization.prepare_qat—Quantization-aware training entry point.
lucid.nn.quantized.dynamic.Linear—The dynamic quantized Linear installed here.