Skip to content

Latest commit

 

History

History

Folders and files

NameName
Last commit message
Last commit date

parent directory

..
 
 
 
 

README.md

quant.cpp -- vLLM Integration Guide

Overview

quant.cpp can be integrated with vLLM to provide extreme KV cache compression during serving. This guide explains how to use quant.cpp as a custom KV cache backend in vLLM, enabling 3-bit and 4-bit KV cache quantization with near-lossless quality.

Architecture

vLLM uses a paged KV cache with a CacheEngine that manages block allocation and GPU memory. quant.cpp integrates at two levels:

  1. Cache Engine: Custom quant.cppCacheEngine replaces the default cache engine, using quant.cpp's paged cache with quantized blocks.

  2. Attention Kernel: Custom attention kernels that operate directly on quantized KV blocks, avoiding dequantization overhead.

vLLM Serving Engine
    |
    +-- ModelRunner
    |       |
    |       +-- Attention layers
    |               |
    |               +-- quant.cppAttention (custom)
    |                       |
    |                       +-- tq_attention() via Python bindings
    |
    +-- CacheEngine
            |
            +-- quant.cppCacheEngine (custom)
                    |
                    +-- tq_cache_create() / tq_cache_append()
                    +-- Block-level quantization during reshape_and_cache

Prerequisites

  1. Build quant.cpp as a shared library:
cd /path/to/quant.cpp
cmake -B build -DCMAKE_BUILD_TYPE=Release -DBUILD_SHARED_LIBS=ON
cmake --build build -j$(nproc)
  1. Install the Python bindings:
cd bindings/python
pip install -e .
  1. Verify installation:
import turboquant
print(turboquant.__version__)  # Should print "0.1.0"

Usage

Option 1: Custom KV Cache dtype

from vllm import LLM, SamplingParams

# Use quant.cpp 3-bit KV cache
llm = LLM(
    model="meta-llama/Llama-3-8B",
    kv_cache_dtype="turbo3",        # quant.cpp 3-bit
    max_model_len=32768,
)

outputs = llm.generate(
    ["Tell me about quantum computing."],
    SamplingParams(temperature=0.7, max_tokens=256),
)

Option 2: Custom Cache Engine (Advanced)

For full control, implement a custom cache engine:

# integrations/vllm/tq_cache_engine.py

import turboquant
import torch
from vllm.worker.cache_engine import CacheEngine


class quant.cppCacheEngine(CacheEngine):
    """Custom vLLM cache engine using quant.cpp compression."""

    def __init__(self, cache_config, model_config, parallel_config):
        super().__init__(cache_config, model_config, parallel_config)

        self.tq_ctx = turboquant.quant.cppContext(
            backend=turboquant.BACKEND_CUDA
        )
        self.key_type = turboquant.TURBO_3B
        self.value_bits = 4
        self.head_dim = model_config.get_head_size()
        self.num_heads = model_config.get_num_kv_heads(parallel_config)

    def allocate_gpu_cache(self):
        """Allocate quantized KV cache on GPU.

        Each block stores quantized keys and values instead of FP16,
        reducing memory by ~5x.
        """
        gpu_cache = []
        fp16_bytes_per_token = self.head_dim * 2  # FP16 = 2 bytes
        tq_key_bytes = turboquant.type_bpe(self.key_type) * self.head_dim / 8
        tq_val_bytes = self.value_bits * self.head_dim / 8
        tq_bytes_per_token = tq_key_bytes + tq_val_bytes

        compression = fp16_bytes_per_token * 2 / tq_bytes_per_token
        print(f"[quant.cpp] KV cache compression: {compression:.1f}x")
        print(f"[quant.cpp] Key type: {turboquant.type_name(self.key_type)}")
        print(f"[quant.cpp] Value bits: {self.value_bits}")

        for layer_idx in range(self.num_layers):
            key_blocks = torch.zeros(
                self.num_gpu_blocks,
                self.block_size,
                self.num_heads,
                int(tq_key_bytes),
                dtype=torch.uint8,
                device="cuda",
            )
            value_blocks = torch.zeros(
                self.num_gpu_blocks,
                self.block_size,
                self.num_heads,
                int(tq_val_bytes),
                dtype=torch.uint8,
                device="cuda",
            )
            gpu_cache.append((key_blocks, value_blocks))

        return gpu_cache

    def swap_in(self, src_to_dst):
        """Swap quantized blocks from CPU to GPU."""
        for src, dst in src_to_dst.items():
            for layer_idx in range(self.num_layers):
                src_key, src_val = self.cpu_cache[layer_idx]
                dst_key, dst_val = self.gpu_cache[layer_idx]
                dst_key[dst].copy_(src_key[src])
                dst_val[dst].copy_(src_val[src])

    def swap_out(self, src_to_dst):
        """Swap quantized blocks from GPU to CPU."""
        for src, dst in src_to_dst.items():
            for layer_idx in range(self.num_layers):
                src_key, src_val = self.gpu_cache[layer_idx]
                dst_key, dst_val = self.cpu_cache[layer_idx]
                dst_key[dst].copy_(src_key[src])
                dst_val[dst].copy_(src_val[src])

    def close(self):
        """Release quant.cpp context."""
        if hasattr(self, 'tq_ctx') and self.tq_ctx is not None:
            self.tq_ctx.close()
            self.tq_ctx = None

