Qwen-Compress / README.md
Gavin-chen's picture
Update README.md
c407840 verified
|
Raw
History Blame Contribute Delete
6.19 kB
metadata
library_name: transformers
license: mit
tags:
  - qwen-compress
  - kv-cache
  - compression
  - qwen
  - model-merging
  - efficiency
datasets:
  - gsm8k
  - ifeval

(有提供繁體中文版readme)

Qwen-Compress: Learned Weighted Pooling for KV Cache Compression

A drop-in KV cache compression method that selectively compresses hidden states via learned weighted pooling between every 4th transformer layer, reducing prefill KV size by up to ~1.75× without retraining the base model.

Overview

Standard autoregressive transformers pay a KV cache cost proportional to total prefill length. Qwen-Compress mitigates this by inserting small HiddenStateCompressor modules at selected layers. Each compressor performs learned weighted pooling: it computes an importance score per token via a learned linear projection, then applies softmax over a sliding window of ratio=4 consecutive hidden states and takes the weighted sum, producing 1 compressed token from 4. This shrinks the KV cache while preserving the final window_size=128 tokens unchanged (tail retention).

The key insight: compress hidden states before they enter the attention QKV projection at the compressor layer. Downstream layers attend to compressed KV positions via correct rotary position embedding (RoPE) indices, avoiding position mismatch.

Inspiration

This work is inspired by DeepSeek's compressed attention mechanism (used in DeepSeek-V4), where inter-layer compression reduces KV cache footprint. Qwen-Compress adapts this concept to the Qwen architecture with a per-sequence compression schedule and decode-phase buffer management.

Architecture

Compressor Module

A single HiddenStateCompressor is a lightweight learned weight layer:

Input:  (B, 4, D)  — 4 consecutive hidden states
          │
    ┌─────┴─────┐
    │  wgate    │  Linear(D, 1) → per-token importance score
    │  + ape    │  Learned position bias (4, 1)
    └─────┬─────┘
          │ softmax over 4 tokens → normalized weights
          │
    Weighted sum → (B, 1, D)  — 1 compressed token

Parameters: ~D (one Linear layer + 4 learned scalars) — negligible vs. base model. No latent bottleneck or QKV attention involved.

Placement

Compressors are inserted at layers where layer_idx % 4 == 3 (every 4th layer). At non-compressor layers, attention runs unchanged.

Compression Schedule (Window + Ratio)

For each sequence at a compressor layer:

true_len   = sequence length (after left-padding)
window     = min(window_size=128, true_len)   ← kept verbatim
compressible = true_len - window
to_compress  = compressible - (compressible % ratio=4)
compressed   = to_compress / 4
residual     = compressible % ratio            ← kept verbatim (0–3 tokens)
tail         = window                          ← last 128 tokens verbatim

KV positions are tracked per-token for correct RoPE indexing. Zero-padded batch entries are masked via kv_valid throughout generation.

Decode-Phase Buffer Compression

During autoregressive decode, new tokens accumulate in an uncompressed buffer (buf_k, buf_v, buf_h). Once the buffer reaches window_size + ratio = 132 tokens, the oldest ratio=4 tokens are compressed into 1 and merged into the persistent KV cache, keeping the buffer at window_size tokens. This bounds the decode-phase KV overhead at window tokens per compressor layer.

Performance

Tested on Qwen3.6-27B (NF4, 64 layers). Compression applied at every 4th layer (i % 4 == 3), totaling 16 compressors.

GSM8K (5-shot)

Filter Baseline Compressed Δ
flexible-extract 0.8749 0.7331 −16.2%
strict-match 0.8658 0.5785 −33.2%

IFEval (0-shot)

Filter Baseline Compressed Δ
inst_level_loose_acc 0.8849 0.8897 +0.5%
inst_level_strict_acc 0.8525 0.8513 −0.1%
prompt_level_loose_acc 0.8299 0.8355 +0.7%
prompt_level_strict_acc 0.7856 0.7856 ±0.0%

Key Takeaways

  • IFEval is virtually unaffected — instruction following, formatting, and constraint satisfaction are preserved.
  • GSM8K shows degradation — multi-step mathematical reasoning suffers under compression, especially strict format matching (#### N).
  • Compression ratio: A 301-token prefill is compressed to ~172 KV entries (1.75×). Effective ratio varies with prompt length (shorter prompts see less benefit; very long prompts approach 4×).

Usage

The reference implementation is a FastAPI server (server.py) that patches a Qwen model with compressors and serves an OpenAI-compatible API.

# Install dependencies
pip install torch transformers accelerate bitsandbytes

# Start the server (loads model, patches compressors, serves on :8001)
python server.py

# Send requests via curl or any OpenAI client
curl http://localhost:8001/v1/chat/completions \
  -H "Content-Type: application/json" \
  -d '{"model": "qwen-compress", "messages": [{"role": "user", "content": "1+1="}]}'

The patching happens in build_franken_qwen() (server.py:344): every 4th layer's attention forward is replaced with get_custom_qwen_forward(compressor, window_size=128, ratio=4), and the compressor weights are loaded from a checkpoint.

Limitations

  • Reasoning degradation: Complex multi-step reasoning (GSM8K) shows 16–33% accuracy drop. The compressor likely disrupts intermediate representations critical for arithmetic reasoning, even with thinking mode disabled.
  • No training recovery: The compressor checkpoint is trained in isolation, not fine-tuned jointly with the base model. Post-training or LoRA-based recovery may close the gap.
  • Context-independent compression: The weighted pooling treats each 4-token window independently. A cross-window or bidirectional design could better preserve global reasoning structure.