Skip to content
VoidIndex - Alexander Lee
Go back

vLLM Quantization

Edit page

One layer, one method. Weights, activations, KV cache: vLLM can shrink all three. FP8 W8A8: almost no loss. INT4 W4A16 at small batch: 2–3× cheaper per query [1].

Key points

Remember four lines: three paths, two batch sizes, GPU sets the limit, kernel is a separate choice.

Formats compared

FormatWeights / activationsHow to get itHardwareUse it for
FP8 W8A8 (fp8, compressed-tensors)FP8 E4M3 / FP8 dynamic per-tokenLLM Compressor FP8_DYNAMIC (no data) or online fp8_per_*SM ≥ 8.9; W8A16 Marlin on SM 7.5–8.6Default on Hopper; high throughput
INT8 W8A8INT8 / INT8LLM Compressor (SmoothQuant + GPTQ)Turing+, x86/Arm CPUAmpere and CPUs, no FP8
INT4 W4A16 (GPTQ, AWQ, compressed-tensors)INT4, group scales / BF16GPTQ or AWQ, needs calibration dataTuring+ via MarlinLow latency, small batch; big model on fewer GPUs
MXFP8FP8, one E8M0 scale per 1×32 blockOnline mxfp8, ModelOptBlackwell for W8A8, else W8A16Blackwell
MXFP4 / NVFP4FP4 E2M1, block scalesCheckpoint (gpt-oss, ModelOpt, Quark) or online mxfp4Blackwell, AMD (Quark), Marlin fallbackMoE experts, very large models
TorchAO, INCVariesTheir own toolsSee support matrix [2]Reuse community checkpoints
GGUF, bitsandbytes (plugins)GGUF k-quants; bnb NF4/INT8.gguf files; bnb on load or pre-quantizedInstall vllm-gguf-plugin / vllm-bnb-pluginllama.cpp models; quick memory savings
FP8 KV cacheKV cache in FP8 (E4M3/E5M2)--kv-cache-dtype fp8; calibrated scales from LLM CompressorCUDA, ROCmLong context; more concurrent requests

How it works

Assign per layer. Then three steps: create, repack, run.

A QuantizationConfig gives each layer a method: LinearMethod or FusedMoEMethod. The method does three jobs:

  1. Create: make the packed weight tensors and load the data.
  2. Repack: after loading, reorder weights into the layout the kernel wants.
  3. Run: call the quantized GEMM in forward.
flowchart TD
  A["config.json quantization_config"] --> B["Detect quant_method"]
  U["--quantization / --quantization-config"] --> B
  B --> C[QuantizationConfig]
  C --> D["get_quant_method per layer"]
  D --> E[LinearMethod]
  D --> F[FusedMoEMethod]
  E --> G["Create + load"]
  F --> G
  G --> H["Repack for kernel"]
  H --> I["Forward: quantized GEMM"]

Usage

Serve a quantized checkpoint. Auto-detected. No flags.

vllm serve RedHatAI/Meta-Llama-3.1-8B-Instruct-FP8

FP8 on load, FP8 KV cache.

vllm serve meta-llama/Llama-3.1-8B-Instruct \
  --quantization fp8_per_block \
  --kv-cache-dtype fp8

Split by layer kind, skip some layers (Python API):

from vllm import LLM

llm = LLM(
    "Qwen/Qwen3.5-35B-A3B",
    quantization="fp8_per_tensor",
    quantization_config={
        "moe": "fp8_per_block",
        "ignore": ["re:.*[qkv]_proj"],
    },
)

Make an FP8 checkpoint with LLM Compressor. Weights: round-to-nearest, per channel. Activations: per token, at run time. So: no calibration data [3].

from llmcompressor import oneshot
from llmcompressor.modifiers.quantization import QuantizationModifier

