Conv2d
Conv2dConv2d(args: object = (), qconfig: QConfig | None = None, kwargs: object = {})Implementing kernel
C++ engine symbols that back this Python API.Quantization-aware 2-D convolution — trainable float kernel, fake-quant per forward.
The training-time stand-in that lucid.quantization.prepare_qat installs in
place of a float lucid.nn.Conv2d — the workhorse of every quantized vision
backbone. It keeps a trainable float kernel but routes both the weight and the
layer output through a lucid.quantization.FakeQuantize on every forward, so
the network feels the int8 rounding error as it learns and adapts its kernel to the
eventual int8 grid. This closes the train / inference accuracy gap: a network trained
with the rounding baked in barely moves when its weights are finally frozen to int8,
whereas plain post-training quantization perturbs a kernel that never saw rounding.
Straight-through estimator (STE). Rounding is a step function whose derivative is
zero almost everywhere, so a literal round would block all gradient flow. The
fake-quant rounds in the forward pass but, in the backward pass, pretends it was the
identity — the incoming gradient passes straight through to the float kernel (clipped
to the quantization range). The kernel thus stays fully trainable while every forward
value it produces is the dequantized number an int8 conv kernel would have computed.
Where it sits. lucid.quantization.prepare_qat deep-copies the float model
and swaps each lucid.nn.Conv2d for this class, attaching the weight and
activation FakeQuantize modules from the layer's qconfig. After training,
lucid.quantization.convert reads the trained kernel together with the
activation observer's final (scale, zero_point) and bakes them into an inference
lucid.nn.quantized.Conv2d whose weight is stored as (typically
per-output-channel) int8 codes.
On each forward the kernel is fake-quantized, the ordinary F.conv2d runs, and the
result is fake-quantized onto the observed activation grid:
where is the 2-D cross-correlation, are the scale /
zero-point the enclosing FakeQuantize derives from its observer (per-output-channel
for the weight), and are the grid bounds. The
straight-through unit derivative is what keeps the layer differentiable despite the
non-differentiable round.
Parameters
lucid.quantization.FakeQuantize modules applied during training.
Required — constructing the layer without one raises ValueError.*argsobject= ()lucid.nn.Conv2d constructor (in_channels, out_channels,
kernel_size, stride, padding, dilation, groups, bias),
which owns the actual trainable kernel and bias.**kwargsobject= ()lucid.nn.Conv2d constructor (in_channels, out_channels,
kernel_size, stride, padding, dilation, groups, bias),
which owns the actual trainable kernel and bias.Attributes
weight_fake_quantFakeQuantizeqconfig.weight(); rounds the float
kernel each forward and tracks its range so convert can pick the int8 qparams.activation_post_processFakeQuantizeqconfig.activation(); calibrates
the activation grid whose (scale, zero_point) convert later bakes in.Notes
- STE differentiability.
roundis applied forward but the gradient is passed through as the identity, so the float kernel trains normally. - Deferred string padding. Integer / tuple
paddingis supported; string padding ("same"/"valid") is deferred, matching the quantized conv. - Both directions are wired for you.
lucid.quantization.prepare_qatswaps the float conv in;lucid.quantization.convertfolds this layer out into the matchinglucid.nn.quantized.Conv2d. Manual construction is rarely needed and requires an explicitqconfig. - Training is slower, not faster. Only the int8 numerics are simulated; the
convolution itself still runs in float with two extra fake-quant ops, so the speed /
memory win arrives only after
convert.
Examples
>>> import lucid, lucid.nn as nn
>>> import lucid.quantization as Q
>>> import lucid.nn.qat as nnqat
>>> model = nn.Sequential(nn.Conv2d(3, 8, 3, padding=1))
>>> qat = Q.prepare_qat(model) # nn.Conv2d -> qat.Conv2d
>>> isinstance(qat[0], nnqat.Conv2d)
True
>>> loss = (qat(lucid.randn(2, 3, 8, 8)) ** 2).mean()
>>> loss.backward() # STE routes grads to the float kernel
>>> qat.eval()
>>> qc = Q.convert(qat) # -> quantized.Conv2d (int8 weight)
>>> type(qc[0]).__name__
'Conv2d'
A qconfig is mandatory — constructing the layer directly without one raises:
>>> nnqat.Conv2d(3, 8, 3)
Traceback (most recent call last):
...
ValueError: qat conv requires a qconfigSee Also
- lucid.nn.quantized.Conv2d—The int8 inference conv
convertbakes this into. - lucid.nn.qat.ConvReLU2d—QAT conv with a fused ReLU before the output observer.
- lucid.quantization.prepare_qat—Swaps the float
Conv2dfor this QAT layer. - lucid.quantization.convert—Bakes the trained QAT layer into the quantized conv.
Used by 1
Constructors
1__init__
→None__init__(args: object = (), qconfig: QConfig | None = None, kwargs: object = {})Initialise the driver; the executable itself is lazy.
Parameters
modelfused_step for semantics.loss_fnfused_step for semantics.optimizerfused_step for semantics.grad_scalerGradScalerfused_step. When provided and enabled, the
trace records the scale → unscale → found_inf → conditional
update plumbing entirely inside the executable so the
user-facing step is identical to a no-scaler call.Raises
ValueErroroptimizer exposes no trainable parameters (every
param_group is empty).Class methods
1Instance methods
1Generate anchors for all FPN levels.
Parameters
feature_mapsimage_sizestridesReturns
TensorList of (H_l × W_l × A_l, 4) anchor tensors, one per level.