mixle.models.qat module¶
Quantization-aware training (QAT): straight-through fake-quant, composed by wrapping.
Post-training quantization (mixle.task.quantize.quantize_mlp()) quantizes an already-trained
float model’s weights to int4/int8 after the fact – the model never saw its own quantization
error during training, so gradient descent had no chance to route around it. QAT simulates that
error DURING training instead: every forward pass runs the weight through a real
quantize-then-dequantize round trip (the exact int4 math already in mixle.task.quantize –
this module does not reimplement it), so the optimizer sees a loss landscape shaped by the precision
it will actually run at. The backward pass uses the straight-through estimator (Bengio et al.): the
quantize/dequantize round trip is a step function with zero gradient almost everywhere, so STE simply
copies the incoming gradient through as if the round trip were the identity – the standard trick
that makes QAT trainable at all.
Composition follows this codebase’s established pattern (see mixle.experimental.program.lora,
the peft/LoRA-style wrapper): QAT is a module-level wrapper around nn.Linear, not a change to the
training loop. GradLeaf’s M-step only ever calls
module.log_density(x) and back-propagates through whatever module is – it has no idea some of
that module’s Linear layers fake-quantize their weights on every forward call, so QAT drops into
GradLeaf.fit/estimate (and, when it exists, the F1 distributed trainer and J4 distillation
students that also route through the same log_density contract) with no changes to
``grad_leaf.py``.
model = build_causal_lm(vocab=…, d_model=…, n_layer=…, n_head=…, block=…) apply_qat(model) # every nn.Linear now fake-quantizes its weight to int4 leaf = GradLeaf(SomeWrapperWithLogDensity(model)) fitted = leaf.estimator().estimate(None, suff_stat) # trains QAT-aware, unmodified M-step
F1 (a real distributed trainer skeleton) and J4 (distillation students) are separate, not-yet-built
roadmap items; this module does not depend on either. What is real and tested here: the STE fake-quant
op itself, and that wrapping a real transformer’s Linear layers with it and training end-to-end beats
post-hoc PTQ at matched int4 size (see mixle/tests/qat_test.py).
- fake_quantize(x, *, bits=4, clip_percentile=None)[source]
Straight-through fake-quantize
xtobits(int4 or int8, permixle.task.quantize._QMAX): forward returns the real quantize->dequantize round trip, backward passes the gradient through unchanged (STE).
- fake_quantize_int4(x, *, clip_percentile=None)[source]
fake_quantize(x, bits=4)– the int4 case this roadmap item targets.
- class QATWrapper(base, *, bits=4, clip_percentile=None, enabled=True)[source]
Bases:
ModuleWrap an
nn.Linearso its weight is straight-through fake-quantized on every forward call: the module computesF.linear(x, fake_quantize(weight), bias)instead ofF.linear(x, weight, bias). Bias stays fp32, matching PTQ’s scheme (mixle.task.quantize.quantize_mlp()) where only weights are quantized.Drop-in composition:
QATWrapper(linear)has the sameforward(x) -> Tensorcontract as theLinearit wraps, so it slots into any module tree (seeapply_qat()) without the surrounding model or training loop changing at all.- forward(x)[source]
Define the computation performed at every call.
Should be overridden by all subclasses.
Note
Although the recipe for forward pass needs to be defined within this function, one should call the
Moduleinstance afterwards instead of this since the former takes care of running the registered hooks while the latter silently ignores them.
- apply_qat(model, *, bits=4, clip_percentile=None)[source]
Replace every
nn.Linearundermodelin place with aQATWrapper, so the whole model trains quantization-aware (straight-through int4 fake-quant on every Linear weight, every forward call). Mirrorsmixle.experimental.program.lora’s wrapping pattern: walknamed_children, swapLinearleaves, recurse into everything else. Weight-tied layers (e.g.CausalLM.headsharingtok.weight) wrap cleanly – only the wrapped module’sforwardchanges, the underlyingnn.Parameter(and anything else pointing at it) is untouched. Returnsmodel(mutated in place) for chaining.
- set_fake_quant_enabled(model, enabled)[source]
Toggle every
QATWrapperundermodelon/off in place.enabled=Falseruns the model at its real fp32 weights (e.g. to check that QAT training didn’t wreck full-precision quality);enabled=True(the default afterapply_qat()) restores the fake-quant forward. Returnsmodelfor chaining.