Skip to main content

LLM Quantization: GPTQ, AWQ, and GGUF for Efficient Deployment

Published: March 19, 2026 Updated: May 8, 2026 Larry Qu 21 min read
Table of Contents

Introduction

Quantization has become essential for deploying large language models efficiently. Loading a 70B parameter model in FP16 requires approximately 140GB of VRAM—beyond even two A100 80GB GPUs. By applying quantization, the same model can run on a single GPU, with INT4 quantization reducing a 70B model to approximately 35GB. This 4x reduction makes frontier models accessible on consumer hardware.

The major quantization methods—GPTQ, AWQ, and GGUF—offer different trade-offs between precision, inference speed, and memory efficiency. Understanding these methods enables practitioners to select appropriate quantization for their deployment scenarios, balancing model quality against resource constraints.

This article explores the foundations of LLM quantization, the major methods and their trade-offs, practical implementation guidance, and deployment strategies. Whether deploying to data centers or edge devices, quantization provides the efficiency needed for practical LLM deployment.

Quantization Fundamentals

Quantization reduces the precision of model weights, typically from 16-bit floating point to 8-bit or 4-bit integers. This reduction decreases memory usage and enables faster computation on hardware that supports low-precision arithmetic.

Precision Levels

Standard model precision uses FP16 (16-bit floating point) or BF16 (16-bit brain float). These formats provide sufficient precision for most applications but consume significant memory. A single parameter in FP16 requires 2 bytes.

INT8 quantization reduces each parameter to 8 bits, halving memory usage compared to FP16. INT4 further reduces to 4 bits, quartering FP16 memory usage. Even lower precisions like INT2 exist but typically cause significant quality degradation.

Quantization Process

Post-training quantization (PTQ) converts a pre-trained model to lower precision without retraining. This is the most common approach, as it doesn’t require the computational resources of training. The process involves analyzing weight distributions and determining optimal quantization parameters.

Quantization-aware training (QAT) simulates quantization during training, allowing the model to adapt to lower precision. This typically produces better results than PTQ but requires access to training data and computational resources.

import torch
import torch.nn as nn
import numpy as np
from typing import Dict, Tuple

class Quantizer:
    """Base quantizer class."""
    
    def quantize(self, weights: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor]:
        """Quantize weights, returning quantized weights and scale."""
        raise NotImplementedError
        
    def dequantize(self, quantized: torch.Tensor, scale: torch.Tensor) -> torch.Tensor:
        """Dequantize weights."""
        raise NotImplementedError


class GPTQQuantizer:
    """GPTQ-style post-training quantization."""
    
    def __init__(self, bits: int = 4, group_size: int = 128):
        self.bits = bits
        self.group_size = group_size
        
    def quantize(self, weights: torch.Tensor) -> Dict:
        """Quantize weights using GPTQ method."""
        # Reshape to groups
        original_shape = weights.shape
        if weights.dim() > 1:
            weights = weights.view(-1, self.group_size)
        
        # Compute quantization parameters per group
        num_groups = weights.shape[0]
        scales = torch.zeros(num_groups, weights.shape[1], device=weights.device)
        zeros = torch.zeros(num_groups, weights.shape[1], device=weights.device)
        quantized = torch.zeros_like(weights, dtype=torch.int32)
        
        for g in range(num_groups):
            group = weights[g]
            # Find min/max for symmetric quantization
            w_max = group.abs().max(dim=1)[0]
            scale = w_max / (2**(self.bits - 1) - 1)
            scales[g] = scale
            
            # Quantize
            q = torch.round(group / scale.unsqueeze(-1)).clamp(-2**(self.bits-1), 2**(self.bits-1)-1)
            quantized[g] = q.to(torch.int32)
            
        # Reshape back
        quantized = quantized.view(*original_shape)
        scales = scales.view(*original_shape[:-1], original_shape[-1])
        
        return {
            "quantized": quantized,
            "scales": scales,
            "bits": self.bits,
            "group_size": self.group_size
        }
    
    def dequantize(self, quantized: torch.Tensor, scales: torch.Tensor) -> torch.Tensor:
        """Dequantize weights."""
        return quantized.float() * scales


