Model Quantization skill for Codex
Smaller, faster models with the quality loss measured. Codex records a full-precision baseline first, then picks a scheme by hardware and bottleneck: FP8 on recent NVIDIA GPUs, INT4 weight-only GPTQ or AWQ when memory is the limit, ONNX Runtime INT8 for CPU encoders. Calibration uses real prompts, sensitive layers stay in higher precision, and the result ships only if task evals, critical prompts, memory and throughput on the target server meet the agreed budget.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- model-quantization Installs to ~/.agents/skills/model-quantization/ for every project on this machine.
curl -fsSL codexskills.dev/cli.mjs -o codex-skills.mjsnode codex-skills.mjs install model-quantization The CLI also searches, updates and removes skills. Run it with no arguments to pick from a list. Needs Node 20.12+.
curl -fsSL codexskills.dev/install.sh \ | sh -s -- --project model-quantization Installs to .agents/skills/model-quantization/ in the current repository. Commit it and everyone who runs Codex there gets it.
curl -fsSLO codexskills.dev/downloads/model-quantization.zipunzip -o model-quantization.zip -d ~/.agents/skills/ The archive holds the skill folder with its generated agents/openai.yaml. Unzip into .agents/skills/ to scope it to a repo.
& ([scriptblock]::Create((irm codexskills.dev/install.ps1))) model-quantization PowerShell 5.1 or 7. Installs to %USERPROFILE%\.agents\skills\. Add -Project for the current repo.
104-line POSIX script. No sudo, no telemetry. Read install.sh
Use it in Codex
Start a prompt with $model-quantization, or pick it from /skills.
You can also just describe the task: Codex reads the description below and loads the playbook when your request matches.
What Codex reads before loading it
Quantizes neural networks and LLMs and measures what it costs in quality - picking weight-only versus weight-and-activation schemes (INT8, INT4, FP8, NF4), GPTQ and AWQ with llm-compressor, bitsandbytes for quick 4-bit loading, torchao and ONNX Runtime dynamic and static INT8 for classic models, calibration data selection, and before-and-after evaluation of perplexity, task accuracy, latency and memory with lm-evaluation-harness. Use when a model does not fit in GPU memory, inference is too slow or expensive, or when choosing a quantized checkpoint format for vLLM, Transformers or ONNX Runtime.
Use it when
- A model does not fit in available GPU memory
- Inference cost or latency must drop without retraining
- Choosing a quantized checkpoint format for vLLM, Transformers or ONNX Runtime
- You need numbers on how much quality quantization costs
Use something else when
- You want GGUF files for llama.cpp or Ollama on CPU and laptops: use llama-cpp-gguf
- The model already fits and the bottleneck is serving configuration: use vllm-serving
How to check the result
Baseline and quantized checkpoints run the same task evals, critical prompts and load tests on the target server, and the report shows the quality delta within budget alongside memory and throughput gains.
The mistake it prevents
Calibrating on generic web text and judging quality only on public benchmarks, which hides damage on your own prompts, structured output and long context.
Quantization is done when the quantized model meets an agreed quality budget against the full-precision baseline on the tasks that matter (for example, no more than 1 point drop on the main eval and no failure on a fixed set of critical prompts), and the memory and latency gains are measured on the target hardware and serving stack, not assumed.
Inspect first
- Read
AGENTS.md; identify the model (architecture, size, dtype), the serving runtime (vLLM, Transformers, TGI, llama.cpp, ONNX Runtime, mobile) and the hardware (GPU generation, CPU, Apple silicon). - Find or build the evaluation: a task eval with a pass threshold plus 50-200 representative prompts. Without a baseline score, quantization quality is a guess.
- Record the baseline: eval scores, tokens/s or requests/s, p95 latency, peak memory.
Choose the scheme
| Goal / hardware | Default | Why |
|---|---|---|
| LLM on NVIDIA Hopper/Ada/Blackwell, serving with vLLM | FP8 weights and activations (FP8_DYNAMIC) | Near-lossless, no calibration data, real speedups |
| LLM must fit on smaller GPUs, memory-bound decoding | INT4 weight-only (W4A16) via GPTQ or AWQ | ~4x smaller weights; small quality loss when calibrated well |
| High-throughput LLM serving on Ampere+ | INT8 weights and activations (W8A8, SmoothQuant + GPTQ) | Compute speedup at large batch |
| Quick experiment or QLoRA fine-tuning in Transformers | bitsandbytes NF4 4-bit | One-line load; slower inference than GPTQ/AWQ kernels |
| CPU or laptop inference | GGUF k-quants (see llama-cpp-gguf) | Best CPU kernels |
| Classic CNN/transformer encoder on CPU | ONNX Runtime INT8 dynamic (static for CNNs) | Easy 2-4x on CPU |
| PyTorch-native model in a PyTorch service | torchao weight-only INT8/INT4 | Stays in PyTorch, works with torch.compile |
Rules of thumb:
- Weight-only helps when decoding is memory-bound (small batch, LLM generation). Weight-and-activation helps when compute-bound (large batch, prefill, encoders).
- Smaller models lose more from 4-bit than large ones; below ~3B parameters, test INT8 or FP8 before INT4.
- Keep
lm_headand embeddings in higher precision unless you have measured otherwise.
LLMs with llm-compressor (output loads in vLLM)
Needs a GPU, pip install llmcompressor (network) and the model weights locally.
from transformers import AutoModelForCausalLM, AutoTokenizer
from datasets import load_dataset
from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import GPTQModifier, QuantizationModifier
MODEL = "path/or/hub-id"
model = AutoModelForCausalLM.from_pretrained(MODEL, torch_dtype="auto")
tok = AutoTokenizer.from_pretrained(MODEL)
# FP8 dynamic: no calibration data
fp8 = QuantizationModifier(targets="Linear", scheme="FP8_DYNAMIC", ignore=["lm_head"])
# INT4 weight-only with GPTQ: calibrate on data that looks like production prompts
ds = load_dataset("json", data_files="calib.jsonl", split="train").shuffle(seed=0).select(range(512))
ds = ds.map(lambda r: tok(tok.apply_chat_template(r["messages"], tokenize=False),
max_length=2048, truncation=True, add_special_tokens=False),
remove_columns=ds.column_names)
w4 = GPTQModifier(targets="Linear", scheme="W4A16", ignore=["lm_head"])
oneshot(model=model, dataset=ds, recipe=w4, max_seq_length=2048, num_calibration_samples=512)
model.save_pretrained("out/model-w4a16", save_compressed=True)
tok.save_pretrained("out/model-w4a16")
- AWQ is available as
AWQModifierin llm-compressor; the standalone AutoAWQ and AutoGPTQ projects are no longer maintained, so do not start new work on them. - Calibration data: 256-512 samples from your real prompt distribution (chat template applied) beat generic web text. For multilingual or code models, include those domains.
- Serve with
vllm serve out/model-w4a16; vLLM reads the quantization config from the checkpoint (see vllm-serving).
Quick 4-bit in Transformers (bitsandbytes)
import torch
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
bnb = BitsAndBytesConfig(load_in_4bit=True, bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16, bnb_4bit_use_double_quant=True)
model = AutoModelForCausalLM.from_pretrained(MODEL, quantization_config=bnb, device_map="auto")
Good for fitting a model to experiment or for QLoRA (see lora-fine-tuning). For production serving throughput, export GPTQ/AWQ/FP8 instead.
Classic models
ONNX Runtime dynamic INT8 (CPU, transformers encoders, MLPs):
from onnxruntime.quantization import quantize_dynamic, QuantType
quantize_dynamic("model.onnx", "model.int8.onnx", weight_type=QuantType.QInt8)
Static INT8 (CNNs) needs a CalibrationDataReader that yields ~100-500 real inputs and
quantize_static(...); run ONNX Runtime’s pre-processing (python -m onnxruntime.quantization.preprocess --input model.onnx --output model.pre.onnx) first.
torchao (PyTorch-native):
from torchao.quantization import quantize_, Int8WeightOnlyConfig
quantize_(model, Int8WeightOnlyConfig()) # in place; then torch.compile(model)
The older torch.ao.quantization eager APIs are being phased out in favour of torchao.
Measuring quality loss
Run the same evals on baseline and quantized checkpoints, same prompts, same decoding (greedy or fixed seed):
lm_eval --model vllm --model_args pretrained=out/model-w4a16,max_model_len=4096 \
--tasks gsm8k,mmlu,hellaswag --batch_size auto --output_path results/w4a16
lm_eval --model vllm --model_args pretrained=$BASELINE,max_model_len=4096 \
--tasks gsm8k,mmlu,hellaswag --batch_size auto --output_path results/baseline
- Public benchmarks are a smoke test. The decision rests on your task eval (see llm-evals) and the critical-prompt set; quantization damage concentrates in math, long context, code and low-resource languages.
- Also compare perplexity on a held-out slice of your own text; a jump of more than a few percent signals a bad calibration set or a layer that should stay unquantized.
- Diff outputs on the critical prompts side by side; look for broken JSON, repetition and language switching.
Measuring the gain
On the target hardware and server: peak GPU memory, max concurrent sequences or KV cache size, tokens/s at the production batch size, time to first token and p95 latency. A 4-bit model that is smaller but slower at your batch size is not a win.
Workflow
- Read
AGENTS.md; record model, runtime, hardware - Baseline evals, critical prompts, latency and memory
- Choose scheme from the table; prepare calibration data from real prompts
- Quantize; save with config and the exact library versions
- Re-run evals and benchmarks; diff critical prompts
- Decide against the quality budget; if it fails, try a higher-precision scheme or leave sensitive layers unquantized before trying more calibration data
Output template
## Quantization report: <model>
Scheme: W4A16 GPTQ (llm-compressor <ver>), calibration: 512 prod prompts, lm_head kept BF16
| Metric | BF16 | W4A16 | Delta |
|---|---|---|---|
| Task eval (pass rate) | 0.842 | 0.835 | -0.7 pt |
| GSM8K | 0.781 | 0.762 | -1.9 pt |
| Peak GPU memory | 31.2 GB | 10.4 GB | -67% |
| Tokens/s at batch 32 | 2,410 | 3,050 | +27% |
Critical prompts: 48/50 unchanged, 2 minor wording changes, 0 failures
Decision: ship / do not ship, reason
Gotchas
- Licence terms of the base model still apply to quantized derivatives; check before publishing a checkpoint, and get user confirmation before uploading anywhere.
- A quantized checkpoint is tied to kernels: a format the runtime cannot accelerate falls back to slow paths or fails to load. Test loading in the exact serving version.
- Calibrating on the eval set inflates scores; keep calibration and eval data disjoint.
- KV cache, not weights, often dominates memory at long context; consider FP8 KV cache in the server before squeezing weights further.
What the zip contains
SKILL.md plus the interface file below. Nothing runs on install.
agents/openai.yaml
Gives the Codex app a display name, a one-line description and a starter prompt for this skill.
interface:
display_name: "Model Quantization"
short_description: "Smaller, faster models with the quality loss measured"
default_prompt: "$model-quantization Quantize our fine-tuned 8B model to 4-bit for vLLM and tell me how much quality we lose" About this skill
Written and maintained by the Codex Skills editors to the same editorial standard as every skill here: a selective description, a real workflow, known pitfalls and a way to verify the work. First published , last updated . MIT licensed. Something wrong or out of date? Tell us.