QDtype
QDtype(name: str, bits: int, signed: bool, quant_min: int, quant_max: int, storage: str)Descriptor for a quantized integer grid — bits, signedness, range, storage.
Where a QScheme fixes the geometry of the affine map, a QDtype
pins the target grid itself: how many bits the codes use, whether they are
signed, the inclusive [quant_min, quant_max] range they span, and the ordinary
Lucid dtype that physically stores them. Every observer and fake-quant reads
quant_min / quant_max off a QDtype to clamp its codes.
Unlike the reference framework, Lucid does not add quantized dtypes to the C++
engine's dtype enum; a QDtype is a pure-Python descriptor and the integer
codes live in an ordinary storage dtype. This keeps the whole quantization
subsystem off the engine's critical path until a real low-precision GEMM is wired
in, and lets 4-bit codes ride inside int8 until bit-packing lands.
Four instances are exported as module-level constants, spanning the grids:
qint8— 8-bit signed ([-128, 127]), stored inint8. The default weight grid: symmetric weights centre on 0, so a signed grid uses its full range. The workhorse of int8 inference.quint8— 8-bit unsigned ([0, 255]), stored inint16(the engine has nouint8, so codes ride in the next wider signed type). The default activation grid: post-ReLU activations are , so an unsigned grid spends all 256 codes on the range that actually occurs.qint4— 4-bit signed ([-8, 7]), stored (unpacked) inint8until 4-bit packing lands. The aggressive weight grid for the real MLX GEMM: about 2x smaller than int8 for a modest accuracy cost, used for memory-bound decode.qint32— 32-bit signed ([-2**31, 2**31 - 1]), stored inint32. The accumulator grid: int8 × int8 partial sums accumulate in int32 before requantization, so it rarely appears as a weight / activation dtype.
Parameters
namestr"qint8", "quint8", …); also drives
__repr__ and appears in module extra_repr strings.bitsint4 / 8 / 32).signedboolzero_point is pinned
(0 when signed, mid-range when unsigned).quant_minintquant_maxintstoragestrquint8
maps to int16 (no engine uint8); qint4 maps to int8 (unpacked)
until 4-bit packing lands.Attributes
name, bits, signed, quant_min, quant_max, storageQDtype is an immutable value object
(@dataclass(frozen=True)), so instances can be shared and compared by value.Notes
- Grid width.
quant_max - quant_min + 1 == 2 ** bitsfor every instance (256for the 8-bit grids,16forqint4). - Signed / unsigned pairing with schemes. A symmetric scheme + signed dtype
pins
zero_pointto 0; a symmetric scheme + unsigned dtype pins it to the grid midpoint. Affine schemes solve forzero_pointregardless. - The MLX low-precision GEMM only consumes
bitsin {4, 8};qint32is an accumulator descriptor, not a kernel input. repr(qint8)is"lucid.quantization.qint8"— the canonical import path, not the field dump — so the descriptor round-trips visually.
Examples
>>> import lucid.quantization as Q
>>> Q.qint8.bits, Q.qint8.signed, (Q.qint8.quant_min, Q.qint8.quant_max)
(8, True, (-128, 127))
>>> Q.quint8.signed, (Q.quint8.quant_min, Q.quint8.quant_max)
(False, (0, 255))
>>> Q.qint4.quant_max - Q.qint4.quant_min + 1 # 2 ** 4
16
>>> repr(Q.qint8)
'lucid.quantization.qint8'See Also
QScheme—The companion descriptor fixing grid geometry (granularity / symmetry).- lucid.quantization.calculate_qparams—Reads
quant_min/quant_maxfor qparams. - lucid.quantization.QParams—Bundles a
QDtypewith a scheme and params.
Used by 15
- lucid.nn.quantized._utils
- lucid.nn.quantized.activation
- lucid.nn.quantized.conv
- lucid.nn.quantized.conv_transpose
- lucid.nn.quantized.dynamic.linear
- lucid.nn.quantized.dynamic.rnn
- lucid.nn.quantized.functional_module
- lucid.nn.quantized.linear
- lucid.nn.quantized.modules
- lucid.quantization
- lucid.quantization._fake_quantize
- lucid.quantization._functional
… 3 more