class AWQQuantizer:
    """Activation-aware Weight Quantization (AWQ)."""
    
    def __init__(self, bits: int = 4, group_size: int = 128):
        self.bits = bits
        self.group_size = group_size
        
    def quantize(self, weights: torch.Tensor, importance: torch.Tensor = None) -> Dict:
        """Quantize weights with activation-aware importance."""
        # Compute importance if not provided
        if importance is None:
            importance = torch.ones_like(weights)
            
        # Reshape to groups
        original_shape = weights.shape
        if weights.dim() > 1:
            weights = weights.view(-1, self.group_size)
            importance = importance.view(-1, self.group_size)
        
        # Weight importance: consider both weight magnitude and importance
        combined_importance = weights.abs() * importance
        
        # Per-channel or per-group quantization
        num_groups = weights.shape[0]
        scales = torch.zeros(num_groups, weights.shape[1], device=weights.device)
        quantized = torch.zeros_like(weights, dtype=torch.int32)
        
        for g in range(num_groups):
            group = weights[g]
            imp = combined_importance[g]
            
            # Importance-weighted quantization
            w_max = (group.abs() * imp).max() / (2**(self.bits - 1) - 1)
            scale = w_max + 1e-8
            scales[g] = scale
            
            # Quantize
            q = torch.round(group / scale).clamp(-2**(self.bits-1), 2**(self.bits-1)-1)
            quantized[g] = q.to(torch.int32)
            
        # Reshape back
        quantized = quantized.view(*original_shape)
        scales = scales.view(*original_shape[:-1], original_shape[-1])
        
        return {
            "quantized": quantized,
            "scales": scales,
            "bits": self.bits,
            "group_size": self.group_size
        }
    
    def dequantize(self, quantized: torch.Tensor, scales: torch.Tensor) -> torch.Tensor:
        """Dequantize weights."""
        return quantized.float() * scales


class GGUFQuantizer:
    """GGUF quantization for local LLM deployment."""
    
    # GGUF quantization types
    Q2_K = 2  # 2-bit K-means quantized
    Q3_K = 3  # 3-bit K-means quantized
    Q4_K = 4  # 4-bit K-means quantized
    Q5_K = 5  # 5-bit K-means quantized
    Q6_K = 6  # 6-bit K-means quantized
    Q8_0 = 8  # 8-bit integer
    F16 = 16  # Half precision
    F32 = 32  # Full precision
    
    def __init__(self, quant_type: int = Q4_K):
        self.quant_type = quant_type
        self.bits_per_value = {
            self.Q2_K: 2, self.Q3_K: 3, self.Q4_K: 4,
            self.Q5_K: 5, self.Q6_K: 6, self.Q8_0: 8
        }
        
    def quantize(self, weights: torch.Tensor) -> Dict:
        """Quantize weights to GGUF format."""
        bits = self.bits_per_value.get(self.quant_type, 4)
        
        # For K-quant types, use K-means clustering
        if self.quant_type in [self.Q2_K, self.Q3_K, self.Q4_K, self.Q5_K, self.Q6_K]:
            return self._kmeans_quantize(weights, bits)
        else:
            return self._int_quantize(weights, bits)
    
    def _kmeans_quantize(self, weights: torch.Tensor, bits: int) -> Dict:
        """K-means quantization for GGUF."""
        from sklearn.cluster import KMeans
        
        original_shape = weights.shape
        weights_flat = weights.float().view(-1).numpy()
        
        # K-means clustering
        n_clusters = 2 ** bits
        kmeans = KMeans(n_clusters=n_clusters, n_init=1, random_state=42)
        kmeans.fit(weights_flat.reshape(-1, 1))
        
        # Get quantized values and centroids
        quantized_flat = kmeans.labels_.astype(np.int32)
        centroids = torch.from_numpy(kmeans.cluster_centers_.flatten().astype(np.float32))
        
        # Reshape back
        quantized = torch.from_numpy(quantized_flat).view(*original_shape)
        
        return {
            "quantized": quantized,
            "centroids": centroids,
            "quant_type": self.quant_type,
            "bits": bits
        }
    
    def _int_quantize(self, weights: torch.Tensor, bits: int) -> Dict:
        """Simple integer quantization."""
        w_max = weights.abs().max()
        scale = w_max / (2**(bits - 1) - 1)
        
        quantized = torch.round(weights / scale).clamp(-2**(bits-1), 2**(bits-1)-1)
        
        return {
            "quantized": quantized.to(torch.int32),
            "scale": scale,
            "quant_type": self.quant_type,
            "bits": bits
        }
    
    def dequantize(self, quantized: torch.Tensor, centroids: torch.Tensor = None, 
                   scale: float = None) -> torch.Tensor:
        """Dequantize weights."""
        if centroids is not None:
            # K-means dequantization
            return centroids[quantized].view_as(quantized).float()
        elif scale is not None:
            # Integer dequantization
            return quantized.float() * scale
        else:
            raise ValueError("Need centroids or scale for dequantization")


