How Qwix Quantizes Any Flax Model Without Touching Its Code
You have a trained Flax model and you want it in int8 — but the model code isn't yours to edit. Qwix quantizes it anyway, by a trick that has nothing to do with the model: it swaps the ops the model calls. For the duration of one forward pass it patches jax.lax.dot_general and jnp.einsum, so every matmul routes through a quantized version while the model's own source never changes. We drive the op-swap live, compute int8/int4/fp8/nf4 grids from Qwix's real bounds, show how PTQ and QAT ride the exact same mechanism, and map what gets quantized where — plus the honest costs of patching a running program.
Concept · AI / ML. The source ↗
A free, interactive, animated visual explainer of How Qwix Quantizes Any Flax Model Without Touching Its Code — built to be understood, not skimmed.
Questions
- How does Qwix quantize a model without changing its code?
- By intercepting the operations the model calls, not the model. When you call qwix.quantize_model, Qwix installs an interceptor that monkey-patches the JAX and Flax functions a model uses to do its heavy math — jax.lax.dot_general, jax.numpy.einsum, jax.lax.conv_general_dilated, flax.linen.Module.param, and a few more. For the duration of one forward pass, calling dot_general runs Qwix's quantized version instead of the real one. The model's source line still reads nn.Dense(64)(x); it just resolves to a different function underneath. The README puts it plainly: Qwix "integrates with models without need to modify their code, so any model can be used."
- What is the difference between PTQ and QAT in Qwix?
- They are two providers riding the same interception mechanism. Both patch the same set of ops; they differ only in what the patched op does. PtqProvider (Post-Training Quantization) actually stores weights as low-bit QArrays and runs the matmul in integer arithmetic, then dequantizes — that is the fast serving path on TPU/GPU. QtProvider (the QAT path) does "fake quantization": it quantizes and immediately dequantizes inside the forward pass so the network feels the rounding error during training but the math stays in floating point. Same rails, different car — you swap PtqProvider(rules) for a QAT provider and nothing else about the wiring changes.
- How does Qwix decide which layers to quantize?
- With a list of QuantizationRule objects, each a regex plus a config. The regex (module_path, default ".*") is matched against every module's path as the model runs; the first rule that matches supplies the quantization types (weight_qtype, act_qtype), the calibration method, and the tile size. A rule with module_path=".*" quantizes everything; a more specific regex targets one block. Qwix deliberately ships no preset recipes: "different quantization schemas are achieved by combinations of quantization configs." get_unused_rules() afterwards tells you which rules never matched, so a typo'd regex is caught rather than silently ignored.
- Does Qwix quantize weights, activations, or both?
- It depends on the rule, and Qwix decides per operand as each op fires. Inside a patched dot_general, the operand that is a Flax parameter (detected by find_param) is the weight — it is quantized whenever weight_qtype is set. The other operand is an activation, and it is only quantized if act_qtype is also set. So "weight-only" quantization (int4 weights, fp16 activations) is just a rule with act_qtype left None. Granularity is per-op: dot_general and einsum support per-channel and sub-channel scales; conv_general_dilated is per-channel.
- What are int8, int4, fp8, and nf4 in Qwix and how do they differ?
- They are the numeric grids a weight can be rounded onto. int8 and int4 are uniform integer grids — evenly spaced steps, with symmetric bounds of 127.5 and 7.5 in Qwix's calibration. fp8 (float8_e4m3fn) is a floating grid with a huge dynamic range (max 448) but only three mantissa bits, so its steps get coarser as magnitude grows. nf4 (NormalFloat-4) is non-uniform: sixteen fixed buckets, quantiles of a normal distribution, clustered densely near zero — which is exactly where a trained weight row concentrates, so nf4 beats plain int4 on Gaussian weights. Because nf4 is a lookup table, Qwix notes it "use[s] a non-linear lookup table of buckets, making direct arithmetic on quantized indices mathematically invalid" — so it cannot run the fast integer-matmul path.
Related explainers
- Evals & Experimental Design
- Optimization Dynamics: Why Adam, Why Warmup, Why Cosine
- Mixture of Experts, Routed Honestly
- Design a Recommendation System (in the LLM Era)
- ML Reliability in Production
- GRPO Advantage: Z-Score Your Siblings, Line by Line
- GPU Arithmetic: the Numbers That Decide ML Systems
- What "Atomic" Means on a Filesystem vs. an Object Store