Skip to content

syedMohib44/kvquant

Repository files navigation

Read the Paper (PDF) | DOI: 10.17605/OSF.IO/9WSKZ | View on GitHub

KVQUANT

Attention-aware KV cache quantization for LLM inference.


The problem

Every token an LLM generates requires storing its Key and Value vectors across all transformer layers the KV cache. It grows linearly with sequence length. For a 70B model at FP16, that is roughly 140 MB per 1,000 tokens. A 32K context window consumes ~4.5 GB of VRAM just for the cache, on top of model weights. On consumer hardware this means truncated context, slow CPU offloading, or out-of-memory errors.

Naïve quantization (uniform grid) fails because KV vectors have heavy-tailed outliers a few large-magnitude coordinates dominate the distribution. Rounding those aggressively collapses attention scores.

KVQUANT compresses the KV cache from 16-bit floats down to 2–4 bits while preserving attention accuracy. It isolates outlier dimensions at higher precision, fits per-layer non-uniform codebooks to the actual KV distribution, and minimises distortion directly on the <Q,K> dot products rather than pointwise values. The result is a 4–8× reduction in KV cache memory with less than 0.5 perplexity degradation enough to run long-context inference on hardware that would otherwise run out of memory.


KVQUANT extends TurboQuant (Zandieh et al., 2025) with five novel extensions and several implementation improvements. It achieves near-optimal KV cache compression by combining information-theoretically grounded vector quantization with transformer-specific structure exploitation.


Results at a glance

Metric Value
Attention-weighted distortion reduction 47--70% per layer (same bit-width)
Delta compression MSE improvement 1.1--2.2x on correlated streams
Low-rank correction MSE reduction ~11% at rank-4, ~19% at rank-8
Codebook lookup speedup vs argmin 14--22x (bucketize)
Test suite 88/88 passing

How it works

1. MSE Distortion vs Bit-Width

KVQuant achieves per-element MSE well within the theoretical upper bound (sqrt3 pi/2) . 4^{-b} across all bit-widths. Both QR and Hadamard rotations stay below the bound.

MSE Bounds


2. Codebook Centroids (True Sphere Distribution)

Unlike the original KVQuant which uses a Gaussian approximation, we fit Lloyd-Max centroids directly to the true unit-sphere marginal distribution f(t) alpha (1 - t^2)^{(d-3)/2}. This gives tighter quantization, especially at low bit-widths and small d.

Codebook Centroids


3. Rotation Effect

Random rotation (QR or Hadamard) transforms raw KV coordinates - which have arbitrary non-Gaussian distributions - into approximately N(0, 1/d), enabling optimal per-coordinate Lloyd-Max quantization.

Rotation Effect


4. Attention-Weighted Quantization (Extension 1)

Rather than allocating bits uniformly, AWQ assigns more bits to tokens that receive high attention from queries. At the same average bit-width, this reduces attention-weighted distortion by 56.5% on average across all distilgpt2 layers.

Attention-Weighted Quantization


5. Delta Compression (Extension 2)

Consecutive KV vectors are highly correlated. Compressing token-to-token deltas delta_t = k_t - k(cap)_{t-1} instead of absolute vectors gives 1.1--2.2x lower MSE, especially in early layers with smoother trajectories.

Delta Compression


6. Adaptive Bit Allocation (Extension 3)

Token importance evolves during generation. An EMA-based importance tracker dynamically reassigns bit-widths as attention scores accumulate, giving more bits to tokens that prove important over time.

Adaptive Allocation


7. Low-Rank Error Correction (Extension 4)

Quantization error R = K - k(cap) is structured - its top singular vectors capture most of the error energy. A rank-r SVD correction reduces MSE by ~11% at rank-4 using only 6.3% extra storage.

Low-Rank Correction


8. Entropy Coding

Codebook indices are non-uniformly distributed after rotation. Huffman coding reduces storage toward the Shannon entropy - at 4-bit, d=128: ~5% savings.

Entropy Coding


9. Full Pipeline Comparison

End-to-end comparison of baseline KVQuant vs the combined KVQUANT pipeline across all layers and bit-widths.

Pipeline Comparison


10. Compression Ratio