class QuantizedModel:
    """Wrapper for quantized models with efficient inference."""
    
    def __init__(self, model: nn.Module, quantizer: str = "gguf", 
                 bits: int = 4, group_size: int = 128):
        self.model = model
        self.quantizer_name = quantizer
        self.bits = bits
        self.group_size = group_size
        
        # Initialize quantizer
        if quantizer == "gptq":
            self.quantizer = GPTQQuantizer(bits, group_size)
        elif quantizer == "awq":
            self.quantizer = AWQQuantizer(bits, group_size)
        elif quantizer == "gguf":
            self.quantizer = GGUFQuantizer(bits)
        else:
            raise ValueError(f"Unknown quantizer: {quantizer}")
            
        # Quantize model
        self.quantized_weights: Dict[str, Dict] = {}
        self._quantize_model()
        
    def _quantize_model(self):
        """Quantize all model weights."""
        for name, param in self.model.named_parameters():
            if param.dim() > 0:  # Skip scalars
                result = self.quantizer.quantize(param.data)
                self.quantized_weights[name] = result
                
    def get_memory_usage(self) -> float:
        """Get memory usage in GB."""
        total_bytes = 0
        for name, result in self.quantized_weights.items():
            quantized = result["quantized"]
            total_bytes += quantized.numel() * quantized.element_size()
        return total_bytes / (1024 ** 3)
    
    def compare_with_original(self, original_model: nn.Module) -> Dict:
        """Compare quantized model with original."""
        original_memory = sum(p.numel() * p.element_size() 
                             for p in original_model.parameters())
        quantized_memory = self.get_memory_usage() * (1024 ** 3)
        
        return {
            "original_memory_gb": original_memory / (1024 ** 3),
            "quantized_memory_gb": quantized_memory,
            "compression_ratio": original_memory / quantized_memory
        }

Quantization Methods Comparison

The major quantization methods have different characteristics suited to different deployment scenarios.

GPTQ

GPTQ (Gradient Post-Training Quantization) uses a layer-wise optimization approach that minimizes quantization error. The method processes layers one at a time, adjusting weights to compensate for quantization errors. GPTQ is well-suited for GPU deployment and provides good quality at 4-bit precision.

GPTQ’s key advantage is its accuracy preservation. The optimization process finds weight adjustments that minimize the impact of quantization. This makes GPTQ particularly effective for models where accuracy is critical.

AWQ

Activation-Aware Weight Quantization (AWQ) considers the importance of weights based on their activation magnitudes. Weights that contribute more to important activations are quantized more carefully. This attention to activation patterns often produces better results than uniform quantization.

AWQ is particularly effective for tasks where certain weights are more critical than others. The method identifies and protects important weights while allowing less important weights to be more aggressively quantized.

GGUF

GGUF (formerly GGML) is designed for local deployment, particularly with the llama.cpp ecosystem. The format includes metadata for efficient loading and supports various quantization levels. GGUF models can be loaded and run with minimal setup.

GGUF’s strength is its ecosystem support. Tools like Ollama, LM Studio, and llama.cpp make GGUF models easy to deploy locally. The format is optimized for CPU inference and provides good performance without GPU requirements.

Quantization Levels

Different quantization levels offer trade-offs between quality and efficiency.

INT8 Quantization

INT8 provides a good balance of quality and efficiency for many applications. The 2x memory reduction compared to FP16 makes larger models accessible, while the minimal quality degradation is acceptable for most use cases. INT8 is well-supported across hardware and frameworks.

INT4 Quantization

INT4 provides 4x memory reduction compared to FP16, enabling deployment of models that would otherwise be impossible. Quality degradation is more noticeable than INT8 but remains acceptable for many applications. INT4 is the standard for deploying frontier models on consumer hardware.

Lower Precisions

INT2 and even binary quantization provide extreme compression but cause significant quality degradation. These precisions are primarily useful for research and specific applications where quality is less important than extreme efficiency.

Quantization Methods Comparison

INT8 Quantization

INT8 provides a good balance of quality and efficiency for many applications. The 2x memory reduction compared to FP16 makes larger models accessible, while the minimal quality degradation is acceptable for most use cases. INT8 is well-supported across hardware and frameworks.

INT4 Quantization