recipe = QuantizationModifier(targets="Linear", scheme="FP8_DYNAMIC", ignore=["lm_head"])
oneshot(model=model, recipe=recipe)
model.save_pretrained("Llama-3-8B-Instruct-FP8-Dynamic", save_compressed=True)

Write your own method, no vLLM changes. Subclass QuantizationConfig, add the decorator, return a method per layer from get_quant_method [2].

from vllm.model_executor.layers.quantization import register_quantization_config
from vllm.model_executor.layers.quantization.base_config import QuantizationConfig

@register_quantization_config("my_quant")
class MyQuantConfig(QuantizationConfig):
    ...  # get_name, get_min_capability, from_config, get_quant_method, ...

Numbers

8-bit: almost lossless. 4-bit: cheapest at small batch. Big batch: W8A8 wins.

Setup: Llama-3.1 Instruct, quantized with LLM Compressor, served on vLLM 0.6.4.post1, 500k+ evaluations [1].

Quality. Arena-Hard, higher is better. All 95% confidence intervals overlap.

ModelBF16FP8 W8A8INT8 W8A8INT4 W4A16
8B25.826.827.224.0
70B57.057.756.357.0
405B67.466.964.666.5

Speed. Synchronous code completion, latency in seconds.

Model / GPUsBF16 latencyINT4 W4A16 latencyCost per query, down by
8B, 1×A600024.59.72.39×
70B, 4×A600061.739.22.96×
405B, 16×H100 → 4×H10050.637.55.15×

Qwen3-8B demo

INT4: 2.6× faster at batch 1, 1.3 points lost. Batch 64: only 1.3×.

Setup: one RTX A5000 (24 GB, SM 8.6, Ampere). vLLM 0.29.1 dev, LLM Compressor 0.15.0. Input 512 tokens, output 128, vllm bench latency. GSM8K 5-shot with lm_eval.

Step 1: quantize. GPTQ W4A16, 512 calibration samples. About 20 minutes on one A5000. 16 GB in, 5.4 GB out.

recipe = GPTQModifier(targets="Linear", scheme="W4A16", ignore=["lm_head"])
oneshot(model=model, dataset=ds, recipe=recipe,
        max_seq_length=2048, num_calibration_samples=512)
model.save_pretrained("Qwen3-8B-W4A16-G128", save_compressed=True)

Step 2: serve. No flags. vLLM reads quant_method: compressed-tensors from config.json.

vllm serve ./Qwen3-8B-W4A16-G128

Step 3: compare.

BF16Online FP8 (fp8_per_tensor)INT4 W4A16 (GPTQ)
KernelBF16 GEMMMarlin W8A16 (weight-only fallback)compressed-tensors W4A16
Weight memory15.27 GiB8.8 GiB5.68 GiB
GSM8K (strict)87.3%86.7% (−0.6)86.0% (−1.3)
Latency, batch 13.17 s1.91 s (1.66×)1.24 s (2.55×)
Latency, batch 164.89 s3.72 s (1.32×)2.95 s (1.66×)
Latency, batch 6412.02 s10.15 s (1.18×)9.44 s (1.27×)

Pitfalls

References

  1. Kurtic et al., "Give Me BF16 or Give Me Death"? Accuracy-Performance Trade-Offs in LLM Quantization (arXiv 2411.02355)
  2. vLLM docs: Quantization (supported formats, hardware matrix, plugins)
  3. vLLM docs: FP8 W8A8 with LLM Compressor
  4. vLLM docs: Online Quantization
  5. vLLM docs: Quantized KV Cache
  6. Frantar et al., MARLIN: Mixed-Precision Auto-Regressive Parallel Inference on LLMs (arXiv 2408.11743)
  7. vLLM source: vllm/model_executor/layers/quantization
  8. vLLM docs: GGUF (vllm-gguf-plugin)

Edit page
Share this post:

Previous Post
How vLLM Sleep Mode Works
Next Post
NVIDIA Data Center GPUs from Pascal to Blackwell