Embedding
ModuleEmbedding(num_embeddings: int, embedding_dim: int, padding_idx: int | None = None)Implementing kernel
C++ engine symbols that back this Python API.Quantized embedding lookup table — per-row int8, dequantize-on-lookup.
The inference-time replacement that lucid.quantization.convert
installs for a calibrated float lucid.nn.Embedding. Embedding
tables dominate the parameter budget of large-vocabulary language models —
a (50000, 768) table is ~150 MB in float32 — so storing the table as
int8 is the single largest memory win quantization offers on the text side,
and this layer is where it lands.
Representation. The learned table (num_embeddings, embedding_dim) is
quantized once, at from_float time, into an int8 code tensor plus
a per-row (per-token) scale / zero_point; the float table is then
dropped. Because each vocabulary row gets its own scale, tokens with very
different embedding magnitudes are all tracked tightly — far more accurately
than a single per-tensor scale. Each forward dequantizes the table back to
float and runs the ordinary lucid.nn.functional.embedding lookup, so
the layer needs no int8 gather kernel and runs unchanged on any device.
Encoding happens once; decode + gather run on every forward. For row , column :
where are the row- scale / zero-point (symmetric
qint8, so ) and the -th input index.
The lookup gathers dequantized rows; only rows actually indexed are touched.
Parameters
num_embeddingsintembedding_dimintpadding_idxint or None= NoneNone.Attributes
Notes
- Instances are normally produced by
lucid.quantization.convert(orfrom_float), not constructed directly: a bare instance holds a zeroed table with identity qparams and returns zeros untilfrom_float/load_state_dictpopulates the buffers. - The table is quantized per-row on axis 0 (one
scaleper token) with a symmetricqint8grid ([-128, 127]). When the source module carries noqconfig, a default per-channelqint8observer calibrates the rows duringfrom_float. - Memory: the int8 codes plus the per-row float scale shrink the table
payload ~
3.55xversusfloat32— the dominant checkpoint saving for text models, whose weight mass is mostly embeddings. - This layer wins on memory, not compute — the gather runs in float after an on-the-fly dequantize.
Examples
>>> import lucid, lucid.nn as nn
>>> import lucid.quantization as Q
>>> emb = nn.Embedding(1000, 64)
>>> qemb = nn.quantized.Embedding.from_float(emb) # per-row int8 table
>>> qemb.weight_int8.dtype
int8
>>> qemb(lucid.tensor([1, 5, 999])).shape # dequantize-on-lookup
(3, 64)
A directly constructed layer is zeroed — a common mistake is to use it
without from_float / load_state_dict:
>>> broken = nn.quantized.Embedding(1000, 64)
>>> bool((broken.weight_int8 == 0).all().item())
TrueSee Also
- lucid.nn.quantized.EmbeddingBag—The pooled (bag-reducing) analogue.
- lucid.nn.quantized.Linear—Per-channel int8 weight for dense layers.
- lucid.quantization.convert—Installs this layer from a calibrated model.