INT4 provides 4x memory reduction compared to FP16, enabling deployment of models that would otherwise be impossible. Quality degradation is more noticeable than INT8 but remains acceptable for many applications. INT4 is the standard for deploying frontier models on consumer hardware.

FP8 Quantization

FP8 (8-bit floating point) is available on H100, H200, and Blackwell GPUs. It provides near-lossless quality with good framework support in vLLM and SGLang. FP8 is the recommended starting point for Hopper and Blackwell hardware.

FP4 and NVFP4

FP4 (4-bit floating point) is available on Blackwell GPUs only. NVFP4 is NVIDIA’s hardware-native FP4 format that achieves higher throughput than AWQ on B200 hardware. Multi-user production deployments benefit most from NVFP4, but single-user chat is better served by GGUF Q6_K (2.4x faster).

Lower Precisions

INT2 and binary quantization provide extreme compression but cause significant quality degradation. These precisions are useful for research and specific applications where quality is less important than extreme efficiency.

Hardware Support Matrix

Format Bit Width GPU Support Framework Support Best For
INT8 8-bit All CUDA GPUs vLLM, TGI, TensorRT Server, near-lossless
FP8 8-bit float H100, H200, B200 vLLM, SGLang, TRT-LLM Hopper/Blackwell, lossless
AWQ INT4 All CUDA GPUs vLLM, SGLang, TRT-LLM Production GPU server
GPTQ INT4 All CUDA GPUs vLLM, AutoGPTQ When AWQ unavailable
GGUF 2-8 bit CPU + NVIDIA + Apple llama.cpp, Ollama Local/edge/CPU inference
NVFP4 4-bit float Blackwell (B200+) TRT-LLM, vLLM Max throughput on B200
EXL2 Variable NVIDIA GPUs ExLlamaV2 When fine control needed

Serving Framework Comparison

Framework Quantization Support Throughput Best For
vLLM AWQ, GPTQ, FP8, NVFP4 Highest Production serving, 100+ concurrent users
SGLang FP8, AWQ Higher than vLLM (~29%) Multi-turn agent workflows
TensorRT-LLM INT8, FP8, INT4, NVFP4 Highest Enterprise, Blackwell optimization
llama.cpp GGUF (2-8 bit) Moderate CPU inference, Mac, edge devices
Ollama GGUF (via llama.cpp) Low-Moderate Local dev, single-user
TGI INT8, FP8 Moderate HuggingFace ecosystem, long context
AutoGPTQ GPTQ N/A (conversion tool) Creating quantized models

Quantization-Aware Training (QAT)

Post-training quantization (PTQ) is the mainstream method because retraining a 70B+ model is prohibitive. However, QAT achieves better quality by simulating quantization effects during training. Google’s Gemma 4 includes official QAT checkpoints that outperform AWQ at the same bit-width.

QLoRA (2023) broke the boundary between training and quantization by letting you fine-tune a model in its quantized state. A single 48GB GPU can fine-tune a 65B model with quality comparable to full 16-bit fine-tuning.

Decision Framework: Choosing a Quantization Method

Scenario Recommended Rationale
Server inference, H100/A100 FP8 Near-lossless, good framework support
Production, constrained VRAM AWQ INT4 Best quality for 4-bit on CUDA
Local dev, single user GGUF Q6_K Best quality-to-speed ratio
Edge/CPU deployment GGUF Q4_K_M Balances quality and memory
Blackwell server (B200) NVFP4 Hardware-native, max throughput
Multi-LoRA deployment GPTQ INT4 Only 4-bit format with LoRA support
Non-English serving GGUF Q6_K Preserves cross-lingual quality
Maximum compression GGUF Q2_K When VRAM is extremely limited

Depth Quantization

Beyond weight quantization, modern methods also quantize activations and KV cache:

  • Weight quantization: Reduces model storage (4x for INT4)
  • Activation quantization: Reduces intermediate computation memory
  • KV cache quantization: NVFP4 halves KV cache memory for long contexts
  • Joint quantization: Quantizes weights + activations together (W4A4)

Deployment Strategies

Deploying quantized models requires attention to infrastructure and optimization.

GPU Deployment

GPU deployment supports both INT8 and INT4 quantization through Tensor Cores and specialized kernels. NVIDIA’s TensorRT provides optimized inference for quantized models, with significant speedups over FP16 inference.

Memory efficiency on GPUs enables larger batch sizes and longer contexts. The reduced memory footprint also enables deployment on smaller GPUs that couldn’t handle FP16 models.

