Embedding
EmbeddingEmbedding(num_embeddings: int, embedding_dim: int, padding_idx: int | None = None, qconfig: QConfig | None = None)Implementing kernel
C++ engine symbols that back this Python API.Quantization-aware embedding — trainable float table, fake-quant every forward.
The training-time stand-in that lucid.quantization.prepare_qat installs in
place of a float lucid.nn.Embedding. It keeps a trainable float lookup
table but fake-quantizes that table on every forward, so the learned embeddings
experience the int8 rounding error while training and settle into values that survive
the eventual int8 storage. This is what lets a QAT-trained embedding match its float
accuracy after conversion, where a naively-quantized table would shift every token
vector by an uncontrolled rounding step.
Only the table is fake-quantized. Unlike the QAT lucid.nn.qat.Linear /
conv layers, there is no activation observer here: the layer's output is a plain
gather of rows that are already on the (fake-)quantized grid, so the output range
is fully determined by the table and a second observer would be redundant. The module
therefore carries a single weight_fake_quant and no activation_post_process.
Straight-through estimator (STE). Rounding a table entry is a step function whose
derivative is zero almost everywhere; the fake-quant rounds in the forward pass but
passes the gradient straight through to the float table in the backward pass, so the
embeddings stay fully trainable. lucid.quantization.convert then reads the
trained table and the weight observer's final qparams and bakes them into an inference
lucid.nn.quantized.Embedding whose rows are stored as int8 codes.
On each forward the table is fake-quantized, then the rows for x are gathered:
where are the scale / zero-point the weight FakeQuantize derives from
its observer and are the grid bounds. The straight-through
unit derivative keeps the table trainable despite the non-differentiable round.
Parameters
num_embeddingsintlucid.nn.Embedding parent.embedding_dimintpadding_idxint or None= Nonelucid.quantization.FakeQuantize
applied to the table during training. Required — constructing the layer without
one raises ValueError.Attributes
weight_fake_quantFakeQuantizeqconfig.weight(); rounds the float
table each forward and tracks its range so convert can pick the int8 qparams.
There is no activation_post_process — the gather output needs no separate grid.Notes
- Table-only fake-quant. Only the weight table is fake-quantized; the layer has no activation observer because a gather of quantized rows is already on the grid.
- STE differentiability.
roundis applied forward but the gradient passes through as the identity, so the float table trains normally. - Both directions are wired for you.
lucid.quantization.prepare_qatswaps the float embedding in;lucid.quantization.convertfolds this layer out into the matchinglucid.nn.quantized.Embedding. Manual construction is rarely needed and requires an explicit keywordqconfig.
Examples
>>> import lucid, lucid.nn as nn
>>> import lucid.quantization as Q
>>> import lucid.nn.qat as nnqat
>>> m = nn.Sequential(nn.Embedding(20, 8))
>>> qat = Q.prepare_qat(m, Q.get_default_qat_qconfig_mapping())
>>> isinstance(qat[0], nnqat.Embedding)
True
>>> _ = qat(lucid.tensor([[1, 5, 9]], dtype=lucid.int64)) # fake-quant + gather
>>> qat.eval()
>>> qc = Q.convert(qat) # -> quantized.Embedding
>>> type(qc[0]).__name__
'Embedding'
A qconfig is mandatory — constructing the layer directly without one raises:
>>> nnqat.Embedding(20, 8)
Traceback (most recent call last):
...
ValueError: qat.Embedding requires a qconfigSee Also
- lucid.nn.quantized.Embedding—The int8 inference embedding
convertbakes into. - lucid.nn.qat.Linear—The QAT layer that does carry an activation observer.
- lucid.quantization.prepare_qat—Swaps the float
Embeddingfor this QAT layer. - lucid.quantization.convert—Bakes the trained QAT table into the quantized embedding.