LinearReLU
LinearLinearReLU(in_features: int, out_features: int, bias: bool = True, qconfig: QConfig | None = None)Quantization-aware fused Linear + ReLU — trainable, fake-quant every forward.
A Linear whose forward applies ReLU before the output fake-quant, so the
activation observer sees the true post-ReLU (non-negative) range and calibrates a grid
that spends all of its codes on values the layer can actually emit. Fusing the two ops
also lets a single activation observer stand in for both, which is exactly the shape
lucid.quantization.convert needs to collapse them into one kernel.
Like the base Linear, the weight and the (post-ReLU) output are fake-quantized
every forward under a straight-through estimator (STE): rounding happens in the forward
pass while gradients pass straight through to the float weight, so it stays fully
trainable and learns to compensate for the eventual int8 rounding. Built from a fused
float lucid.nn.intrinsic.LinearReLU by
lucid.quantization.prepare_qat; lucid.quantization.convert folds it into
a single quantized lucid.nn.quantized.LinearReLU.
On each forward the weight is fake-quantized, F.linear runs, ReLU clips, and the
result is fake-quantized onto the observed post-ReLU grid:
where are the observer's scale / zero-point and the grid bounds. Because the activation observer sits outside the ReLU, the calibrated describe the non-negative output.
Parameters
in_featuresintlucid.nn.Linear parent.out_featuresintbiasbool= Truelucid.quantization.FakeQuantize modules applied during training.
Required — constructing the layer without one raises ValueError.Attributes
weight_fake_quantFakeQuantizeqconfig.weight(); rounds the float
weight each forward and tracks its range for convert.activation_post_processFakeQuantizeqconfig.activation(); here it
observes the post-ReLU range so the baked-in grid matches inference.Notes
- STE differentiability. Rounding is applied forward but the gradient passes through as the identity, so both the weight and the pre-ReLU activations keep clean gradients and the float weight trains normally.
- Post-ReLU calibration. Observing after the ReLU means the calibrated
(scale, zero_point)cover only , doubling the effective resolution versus observing a symmetric pre-ReLU range. - Both directions are wired for you.
lucid.quantization.prepare_qatbuilds this from a fused floatlucid.nn.intrinsic.LinearReLU;lucid.quantization.convertfolds it into the quantizedlucid.nn.quantized.LinearReLU.
Examples
>>> import lucid, lucid.nn as nn
>>> import lucid.quantization as Q
>>> import lucid.nn.qat as nnqat
>>> m = nn.Sequential(nn.Linear(32, 16), nn.ReLU())
>>> fused = Q.fuse_modules(m, [["0", "1"]]) # -> nni.LinearReLU marker
>>> qat = Q.prepare_qat(fused, Q.get_default_qat_qconfig_mapping())
>>> isinstance(qat[0], nnqat.LinearReLU)
True
>>> loss = (qat(lucid.randn(8, 32)) ** 2).mean()
>>> loss.backward() # STE trains the fused float weight
>>> qat.eval()
>>> qc = Q.convert(qat) # -> quantized.LinearReLU
>>> type(qc[0]).__name__
'LinearReLU'See Also
- lucid.nn.quantized.LinearReLU—The fused int8 inference layer
convertbakes into. - lucid.nn.intrinsic.LinearReLU—The plain-float fused marker this is built from.
- lucid.nn.qat.Linear—The un-fused QAT base class.
- lucid.quantization.fuse_modules_qat—Fuses
Linear+ReLUstraight to QAT.