CPU Deployment

CPU deployment is practical for GGUF models, which are optimized for CPU inference. This enables deployment without GPU hardware, though inference is slower than GPU deployment. CPU deployment is suitable for development, testing, and applications with modest throughput requirements.

Edge Deployment

Edge deployment benefits significantly from quantization. Devices with limited memory and compute can run quantized models that would be impossible at full precision. The specific quantization level depends on the device’s capabilities.

Quality Evaluation

Evaluating quantized models requires attention to both automated metrics and human evaluation.

Automated Metrics

Perplexity measures language modeling quality and is sensitive to quantization effects. Lower perplexity indicates better quality. Comparing perplexity between original and quantized models quantifies the quality impact.

Task-specific metrics evaluate performance on relevant tasks. For question answering, retrieval accuracy; for code generation, compilation success rate. These metrics capture the practical impact of quantization.

Human Evaluation

Human evaluation provides the most reliable assessment of quality. Humans can detect subtle quality degradation that automated metrics miss. For production deployment, human evaluation of quantized models is recommended.

Challenges and Limitations

Quantization faces several challenges.

Quality Degradation

Aggressive quantization causes quality degradation, particularly for smaller models and complex tasks. The trade-off between compression and quality must be carefully managed based on application requirements.

Case Study: Quantizing a Customer-Facing Chat Model

A production team quantized a 70B Llama-3.1 model for a customer-facing chatbot serving 5M queries/month.

Setup

  • Base model: Llama-3.1-70B-Instruct (FP16: 140GB)
  • Target: Deploy on 4x A100 80GB (320GB total, need headroom for KV cache)
  • Method: AWQ INT4 (reduces 70B model to ~35GB)
  • Serving: vLLM with continuous batching, 8 A100 80GB

Results

Metric FP16 (8x A100) AWQ INT4 (4x A100) AWQ INT4 (8x A100)
Model memory 140GB 35GB 35GB
KV cache memory 80GB 80GB 200GB
Throughput 450 tok/s 890 tok/s 1,850 tok/s
P50 latency 1.8s 0.9s 0.5s
P95 latency 3.2s 1.6s 1.1s
MMLU score 82.4% 80.1% 80.1%
Monthly cost $18,432 $5,376 $10,752

Key Takeaways

  • AWQ INT4 saved 71% in GPU costs (8x → 4x A100) while maintaining 97% of MMLU quality
  • With 8x A100, the freed memory enabled larger KV caches and 4.1x throughput vs FP16
  • Quality impact was acceptable: 80.1% vs 82.4% MMLU (97% retention)
  • Customer satisfaction scores were within 2% of FP16 baseline

Quantization Pipeline: End-to-End Workflow

def quantization_pipeline(model_name: str, method: str = "awq",
                          output_dir: str = "./quantized"):
    """End-to-end quantization pipeline."""
    import torch
    from transformers import AutoModelForCausalLM, AutoTokenizer

    print(f"Loading model: {model_name}")
    model = AutoModelForCausalLM.from_pretrained(
        model_name, torch_dtype=torch.float16, device_map="auto"
    )
    tokenizer = AutoTokenizer.from_pretrained(model_name)
    model_size_gb = sum(p.numel() * p.element_size() for p in model.parameters()) / (1024**3)
    print(f"Original model size: {model_size_gb:.1f} GB")

    if method == "awq":
        from awq import AutoAWQForCausalLM
        awq_model = AutoAWQForCausalLM.from_pretrained(model_name)
        awq_model.quantize(tokenizer, quant_config={"bits": 4, "group_size": 128})
        awq_model.save_quantized(output_dir)
        print(f"AWQ quantized model saved to {output_dir}")

    elif method == "gptq":
        from auto_gptq import AutoGPTQForCausalLM
        gptq_model = AutoGPTQForCausalLM.from_pretrained(model_name)
        gptq_model.quantize(tokenizer, bits=4, group_size=128, desc_act=False)
        gptq_model.save_quantized(output_dir)
        print(f"GPTQ quantized model saved to {output_dir}")

    elif method == "gguf":
        import subprocess
        subprocess.run([
            "python3", "-m", "llama.cpp.convert",
            "--model", model_name,
            "--output", f"{output_dir}/model.gguf",
            "--quantize", "q4_k_m"
        ])
        print(f"GGUF quantized model saved to {output_dir}/model.gguf")

    quantized_gb = sum(f.stat().st_size for f in Path(output_dir).rglob("*")
                       if f.is_file()) / (1024**3)
    print(f"Quantized model size: {quantized_gb:.1f} GB")
    print(f"Compression ratio: {model_size_gb / quantized_gb:.1f}x")
    return {"method": method, "original_gb": model_size_gb, "quantized_gb": quantized_gb}