Option 3: Monkey-patch reshape_and_cache

For minimal code changes, you can monkey-patch vLLM's reshape_and_cache:

import turboquant
import vllm.worker.cache_engine as ce

_orig_reshape = ce.reshape_and_cache

def tq_reshape_and_cache(key, value, key_cache, value_cache,
                          slot_mapping, kv_cache_dtype, kv_scale):
    if kv_cache_dtype == "turbo3":
        ctx = turboquant.quant.cppContext()
        # Quantize keys in-place
        for i, slot in enumerate(slot_mapping):
            k = key[i].cpu().numpy()
            v = value[i].cpu().numpy()
            qk = ctx.quantize_keys(k, turboquant.TURBO_3B)
            qv = ctx.quantize_values(v, bits=4)
            # Store in cache block at slot position
            # ... (implementation depends on vLLM version)
        ctx.close()
    else:
        _orig_reshape(key, value, key_cache, value_cache,
                       slot_mapping, kv_cache_dtype, kv_scale)

ce.reshape_and_cache = tq_reshape_and_cache

Supported Quantization Modes

Mode Key Bits Value Bits Memory Savings Quality
turbo3 3 4 ~5x Near-lossless
turbo4 4 4 ~4x Near-lossless
polar3 3 4 ~5x Good
polar4 4 4 ~4x Very good
qjl1 1 2 ~12x Moderate
uniform4 4 4 ~4x Good baseline

Performance Considerations

  1. Quantization overhead: quant.cpp adds ~5-10us per token during prefill for key/value quantization. This is amortized over the lifetime of the cache.

  2. Attention latency: Quantized attention is typically faster than FP16 attention for long sequences (>4K tokens) due to reduced memory bandwidth.

  3. Throughput improvement: With 5x less KV cache memory, vLLM can serve ~3-4x more concurrent requests, significantly improving throughput.

  4. GPU memory: The primary benefit is reduced GPU memory for KV cache, allowing longer contexts or more concurrent sequences.

Benchmarking

import time
import turboquant
import numpy as np

ctx = turboquant.quant.cppContext()
head_dim = 128

# Benchmark quantization latency
keys = np.random.randn(1000, head_dim).astype(np.float32)
t0 = time.perf_counter()
qdata = ctx.quantize_keys(keys, turboquant.TURBO_3B)
t1 = time.perf_counter()
print(f"Quantize 1000 keys: {(t1-t0)*1000:.2f} ms")

# Benchmark attention latency
query = np.random.randn(head_dim).astype(np.float32)
t0 = time.perf_counter()
scores = ctx.attention(query, qdata, 1000, turboquant.TURBO_3B)
t1 = time.perf_counter()
print(f"Attention (seq_len=1000): {(t1-t0)*1000:.2f} ms")

ctx.close()

Known Limitations

  1. CUDA backend: The CUDA backend for quant.cpp is under development. Currently, quantization runs on CPU with data transfer to/from GPU.

  2. Continuous batching: Full continuous batching support requires custom attention kernels that are being developed.

  3. Tensor parallelism: KV cache quantization is compatible with tensor parallelism; each GPU independently quantizes its assigned heads.

  4. Speculative decoding: Compatible with speculative decoding as long as the draft model uses the same KV cache dtype.

Roadmap

  • Native CUDA quantization kernels (avoid CPU roundtrip)
  • Fused reshape_and_cache + quantize kernel
  • PagedAttention integration with quantized blocks
  • Prefix caching support with quantized blocks
  • Chunked prefill optimization

License

Apache 2.0 -- same as quant.cpp