Instructions to use Gavin-chen/Qwen-Compress with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Transformers
How to use Gavin-chen/Qwen-Compress with Transformers:
# Load model directly from transformers import AutoModel model = AutoModel.from_pretrained("Gavin-chen/Qwen-Compress", dtype="auto") - Notebooks
- Google Colab
- Kaggle
| 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. | |
| ```bash | |
| # 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. | |