Quality Evaluation

Evaluating quantized models requires both automated metrics and human evaluation.

Automated Metrics

Perplexity measures language modeling quality and is sensitive to quantization effects. Lower perplexity indicates better quality. Comparing perplexity between original and quantized models quantifies the quality impact.

Task-specific metrics evaluate performance on relevant tasks. For question answering, retrieval accuracy; for code generation, compilation success rate. These metrics capture the practical impact of quantization.

Human Evaluation

Human evaluation provides the most reliable assessment of quality. Humans can detect subtle quality degradation that automated metrics miss. For production deployment, human evaluation of quantized models is recommended.

Validation Checklist

  1. Run standardized benchmarks (MMLU, GSM8K, HumanEval) on both versions
  2. Compare perplexity on held-out data representative of production inputs
  3. Conduct A/B test with 5-10% of production traffic
  4. Monitor user feedback and satisfaction scores
  5. Validate safety alignment is preserved
  6. For multilingual deployments, test on all target languages

Hardware Support

Not all hardware supports all quantization levels equally. Some devices have better support for INT8 than INT4. Understanding hardware capabilities is essential for selecting appropriate quantization.

Kernel Availability

Quantization speed depends on optimized kernel availability. GPTQ without the Marlin kernel is slower than FP16 on some hardware. AWQ has excellent kernel support in vLLM. Always verify kernel support for your specific GPU model before committing to a quantization method.

Calibration Data Mismatch

If calibration data does not represent production inputs, quantization quality will suffer. Use domain-specific calibration data for specialized deployments. A general-purpose model should use diverse calibration data from multiple domains.

Quantization Cost-Benefit Analysis

Scenario Method Hardware Saved Quality Loss Payback Period
70B model serving AWQ INT4 50% GPU reduction 3% Immediate
Local inference GGUF Q4_K_M Any GPU + CPU 5% N/A (enables deployment)
Blackwell server NVFP4 25% GPU reduction 1-2% Immediate
Mobile deployment Q2_K Enables on-device 10-15% N/A (enables deployment)
Research evaluation INT8 FP8 50% GPU <1% Immediate

Quantization Decision Summary

Constraint Recommended Rationale
Maximum quality FP8 (H100/B200) or INT8 (A100) Near-lossless, widely supported
Production GPU serving AWQ INT4 Best quality-to-speed for INT4 on CUDA
Minimize VRAM GGUF Q2_K or Q3_K Extreme compression with CPU offloading
Local single user GGUF Q6_K Best quality-to-speed for local deployment
Multi-tenant production NVFP4 (B200) or AWQ (A100) Hardware-native or production-tested
Multi-LoRA serving GPTQ INT4 Only 4-bit format with LoRA adapter support
Non-English users Q6_K GGUF Preserves 98-99% quality across languages
Maximum throughput Marlin/GPTQ INT4 712 tok/s vs 461 FP16 baseline
Research/experimentation Bitsandbytes INT4 Simplest API, no pre-quantization needed

Resources

Calibration Data

GPTQ and similar methods require calibration data for optimal quantization. The choice of calibration data affects quantization quality. Using representative data is important for good results.

Calibration Data Best Practices

GPTQ and AWQ require calibration data for optimal quantization. The choice of calibration data significantly affects quality.

Dataset Size Best For Notes
Wikitext-2 ~4M tokens General language models Most common choice
C4 ~180M tokens Diverse text Good for general-purpose models
Pile ~800M tokens Broad coverage Large, covers many domains
Custom domain data Variable Domain-specific models Best for specialized deployments

Calibration Tips

  1. Use 128-512 samples — more than 512 provides diminishing returns
  2. Match your deployment domain — calibration data should resemble production inputs
  3. Avoid over-calibration — too much calibration data can overfit the quantization to specific patterns
  4. Mix domains — for general-purpose models, mix data from multiple domains

Troubleshooting Quantization

Problem: Quantized Model Quality Is Too Low

Symptom: The quantized model produces noticeably worse outputs than FP16.

Root cause: Wrong quantization method or bit-width for the model size and task.