Storage cost vs reconstruction quality across configurations.

Compression Ratio


11. Perplexity Validation

End-to-end perplexity on distilgpt2 confirms that quantization quality improvements preserve language modelling performance.

Perplexity Validation



Using the package

Install via pip and use from any Python environment. No repo clone needed.


Installation

pip install kvquant-plus-plus           # GPU kernels (Triton JIT) included
pip install "kvquant-plus-plus[cuda]"   # same + explicit Triton version pin

Requirements: Python >= 3.10, PyTorch >= 2.1, Transformers >= 4.40

GPU kernels (Triton JIT softmax, flash attention, matmul) are included in the base install no [cuda] extra needed. All paths fall back to pure PyTorch automatically on CPU, AMD, or Apple MPS.


generate() full response

Pass a prompt string, get back text. The model is downloaded on first call and cached in memory for subsequent calls.

from kvquant import generate

out = generate("What is machine learning?")
print(out.text)
print(f"{out.compression_ratio:.1f}x smaller than float16")

# With a system prompt
out = generate(
    "What is machine learning?",
    system="You are a helpful assistant. Be concise.",
)
print(out.text)

GenerateResult fields: .text, .bits, .avg_bits_per_dim, .compression_ratio, .model, .prompt


stream() print tokens as they arrive

from kvquant import stream

for token in stream("Explain transformers in simple terms"):
    print(token, end="", flush=True)
print()

System prompt role, persona, or instructions

Pass any combination of system and user prompt. Both are quantized together in a single prefill pass no extra compute cost.

from kvquant import generate, stream

# System prompt sets the model's role; user prompt is the question
out = generate(
    "What is quantum entanglement?",
    system="You are a physics professor. Answer at undergraduate level.",
    bits=3,
)
print(out.text)

# Long document analysis system sets the task, user supplies the document.
# prefill_chunk_size keeps each attention step at chunk² instead of T²,
# so even a 10 000-token contract fits on an 8 GB GPU.
contract = open("contract.txt").read()
out = generate(
    contract + "\n\nList all payment terms.",
    system="You are a legal analyst. Extract facts only, be concise.",
    bits=3,
    max_new_tokens=300,
    prefill_chunk_size=512,   # lower (256) = less VRAM, higher (1024) = faster
)
print(out.text)

# Streaming with a system prompt
for token in stream(
    "Explain gradient descent.",
    system="You are a machine learning tutor. Use simple analogies.",
    bits=3,
):
    print(token, end="", flush=True)
print()

Bit-width options (2 / 3 / 4)

from kvquant import generate

# 2-bit: most compressed (~8x smaller than float16), some quality loss
out = generate("Summarise the French Revolution", bits=2)
print(f"{out.compression_ratio:.1f}x  {out.text}")

# 3-bit: recommended default (~5x smaller, minimal quality loss)
out = generate("Summarise the French Revolution", bits=3)
print(f"{out.compression_ratio:.1f}x  {out.text}")

# 4-bit: best quality (~4x smaller, near-lossless)
out = generate("Summarise the French Revolution", bits=4)
print(f"{out.compression_ratio:.1f}x  {out.text}")

Sampling creative / varied output

from kvquant import generate

out = generate("Once upon a time", bits=3, temperature=0.0)   # greedy
out = generate("Once upon a time", bits=3, temperature=0.8)   # sampling
out = generate("Once upon a time", bits=3, temperature=0.8, top_p=0.95)  # nucleus

print(out.text)

Different models

Works with any HuggingFace causal LM instruct, base, and hybrid architectures (Qwen, Llama, Phi, Mistral, Falcon, Gemma …).

from kvquant import generate

out = generate("What is quantum entanglement?",
               model="Qwen/Qwen2.5-1.5B-Instruct", bits=3)       # default

out = generate("What is the capital of France?",
               model="TinyLlama/TinyLlama-1.1B-Chat-v1.0", bits=3)  # fast/small

out = generate("Explain the Turing test",
               model="Qwen/Qwen2.5-7B-Instruct", bits=3)          # larger

out = generate("The Eiffel Tower is located in",
               model="distilgpt2", bits=3, raw=True)               # base model

print(out.text)

Multi-GPU large models

