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(*args, **kwargs)[source]
Bases:
objectTorch-absent stand-in: keeps
QATWrapperalways importable (mixle.modelsimports it unconditionally, matching every other torch-optional model in this package – seeGaussianProcessRegressor), raising only when actually constructed, since it must subclassnn.Moduleto be real andnndoes not exist here.- Parameters:
args (Any)
kwargs (Any)
- apply_qat(model, *, bits=4, clip_percentile=None, reconfigure=False)[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.