Solutions:

  1. Try AWQ instead of GPTQ (typically 1-2% better quality)
  2. Increase bit-width (Q6_K instead of Q4_K_M for GGUF)
  3. Use FP8 if hardware supports it (near-lossless)
  4. Try QAT checkpoints if available (e.g., Gemma 4)
  5. Validate calibration data matches deployment domain

Problem: Quantized Model Is Slower Than Expected

Symptom: INT4 model runs slower than FP16 baseline.

Root cause: Not all quantization methods include optimized kernels. GPTQ without Marlin kernel can be slower than FP16.

Solutions:

  1. Use AWQ instead of GPTQ (better kernel optimization in vLLM)
  2. Ensure Marlin kernel is enabled for GPTQ (712 tok/s vs 276 tok/s)
  3. Check that the GPU supports the precision natively
  4. Avoid GGUF for high-throughput serving (designed for single-user)

Problem: GPU Memory Still Insufficient

Symptom: Even with INT4, the model doesn’t fit in available VRAM.

Root cause: The KV cache for long contexts consumes significant memory beyond weights.

Solutions:

  1. Enable KV cache quantization (NVFP4 on Blackwell, FP8 on Hopper)
  2. Reduce max sequence length
  3. Enable vLLM’s prefix caching
  4. Use a smaller model (8B instead of 70B) with higher precision
  5. Use GGUF with CPU offloading for memory-constrained environments

Problem: Multi-Language Quality Degradation

Symptom: Aggressive quantization hurts low-resource languages more than English.

Root cause: LLMs allocate less weight capacity to languages with less training data.

Solutions:

  1. Use Q6_K GGUF instead of Q4_K_M (98-99% quality vs 90-95%)
  2. Validate on non-English test data before deploying
  3. Consider quantization-aware training for multilingual use cases

Performance Benchmarks

Throughput Comparison (Qwen2.5-32B on H200)

Method Tokens/sec vs FP16 Quality (Perplexity)
FP16 (baseline) 461 1x 5.2
Marlin (GPTQ INT4) 712 1.54x 5.4
AWQ INT4 670 1.45x 5.3
GPTQ INT4 (no Marlin) 276 0.60x 5.4
GGUF Q4_K_M 520 1.13x 5.5
BitsandBytes INT4 410 0.89x 5.3

Marlin kernel makes GPTQ faster than FP16. Without Marlin, GPTQ is actually slower than FP16 — always check kernel support.

Memory Reduction

Method 70B Model Memory 7B Model Memory
FP16 140 GB 14 GB
INT8 70 GB 7 GB
INT4 (AWQ/GPTQ) 35 GB 3.5 GB
GGUF Q4_K_M ~35 GB ~3.5 GB
GGUF Q2_K ~20 GB ~2 GB

Quality Retention by Method

Method MMLU (vs FP16) GSM8K (vs FP16) HumanEval (vs FP16)
FP8 99.8% 99.9% 99.7%
INT8 99.5% 99.3% 99.1%
AWQ INT4 97.2% 96.8% 95.5%
GPTQ INT4 96.5% 95.8% 94.2%
GGUF Q6_K 98.1% 97.5% 96.8%
GGUF Q4_K_M 95.3% 94.1% 93.0%

Quantization Ecosystem Tools

Tool Purpose Formats When to Use
AutoGPTQ Convert models to GPTQ GPTQ Custom model quantization for GPU
AutoAWQ Convert models to AWQ AWQ Best INT4 quality on CUDA
llama.cpp Convert/run GGUF models GGUF Local CPU/GPU inference
HuggingFace Transformers Load quantized models Bitsandbytes, GPTQ Quick testing, integration
TensorRT Model Optimizer NVIDIA enterprise quantization INT8, FP8, INT4, NVFP4 Production on NVIDIA hardware
vLLM Serve quantized models AWQ, GPTQ, FP8, NVFP4 Production serving
SGLang Serve with RadixAttention FP8, AWQ Multi-turn agent workflows

Future Directions

Resources

Quantization and Non-English Languages

A critical consideration for multilingual deployments: aggressive quantization hurts low-resource languages more than English.

Why Non-English Quality Degrades Faster

LLMs allocate weight capacity proportional to training data volume. English dominates most training corpora (40-60%), so English tokens get the most model capacity. Languages with less training data have lower weight allocation, making them more vulnerable to precision loss during quantization. At 4-bit precision, the limited representational capacity is disproportionately consumed by high-frequency English patterns.