from kvquant import generate

out = generate(
    "Explain quantum computing in detail",
    model="meta-llama/Llama-3.1-8B-Instruct",
    bits=3,
    device_map="auto",   # spreads model across all available GPUs
)
print(out.text)

Long documents contracts, papers, codebases

Prefill attention is O(T²). A 6 000-token contract on an 8 GB GPU OOMs in a single forward pass. prefill_chunk_size processes context in chunks each step stays at chunk² × heads while the full context accumulates in the KV cache. No information is lost.

from kvquant import generate, stream

contract = open("contract.txt").read()
out = generate(
    contract + "\n\nList all payment terms.",
    system="You are a legal analyst. Extract facts only.",
    bits=3,
    max_new_tokens=300,
    prefill_chunk_size=512,
)
print(out.text)

# Tune for your GPU:
#   prefill_chunk_size=256   →  ~0.5 GB prefill VRAM  (safe, slower)
#   prefill_chunk_size=512   →  ~1.5 GB prefill VRAM  (default)
#   prefill_chunk_size=1024  →  ~4.0 GB prefill VRAM  (faster)

for token in stream(
    contract + "\n\nWhat are the termination conditions?",
    system="You are a legal analyst.",
    bits=3,
    prefill_chunk_size=256,
):
    print(token, end="", flush=True)
print()

Low-rank error correction (+11% quality)

from kvquant import generate

out = generate(
    "Explain the history of the Roman Empire",
    bits=3,
    correction_rank=4,    # 0 = off (default), 4 = recommended at 2–3 bit
    max_new_tokens=300,
)
print(out.text)

Repetition control

from kvquant import generate

# Default repetition_penalty=1.3 handles most cases
# Raise to 1.5–2.0 if you see looping at 2-bit
out = generate("Tell me about AI", bits=2, repetition_penalty=1.5)
print(out.text)

Low-level API

For integrating directly into a training loop, custom transformer, or research pipeline. All classes accept KV tensors as-is (T, head_dim), (B, H, T, head_dim), or anything in between.

KVCacheQuantizer

from kvquant import KVCacheQuantizer

quant = KVCacheQuantizer(head_dim=head_dim, num_bits=3)
quant.calibrate(keys, values)           # calibrate on actual prefill KVs

k_c, v_c     = quant.compress_kv(keys, values)
k_hat, v_hat = quant.decompress_kv(k_c, v_c)

print(f"avg bits/dim: {quant.avg_bits:.2f}")

AttentionWeightedQuantizer

from kvquant import AttentionWeightedQuantizer

awq = AttentionWeightedQuantizer(dim=head_dim, hi_bits=4, lo_bits=2, top_fraction=0.5)
compressed = awq.quantize(keys, query)
k_hat      = awq.dequantize(compressed)
print(f"avg bits: {awq.avg_bits:.2f}")

DeltaKVCache

from kvquant import DeltaKVCache

cache = DeltaKVCache(head_dim=head_dim, num_bits=3)
for k_new, v_new in token_stream:
    cache.push(k_new, v_new)

K_hat, V_hat = cache.get()

AdaptiveKVCache

from kvquant import AdaptiveKVCache

cache = AdaptiveKVCache(head_dim=head_dim)
for k_new, v_new in token_stream:
    cache.push(k_new, v_new)

cache.attend(attn_weights)    # attn_weights: (..., T)
K_hat, V_hat = cache.get()
print(cache.bit_allocation()) # e.g. {4: 8, 3: 50, 2: 60, 1: 10}

LowRankCorrection

from kvquant import LowRankCorrection, KVQuantMSE

lrc = LowRankCorrection(quantizer=KVQuantMSE(dim=head_dim, num_bits=2), rank=4)
k_corrected = lrc.forward(keys)
energy = lrc.residual_rank_analysis(keys, max_rank=8)

ProductQuantizer

from kvquant import ProductQuantizer

pq = ProductQuantizer(dim=head_dim, num_subspaces=16, bits_per_subspace=8)
pq.calibrate(keys.reshape(-1, head_dim))

compressed = pq.quantize(keys.reshape(-1, head_dim))
k_hat      = pq.dequantize(compressed)
print(f"compression ratio: {pq.compression_ratio():.1f}x vs float32")