Mitigation Strategies

  1. Use higher precision: Q6_K preserves 98-99% quality across languages vs Q4_K_M at 90-95%
  2. Validate per language: Test quantization quality on each target language separately
  3. Language-specific calibration: Include non-English data in calibration datasets
  4. Prefer QAT: Quantization-aware training preserves multilingual capability better than PTQ LLMs allocate weight capacity proportional to training data volume, and English dominates most training corpora (40-60%).
Quantization Level English Quality Non-English Quality
Q6_K GGUF 98-99% preserved 98-99% preserved
Q4_K_M GGUF 95-97% preserved 90-95% preserved
NVFP4 93-96% preserved 80-92% preserved

If serving customers in non-English languages, Q6_K is the safest choice. The extra VRAM cost is justified for maintaining quality across all languages.

Frequently Asked Questions

Q: Can I quantize a model without accessing its weights? A: No, quantization requires access to the model’s weight values. For API-only models, you cannot quantize — you must use the provider’s built-in optimized inference. Only open-weight models (Llama, Qwen, Mistral, etc.) can be quantized.

Q: Does quantization affect safety guardrails? A: Yes, aggressive quantization can degrade safety alignment. Models that refuse harmful requests at FP16 may become more likely to comply at INT4. Always validate safety behavior after quantization.

Q: Can I combine quantization with distillation? A: Yes, and this is recommended. Distill first (reduce parameter count), then quantize (reduce precision per parameter). This achieves 20x+ total compression with 90-95% quality retention.

Q: How do I verify quantization quality? A: Run standardized benchmarks (MMLU, GSM8K, HumanEval) on both FP16 and quantized versions. Compare perplexity on held-out data. For production, run A/B tests comparing quantized vs non-quantized outputs.

Q: Which models have official quantized checkpoints? A: Many open-weight models have community quantized versions. Some vendors (Google with Gemma, Meta with Llama) release official quantized checkpoints. Official QAT checkpoints generally outperform PTQ methods.

When Not to Quantize

Quantization is not always the right choice. Consider alternatives in these scenarios:

Scenario Better Approach Rationale
API-only models Use provider’s built-in inference Cannot access weights
Maximum quality required Distillation first, then quantize Distillation changes architecture
Rapidly evolving models Wait for stable version Quantization is model-specific
Simple/small tasks (<7B) Use FP16 on available hardware Quality loss may not justify savings
Debugging/development Use FP16 for clarity Quantization adds debugging complexity

Conclusion

Quantization has become essential for practical LLM deployment, enabling frontier models to run on accessible hardware. The major methods — GPTQ, AWQ, and GGUF — provide different trade-offs suited to different scenarios.

The key to effective quantization is matching the method and precision level to the deployment requirements. FP8 provides near-lossless quality on Hopper/Blackwell hardware. AWQ INT4 is the best choice for production GPU serving when VRAM is constrained. GGUF Q6_K is ideal for local deployment. NVFP4 maximizes throughput on Blackwell hardware.

For practitioners, quantization provides a practical path to deploying capable AI systems within resource constraints. The investment in understanding quantization methods pays dividends in deployment efficiency and model accessibility. When combined with distillation, quantization enables deploying frontier-level models on consumer hardware with acceptable quality.

Quick Reference: Common Quantization Commands

# AWQ quantization
python3 -m awq.quantize --model_path meta-llama/Llama-3.3-70B --quant_method awq

# GPTQ quantization with Marlin kernel
python3 -m auto_gptq.quantize --model_id meta-llama/Llama-3.3-70B --bits 4 --marlin

# GGUF conversion + quantization
python3 convert.py --model meta-llama/Llama-3.3-70B --outfile model-f16.gguf
./quantize model-f16.gguf model-q4_k_m.gguf q4_k_m

# vLLM serving with quantized model
python3 -m vllm.entrypoints.openai.api_server \
    --model meta-llama/Llama-3.3-70B \
    --quantization awq \
    --dtype auto

Quantization Workflow Summary

  1. Select model and target deployment hardware
  2. Choose quantization method based on hardware and quality requirements
  3. Prepare calibration data (128-512 samples matching deployment domain)
  4. Quantize the model using appropriate tools (AutoAWQ, AutoGPTQ, llama.cpp)
  5. Validate quality on standardized benchmarks and domain-specific tests
  6. Deploy with a compatible serving framework (vLLM, llama.cpp, TensorRT-LLM)
  7. Monitor quality, latency, and throughput in production

Comments

👍 Was this article helpful?