GPU Acceleration

GPU kernels (Triton JIT) are included in the base install and compile on first use no build step needed. Falls back to PyTorch automatically on CPU, AMD, or Apple MPS.

For full details on supported GPUs, backends, and performance numbers see the cuda-triton-multiarch README.


Implementation improvements over reference

Reference KVQUANT
Codebook distribution Gaussian approximation True sphere marginal
Rotation May produce reflections (det = -1) SO(d) enforced (det = +1)
Nearest-centroid lookup argmin - O(N.d.k) bucketize - O(N.d.log k), 14--22x faster
FWHT 2 clones per butterfly level In-place, 1 allocation per level
FWHT (CUDA) Multiple kernel launches torch.compile fused, 2--3x faster
PQ encode (CUDA) M sequential cdist calls Triton kernel, all subspaces in 1 launch, 10--15x faster
Attention softmax (CUDA) Eager F.softmax Triton fused exp+sum+div, 3.5--8x faster
QJL projection (CUDA) Separate matmul + compare torch.compile fused, 1.5--2x faster
Extensions None 5 novel extensions
Entropy coding Not present Huffman coding on indices
High-level API None KVCacheQuantizer for (B,H,T,d) tensors


For contributors

Clone the repo and set up a local dev environment.


Setup

conda create -n kvquant python=3.11 -y
conda activate kvquant

# Install PyTorch follow https://pytorch.org/get-started/locally/ or CPU-only:
pip install torch --index-url https://download.pytorch.org/whl/cpu

# Install package in editable mode
pip install -e .          # base + GPU kernels
pip install -e ".[dev]"   # + pytest

Running demos

# Basic quantization (no model download needed)
python -m kvquant.demo

# All 5 extensions on real data
python -m kvquant.demo_extensions

# Regenerate all plots
python -m kvquant.visualize

# Perplexity benchmark
python -m kvquant.eval_ppl
python -m kvquant.eval_ppl --model gpt2-medium --correction-rank 4

# Interactive generation at different bit-widths
python -m kvquant.demo_llm --model Qwen/Qwen2.5-1.5B-Instruct --prompt "Explain ML" --max-new-tokens 80

Running tests

python -m pytest test_kvquant.py -v   # 88 tests, all pass

Building CUDA flash attention extensions (optional)

Build instructions, requirements, and supported SM targets are in the cuda-triton-multiarch README.


Architecture

kvquant/
|-- quantizer.py          # KVQuantMSE, KVQuantIP
|-- rotation.py           # RandomRotation (QR), HadamardRotation (WHT)
|-- codebook.py           # Lloyd-Max on true sphere distribution
|-- entropy.py            # Huffman coding on indices
|-- outlier.py            # Per-channel bit allocation for outliers
|-- kv_cache.py           # KVCacheQuantizer - high-level (B,H,T,d) API
|-- attn_weighted.py      # Extension 1: attention-weighted quantization
|-- delta.py              # Extension 2: delta / temporal compression
|-- adaptive.py           # Extension 3: EMA-based adaptive bit allocation
|-- correction.py         # Extension 4: low-rank error correction
|-- product_quantizer.py  # Extension 5: product quantization
+-- csrc/                 # GPU acceleration
    |-- pq_encode.py      # Triton PQ encode kernel (10-15x vs cdist loop)
    |-- softmax.py        # Triton row-wise softmax (3.5-8x vs F.softmax)
    +-- attention.py      # Flash attention bridge: WMMA/CuTe/Triton/SDPA

Citation

@misc{kvquantpp2025,
  title   = {KVQuant: Attention Aware, Structure Exploiting Extensions to KV Cache Compression via Near Optimal Vector Quantization},
  author  = {Uddin, Syed Muheeb},
  year    = {2025},
  doi     = {10.17605/OSF.IO/9WSKZ},
  url     = {https://doi.org/10.17605/OSF.IO/9WSKZ},
  note    = {Extensions of TurboQuant: Online Vector Quantization with Near-optimal Distortion Rate (Zandieh et al., arXiv:2504.19874)}
}

Releases

Packages

Contributors

Languages