AttnVQ submission
Browse files- .gitignore +2 -0
- README.md +88 -0
- artifacts/codebooks.pt +3 -0
- artifacts/longbench_cheap_metrics.json +1192 -0
- artifacts/longbench_results.json +632 -0
- artifacts/turbo_codebooks.pt +3 -0
- benchmark.py +782 -0
- generate.py +25 -0
- longbench_eval.py +763 -0
- turbo_benchmark.py +531 -0
- vqkv/__init__.py +0 -0
- vqkv/compressed_cache.py +245 -0
- vqkv/metrics.py +138 -0
- vqkv/quantizers.py +618 -0
.gitignore
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
|
|
|
|
|
|
|
| 1 |
+
.DS_Store
|
| 2 |
+
.DS_Store
|
README.md
CHANGED
|
@@ -1,3 +1,91 @@
|
|
| 1 |
---
|
| 2 |
license: apache-2.0
|
| 3 |
---
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
---
|
| 2 |
license: apache-2.0
|
| 3 |
---
|
| 4 |
+
|
| 5 |
+
# AttnVQ — Attention-Aware KV Cache Quantization
|
| 6 |
+
|
| 7 |
+
Training-free **product vector quantization** of the KV cache for long-context LLMs. AttnVQ fits small per-subspace codebooks with LBG, but scores distortion by **attention-output error** (and key cosine / inner-product bias), not cache MSE. Calibration is light: **10–15 agent traces, ~15 s on GPU** — enough to capture the **model's** K/V geometry (data-aware, not corpus-dependent).
|
| 8 |
+
|
| 9 |
+
Primary target: **Laguna-XS.2** (model-agnostic). Only the **10 full-attention layers** are compressed; 30 sliding-window layers stay fp16.
|
| 10 |
+
|
| 11 |
+
## What this repo offers
|
| 12 |
+
|
| 13 |
+
| Component | Description |
|
| 14 |
+
|---|---|
|
| 15 |
+
| **`generate.py`** | Minimal inference: `VQQuantizedCache` → `model.generate()` (uint8 indices, real memory savings) |
|
| 16 |
+
| **`vqkv/`** | Quantizers (ProductVQ, RoPESplit, scalar/KIVI baselines), attention-aware metrics, compressed cache |
|
| 17 |
+
| **`benchmark.py`** | Fit codebooks + **cheap metrics** (key cosine, attn-output error, ip-bias) on real cache dumps |
|
| 18 |
+
| **`turbo_benchmark.py`** | Faithful **TurboQuant** baseline (Haar rotation + Lloyd-Max + QJL) |
|
| 19 |
+
| **`longbench_eval.py`** | LongBench v1 proxy metrics + optional end-to-end task scoring |
|
| 20 |
+
| **`artifacts/`** | Pre-fit codebooks and LongBench results |
|
| 21 |
+
|
| 22 |
+
**Variants:** `productvq-*` (AttnVQ), `ropesplit-1b` (RoPE-half split for Laguna), scalar/KIVI/sign/ternary baselines, TurboQuant MSE/Prod.
|
| 23 |
+
|
| 24 |
+
## Headline results (Laguna-XS.2)
|
| 25 |
+
|
| 26 |
+
**Memory @ 131K context** (full-attention layers only):
|
| 27 |
+
|
| 28 |
+
| Config | KV cache |
|
| 29 |
+
|---|---|
|
| 30 |
+
| fp16 | 5.4 GB |
|
| 31 |
+
| AttnVQ 2-bit (`productvq-32x256-2b`) | 0.73 GB (7.4×) |
|
| 32 |
+
| AttnVQ 1-bit (`productvq-16x256-1b`) | 0.40 GB (14×) |
|
| 33 |
+
|
| 34 |
+
**LongBench v1** (mean F1 over qasper, 2wikimqa, hotpotqa, repobench-p; single 15-trace codebook):
|
| 35 |
+
|
| 36 |
+
- **2-bit:** ~96% of fp16 — TurboQuant ~83%, INT2 ~75%
|
| 37 |
+
- **1-bit:** AttnVQ and RoPESplit beat every iso-budget baseline on every task
|
| 38 |
+
- **0.5-bit:** only VQ reaches this regime at all
|
| 39 |
+
|
| 40 |
+
Full numbers: `artifacts/longbench_results.json`, `artifacts/longbench_cheap_metrics.json`.
|
| 41 |
+
|
| 42 |
+
**Note:** Wall-clock speedup requires a fused dequant kernel.
|
| 43 |
+
|
| 44 |
+
|
| 45 |
+
## Quick start
|
| 46 |
+
Tested on CUDA 12.4 / NVIDIA A100.
|
| 47 |
+
|
| 48 |
+
```bash
|
| 49 |
+
pip install "git+https://github.com/huggingface/transformers.git" \
|
| 50 |
+
accelerate datasets torch==2.9.1 torchvision tqdm
|
| 51 |
+
python generate.py
|
| 52 |
+
```
|
| 53 |
+
|
| 54 |
+
Use fitted codebooks for memory efficient long context generation:
|
| 55 |
+
```py
|
| 56 |
+
import torch
|
| 57 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 58 |
+
from vqkv.compressed_cache import VQQuantizedCache
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
# load model
|
| 62 |
+
tok = AutoTokenizer.from_pretrained("poolside/Laguna-XS.2", trust_remote_code=True, fix_mistral_regex=True)
|
| 63 |
+
model = AutoModelForCausalLM.from_pretrained("poolside/Laguna-XS.2", torch_dtype=torch.bfloat16, device_map="cuda", trust_remote_code=True).eval()
|
| 64 |
+
|
| 65 |
+
# load codebooks or fit and use your own
|
| 66 |
+
CODEBOOKS_PATH = "artifacts/codebooks.pt"
|
| 67 |
+
codebooks = torch.load(CODEBOOKS_PATH, map_location="cuda", weights_only=False)
|
| 68 |
+
|
| 69 |
+
# build cache
|
| 70 |
+
quantizers, layers = codebooks["fitted"]["productvq-32x256-2b"], blob["meta"]["full_layers"]
|
| 71 |
+
cache = VQQuantizedCache(quantizers, layers) # persists uint8 codebook indices
|
| 72 |
+
|
| 73 |
+
# generate
|
| 74 |
+
ids = tok("Hello", return_tensors="pt").to(model.device)
|
| 75 |
+
out = model.generate(**ids, max_new_tokens=32, past_key_values=cache, use_cache=True)
|
| 76 |
+
print(tok.decode(out[0, ids["input_ids"].shape[1]:], skip_special_tokens=True))
|
| 77 |
+
|
| 78 |
+
# print memory footprint
|
| 79 |
+
print(cache.memory_footprint())
|
| 80 |
+
```
|
| 81 |
+
|
| 82 |
+
## Reproduce
|
| 83 |
+
|
| 84 |
+
Precomputed results are under `artifacts/`. To re-fit and evaluate:
|
| 85 |
+
|
| 86 |
+
```bash
|
| 87 |
+
python benchmark.py --stage fit
|
| 88 |
+
python turbo_benchmark.py --stage fit
|
| 89 |
+
python longbench_eval.py --stage cheap --n_eval 50 # cheap metrics
|
| 90 |
+
python longbench_eval.py --stage generate --n_eval 50 # slow: full generation & task metrics
|
| 91 |
+
```
|
artifacts/codebooks.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:30f0ac721169cfc655f1d6ca5ac07bc817ec434b6149437fb247e5052be11028
|
| 3 |
+
size 17489175
|
artifacts/longbench_cheap_metrics.json
ADDED
|
@@ -0,0 +1,1192 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"config": "scalar-int4",
|
| 4 |
+
"bits_per_elt": 4.25,
|
| 5 |
+
"n_traces": 50,
|
| 6 |
+
"key_cos": 0.99257,
|
| 7 |
+
"val_cos": 0.99503,
|
| 8 |
+
"key_mse": 0.02338,
|
| 9 |
+
"val_mse": 0.0001,
|
| 10 |
+
"attn_cos": 0.99966,
|
| 11 |
+
"attn_output_error": 0.02973,
|
| 12 |
+
"ip_rel": 1.34455,
|
| 13 |
+
"ip_bias": 0.0001,
|
| 14 |
+
"task": "qasper"
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"config": "scalar-int2",
|
| 18 |
+
"bits_per_elt": 2.25,
|
| 19 |
+
"n_traces": 50,
|
| 20 |
+
"key_cos": 0.86766,
|
| 21 |
+
"val_cos": 0.89301,
|
| 22 |
+
"key_mse": 0.46759,
|
| 23 |
+
"val_mse": 0.00257,
|
| 24 |
+
"attn_cos": 0.99046,
|
| 25 |
+
"attn_output_error": 0.15708,
|
| 26 |
+
"ip_rel": 4.9622,
|
| 27 |
+
"ip_bias": 0.00096,
|
| 28 |
+
"task": "qasper"
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"config": "kivi-int2",
|
| 32 |
+
"bits_per_elt": 2.25,
|
| 33 |
+
"n_traces": 50,
|
| 34 |
+
"key_cos": 0.86152,
|
| 35 |
+
"val_cos": 0.89301,
|
| 36 |
+
"key_mse": 0.54308,
|
| 37 |
+
"val_mse": 0.00257,
|
| 38 |
+
"attn_cos": 0.99043,
|
| 39 |
+
"attn_output_error": 0.15729,
|
| 40 |
+
"ip_rel": 5.98639,
|
| 41 |
+
"ip_bias": -5e-05,
|
| 42 |
+
"task": "qasper"
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"config": "turboquant~-int2",
|
| 46 |
+
"bits_per_elt": 2.125,
|
| 47 |
+
"n_traces": 50,
|
| 48 |
+
"key_cos": 0.64016,
|
| 49 |
+
"val_cos": 0.64168,
|
| 50 |
+
"key_mse": 2.17849,
|
| 51 |
+
"val_mse": 0.01394,
|
| 52 |
+
"attn_cos": 0.36733,
|
| 53 |
+
"attn_output_error": 2.47328,
|
| 54 |
+
"ip_rel": 9.66254,
|
| 55 |
+
"ip_bias": -0.00053,
|
| 56 |
+
"task": "qasper"
|
| 57 |
+
},
|
| 58 |
+
{
|
| 59 |
+
"config": "productvq-16x256-1b",
|
| 60 |
+
"bits_per_elt": 1.0,
|
| 61 |
+
"n_traces": 50,
|
| 62 |
+
"key_cos": 0.8606,
|
| 63 |
+
"val_cos": 0.82426,
|
| 64 |
+
"key_mse": 0.43329,
|
| 65 |
+
"val_mse": 0.00342,
|
| 66 |
+
"attn_cos": 0.97447,
|
| 67 |
+
"attn_output_error": 0.33079,
|
| 68 |
+
"ip_rel": 4.05013,
|
| 69 |
+
"ip_bias": 0.00015,
|
| 70 |
+
"task": "qasper"
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"config": "productvq-8x256-0.5b",
|
| 74 |
+
"bits_per_elt": 0.5,
|
| 75 |
+
"n_traces": 50,
|
| 76 |
+
"key_cos": 0.73386,
|
| 77 |
+
"val_cos": 0.67074,
|
| 78 |
+
"key_mse": 0.76039,
|
| 79 |
+
"val_mse": 0.00583,
|
| 80 |
+
"attn_cos": 0.91293,
|
| 81 |
+
"attn_output_error": 0.50347,
|
| 82 |
+
"ip_rel": 4.9189,
|
| 83 |
+
"ip_bias": 0.00143,
|
| 84 |
+
"task": "qasper"
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
"config": "ropesplit-1b",
|
| 88 |
+
"bits_per_elt": 1.0,
|
| 89 |
+
"n_traces": 50,
|
| 90 |
+
"key_cos": 0.84851,
|
| 91 |
+
"val_cos": 0.82369,
|
| 92 |
+
"key_mse": 0.46147,
|
| 93 |
+
"val_mse": 0.00342,
|
| 94 |
+
"attn_cos": 0.97455,
|
| 95 |
+
"attn_output_error": 0.33236,
|
| 96 |
+
"ip_rel": 3.94593,
|
| 97 |
+
"ip_bias": 0.00051,
|
| 98 |
+
"task": "qasper"
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"config": "sign-1bit",
|
| 102 |
+
"bits_per_elt": 1.125,
|
| 103 |
+
"n_traces": 50,
|
| 104 |
+
"key_cos": 0.82715,
|
| 105 |
+
"val_cos": 0.79949,
|
| 106 |
+
"key_mse": 0.51141,
|
| 107 |
+
"val_mse": 0.00367,
|
| 108 |
+
"attn_cos": 0.97079,
|
| 109 |
+
"attn_output_error": 0.40264,
|
| 110 |
+
"ip_rel": 3.99374,
|
| 111 |
+
"ip_bias": 0.00027,
|
| 112 |
+
"task": "qasper"
|
| 113 |
+
},
|
| 114 |
+
{
|
| 115 |
+
"config": "ternary-bitnet",
|
| 116 |
+
"bits_per_elt": 1.71,
|
| 117 |
+
"n_traces": 50,
|
| 118 |
+
"key_cos": 0.91306,
|
| 119 |
+
"val_cos": 0.90047,
|
| 120 |
+
"key_mse": 0.26966,
|
| 121 |
+
"val_mse": 0.00192,
|
| 122 |
+
"attn_cos": 0.98765,
|
| 123 |
+
"attn_output_error": 0.2377,
|
| 124 |
+
"ip_rel": 3.23327,
|
| 125 |
+
"ip_bias": -6e-05,
|
| 126 |
+
"task": "qasper"
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"config": "productvq-64x256-4b",
|
| 130 |
+
"bits_per_elt": 4.0,
|
| 131 |
+
"n_traces": 50,
|
| 132 |
+
"key_cos": 0.99599,
|
| 133 |
+
"val_cos": 0.99485,
|
| 134 |
+
"key_mse": 0.01346,
|
| 135 |
+
"val_mse": 0.00012,
|
| 136 |
+
"attn_cos": 0.99959,
|
| 137 |
+
"attn_output_error": 0.03627,
|
| 138 |
+
"ip_rel": 0.76086,
|
| 139 |
+
"ip_bias": -2e-05,
|
| 140 |
+
"task": "qasper"
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"config": "productvq-32x256-2b",
|
| 144 |
+
"bits_per_elt": 2.0,
|
| 145 |
+
"n_traces": 50,
|
| 146 |
+
"key_cos": 0.95936,
|
| 147 |
+
"val_cos": 0.94781,
|
| 148 |
+
"key_mse": 0.13408,
|
| 149 |
+
"val_mse": 0.00108,
|
| 150 |
+
"attn_cos": 0.99537,
|
| 151 |
+
"attn_output_error": 0.13766,
|
| 152 |
+
"ip_rel": 2.72405,
|
| 153 |
+
"ip_bias": 0.00031,
|
| 154 |
+
"task": "qasper"
|
| 155 |
+
},
|
| 156 |
+
{
|
| 157 |
+
"config": "turbo-mse-4b",
|
| 158 |
+
"bits_per_elt": 4.125,
|
| 159 |
+
"n_traces": 50,
|
| 160 |
+
"key_cos": 0.99504,
|
| 161 |
+
"val_cos": 0.99504,
|
| 162 |
+
"key_mse": 0.01612,
|
| 163 |
+
"val_mse": 0.0001,
|
| 164 |
+
"attn_cos": 0.9996,
|
| 165 |
+
"attn_output_error": 0.03475,
|
| 166 |
+
"ip_rel": 0.9187,
|
| 167 |
+
"ip_bias": -3e-05,
|
| 168 |
+
"task": "qasper"
|
| 169 |
+
},
|
| 170 |
+
{
|
| 171 |
+
"config": "turbo-prod-4b (K:+qjl)",
|
| 172 |
+
"bits_per_elt": 5.125,
|
| 173 |
+
"n_traces": 50,
|
| 174 |
+
"key_cos": 0.99504,
|
| 175 |
+
"val_cos": 0.99504,
|
| 176 |
+
"key_mse": 0.01613,
|
| 177 |
+
"val_mse": 0.0001,
|
| 178 |
+
"attn_cos": 0.9996,
|
| 179 |
+
"attn_output_error": 0.03478,
|
| 180 |
+
"ip_rel": 1.04771,
|
| 181 |
+
"ip_bias": 4e-05,
|
| 182 |
+
"task": "qasper"
|
| 183 |
+
},
|
| 184 |
+
{
|
| 185 |
+
"config": "turbo-mse-2b",
|
| 186 |
+
"bits_per_elt": 2.125,
|
| 187 |
+
"n_traces": 50,
|
| 188 |
+
"key_cos": 0.94071,
|
| 189 |
+
"val_cos": 0.9407,
|
| 190 |
+
"key_mse": 0.18395,
|
| 191 |
+
"val_mse": 0.00118,
|
| 192 |
+
"attn_cos": 0.99373,
|
| 193 |
+
"attn_output_error": 0.16035,
|
| 194 |
+
"ip_rel": 3.38128,
|
| 195 |
+
"ip_bias": 0.00046,
|
| 196 |
+
"task": "qasper"
|
| 197 |
+
},
|
| 198 |
+
{
|
| 199 |
+
"config": "turbo-prod-2b (K:+qjl)",
|
| 200 |
+
"bits_per_elt": 3.125,
|
| 201 |
+
"n_traces": 50,
|
| 202 |
+
"key_cos": 0.94071,
|
| 203 |
+
"val_cos": 0.94071,
|
| 204 |
+
"key_mse": 0.18395,
|
| 205 |
+
"val_mse": 0.00118,
|
| 206 |
+
"attn_cos": 0.99373,
|
| 207 |
+
"attn_output_error": 0.1604,
|
| 208 |
+
"ip_rel": 3.49197,
|
| 209 |
+
"ip_bias": -0.00021,
|
| 210 |
+
"task": "qasper"
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"config": "turbo-mse-1b",
|
| 214 |
+
"bits_per_elt": 1.125,
|
| 215 |
+
"n_traces": 50,
|
| 216 |
+
"key_cos": 0.7997,
|
| 217 |
+
"val_cos": 0.79979,
|
| 218 |
+
"key_mse": 0.57262,
|
| 219 |
+
"val_mse": 0.00367,
|
| 220 |
+
"attn_cos": 0.97083,
|
| 221 |
+
"attn_output_error": 0.40155,
|
| 222 |
+
"ip_rel": 5.04924,
|
| 223 |
+
"ip_bias": -0.00028,
|
| 224 |
+
"task": "qasper"
|
| 225 |
+
},
|
| 226 |
+
{
|
| 227 |
+
"config": "turbo-prod-1b (K:+qjl)",
|
| 228 |
+
"bits_per_elt": 2.125,
|
| 229 |
+
"n_traces": 50,
|
| 230 |
+
"key_cos": 0.7997,
|
| 231 |
+
"val_cos": 0.79979,
|
| 232 |
+
"key_mse": 0.57262,
|
| 233 |
+
"val_mse": 0.00367,
|
| 234 |
+
"attn_cos": 0.97084,
|
| 235 |
+
"attn_output_error": 0.40162,
|
| 236 |
+
"ip_rel": 7.50776,
|
| 237 |
+
"ip_bias": -6e-05,
|
| 238 |
+
"task": "qasper"
|
| 239 |
+
},
|
| 240 |
+
{
|
| 241 |
+
"config": "scalar-int4",
|
| 242 |
+
"bits_per_elt": 4.25,
|
| 243 |
+
"n_traces": 47,
|
| 244 |
+
"key_cos": 0.99254,
|
| 245 |
+
"val_cos": 0.99503,
|
| 246 |
+
"key_mse": 0.02381,
|
| 247 |
+
"val_mse": 0.0001,
|
| 248 |
+
"attn_cos": 0.99968,
|
| 249 |
+
"attn_output_error": 0.02836,
|
| 250 |
+
"ip_rel": 0.95936,
|
| 251 |
+
"ip_bias": 0.00013,
|
| 252 |
+
"task": "2wikimqa"
|
| 253 |
+
},
|
| 254 |
+
{
|
| 255 |
+
"config": "scalar-int2",
|
| 256 |
+
"bits_per_elt": 2.25,
|
| 257 |
+
"n_traces": 47,
|
| 258 |
+
"key_cos": 0.86637,
|
| 259 |
+
"val_cos": 0.89305,
|
| 260 |
+
"key_mse": 0.48028,
|
| 261 |
+
"val_mse": 0.00262,
|
| 262 |
+
"attn_cos": 0.99088,
|
| 263 |
+
"attn_output_error": 0.15136,
|
| 264 |
+
"ip_rel": 4.48127,
|
| 265 |
+
"ip_bias": 0.00021,
|
| 266 |
+
"task": "2wikimqa"
|
| 267 |
+
},
|
| 268 |
+
{
|
| 269 |
+
"config": "kivi-int2",
|
| 270 |
+
"bits_per_elt": 2.25,
|
| 271 |
+
"n_traces": 47,
|
| 272 |
+
"key_cos": 0.85589,
|
| 273 |
+
"val_cos": 0.89305,
|
| 274 |
+
"key_mse": 0.57652,
|
| 275 |
+
"val_mse": 0.00262,
|
| 276 |
+
"attn_cos": 0.99085,
|
| 277 |
+
"attn_output_error": 0.1516,
|
| 278 |
+
"ip_rel": 5.84995,
|
| 279 |
+
"ip_bias": -0.0007,
|
| 280 |
+
"task": "2wikimqa"
|
| 281 |
+
},
|
| 282 |
+
{
|
| 283 |
+
"config": "turboquant~-int2",
|
| 284 |
+
"bits_per_elt": 2.125,
|
| 285 |
+
"n_traces": 47,
|
| 286 |
+
"key_cos": 0.64105,
|
| 287 |
+
"val_cos": 0.64241,
|
| 288 |
+
"key_mse": 2.21716,
|
| 289 |
+
"val_mse": 0.0142,
|
| 290 |
+
"attn_cos": 0.39052,
|
| 291 |
+
"attn_output_error": 2.37009,
|
| 292 |
+
"ip_rel": 13.81316,
|
| 293 |
+
"ip_bias": 0.0022,
|
| 294 |
+
"task": "2wikimqa"
|
| 295 |
+
},
|
| 296 |
+
{
|
| 297 |
+
"config": "productvq-16x256-1b",
|
| 298 |
+
"bits_per_elt": 1.0,
|
| 299 |
+
"n_traces": 47,
|
| 300 |
+
"key_cos": 0.86048,
|
| 301 |
+
"val_cos": 0.82425,
|
| 302 |
+
"key_mse": 0.44288,
|
| 303 |
+
"val_mse": 0.00349,
|
| 304 |
+
"attn_cos": 0.97475,
|
| 305 |
+
"attn_output_error": 0.34054,
|
| 306 |
+
"ip_rel": 4.03823,
|
| 307 |
+
"ip_bias": 0.00044,
|
| 308 |
+
"task": "2wikimqa"
|
| 309 |
+
},
|
| 310 |
+
{
|
| 311 |
+
"config": "productvq-8x256-0.5b",
|
| 312 |
+
"bits_per_elt": 0.5,
|
| 313 |
+
"n_traces": 47,
|
| 314 |
+
"key_cos": 0.7317,
|
| 315 |
+
"val_cos": 0.66903,
|
| 316 |
+
"key_mse": 0.78213,
|
| 317 |
+
"val_mse": 0.00598,
|
| 318 |
+
"attn_cos": 0.91045,
|
| 319 |
+
"attn_output_error": 0.52276,
|
| 320 |
+
"ip_rel": 4.68996,
|
| 321 |
+
"ip_bias": 0.00175,
|
| 322 |
+
"task": "2wikimqa"
|
| 323 |
+
},
|
| 324 |
+
{
|
| 325 |
+
"config": "ropesplit-1b",
|
| 326 |
+
"bits_per_elt": 1.0,
|
| 327 |
+
"n_traces": 47,
|
| 328 |
+
"key_cos": 0.84739,
|
| 329 |
+
"val_cos": 0.8237,
|
| 330 |
+
"key_mse": 0.4739,
|
| 331 |
+
"val_mse": 0.0035,
|
| 332 |
+
"attn_cos": 0.97487,
|
| 333 |
+
"attn_output_error": 0.34152,
|
| 334 |
+
"ip_rel": 4.43052,
|
| 335 |
+
"ip_bias": 0.00079,
|
| 336 |
+
"task": "2wikimqa"
|
| 337 |
+
},
|
| 338 |
+
{
|
| 339 |
+
"config": "sign-1bit",
|
| 340 |
+
"bits_per_elt": 1.125,
|
| 341 |
+
"n_traces": 47,
|
| 342 |
+
"key_cos": 0.82765,
|
| 343 |
+
"val_cos": 0.79952,
|
| 344 |
+
"key_mse": 0.51975,
|
| 345 |
+
"val_mse": 0.00374,
|
| 346 |
+
"attn_cos": 0.97069,
|
| 347 |
+
"attn_output_error": 0.40168,
|
| 348 |
+
"ip_rel": 4.59382,
|
| 349 |
+
"ip_bias": -0.00051,
|
| 350 |
+
"task": "2wikimqa"
|
| 351 |
+
},
|
| 352 |
+
{
|
| 353 |
+
"config": "ternary-bitnet",
|
| 354 |
+
"bits_per_elt": 1.71,
|
| 355 |
+
"n_traces": 47,
|
| 356 |
+
"key_cos": 0.91339,
|
| 357 |
+
"val_cos": 0.90049,
|
| 358 |
+
"key_mse": 0.27376,
|
| 359 |
+
"val_mse": 0.00196,
|
| 360 |
+
"attn_cos": 0.98759,
|
| 361 |
+
"attn_output_error": 0.23654,
|
| 362 |
+
"ip_rel": 3.65978,
|
| 363 |
+
"ip_bias": 0.00062,
|
| 364 |
+
"task": "2wikimqa"
|
| 365 |
+
},
|
| 366 |
+
{
|
| 367 |
+
"config": "productvq-64x256-4b",
|
| 368 |
+
"bits_per_elt": 4.0,
|
| 369 |
+
"n_traces": 47,
|
| 370 |
+
"key_cos": 0.99596,
|
| 371 |
+
"val_cos": 0.99487,
|
| 372 |
+
"key_mse": 0.01392,
|
| 373 |
+
"val_mse": 0.00012,
|
| 374 |
+
"attn_cos": 0.9996,
|
| 375 |
+
"attn_output_error": 0.03607,
|
| 376 |
+
"ip_rel": 0.86073,
|
| 377 |
+
"ip_bias": -5e-05,
|
| 378 |
+
"task": "2wikimqa"
|
| 379 |
+
},
|
| 380 |
+
{
|
| 381 |
+
"config": "productvq-32x256-2b",
|
| 382 |
+
"bits_per_elt": 2.0,
|
| 383 |
+
"n_traces": 47,
|
| 384 |
+
"key_cos": 0.95941,
|
| 385 |
+
"val_cos": 0.94804,
|
| 386 |
+
"key_mse": 0.13688,
|
| 387 |
+
"val_mse": 0.0011,
|
| 388 |
+
"attn_cos": 0.99554,
|
| 389 |
+
"attn_output_error": 0.13911,
|
| 390 |
+
"ip_rel": 2.10964,
|
| 391 |
+
"ip_bias": 5e-05,
|
| 392 |
+
"task": "2wikimqa"
|
| 393 |
+
},
|
| 394 |
+
{
|
| 395 |
+
"config": "turbo-mse-4b",
|
| 396 |
+
"bits_per_elt": 4.125,
|
| 397 |
+
"n_traces": 47,
|
| 398 |
+
"key_cos": 0.99505,
|
| 399 |
+
"val_cos": 0.99504,
|
| 400 |
+
"key_mse": 0.01637,
|
| 401 |
+
"val_mse": 0.00011,
|
| 402 |
+
"attn_cos": 0.99961,
|
| 403 |
+
"attn_output_error": 0.03411,
|
| 404 |
+
"ip_rel": 0.86796,
|
| 405 |
+
"ip_bias": 8e-05,
|
| 406 |
+
"task": "2wikimqa"
|
| 407 |
+
},
|
| 408 |
+
{
|
| 409 |
+
"config": "turbo-prod-4b (K:+qjl)",
|
| 410 |
+
"bits_per_elt": 5.125,
|
| 411 |
+
"n_traces": 47,
|
| 412 |
+
"key_cos": 0.99505,
|
| 413 |
+
"val_cos": 0.99504,
|
| 414 |
+
"key_mse": 0.01637,
|
| 415 |
+
"val_mse": 0.00011,
|
| 416 |
+
"attn_cos": 0.99961,
|
| 417 |
+
"attn_output_error": 0.03415,
|
| 418 |
+
"ip_rel": 1.15955,
|
| 419 |
+
"ip_bias": 1e-05,
|
| 420 |
+
"task": "2wikimqa"
|
| 421 |
+
},
|
| 422 |
+
{
|
| 423 |
+
"config": "turbo-mse-2b",
|
| 424 |
+
"bits_per_elt": 2.125,
|
| 425 |
+
"n_traces": 47,
|
| 426 |
+
"key_cos": 0.94077,
|
| 427 |
+
"val_cos": 0.94072,
|
| 428 |
+
"key_mse": 0.18695,
|
| 429 |
+
"val_mse": 0.0012,
|
| 430 |
+
"attn_cos": 0.99375,
|
| 431 |
+
"attn_output_error": 0.15968,
|
| 432 |
+
"ip_rel": 2.78259,
|
| 433 |
+
"ip_bias": 0.00031,
|
| 434 |
+
"task": "2wikimqa"
|
| 435 |
+
},
|
| 436 |
+
{
|
| 437 |
+
"config": "turbo-prod-2b (K:+qjl)",
|
| 438 |
+
"bits_per_elt": 3.125,
|
| 439 |
+
"n_traces": 47,
|
| 440 |
+
"key_cos": 0.94077,
|
| 441 |
+
"val_cos": 0.94072,
|
| 442 |
+
"key_mse": 0.18696,
|
| 443 |
+
"val_mse": 0.0012,
|
| 444 |
+
"attn_cos": 0.99375,
|
| 445 |
+
"attn_output_error": 0.15975,
|
| 446 |
+
"ip_rel": 3.76935,
|
| 447 |
+
"ip_bias": -0.00026,
|
| 448 |
+
"task": "2wikimqa"
|
| 449 |
+
},
|
| 450 |
+
{
|
| 451 |
+
"config": "turbo-mse-1b",
|
| 452 |
+
"bits_per_elt": 1.125,
|
| 453 |
+
"n_traces": 47,
|
| 454 |
+
"key_cos": 0.79981,
|
| 455 |
+
"val_cos": 0.79987,
|
| 456 |
+
"key_mse": 0.58236,
|
| 457 |
+
"val_mse": 0.00374,
|
| 458 |
+
"attn_cos": 0.97061,
|
| 459 |
+
"attn_output_error": 0.40143,
|
| 460 |
+
"ip_rel": 4.56288,
|
| 461 |
+
"ip_bias": -0.00021,
|
| 462 |
+
"task": "2wikimqa"
|
| 463 |
+
},
|
| 464 |
+
{
|
| 465 |
+
"config": "turbo-prod-1b (K:+qjl)",
|
| 466 |
+
"bits_per_elt": 2.125,
|
| 467 |
+
"n_traces": 47,
|
| 468 |
+
"key_cos": 0.79981,
|
| 469 |
+
"val_cos": 0.79986,
|
| 470 |
+
"key_mse": 0.58237,
|
| 471 |
+
"val_mse": 0.00374,
|
| 472 |
+
"attn_cos": 0.97061,
|
| 473 |
+
"attn_output_error": 0.40152,
|
| 474 |
+
"ip_rel": 6.74448,
|
| 475 |
+
"ip_bias": -0.00105,
|
| 476 |
+
"task": "2wikimqa"
|
| 477 |
+
},
|
| 478 |
+
{
|
| 479 |
+
"config": "scalar-int4",
|
| 480 |
+
"bits_per_elt": 4.25,
|
| 481 |
+
"n_traces": 50,
|
| 482 |
+
"key_cos": 0.99257,
|
| 483 |
+
"val_cos": 0.99502,
|
| 484 |
+
"key_mse": 0.02373,
|
| 485 |
+
"val_mse": 0.0001,
|
| 486 |
+
"attn_cos": 0.99969,
|
| 487 |
+
"attn_output_error": 0.02754,
|
| 488 |
+
"ip_rel": 1.21463,
|
| 489 |
+
"ip_bias": -0.0001,
|
| 490 |
+
"task": "hotpotqa"
|
| 491 |
+
},
|
| 492 |
+
{
|
| 493 |
+
"config": "scalar-int2",
|
| 494 |
+
"bits_per_elt": 2.25,
|
| 495 |
+
"n_traces": 50,
|
| 496 |
+
"key_cos": 0.86642,
|
| 497 |
+
"val_cos": 0.893,
|
| 498 |
+
"key_mse": 0.48208,
|
| 499 |
+
"val_mse": 0.0026,
|
| 500 |
+
"attn_cos": 0.99106,
|
| 501 |
+
"attn_output_error": 0.14817,
|
| 502 |
+
"ip_rel": 4.89558,
|
| 503 |
+
"ip_bias": 0.00069,
|
| 504 |
+
"task": "hotpotqa"
|
| 505 |
+
},
|
| 506 |
+
{
|
| 507 |
+
"config": "kivi-int2",
|
| 508 |
+
"bits_per_elt": 2.25,
|
| 509 |
+
"n_traces": 50,
|
| 510 |
+
"key_cos": 0.84639,
|
| 511 |
+
"val_cos": 0.893,
|
| 512 |
+
"key_mse": 0.61765,
|
| 513 |
+
"val_mse": 0.0026,
|
| 514 |
+
"attn_cos": 0.99102,
|
| 515 |
+
"attn_output_error": 0.14845,
|
| 516 |
+
"ip_rel": 6.11069,
|
| 517 |
+
"ip_bias": 0.0004,
|
| 518 |
+
"task": "hotpotqa"
|
| 519 |
+
},
|
| 520 |
+
{
|
| 521 |
+
"config": "turboquant~-int2",
|
| 522 |
+
"bits_per_elt": 2.125,
|
| 523 |
+
"n_traces": 50,
|
| 524 |
+
"key_cos": 0.64118,
|
| 525 |
+
"val_cos": 0.64228,
|
| 526 |
+
"key_mse": 2.22279,
|
| 527 |
+
"val_mse": 0.01408,
|
| 528 |
+
"attn_cos": 0.3972,
|
| 529 |
+
"attn_output_error": 2.2878,
|
| 530 |
+
"ip_rel": 10.22977,
|
| 531 |
+
"ip_bias": 0.00486,
|
| 532 |
+
"task": "hotpotqa"
|
| 533 |
+
},
|
| 534 |
+
{
|
| 535 |
+
"config": "productvq-16x256-1b",
|
| 536 |
+
"bits_per_elt": 1.0,
|
| 537 |
+
"n_traces": 50,
|
| 538 |
+
"key_cos": 0.85939,
|
| 539 |
+
"val_cos": 0.82403,
|
| 540 |
+
"key_mse": 0.44685,
|
| 541 |
+
"val_mse": 0.00347,
|
| 542 |
+
"attn_cos": 0.97507,
|
| 543 |
+
"attn_output_error": 0.34394,
|
| 544 |
+
"ip_rel": 3.87267,
|
| 545 |
+
"ip_bias": -0.00045,
|
| 546 |
+
"task": "hotpotqa"
|
| 547 |
+
},
|
| 548 |
+
{
|
| 549 |
+
"config": "productvq-8x256-0.5b",
|
| 550 |
+
"bits_per_elt": 0.5,
|
| 551 |
+
"n_traces": 50,
|
| 552 |
+
"key_cos": 0.72974,
|
| 553 |
+
"val_cos": 0.66901,
|
| 554 |
+
"key_mse": 0.78885,
|
| 555 |
+
"val_mse": 0.00593,
|
| 556 |
+
"attn_cos": 0.9105,
|
| 557 |
+
"attn_output_error": 0.52521,
|
| 558 |
+
"ip_rel": 4.54048,
|
| 559 |
+
"ip_bias": 0.00067,
|
| 560 |
+
"task": "hotpotqa"
|
| 561 |
+
},
|
| 562 |
+
{
|
| 563 |
+
"config": "ropesplit-1b",
|
| 564 |
+
"bits_per_elt": 1.0,
|
| 565 |
+
"n_traces": 50,
|
| 566 |
+
"key_cos": 0.8452,
|
| 567 |
+
"val_cos": 0.82348,
|
| 568 |
+
"key_mse": 0.48031,
|
| 569 |
+
"val_mse": 0.00347,
|
| 570 |
+
"attn_cos": 0.97524,
|
| 571 |
+
"attn_output_error": 0.34501,
|
| 572 |
+
"ip_rel": 4.81682,
|
| 573 |
+
"ip_bias": -0.00074,
|
| 574 |
+
"task": "hotpotqa"
|
| 575 |
+
},
|
| 576 |
+
{
|
| 577 |
+
"config": "sign-1bit",
|
| 578 |
+
"bits_per_elt": 1.125,
|
| 579 |
+
"n_traces": 50,
|
| 580 |
+
"key_cos": 0.82812,
|
| 581 |
+
"val_cos": 0.79949,
|
| 582 |
+
"key_mse": 0.51988,
|
| 583 |
+
"val_mse": 0.00371,
|
| 584 |
+
"attn_cos": 0.97041,
|
| 585 |
+
"attn_output_error": 0.40237,
|
| 586 |
+
"ip_rel": 4.76672,
|
| 587 |
+
"ip_bias": -0.00047,
|
| 588 |
+
"task": "hotpotqa"
|
| 589 |
+
},
|
| 590 |
+
{
|
| 591 |
+
"config": "ternary-bitnet",
|
| 592 |
+
"bits_per_elt": 1.71,
|
| 593 |
+
"n_traces": 50,
|
| 594 |
+
"key_cos": 0.91369,
|
| 595 |
+
"val_cos": 0.90047,
|
| 596 |
+
"key_mse": 0.27374,
|
| 597 |
+
"val_mse": 0.00194,
|
| 598 |
+
"attn_cos": 0.98747,
|
| 599 |
+
"attn_output_error": 0.23722,
|
| 600 |
+
"ip_rel": 3.34637,
|
| 601 |
+
"ip_bias": 0.00039,
|
| 602 |
+
"task": "hotpotqa"
|
| 603 |
+
},
|
| 604 |
+
{
|
| 605 |
+
"config": "productvq-64x256-4b",
|
| 606 |
+
"bits_per_elt": 4.0,
|
| 607 |
+
"n_traces": 50,
|
| 608 |
+
"key_cos": 0.99591,
|
| 609 |
+
"val_cos": 0.99486,
|
| 610 |
+
"key_mse": 0.01412,
|
| 611 |
+
"val_mse": 0.00012,
|
| 612 |
+
"attn_cos": 0.9996,
|
| 613 |
+
"attn_output_error": 0.03707,
|
| 614 |
+
"ip_rel": 0.79562,
|
| 615 |
+
"ip_bias": -7e-05,
|
| 616 |
+
"task": "hotpotqa"
|
| 617 |
+
},
|
| 618 |
+
{
|
| 619 |
+
"config": "productvq-32x256-2b",
|
| 620 |
+
"bits_per_elt": 2.0,
|
| 621 |
+
"n_traces": 50,
|
| 622 |
+
"key_cos": 0.9591,
|
| 623 |
+
"val_cos": 0.94788,
|
| 624 |
+
"key_mse": 0.13817,
|
| 625 |
+
"val_mse": 0.00109,
|
| 626 |
+
"attn_cos": 0.99563,
|
| 627 |
+
"attn_output_error": 0.14155,
|
| 628 |
+
"ip_rel": 3.0345,
|
| 629 |
+
"ip_bias": 0.00029,
|
| 630 |
+
"task": "hotpotqa"
|
| 631 |
+
},
|
| 632 |
+
{
|
| 633 |
+
"config": "turbo-mse-4b",
|
| 634 |
+
"bits_per_elt": 4.125,
|
| 635 |
+
"n_traces": 50,
|
| 636 |
+
"key_cos": 0.99504,
|
| 637 |
+
"val_cos": 0.99504,
|
| 638 |
+
"key_mse": 0.01644,
|
| 639 |
+
"val_mse": 0.0001,
|
| 640 |
+
"attn_cos": 0.99962,
|
| 641 |
+
"attn_output_error": 0.0335,
|
| 642 |
+
"ip_rel": 0.95323,
|
| 643 |
+
"ip_bias": -1e-05,
|
| 644 |
+
"task": "hotpotqa"
|
| 645 |
+
},
|
| 646 |
+
{
|
| 647 |
+
"config": "turbo-prod-4b (K:+qjl)",
|
| 648 |
+
"bits_per_elt": 5.125,
|
| 649 |
+
"n_traces": 50,
|
| 650 |
+
"key_cos": 0.99504,
|
| 651 |
+
"val_cos": 0.99504,
|
| 652 |
+
"key_mse": 0.01644,
|
| 653 |
+
"val_mse": 0.0001,
|
| 654 |
+
"attn_cos": 0.99962,
|
| 655 |
+
"attn_output_error": 0.03354,
|
| 656 |
+
"ip_rel": 1.13378,
|
| 657 |
+
"ip_bias": 0.00023,
|
| 658 |
+
"task": "hotpotqa"
|
| 659 |
+
},
|
| 660 |
+
{
|
| 661 |
+
"config": "turbo-mse-2b",
|
| 662 |
+
"bits_per_elt": 2.125,
|
| 663 |
+
"n_traces": 50,
|
| 664 |
+
"key_cos": 0.94075,
|
| 665 |
+
"val_cos": 0.94072,
|
| 666 |
+
"key_mse": 0.18757,
|
| 667 |
+
"val_mse": 0.00119,
|
| 668 |
+
"attn_cos": 0.99379,
|
| 669 |
+
"attn_output_error": 0.15863,
|
| 670 |
+
"ip_rel": 3.15094,
|
| 671 |
+
"ip_bias": -0.00013,
|
| 672 |
+
"task": "hotpotqa"
|
| 673 |
+
},
|
| 674 |
+
{
|
| 675 |
+
"config": "turbo-prod-2b (K:+qjl)",
|
| 676 |
+
"bits_per_elt": 3.125,
|
| 677 |
+
"n_traces": 50,
|
| 678 |
+
"key_cos": 0.94075,
|
| 679 |
+
"val_cos": 0.94072,
|
| 680 |
+
"key_mse": 0.18757,
|
| 681 |
+
"val_mse": 0.00119,
|
| 682 |
+
"attn_cos": 0.99379,
|
| 683 |
+
"attn_output_error": 0.1587,
|
| 684 |
+
"ip_rel": 3.73498,
|
| 685 |
+
"ip_bias": 9e-05,
|
| 686 |
+
"task": "hotpotqa"
|
| 687 |
+
},
|
| 688 |
+
{
|
| 689 |
+
"config": "turbo-mse-1b",
|
| 690 |
+
"bits_per_elt": 1.125,
|
| 691 |
+
"n_traces": 50,
|
| 692 |
+
"key_cos": 0.79977,
|
| 693 |
+
"val_cos": 0.79987,
|
| 694 |
+
"key_mse": 0.58414,
|
| 695 |
+
"val_mse": 0.00371,
|
| 696 |
+
"attn_cos": 0.97038,
|
| 697 |
+
"attn_output_error": 0.40117,
|
| 698 |
+
"ip_rel": 3.84738,
|
| 699 |
+
"ip_bias": 7e-05,
|
| 700 |
+
"task": "hotpotqa"
|
| 701 |
+
},
|
| 702 |
+
{
|
| 703 |
+
"config": "turbo-prod-1b (K:+qjl)",
|
| 704 |
+
"bits_per_elt": 2.125,
|
| 705 |
+
"n_traces": 50,
|
| 706 |
+
"key_cos": 0.79976,
|
| 707 |
+
"val_cos": 0.79986,
|
| 708 |
+
"key_mse": 0.58415,
|
| 709 |
+
"val_mse": 0.00371,
|
| 710 |
+
"attn_cos": 0.97038,
|
| 711 |
+
"attn_output_error": 0.40125,
|
| 712 |
+
"ip_rel": 6.33608,
|
| 713 |
+
"ip_bias": 0.0003,
|
| 714 |
+
"task": "hotpotqa"
|
| 715 |
+
},
|
| 716 |
+
{
|
| 717 |
+
"config": "scalar-int4",
|
| 718 |
+
"bits_per_elt": 4.25,
|
| 719 |
+
"n_traces": 50,
|
| 720 |
+
"key_cos": 0.99253,
|
| 721 |
+
"val_cos": 0.99503,
|
| 722 |
+
"key_mse": 0.02406,
|
| 723 |
+
"val_mse": 0.00011,
|
| 724 |
+
"attn_cos": 0.99968,
|
| 725 |
+
"attn_output_error": 0.02813,
|
| 726 |
+
"ip_rel": 1.03821,
|
| 727 |
+
"ip_bias": -0.00024,
|
| 728 |
+
"task": "passage_retrieval_en"
|
| 729 |
+
},
|
| 730 |
+
{
|
| 731 |
+
"config": "scalar-int2",
|
| 732 |
+
"bits_per_elt": 2.25,
|
| 733 |
+
"n_traces": 50,
|
| 734 |
+
"key_cos": 0.86483,
|
| 735 |
+
"val_cos": 0.89305,
|
| 736 |
+
"key_mse": 0.493,
|
| 737 |
+
"val_mse": 0.00263,
|
| 738 |
+
"attn_cos": 0.99115,
|
| 739 |
+
"attn_output_error": 0.14928,
|
| 740 |
+
"ip_rel": 5.79172,
|
| 741 |
+
"ip_bias": 0.00156,
|
| 742 |
+
"task": "passage_retrieval_en"
|
| 743 |
+
},
|
| 744 |
+
{
|
| 745 |
+
"config": "kivi-int2",
|
| 746 |
+
"bits_per_elt": 2.25,
|
| 747 |
+
"n_traces": 50,
|
| 748 |
+
"key_cos": 0.84588,
|
| 749 |
+
"val_cos": 0.89305,
|
| 750 |
+
"key_mse": 0.62044,
|
| 751 |
+
"val_mse": 0.00263,
|
| 752 |
+
"attn_cos": 0.99111,
|
| 753 |
+
"attn_output_error": 0.14955,
|
| 754 |
+
"ip_rel": 4.84881,
|
| 755 |
+
"ip_bias": 0.00017,
|
| 756 |
+
"task": "passage_retrieval_en"
|
| 757 |
+
},
|
| 758 |
+
{
|
| 759 |
+
"config": "turboquant~-int2",
|
| 760 |
+
"bits_per_elt": 2.125,
|
| 761 |
+
"n_traces": 50,
|
| 762 |
+
"key_cos": 0.64121,
|
| 763 |
+
"val_cos": 0.64206,
|
| 764 |
+
"key_mse": 2.23468,
|
| 765 |
+
"val_mse": 0.01425,
|
| 766 |
+
"attn_cos": 0.386,
|
| 767 |
+
"attn_output_error": 2.34058,
|
| 768 |
+
"ip_rel": 11.80499,
|
| 769 |
+
"ip_bias": 0.00126,
|
| 770 |
+
"task": "passage_retrieval_en"
|
| 771 |
+
},
|
| 772 |
+
{
|
| 773 |
+
"config": "productvq-16x256-1b",
|
| 774 |
+
"bits_per_elt": 1.0,
|
| 775 |
+
"n_traces": 50,
|
| 776 |
+
"key_cos": 0.86038,
|
| 777 |
+
"val_cos": 0.82379,
|
| 778 |
+
"key_mse": 0.44569,
|
| 779 |
+
"val_mse": 0.00351,
|
| 780 |
+
"attn_cos": 0.97498,
|
| 781 |
+
"attn_output_error": 0.34639,
|
| 782 |
+
"ip_rel": 3.6548,
|
| 783 |
+
"ip_bias": 0.00048,
|
| 784 |
+
"task": "passage_retrieval_en"
|
| 785 |
+
},
|
| 786 |
+
{
|
| 787 |
+
"config": "productvq-8x256-0.5b",
|
| 788 |
+
"bits_per_elt": 0.5,
|
| 789 |
+
"n_traces": 50,
|
| 790 |
+
"key_cos": 0.73171,
|
| 791 |
+
"val_cos": 0.66755,
|
| 792 |
+
"key_mse": 0.78719,
|
| 793 |
+
"val_mse": 0.00602,
|
| 794 |
+
"attn_cos": 0.9103,
|
| 795 |
+
"attn_output_error": 0.52946,
|
| 796 |
+
"ip_rel": 4.94806,
|
| 797 |
+
"ip_bias": -0.00091,
|
| 798 |
+
"task": "passage_retrieval_en"
|
| 799 |
+
},
|
| 800 |
+
{
|
| 801 |
+
"config": "ropesplit-1b",
|
| 802 |
+
"bits_per_elt": 1.0,
|
| 803 |
+
"n_traces": 50,
|
| 804 |
+
"key_cos": 0.84593,
|
| 805 |
+
"val_cos": 0.82324,
|
| 806 |
+
"key_mse": 0.48009,
|
| 807 |
+
"val_mse": 0.00352,
|
| 808 |
+
"attn_cos": 0.97512,
|
| 809 |
+
"attn_output_error": 0.34746,
|
| 810 |
+
"ip_rel": 4.24382,
|
| 811 |
+
"ip_bias": 0.00107,
|
| 812 |
+
"task": "passage_retrieval_en"
|
| 813 |
+
},
|
| 814 |
+
{
|
| 815 |
+
"config": "sign-1bit",
|
| 816 |
+
"bits_per_elt": 1.125,
|
| 817 |
+
"n_traces": 50,
|
| 818 |
+
"key_cos": 0.82941,
|
| 819 |
+
"val_cos": 0.7995,
|
| 820 |
+
"key_mse": 0.51788,
|
| 821 |
+
"val_mse": 0.00375,
|
| 822 |
+
"attn_cos": 0.9717,
|
| 823 |
+
"attn_output_error": 0.40183,
|
| 824 |
+
"ip_rel": 4.47009,
|
| 825 |
+
"ip_bias": -0.00038,
|
| 826 |
+
"task": "passage_retrieval_en"
|
| 827 |
+
},
|
| 828 |
+
{
|
| 829 |
+
"config": "ternary-bitnet",
|
| 830 |
+
"bits_per_elt": 1.71,
|
| 831 |
+
"n_traces": 50,
|
| 832 |
+
"key_cos": 0.91408,
|
| 833 |
+
"val_cos": 0.90049,
|
| 834 |
+
"key_mse": 0.27345,
|
| 835 |
+
"val_mse": 0.00197,
|
| 836 |
+
"attn_cos": 0.98803,
|
| 837 |
+
"attn_output_error": 0.23646,
|
| 838 |
+
"ip_rel": 3.31466,
|
| 839 |
+
"ip_bias": 0.00039,
|
| 840 |
+
"task": "passage_retrieval_en"
|
| 841 |
+
},
|
| 842 |
+
{
|
| 843 |
+
"config": "productvq-64x256-4b",
|
| 844 |
+
"bits_per_elt": 4.0,
|
| 845 |
+
"n_traces": 50,
|
| 846 |
+
"key_cos": 0.99592,
|
| 847 |
+
"val_cos": 0.99482,
|
| 848 |
+
"key_mse": 0.01414,
|
| 849 |
+
"val_mse": 0.00012,
|
| 850 |
+
"attn_cos": 0.9996,
|
| 851 |
+
"attn_output_error": 0.03737,
|
| 852 |
+
"ip_rel": 0.97058,
|
| 853 |
+
"ip_bias": 0.00012,
|
| 854 |
+
"task": "passage_retrieval_en"
|
| 855 |
+
},
|
| 856 |
+
{
|
| 857 |
+
"config": "productvq-32x256-2b",
|
| 858 |
+
"bits_per_elt": 2.0,
|
| 859 |
+
"n_traces": 50,
|
| 860 |
+
"key_cos": 0.9593,
|
| 861 |
+
"val_cos": 0.94794,
|
| 862 |
+
"key_mse": 0.13801,
|
| 863 |
+
"val_mse": 0.0011,
|
| 864 |
+
"attn_cos": 0.99558,
|
| 865 |
+
"attn_output_error": 0.1428,
|
| 866 |
+
"ip_rel": 2.48128,
|
| 867 |
+
"ip_bias": -0.00033,
|
| 868 |
+
"task": "passage_retrieval_en"
|
| 869 |
+
},
|
| 870 |
+
{
|
| 871 |
+
"config": "turbo-mse-4b",
|
| 872 |
+
"bits_per_elt": 4.125,
|
| 873 |
+
"n_traces": 50,
|
| 874 |
+
"key_cos": 0.99504,
|
| 875 |
+
"val_cos": 0.99504,
|
| 876 |
+
"key_mse": 0.01652,
|
| 877 |
+
"val_mse": 0.00011,
|
| 878 |
+
"attn_cos": 0.99962,
|
| 879 |
+
"attn_output_error": 0.03385,
|
| 880 |
+
"ip_rel": 0.90062,
|
| 881 |
+
"ip_bias": -0.00015,
|
| 882 |
+
"task": "passage_retrieval_en"
|
| 883 |
+
},
|
| 884 |
+
{
|
| 885 |
+
"config": "turbo-prod-4b (K:+qjl)",
|
| 886 |
+
"bits_per_elt": 5.125,
|
| 887 |
+
"n_traces": 50,
|
| 888 |
+
"key_cos": 0.99504,
|
| 889 |
+
"val_cos": 0.99504,
|
| 890 |
+
"key_mse": 0.01652,
|
| 891 |
+
"val_mse": 0.00011,
|
| 892 |
+
"attn_cos": 0.99962,
|
| 893 |
+
"attn_output_error": 0.0339,
|
| 894 |
+
"ip_rel": 1.23727,
|
| 895 |
+
"ip_bias": -0.00021,
|
| 896 |
+
"task": "passage_retrieval_en"
|
| 897 |
+
},
|
| 898 |
+
{
|
| 899 |
+
"config": "turbo-mse-2b",
|
| 900 |
+
"bits_per_elt": 2.125,
|
| 901 |
+
"n_traces": 50,
|
| 902 |
+
"key_cos": 0.94075,
|
| 903 |
+
"val_cos": 0.94073,
|
| 904 |
+
"key_mse": 0.18858,
|
| 905 |
+
"val_mse": 0.00121,
|
| 906 |
+
"attn_cos": 0.99395,
|
| 907 |
+
"attn_output_error": 0.1586,
|
| 908 |
+
"ip_rel": 3.42166,
|
| 909 |
+
"ip_bias": 0.00037,
|
| 910 |
+
"task": "passage_retrieval_en"
|
| 911 |
+
},
|
| 912 |
+
{
|
| 913 |
+
"config": "turbo-prod-2b (K:+qjl)",
|
| 914 |
+
"bits_per_elt": 3.125,
|
| 915 |
+
"n_traces": 50,
|
| 916 |
+
"key_cos": 0.94075,
|
| 917 |
+
"val_cos": 0.94073,
|
| 918 |
+
"key_mse": 0.18858,
|
| 919 |
+
"val_mse": 0.00121,
|
| 920 |
+
"attn_cos": 0.99395,
|
| 921 |
+
"attn_output_error": 0.15867,
|
| 922 |
+
"ip_rel": 3.99742,
|
| 923 |
+
"ip_bias": 0.00031,
|
| 924 |
+
"task": "passage_retrieval_en"
|
| 925 |
+
},
|
| 926 |
+
{
|
| 927 |
+
"config": "turbo-mse-1b",
|
| 928 |
+
"bits_per_elt": 1.125,
|
| 929 |
+
"n_traces": 50,
|
| 930 |
+
"key_cos": 0.79977,
|
| 931 |
+
"val_cos": 0.79989,
|
| 932 |
+
"key_mse": 0.58729,
|
| 933 |
+
"val_mse": 0.00375,
|
| 934 |
+
"attn_cos": 0.97165,
|
| 935 |
+
"attn_output_error": 0.4003,
|
| 936 |
+
"ip_rel": 4.29184,
|
| 937 |
+
"ip_bias": 0.00013,
|
| 938 |
+
"task": "passage_retrieval_en"
|
| 939 |
+
},
|
| 940 |
+
{
|
| 941 |
+
"config": "turbo-prod-1b (K:+qjl)",
|
| 942 |
+
"bits_per_elt": 2.125,
|
| 943 |
+
"n_traces": 50,
|
| 944 |
+
"key_cos": 0.79976,
|
| 945 |
+
"val_cos": 0.79989,
|
| 946 |
+
"key_mse": 0.5873,
|
| 947 |
+
"val_mse": 0.00375,
|
| 948 |
+
"attn_cos": 0.97165,
|
| 949 |
+
"attn_output_error": 0.4004,
|
| 950 |
+
"ip_rel": 6.17042,
|
| 951 |
+
"ip_bias": -0.00022,
|
| 952 |
+
"task": "passage_retrieval_en"
|
| 953 |
+
},
|
| 954 |
+
{
|
| 955 |
+
"config": "scalar-int4",
|
| 956 |
+
"bits_per_elt": 4.25,
|
| 957 |
+
"n_traces": 50,
|
| 958 |
+
"key_cos": 0.99235,
|
| 959 |
+
"val_cos": 0.99503,
|
| 960 |
+
"key_mse": 0.02448,
|
| 961 |
+
"val_mse": 0.0001,
|
| 962 |
+
"attn_cos": 0.99966,
|
| 963 |
+
"attn_output_error": 0.02895,
|
| 964 |
+
"ip_rel": 1.16444,
|
| 965 |
+
"ip_bias": -8e-05,
|
| 966 |
+
"task": "repobench-p"
|
| 967 |
+
},
|
| 968 |
+
{
|
| 969 |
+
"config": "scalar-int2",
|
| 970 |
+
"bits_per_elt": 2.25,
|
| 971 |
+
"n_traces": 50,
|
| 972 |
+
"key_cos": 0.86482,
|
| 973 |
+
"val_cos": 0.89308,
|
| 974 |
+
"key_mse": 0.48425,
|
| 975 |
+
"val_mse": 0.00243,
|
| 976 |
+
"attn_cos": 0.99033,
|
| 977 |
+
"attn_output_error": 0.15413,
|
| 978 |
+
"ip_rel": 5.37172,
|
| 979 |
+
"ip_bias": -0.00046,
|
| 980 |
+
"task": "repobench-p"
|
| 981 |
+
},
|
| 982 |
+
{
|
| 983 |
+
"config": "kivi-int2",
|
| 984 |
+
"bits_per_elt": 2.25,
|
| 985 |
+
"n_traces": 50,
|
| 986 |
+
"key_cos": 0.85308,
|
| 987 |
+
"val_cos": 0.89308,
|
| 988 |
+
"key_mse": 0.59033,
|
| 989 |
+
"val_mse": 0.00243,
|
| 990 |
+
"attn_cos": 0.9903,
|
| 991 |
+
"attn_output_error": 0.1544,
|
| 992 |
+
"ip_rel": 5.35,
|
| 993 |
+
"ip_bias": -0.0002,
|
| 994 |
+
"task": "repobench-p"
|
| 995 |
+
},
|
| 996 |
+
{
|
| 997 |
+
"config": "turboquant~-int2",
|
| 998 |
+
"bits_per_elt": 2.125,
|
| 999 |
+
"n_traces": 50,
|
| 1000 |
+
"key_cos": 0.64046,
|
| 1001 |
+
"val_cos": 0.64283,
|
| 1002 |
+
"key_mse": 2.21611,
|
| 1003 |
+
"val_mse": 0.01318,
|
| 1004 |
+
"attn_cos": 0.38991,
|
| 1005 |
+
"attn_output_error": 2.35685,
|
| 1006 |
+
"ip_rel": 11.24984,
|
| 1007 |
+
"ip_bias": -0.00501,
|
| 1008 |
+
"task": "repobench-p"
|
| 1009 |
+
},
|
| 1010 |
+
{
|
| 1011 |
+
"config": "productvq-16x256-1b",
|
| 1012 |
+
"bits_per_elt": 1.0,
|
| 1013 |
+
"n_traces": 50,
|
| 1014 |
+
"key_cos": 0.86444,
|
| 1015 |
+
"val_cos": 0.8248,
|
| 1016 |
+
"key_mse": 0.43171,
|
| 1017 |
+
"val_mse": 0.0032,
|
| 1018 |
+
"attn_cos": 0.97492,
|
| 1019 |
+
"attn_output_error": 0.32172,
|
| 1020 |
+
"ip_rel": 4.49992,
|
| 1021 |
+
"ip_bias": -0.0003,
|
| 1022 |
+
"task": "repobench-p"
|
| 1023 |
+
},
|
| 1024 |
+
{
|
| 1025 |
+
"config": "productvq-8x256-0.5b",
|
| 1026 |
+
"bits_per_elt": 0.5,
|
| 1027 |
+
"n_traces": 50,
|
| 1028 |
+
"key_cos": 0.74333,
|
| 1029 |
+
"val_cos": 0.67596,
|
| 1030 |
+
"key_mse": 0.75351,
|
| 1031 |
+
"val_mse": 0.00546,
|
| 1032 |
+
"attn_cos": 0.91412,
|
| 1033 |
+
"attn_output_error": 0.49052,
|
| 1034 |
+
"ip_rel": 6.03475,
|
| 1035 |
+
"ip_bias": -0.00016,
|
| 1036 |
+
"task": "repobench-p"
|
| 1037 |
+
},
|
| 1038 |
+
{
|
| 1039 |
+
"config": "ropesplit-1b",
|
| 1040 |
+
"bits_per_elt": 1.0,
|
| 1041 |
+
"n_traces": 50,
|
| 1042 |
+
"key_cos": 0.8528,
|
| 1043 |
+
"val_cos": 0.82423,
|
| 1044 |
+
"key_mse": 0.45971,
|
| 1045 |
+
"val_mse": 0.0032,
|
| 1046 |
+
"attn_cos": 0.97494,
|
| 1047 |
+
"attn_output_error": 0.32322,
|
| 1048 |
+
"ip_rel": 4.06041,
|
| 1049 |
+
"ip_bias": -0.0002,
|
| 1050 |
+
"task": "repobench-p"
|
| 1051 |
+
},
|
| 1052 |
+
{
|
| 1053 |
+
"config": "sign-1bit",
|
| 1054 |
+
"bits_per_elt": 1.125,
|
| 1055 |
+
"n_traces": 50,
|
| 1056 |
+
"key_cos": 0.82897,
|
| 1057 |
+
"val_cos": 0.79969,
|
| 1058 |
+
"key_mse": 0.5174,
|
| 1059 |
+
"val_mse": 0.00347,
|
| 1060 |
+
"attn_cos": 0.96847,
|
| 1061 |
+
"attn_output_error": 0.40384,
|
| 1062 |
+
"ip_rel": 4.84825,
|
| 1063 |
+
"ip_bias": -0.00062,
|
| 1064 |
+
"task": "repobench-p"
|
| 1065 |
+
},
|
| 1066 |
+
{
|
| 1067 |
+
"config": "ternary-bitnet",
|
| 1068 |
+
"bits_per_elt": 1.71,
|
| 1069 |
+
"n_traces": 50,
|
| 1070 |
+
"key_cos": 0.91402,
|
| 1071 |
+
"val_cos": 0.90054,
|
| 1072 |
+
"key_mse": 0.27299,
|
| 1073 |
+
"val_mse": 0.00182,
|
| 1074 |
+
"attn_cos": 0.9867,
|
| 1075 |
+
"attn_output_error": 0.23861,
|
| 1076 |
+
"ip_rel": 2.86088,
|
| 1077 |
+
"ip_bias": -0.0002,
|
| 1078 |
+
"task": "repobench-p"
|
| 1079 |
+
},
|
| 1080 |
+
{
|
| 1081 |
+
"config": "productvq-64x256-4b",
|
| 1082 |
+
"bits_per_elt": 4.0,
|
| 1083 |
+
"n_traces": 50,
|
| 1084 |
+
"key_cos": 0.99606,
|
| 1085 |
+
"val_cos": 0.99508,
|
| 1086 |
+
"key_mse": 0.01358,
|
| 1087 |
+
"val_mse": 0.00011,
|
| 1088 |
+
"attn_cos": 0.99962,
|
| 1089 |
+
"attn_output_error": 0.0346,
|
| 1090 |
+
"ip_rel": 0.71153,
|
| 1091 |
+
"ip_bias": -6e-05,
|
| 1092 |
+
"task": "repobench-p"
|
| 1093 |
+
},
|
| 1094 |
+
{
|
| 1095 |
+
"config": "productvq-32x256-2b",
|
| 1096 |
+
"bits_per_elt": 2.0,
|
| 1097 |
+
"n_traces": 50,
|
| 1098 |
+
"key_cos": 0.9603,
|
| 1099 |
+
"val_cos": 0.94747,
|
| 1100 |
+
"key_mse": 0.13409,
|
| 1101 |
+
"val_mse": 0.001,
|
| 1102 |
+
"attn_cos": 0.99551,
|
| 1103 |
+
"attn_output_error": 0.13328,
|
| 1104 |
+
"ip_rel": 2.39037,
|
| 1105 |
+
"ip_bias": -5e-05,
|
| 1106 |
+
"task": "repobench-p"
|
| 1107 |
+
},
|
| 1108 |
+
{
|
| 1109 |
+
"config": "turbo-mse-4b",
|
| 1110 |
+
"bits_per_elt": 4.125,
|
| 1111 |
+
"n_traces": 50,
|
| 1112 |
+
"key_cos": 0.99504,
|
| 1113 |
+
"val_cos": 0.99503,
|
| 1114 |
+
"key_mse": 0.01641,
|
| 1115 |
+
"val_mse": 0.0001,
|
| 1116 |
+
"attn_cos": 0.99958,
|
| 1117 |
+
"attn_output_error": 0.03482,
|
| 1118 |
+
"ip_rel": 0.9464,
|
| 1119 |
+
"ip_bias": 6e-05,
|
| 1120 |
+
"task": "repobench-p"
|
| 1121 |
+
},
|
| 1122 |
+
{
|
| 1123 |
+
"config": "turbo-prod-4b (K:+qjl)",
|
| 1124 |
+
"bits_per_elt": 5.125,
|
| 1125 |
+
"n_traces": 50,
|
| 1126 |
+
"key_cos": 0.99504,
|
| 1127 |
+
"val_cos": 0.99503,
|
| 1128 |
+
"key_mse": 0.01641,
|
| 1129 |
+
"val_mse": 0.0001,
|
| 1130 |
+
"attn_cos": 0.99958,
|
| 1131 |
+
"attn_output_error": 0.03486,
|
| 1132 |
+
"ip_rel": 1.26089,
|
| 1133 |
+
"ip_bias": 7e-05,
|
| 1134 |
+
"task": "repobench-p"
|
| 1135 |
+
},
|
| 1136 |
+
{
|
| 1137 |
+
"config": "turbo-mse-2b",
|
| 1138 |
+
"bits_per_elt": 2.125,
|
| 1139 |
+
"n_traces": 50,
|
| 1140 |
+
"key_cos": 0.94071,
|
| 1141 |
+
"val_cos": 0.94064,
|
| 1142 |
+
"key_mse": 0.18716,
|
| 1143 |
+
"val_mse": 0.00112,
|
| 1144 |
+
"attn_cos": 0.99335,
|
| 1145 |
+
"attn_output_error": 0.16086,
|
| 1146 |
+
"ip_rel": 3.0084,
|
| 1147 |
+
"ip_bias": 0.00063,
|
| 1148 |
+
"task": "repobench-p"
|
| 1149 |
+
},
|
| 1150 |
+
{
|
| 1151 |
+
"config": "turbo-prod-2b (K:+qjl)",
|
| 1152 |
+
"bits_per_elt": 3.125,
|
| 1153 |
+
"n_traces": 50,
|
| 1154 |
+
"key_cos": 0.94071,
|
| 1155 |
+
"val_cos": 0.94064,
|
| 1156 |
+
"key_mse": 0.18716,
|
| 1157 |
+
"val_mse": 0.00112,
|
| 1158 |
+
"attn_cos": 0.99336,
|
| 1159 |
+
"attn_output_error": 0.1609,
|
| 1160 |
+
"ip_rel": 4.07001,
|
| 1161 |
+
"ip_bias": 8e-05,
|
| 1162 |
+
"task": "repobench-p"
|
| 1163 |
+
},
|
| 1164 |
+
{
|
| 1165 |
+
"config": "turbo-mse-1b",
|
| 1166 |
+
"bits_per_elt": 1.125,
|
| 1167 |
+
"n_traces": 50,
|
| 1168 |
+
"key_cos": 0.79966,
|
| 1169 |
+
"val_cos": 0.79959,
|
| 1170 |
+
"key_mse": 0.58273,
|
| 1171 |
+
"val_mse": 0.00347,
|
| 1172 |
+
"attn_cos": 0.96834,
|
| 1173 |
+
"attn_output_error": 0.40316,
|
| 1174 |
+
"ip_rel": 4.66947,
|
| 1175 |
+
"ip_bias": -0.00025,
|
| 1176 |
+
"task": "repobench-p"
|
| 1177 |
+
},
|
| 1178 |
+
{
|
| 1179 |
+
"config": "turbo-prod-1b (K:+qjl)",
|
| 1180 |
+
"bits_per_elt": 2.125,
|
| 1181 |
+
"n_traces": 50,
|
| 1182 |
+
"key_cos": 0.79965,
|
| 1183 |
+
"val_cos": 0.79959,
|
| 1184 |
+
"key_mse": 0.58274,
|
| 1185 |
+
"val_mse": 0.00347,
|
| 1186 |
+
"attn_cos": 0.96834,
|
| 1187 |
+
"attn_output_error": 0.40325,
|
| 1188 |
+
"ip_rel": 7.08782,
|
| 1189 |
+
"ip_bias": -0.00021,
|
| 1190 |
+
"task": "repobench-p"
|
| 1191 |
+
}
|
| 1192 |
+
]
|
artifacts/longbench_results.json
ADDED
|
@@ -0,0 +1,632 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
[
|
| 2 |
+
{
|
| 3 |
+
"task": "qasper",
|
| 4 |
+
"config": "fp16",
|
| 5 |
+
"bpe": 16.0,
|
| 6 |
+
"f1": 0.3584,
|
| 7 |
+
"delta_f1": 0.0
|
| 8 |
+
},
|
| 9 |
+
{
|
| 10 |
+
"task": "qasper",
|
| 11 |
+
"config": "scalar-int4",
|
| 12 |
+
"bpe": 4.25,
|
| 13 |
+
"f1": 0.3565,
|
| 14 |
+
"delta_f1": 0.0031
|
| 15 |
+
},
|
| 16 |
+
{
|
| 17 |
+
"task": "qasper",
|
| 18 |
+
"config": "scalar-int2",
|
| 19 |
+
"bpe": 2.25,
|
| 20 |
+
"f1": 0.2531,
|
| 21 |
+
"delta_f1": -0.1003
|
| 22 |
+
},
|
| 23 |
+
{
|
| 24 |
+
"task": "qasper",
|
| 25 |
+
"config": "kivi-int2",
|
| 26 |
+
"bpe": 2.25,
|
| 27 |
+
"f1": 0.27,
|
| 28 |
+
"delta_f1": -0.0834
|
| 29 |
+
},
|
| 30 |
+
{
|
| 31 |
+
"task": "qasper",
|
| 32 |
+
"config": "turboquant~-int2",
|
| 33 |
+
"bpe": 2.125,
|
| 34 |
+
"f1": 0.0927,
|
| 35 |
+
"delta_f1": -0.2607
|
| 36 |
+
},
|
| 37 |
+
{
|
| 38 |
+
"task": "qasper",
|
| 39 |
+
"config": "productvq-16x256-1b",
|
| 40 |
+
"bpe": 1.0,
|
| 41 |
+
"f1": 0.2049,
|
| 42 |
+
"delta_f1": -0.1485
|
| 43 |
+
},
|
| 44 |
+
{
|
| 45 |
+
"task": "qasper",
|
| 46 |
+
"config": "productvq-8x256-0.5b",
|
| 47 |
+
"bpe": 0.5,
|
| 48 |
+
"f1": 0.146,
|
| 49 |
+
"delta_f1": -0.2074
|
| 50 |
+
},
|
| 51 |
+
{
|
| 52 |
+
"task": "qasper",
|
| 53 |
+
"config": "ropesplit-1b",
|
| 54 |
+
"bpe": 1.0,
|
| 55 |
+
"f1": 0.2108,
|
| 56 |
+
"delta_f1": -0.1426
|
| 57 |
+
},
|
| 58 |
+
{
|
| 59 |
+
"task": "qasper",
|
| 60 |
+
"config": "sign-1bit",
|
| 61 |
+
"bpe": 1.125,
|
| 62 |
+
"f1": 0.1731,
|
| 63 |
+
"delta_f1": -0.1803
|
| 64 |
+
},
|
| 65 |
+
{
|
| 66 |
+
"task": "qasper",
|
| 67 |
+
"config": "ternary-bitnet",
|
| 68 |
+
"bpe": 1.71,
|
| 69 |
+
"f1": 0.221,
|
| 70 |
+
"delta_f1": -0.1325
|
| 71 |
+
},
|
| 72 |
+
{
|
| 73 |
+
"task": "qasper",
|
| 74 |
+
"config": "productvq-64x256-4b",
|
| 75 |
+
"bpe": 4.0,
|
| 76 |
+
"f1": 0.349,
|
| 77 |
+
"delta_f1": -0.0044
|
| 78 |
+
},
|
| 79 |
+
{
|
| 80 |
+
"task": "qasper",
|
| 81 |
+
"config": "productvq-32x256-2b",
|
| 82 |
+
"bpe": 2.0,
|
| 83 |
+
"f1": 0.3092,
|
| 84 |
+
"delta_f1": -0.0442
|
| 85 |
+
},
|
| 86 |
+
{
|
| 87 |
+
"task": "qasper",
|
| 88 |
+
"config": "turbo-mse-4b",
|
| 89 |
+
"bpe": 4.125,
|
| 90 |
+
"f1": 0.3582,
|
| 91 |
+
"delta_f1": 0.0048
|
| 92 |
+
},
|
| 93 |
+
{
|
| 94 |
+
"task": "qasper",
|
| 95 |
+
"config": "turbo-prod-4b (K:+qjl)",
|
| 96 |
+
"bpe": 5.125,
|
| 97 |
+
"f1": 0.3363,
|
| 98 |
+
"delta_f1": -0.0171
|
| 99 |
+
},
|
| 100 |
+
{
|
| 101 |
+
"task": "qasper",
|
| 102 |
+
"config": "turbo-mse-2b",
|
| 103 |
+
"bpe": 2.125,
|
| 104 |
+
"f1": 0.2605,
|
| 105 |
+
"delta_f1": -0.0929
|
| 106 |
+
},
|
| 107 |
+
{
|
| 108 |
+
"task": "qasper",
|
| 109 |
+
"config": "turbo-prod-2b (K:+qjl)",
|
| 110 |
+
"bpe": 3.125,
|
| 111 |
+
"f1": 0.2684,
|
| 112 |
+
"delta_f1": -0.085
|
| 113 |
+
},
|
| 114 |
+
{
|
| 115 |
+
"task": "qasper",
|
| 116 |
+
"config": "turbo-mse-1b",
|
| 117 |
+
"bpe": 1.125,
|
| 118 |
+
"f1": 0.1645,
|
| 119 |
+
"delta_f1": -0.1889
|
| 120 |
+
},
|
| 121 |
+
{
|
| 122 |
+
"task": "qasper",
|
| 123 |
+
"config": "turbo-prod-1b (K:+qjl)",
|
| 124 |
+
"bpe": 2.125,
|
| 125 |
+
"f1": 0.188,
|
| 126 |
+
"delta_f1": -0.1654
|
| 127 |
+
},
|
| 128 |
+
{
|
| 129 |
+
"task": "2wikimqa",
|
| 130 |
+
"config": "fp16",
|
| 131 |
+
"bpe": 16.0,
|
| 132 |
+
"f1": 0.3163,
|
| 133 |
+
"delta_f1": 0.0
|
| 134 |
+
},
|
| 135 |
+
{
|
| 136 |
+
"task": "2wikimqa",
|
| 137 |
+
"config": "scalar-int4",
|
| 138 |
+
"bpe": 4.25,
|
| 139 |
+
"f1": 0.3041,
|
| 140 |
+
"delta_f1": -0.0121
|
| 141 |
+
},
|
| 142 |
+
{
|
| 143 |
+
"task": "2wikimqa",
|
| 144 |
+
"config": "scalar-int2",
|
| 145 |
+
"bpe": 2.25,
|
| 146 |
+
"f1": 0.2227,
|
| 147 |
+
"delta_f1": -0.0936
|
| 148 |
+
},
|
| 149 |
+
{
|
| 150 |
+
"task": "2wikimqa",
|
| 151 |
+
"config": "kivi-int2",
|
| 152 |
+
"bpe": 2.25,
|
| 153 |
+
"f1": 0.1952,
|
| 154 |
+
"delta_f1": -0.1211
|
| 155 |
+
},
|
| 156 |
+
{
|
| 157 |
+
"task": "2wikimqa",
|
| 158 |
+
"config": "turboquant~-int2",
|
| 159 |
+
"bpe": 2.125,
|
| 160 |
+
"f1": 0.0392,
|
| 161 |
+
"delta_f1": -0.2771
|
| 162 |
+
},
|
| 163 |
+
{
|
| 164 |
+
"task": "2wikimqa",
|
| 165 |
+
"config": "productvq-16x256-1b",
|
| 166 |
+
"bpe": 1.0,
|
| 167 |
+
"f1": 0.2314,
|
| 168 |
+
"delta_f1": -0.0849
|
| 169 |
+
},
|
| 170 |
+
{
|
| 171 |
+
"task": "2wikimqa",
|
| 172 |
+
"config": "productvq-8x256-0.5b",
|
| 173 |
+
"bpe": 0.5,
|
| 174 |
+
"f1": 0.0979,
|
| 175 |
+
"delta_f1": -0.2184
|
| 176 |
+
},
|
| 177 |
+
{
|
| 178 |
+
"task": "2wikimqa",
|
| 179 |
+
"config": "ropesplit-1b",
|
| 180 |
+
"bpe": 1.0,
|
| 181 |
+
"f1": 0.2485,
|
| 182 |
+
"delta_f1": -0.0678
|
| 183 |
+
},
|
| 184 |
+
{
|
| 185 |
+
"task": "2wikimqa",
|
| 186 |
+
"config": "sign-1bit",
|
| 187 |
+
"bpe": 1.125,
|
| 188 |
+
"f1": 0.1601,
|
| 189 |
+
"delta_f1": -0.1562
|
| 190 |
+
},
|
| 191 |
+
{
|
| 192 |
+
"task": "2wikimqa",
|
| 193 |
+
"config": "ternary-bitnet",
|
| 194 |
+
"bpe": 1.71,
|
| 195 |
+
"f1": 0.244,
|
| 196 |
+
"delta_f1": -0.0723
|
| 197 |
+
},
|
| 198 |
+
{
|
| 199 |
+
"task": "2wikimqa",
|
| 200 |
+
"config": "productvq-64x256-4b",
|
| 201 |
+
"bpe": 4.0,
|
| 202 |
+
"f1": 0.2542,
|
| 203 |
+
"delta_f1": -0.0621
|
| 204 |
+
},
|
| 205 |
+
{
|
| 206 |
+
"task": "2wikimqa",
|
| 207 |
+
"config": "productvq-32x256-2b",
|
| 208 |
+
"bpe": 2.0,
|
| 209 |
+
"f1": 0.3099,
|
| 210 |
+
"delta_f1": -0.0064
|
| 211 |
+
},
|
| 212 |
+
{
|
| 213 |
+
"task": "2wikimqa",
|
| 214 |
+
"config": "turbo-mse-4b",
|
| 215 |
+
"bpe": 4.125,
|
| 216 |
+
"f1": 0.301,
|
| 217 |
+
"delta_f1": -0.0152
|
| 218 |
+
},
|
| 219 |
+
{
|
| 220 |
+
"task": "2wikimqa",
|
| 221 |
+
"config": "turbo-prod-4b (K:+qjl)",
|
| 222 |
+
"bpe": 5.125,
|
| 223 |
+
"f1": 0.3204,
|
| 224 |
+
"delta_f1": 0.0041
|
| 225 |
+
},
|
| 226 |
+
{
|
| 227 |
+
"task": "2wikimqa",
|
| 228 |
+
"config": "turbo-mse-2b",
|
| 229 |
+
"bpe": 2.125,
|
| 230 |
+
"f1": 0.2739,
|
| 231 |
+
"delta_f1": -0.0424
|
| 232 |
+
},
|
| 233 |
+
{
|
| 234 |
+
"task": "2wikimqa",
|
| 235 |
+
"config": "turbo-prod-2b (K:+qjl)",
|
| 236 |
+
"bpe": 3.125,
|
| 237 |
+
"f1": 0.2502,
|
| 238 |
+
"delta_f1": -0.0661
|
| 239 |
+
},
|
| 240 |
+
{
|
| 241 |
+
"task": "2wikimqa",
|
| 242 |
+
"config": "turbo-mse-1b",
|
| 243 |
+
"bpe": 1.125,
|
| 244 |
+
"f1": 0.2113,
|
| 245 |
+
"delta_f1": -0.105
|
| 246 |
+
},
|
| 247 |
+
{
|
| 248 |
+
"task": "2wikimqa",
|
| 249 |
+
"config": "turbo-prod-1b (K:+qjl)",
|
| 250 |
+
"bpe": 2.125,
|
| 251 |
+
"f1": 0.1891,
|
| 252 |
+
"delta_f1": -0.1272
|
| 253 |
+
},
|
| 254 |
+
{
|
| 255 |
+
"task": "hotpotqa",
|
| 256 |
+
"config": "fp16",
|
| 257 |
+
"bpe": 16.0,
|
| 258 |
+
"f1": 0.4018,
|
| 259 |
+
"delta_f1": 0.0
|
| 260 |
+
},
|
| 261 |
+
{
|
| 262 |
+
"task": "hotpotqa",
|
| 263 |
+
"config": "scalar-int4",
|
| 264 |
+
"bpe": 4.25,
|
| 265 |
+
"f1": 0.3873,
|
| 266 |
+
"delta_f1": 0.0155
|
| 267 |
+
},
|
| 268 |
+
{
|
| 269 |
+
"task": "hotpotqa",
|
| 270 |
+
"config": "scalar-int2",
|
| 271 |
+
"bpe": 2.25,
|
| 272 |
+
"f1": 0.2828,
|
| 273 |
+
"delta_f1": -0.089
|
| 274 |
+
},
|
| 275 |
+
{
|
| 276 |
+
"task": "hotpotqa",
|
| 277 |
+
"config": "kivi-int2",
|
| 278 |
+
"bpe": 2.25,
|
| 279 |
+
"f1": 0.2158,
|
| 280 |
+
"delta_f1": -0.156
|
| 281 |
+
},
|
| 282 |
+
{
|
| 283 |
+
"task": "hotpotqa",
|
| 284 |
+
"config": "turboquant~-int2",
|
| 285 |
+
"bpe": 2.125,
|
| 286 |
+
"f1": 0.0118,
|
| 287 |
+
"delta_f1": -0.36
|
| 288 |
+
},
|
| 289 |
+
{
|
| 290 |
+
"task": "hotpotqa",
|
| 291 |
+
"config": "productvq-16x256-1b",
|
| 292 |
+
"bpe": 1.0,
|
| 293 |
+
"f1": 0.1762,
|
| 294 |
+
"delta_f1": -0.1956
|
| 295 |
+
},
|
| 296 |
+
{
|
| 297 |
+
"task": "hotpotqa",
|
| 298 |
+
"config": "productvq-8x256-0.5b",
|
| 299 |
+
"bpe": 0.5,
|
| 300 |
+
"f1": 0.1524,
|
| 301 |
+
"delta_f1": -0.2194
|
| 302 |
+
},
|
| 303 |
+
{
|
| 304 |
+
"task": "hotpotqa",
|
| 305 |
+
"config": "ropesplit-1b",
|
| 306 |
+
"bpe": 1.0,
|
| 307 |
+
"f1": 0.2318,
|
| 308 |
+
"delta_f1": -0.14
|
| 309 |
+
},
|
| 310 |
+
{
|
| 311 |
+
"task": "hotpotqa",
|
| 312 |
+
"config": "sign-1bit",
|
| 313 |
+
"bpe": 1.125,
|
| 314 |
+
"f1": 0.1167,
|
| 315 |
+
"delta_f1": -0.2551
|
| 316 |
+
},
|
| 317 |
+
{
|
| 318 |
+
"task": "hotpotqa",
|
| 319 |
+
"config": "ternary-bitnet",
|
| 320 |
+
"bpe": 1.71,
|
| 321 |
+
"f1": 0.3327,
|
| 322 |
+
"delta_f1": -0.0391
|
| 323 |
+
},
|
| 324 |
+
{
|
| 325 |
+
"task": "hotpotqa",
|
| 326 |
+
"config": "productvq-64x256-4b",
|
| 327 |
+
"bpe": 4.0,
|
| 328 |
+
"f1": 0.4212,
|
| 329 |
+
"delta_f1": 0.0494
|
| 330 |
+
},
|
| 331 |
+
{
|
| 332 |
+
"task": "hotpotqa",
|
| 333 |
+
"config": "productvq-32x256-2b",
|
| 334 |
+
"bpe": 2.0,
|
| 335 |
+
"f1": 0.3679,
|
| 336 |
+
"delta_f1": -0.0039
|
| 337 |
+
},
|
| 338 |
+
{
|
| 339 |
+
"task": "hotpotqa",
|
| 340 |
+
"config": "turbo-mse-4b",
|
| 341 |
+
"bpe": 4.125,
|
| 342 |
+
"f1": 0.4002,
|
| 343 |
+
"delta_f1": 0.0284
|
| 344 |
+
},
|
| 345 |
+
{
|
| 346 |
+
"task": "hotpotqa",
|
| 347 |
+
"config": "turbo-prod-4b (K:+qjl)",
|
| 348 |
+
"bpe": 5.125,
|
| 349 |
+
"f1": 0.3697,
|
| 350 |
+
"delta_f1": -0.0021
|
| 351 |
+
},
|
| 352 |
+
{
|
| 353 |
+
"task": "hotpotqa",
|
| 354 |
+
"config": "turbo-mse-2b",
|
| 355 |
+
"bpe": 2.125,
|
| 356 |
+
"f1": 0.2987,
|
| 357 |
+
"delta_f1": -0.0731
|
| 358 |
+
},
|
| 359 |
+
{
|
| 360 |
+
"task": "hotpotqa",
|
| 361 |
+
"config": "turbo-prod-2b (K:+qjl)",
|
| 362 |
+
"bpe": 3.125,
|
| 363 |
+
"f1": 0.3223,
|
| 364 |
+
"delta_f1": -0.0495
|
| 365 |
+
},
|
| 366 |
+
{
|
| 367 |
+
"task": "hotpotqa",
|
| 368 |
+
"config": "turbo-mse-1b",
|
| 369 |
+
"bpe": 1.125,
|
| 370 |
+
"f1": 0.1526,
|
| 371 |
+
"delta_f1": -0.2192
|
| 372 |
+
},
|
| 373 |
+
{
|
| 374 |
+
"task": "hotpotqa",
|
| 375 |
+
"config": "turbo-prod-1b (K:+qjl)",
|
| 376 |
+
"bpe": 2.125,
|
| 377 |
+
"f1": 0.1677,
|
| 378 |
+
"delta_f1": -0.2041
|
| 379 |
+
},
|
| 380 |
+
{
|
| 381 |
+
"task": "passage_retrieval_en",
|
| 382 |
+
"config": "fp16",
|
| 383 |
+
"bpe": 16.0,
|
| 384 |
+
"f1": 1.0,
|
| 385 |
+
"delta_f1": 0.0
|
| 386 |
+
},
|
| 387 |
+
{
|
| 388 |
+
"task": "passage_retrieval_en",
|
| 389 |
+
"config": "scalar-int4",
|
| 390 |
+
"bpe": 4.25,
|
| 391 |
+
"f1": 0.98,
|
| 392 |
+
"delta_f1": -0.02
|
| 393 |
+
},
|
| 394 |
+
{
|
| 395 |
+
"task": "passage_retrieval_en",
|
| 396 |
+
"config": "scalar-int2",
|
| 397 |
+
"bpe": 2.25,
|
| 398 |
+
"f1": 0.74,
|
| 399 |
+
"delta_f1": -0.26
|
| 400 |
+
},
|
| 401 |
+
{
|
| 402 |
+
"task": "passage_retrieval_en",
|
| 403 |
+
"config": "kivi-int2",
|
| 404 |
+
"bpe": 2.25,
|
| 405 |
+
"f1": 0.62,
|
| 406 |
+
"delta_f1": -0.38
|
| 407 |
+
},
|
| 408 |
+
{
|
| 409 |
+
"task": "passage_retrieval_en",
|
| 410 |
+
"config": "turboquant~-int2",
|
| 411 |
+
"bpe": 2.125,
|
| 412 |
+
"f1": 0.02,
|
| 413 |
+
"delta_f1": -0.98
|
| 414 |
+
},
|
| 415 |
+
{
|
| 416 |
+
"task": "passage_retrieval_en",
|
| 417 |
+
"config": "productvq-16x256-1b",
|
| 418 |
+
"bpe": 1.0,
|
| 419 |
+
"f1": 0.26,
|
| 420 |
+
"delta_f1": -0.74
|
| 421 |
+
},
|
| 422 |
+
{
|
| 423 |
+
"task": "passage_retrieval_en",
|
| 424 |
+
"config": "productvq-8x256-0.5b",
|
| 425 |
+
"bpe": 0.5,
|
| 426 |
+
"f1": 0.06,
|
| 427 |
+
"delta_f1": -0.94
|
| 428 |
+
},
|
| 429 |
+
{
|
| 430 |
+
"task": "passage_retrieval_en",
|
| 431 |
+
"config": "ropesplit-1b",
|
| 432 |
+
"bpe": 1.0,
|
| 433 |
+
"f1": 0.1,
|
| 434 |
+
"delta_f1": -0.9
|
| 435 |
+
},
|
| 436 |
+
{
|
| 437 |
+
"task": "passage_retrieval_en",
|
| 438 |
+
"config": "sign-1bit",
|
| 439 |
+
"bpe": 1.125,
|
| 440 |
+
"f1": 0.08,
|
| 441 |
+
"delta_f1": -0.92
|
| 442 |
+
},
|
| 443 |
+
{
|
| 444 |
+
"task": "passage_retrieval_en",
|
| 445 |
+
"config": "ternary-bitnet",
|
| 446 |
+
"bpe": 1.71,
|
| 447 |
+
"f1": 0.26,
|
| 448 |
+
"delta_f1": -0.74
|
| 449 |
+
},
|
| 450 |
+
{
|
| 451 |
+
"task": "passage_retrieval_en",
|
| 452 |
+
"config": "productvq-64x256-4b",
|
| 453 |
+
"bpe": 4.0,
|
| 454 |
+
"f1": 1.0,
|
| 455 |
+
"delta_f1": 0.0
|
| 456 |
+
},
|
| 457 |
+
{
|
| 458 |
+
"task": "passage_retrieval_en",
|
| 459 |
+
"config": "productvq-32x256-2b",
|
| 460 |
+
"bpe": 2.0,
|
| 461 |
+
"f1": 0.72,
|
| 462 |
+
"delta_f1": -0.28
|
| 463 |
+
},
|
| 464 |
+
{
|
| 465 |
+
"task": "passage_retrieval_en",
|
| 466 |
+
"config": "turbo-mse-4b",
|
| 467 |
+
"bpe": 4.125,
|
| 468 |
+
"f1": 1.0,
|
| 469 |
+
"delta_f1": 0.0
|
| 470 |
+
},
|
| 471 |
+
{
|
| 472 |
+
"task": "passage_retrieval_en",
|
| 473 |
+
"config": "turbo-prod-4b (K:+qjl)",
|
| 474 |
+
"bpe": 5.125,
|
| 475 |
+
"f1": 1.0,
|
| 476 |
+
"delta_f1": 0.0
|
| 477 |
+
},
|
| 478 |
+
{
|
| 479 |
+
"task": "passage_retrieval_en",
|
| 480 |
+
"config": "turbo-mse-2b",
|
| 481 |
+
"bpe": 2.125,
|
| 482 |
+
"f1": 0.84,
|
| 483 |
+
"delta_f1": -0.16
|
| 484 |
+
},
|
| 485 |
+
{
|
| 486 |
+
"task": "passage_retrieval_en",
|
| 487 |
+
"config": "turbo-prod-2b (K:+qjl)",
|
| 488 |
+
"bpe": 3.125,
|
| 489 |
+
"f1": 0.9,
|
| 490 |
+
"delta_f1": -0.1
|
| 491 |
+
},
|
| 492 |
+
{
|
| 493 |
+
"task": "passage_retrieval_en",
|
| 494 |
+
"config": "turbo-mse-1b",
|
| 495 |
+
"bpe": 1.125,
|
| 496 |
+
"f1": 0.06,
|
| 497 |
+
"delta_f1": -0.94
|
| 498 |
+
},
|
| 499 |
+
{
|
| 500 |
+
"task": "passage_retrieval_en",
|
| 501 |
+
"config": "turbo-prod-1b (K:+qjl)",
|
| 502 |
+
"bpe": 2.125,
|
| 503 |
+
"f1": 0.02,
|
| 504 |
+
"delta_f1": -0.98
|
| 505 |
+
},
|
| 506 |
+
{
|
| 507 |
+
"task": "repobench-p",
|
| 508 |
+
"config": "fp16",
|
| 509 |
+
"bpe": 16.0,
|
| 510 |
+
"f1": 0.196,
|
| 511 |
+
"delta_f1": 0.0
|
| 512 |
+
},
|
| 513 |
+
{
|
| 514 |
+
"task": "repobench-p",
|
| 515 |
+
"config": "scalar-int4",
|
| 516 |
+
"bpe": 4.25,
|
| 517 |
+
"f1": 0.1949,
|
| 518 |
+
"delta_f1": 0.0089
|
| 519 |
+
},
|
| 520 |
+
{
|
| 521 |
+
"task": "repobench-p",
|
| 522 |
+
"config": "scalar-int2",
|
| 523 |
+
"bpe": 2.25,
|
| 524 |
+
"f1": 0.1671,
|
| 525 |
+
"delta_f1": -0.0189
|
| 526 |
+
},
|
| 527 |
+
{
|
| 528 |
+
"task": "repobench-p",
|
| 529 |
+
"config": "kivi-int2",
|
| 530 |
+
"bpe": 2.25,
|
| 531 |
+
"f1": 0.1744,
|
| 532 |
+
"delta_f1": -0.0116
|
| 533 |
+
},
|
| 534 |
+
{
|
| 535 |
+
"task": "repobench-p",
|
| 536 |
+
"config": "turboquant~-int2",
|
| 537 |
+
"bpe": 2.125,
|
| 538 |
+
"f1": 0.0757,
|
| 539 |
+
"delta_f1": -0.1103
|
| 540 |
+
},
|
| 541 |
+
{
|
| 542 |
+
"task": "repobench-p",
|
| 543 |
+
"config": "productvq-16x256-1b",
|
| 544 |
+
"bpe": 1.0,
|
| 545 |
+
"f1": 0.1693,
|
| 546 |
+
"delta_f1": -0.0167
|
| 547 |
+
},
|
| 548 |
+
{
|
| 549 |
+
"task": "repobench-p",
|
| 550 |
+
"config": "productvq-8x256-0.5b",
|
| 551 |
+
"bpe": 0.5,
|
| 552 |
+
"f1": 0.1483,
|
| 553 |
+
"delta_f1": -0.0376
|
| 554 |
+
},
|
| 555 |
+
{
|
| 556 |
+
"task": "repobench-p",
|
| 557 |
+
"config": "ropesplit-1b",
|
| 558 |
+
"bpe": 1.0,
|
| 559 |
+
"f1": 0.1618,
|
| 560 |
+
"delta_f1": -0.0241
|
| 561 |
+
},
|
| 562 |
+
{
|
| 563 |
+
"task": "repobench-p",
|
| 564 |
+
"config": "sign-1bit",
|
| 565 |
+
"bpe": 1.125,
|
| 566 |
+
"f1": 0.1309,
|
| 567 |
+
"delta_f1": -0.0551
|
| 568 |
+
},
|
| 569 |
+
{
|
| 570 |
+
"task": "repobench-p",
|
| 571 |
+
"config": "ternary-bitnet",
|
| 572 |
+
"bpe": 1.71,
|
| 573 |
+
"f1": 0.1675,
|
| 574 |
+
"delta_f1": -0.0185
|
| 575 |
+
},
|
| 576 |
+
{
|
| 577 |
+
"task": "repobench-p",
|
| 578 |
+
"config": "productvq-64x256-4b",
|
| 579 |
+
"bpe": 4.0,
|
| 580 |
+
"f1": 0.1891,
|
| 581 |
+
"delta_f1": 0.0032
|
| 582 |
+
},
|
| 583 |
+
{
|
| 584 |
+
"task": "repobench-p",
|
| 585 |
+
"config": "productvq-32x256-2b",
|
| 586 |
+
"bpe": 2.0,
|
| 587 |
+
"f1": 0.192,
|
| 588 |
+
"delta_f1": 0.0061
|
| 589 |
+
},
|
| 590 |
+
{
|
| 591 |
+
"task": "repobench-p",
|
| 592 |
+
"config": "turbo-mse-4b",
|
| 593 |
+
"bpe": 4.125,
|
| 594 |
+
"f1": 0.1854,
|
| 595 |
+
"delta_f1": -0.0005
|
| 596 |
+
},
|
| 597 |
+
{
|
| 598 |
+
"task": "repobench-p",
|
| 599 |
+
"config": "turbo-prod-4b (K:+qjl)",
|
| 600 |
+
"bpe": 5.125,
|
| 601 |
+
"f1": 0.1832,
|
| 602 |
+
"delta_f1": -0.0028
|
| 603 |
+
},
|
| 604 |
+
{
|
| 605 |
+
"task": "repobench-p",
|
| 606 |
+
"config": "turbo-mse-2b",
|
| 607 |
+
"bpe": 2.125,
|
| 608 |
+
"f1": 0.1874,
|
| 609 |
+
"delta_f1": 0.0015
|
| 610 |
+
},
|
| 611 |
+
{
|
| 612 |
+
"task": "repobench-p",
|
| 613 |
+
"config": "turbo-prod-2b (K:+qjl)",
|
| 614 |
+
"bpe": 3.125,
|
| 615 |
+
"f1": 0.193,
|
| 616 |
+
"delta_f1": 0.007
|
| 617 |
+
},
|
| 618 |
+
{
|
| 619 |
+
"task": "repobench-p",
|
| 620 |
+
"config": "turbo-mse-1b",
|
| 621 |
+
"bpe": 1.125,
|
| 622 |
+
"f1": 0.1579,
|
| 623 |
+
"delta_f1": -0.0281
|
| 624 |
+
},
|
| 625 |
+
{
|
| 626 |
+
"task": "repobench-p",
|
| 627 |
+
"config": "turbo-prod-1b (K:+qjl)",
|
| 628 |
+
"bpe": 2.125,
|
| 629 |
+
"f1": 0.1571,
|
| 630 |
+
"delta_f1": -0.0289
|
| 631 |
+
}
|
| 632 |
+
]
|
artifacts/turbo_codebooks.pt
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:f6167b29f4be685cb01d41aab6443f7b0e491512a05f729bfeb14d46de721fde
|
| 3 |
+
size 9917315
|
benchmark.py
ADDED
|
@@ -0,0 +1,782 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
benchmark.py — fit AttnVQ codebooks and evaluate on real Laguna-XS.2 caches.
|
| 3 |
+
|
| 4 |
+
Stages:
|
| 5 |
+
dump capture post-RoPE K/V from full-attention layers
|
| 6 |
+
fit per-layer LBG codebooks → artifacts/codebooks.pt
|
| 7 |
+
cheap proxy metrics (key cosine, attn-output error, ip-bias)
|
| 8 |
+
swebench optional resolve-rate delta on SWE-bench Verified (needs Docker)
|
| 9 |
+
|
| 10 |
+
Usage:
|
| 11 |
+
python benchmark.py --stage fit
|
| 12 |
+
python benchmark.py --stage cheap --n_eval 64
|
| 13 |
+
python benchmark.py --stage dump
|
| 14 |
+
"""
|
| 15 |
+
|
| 16 |
+
from __future__ import annotations
|
| 17 |
+
|
| 18 |
+
import argparse
|
| 19 |
+
import json
|
| 20 |
+
import os
|
| 21 |
+
import time
|
| 22 |
+
from concurrent.futures import ThreadPoolExecutor
|
| 23 |
+
from dataclasses import dataclass, asdict
|
| 24 |
+
|
| 25 |
+
import torch
|
| 26 |
+
|
| 27 |
+
from vqkv.quantizers import (ScalarKV, KIVIScalarKV, ProductVQKV, RoPESplitVQKV,
|
| 28 |
+
SignScalarKV, TernaryScalarKV)
|
| 29 |
+
from vqkv.metrics import (key_cosine, cache_mse, inner_product_distortion,
|
| 30 |
+
attention_output, attn_output_cosine, attn_output_error)
|
| 31 |
+
|
| 32 |
+
MODEL_ID = os.environ.get("LAGUNA_ID", "poolside/Laguna-XS.2")
|
| 33 |
+
ARTIFACT_DIR = os.environ.get("VQKV_ARTIFACTS", "./artifacts")
|
| 34 |
+
os.makedirs(ARTIFACT_DIR, exist_ok=True)
|
| 35 |
+
|
| 36 |
+
CALIB_DATASET = os.environ.get("CALIB_DATASET", "SWE-bench/SWE-smith-trajectories")
|
| 37 |
+
CALIB_SPLIT = os.environ.get("CALIB_SPLIT", "tool")
|
| 38 |
+
CALIB_SOURCE = os.environ.get("CALIB_SOURCE", "swesmith") # swesmith | longbench-hotpotqa
|
| 39 |
+
|
| 40 |
+
_HOTPOTQA_PROMPT = (
|
| 41 |
+
"Answer the question based on the given passages. "
|
| 42 |
+
"Only give me the answer and do not output any other words.\n\n"
|
| 43 |
+
"The following are given passages.\n{context}\n\n"
|
| 44 |
+
"Answer the question based on the given passages. "
|
| 45 |
+
"Only give me the answer and do not output any other words.\n\n"
|
| 46 |
+
"Question: {input}\nAnswer:"
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
# ============================================================================
|
| 50 |
+
# Cache configurations under test. Each is a (name, factory) where factory()
|
| 51 |
+
# returns an unfitted quantizer. `None` means the fp16 baseline (no quant).
|
| 52 |
+
# ============================================================================
|
| 53 |
+
def cache_configs():
|
| 54 |
+
return [
|
| 55 |
+
("fp16 (baseline)", None),
|
| 56 |
+
("scalar-int4", lambda: ScalarKV(nbits=4)),
|
| 57 |
+
("scalar-int2", lambda: ScalarKV(nbits=2)),
|
| 58 |
+
("kivi-int2", lambda: KIVIScalarKV(nbits=2)),
|
| 59 |
+
("productvq-64x256-4b", lambda: ProductVQKV(n_sub=64, n_codes=256, iters=15)),
|
| 60 |
+
("productvq-32x256-2b", lambda: ProductVQKV(n_sub=32, n_codes=256, iters=15)),
|
| 61 |
+
("productvq-16x256-1b", lambda: ProductVQKV(n_sub=16, n_codes=256, iters=15)),
|
| 62 |
+
("productvq-8x256-0.5b", lambda: ProductVQKV(n_sub=8, n_codes=256, iters=15)),
|
| 63 |
+
("ropesplit-1b", lambda: RoPESplitVQKV(n_sub_half=8, n_codes=256, iters=15)),
|
| 64 |
+
("sign-1bit", lambda: SignScalarKV(per_channel_key=True)),
|
| 65 |
+
("ternary-bitnet", lambda: TernaryScalarKV(alpha=0.7, per_channel_key=True)),
|
| 66 |
+
]
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
# ============================================================================
|
| 70 |
+
# Model + layer-structure loading
|
| 71 |
+
# ============================================================================
|
| 72 |
+
def load_model_and_meta():
|
| 73 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 74 |
+
tok = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
|
| 75 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 76 |
+
MODEL_ID, torch_dtype=torch.bfloat16, device_map="cuda",
|
| 77 |
+
trust_remote_code=True)
|
| 78 |
+
model.eval()
|
| 79 |
+
cfg = model.config
|
| 80 |
+
full_layers = [i for i, t in enumerate(cfg.layer_types) if t == "full_attention"]
|
| 81 |
+
meta = {
|
| 82 |
+
"full_layers": full_layers,
|
| 83 |
+
"n_kv_heads": cfg.num_key_value_heads,
|
| 84 |
+
"n_q_heads": cfg.num_attention_heads,
|
| 85 |
+
"head_dim": cfg.head_dim,
|
| 86 |
+
"n_layers": cfg.num_hidden_layers,
|
| 87 |
+
}
|
| 88 |
+
print(f"[meta] full-attention layers ({len(full_layers)}): {full_layers}")
|
| 89 |
+
print(f"[meta] kv_heads={meta['n_kv_heads']} head_dim={meta['head_dim']}")
|
| 90 |
+
assert len(full_layers) > 0, "no full-attention layers found; check config"
|
| 91 |
+
return model, tok, meta
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
# Trace flattening (SWE-smith messages JSON, nebius trajectory/tool roles).
|
| 95 |
+
def flatten_trace(example, tok) -> str:
|
| 96 |
+
"""Flatten one trajectory to a string via the model's chat template."""
|
| 97 |
+
raw = (example.get("messages") or example.get("trajectory")
|
| 98 |
+
or example.get("conversations"))
|
| 99 |
+
if raw is None:
|
| 100 |
+
return json.dumps(example)[:200_000]
|
| 101 |
+
|
| 102 |
+
# SWE-smith stores 'messages' as a JSON string, not a native list.
|
| 103 |
+
if isinstance(raw, str):
|
| 104 |
+
try:
|
| 105 |
+
raw = json.loads(raw)
|
| 106 |
+
except json.JSONDecodeError:
|
| 107 |
+
return raw[:200_000]
|
| 108 |
+
|
| 109 |
+
norm = []
|
| 110 |
+
for m in raw:
|
| 111 |
+
role = m.get("role") or m.get("from") or "user"
|
| 112 |
+
content = m.get("content") or m.get("value") or ""
|
| 113 |
+
|
| 114 |
+
# SWE-smith: content can be a list of {"type":"text","text":"..."} blocks
|
| 115 |
+
if isinstance(content, list):
|
| 116 |
+
content = "\n".join(
|
| 117 |
+
item.get("text", str(item)) if isinstance(item, dict) else str(item)
|
| 118 |
+
for item in content
|
| 119 |
+
)
|
| 120 |
+
|
| 121 |
+
# nebius: assistant turns carry tool_calls alongside content
|
| 122 |
+
tool_calls = m.get("tool_calls")
|
| 123 |
+
if tool_calls:
|
| 124 |
+
tc_text = json.dumps(tool_calls, ensure_ascii=False)
|
| 125 |
+
content = (content + "\n" + tc_text).strip() if content else tc_text
|
| 126 |
+
|
| 127 |
+
# 'tool' role (observation) has no equivalent in most chat templates;
|
| 128 |
+
# map it to 'user' so the template accepts it.
|
| 129 |
+
role = {"human": "user", "gpt": "assistant", "tool": "user"}.get(role, role)
|
| 130 |
+
|
| 131 |
+
if not content.strip():
|
| 132 |
+
continue
|
| 133 |
+
norm.append({"role": role, "content": content})
|
| 134 |
+
|
| 135 |
+
# Merge consecutive same-role messages produced by tool->user collapsing.
|
| 136 |
+
merged: list[dict] = []
|
| 137 |
+
for m in norm:
|
| 138 |
+
if merged and merged[-1]["role"] == m["role"]:
|
| 139 |
+
merged[-1]["content"] += "\n\n" + m["content"]
|
| 140 |
+
else:
|
| 141 |
+
merged.append(dict(m))
|
| 142 |
+
|
| 143 |
+
try:
|
| 144 |
+
return tok.apply_chat_template(merged, tokenize=False,
|
| 145 |
+
add_generation_prompt=False)
|
| 146 |
+
except Exception:
|
| 147 |
+
return "\n\n".join(f"{m['role']}: {m['content']}" for m in merged)
|
| 148 |
+
|
| 149 |
+
|
| 150 |
+
def flatten_longbench(example) -> str:
|
| 151 |
+
"""Format a LongBench hotpotqa example (context + input) as a plain string."""
|
| 152 |
+
return _HOTPOTQA_PROMPT.format(
|
| 153 |
+
context=example["context"], input=example["input"])
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
def _load_longbench_hotpotqa():
|
| 157 |
+
"""Load THUDM/LongBench hotpotqa, bypassing the deprecated dataset script."""
|
| 158 |
+
from datasets import load_dataset as _ld
|
| 159 |
+
for fname in ("hotpotqa_e.jsonl", "hotpotqa.jsonl"):
|
| 160 |
+
try:
|
| 161 |
+
return _ld(
|
| 162 |
+
"json",
|
| 163 |
+
data_files=f"hf://datasets/THUDM/LongBench/data/{fname}",
|
| 164 |
+
split="train",
|
| 165 |
+
)
|
| 166 |
+
except Exception:
|
| 167 |
+
continue
|
| 168 |
+
return _ld("THUDM/LongBench", name="hotpotqa", split="test")
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def _load_calib_dump_dataset(n_calib: int, calib_source: str, tok):
|
| 172 |
+
"""Return (dataset, text_fn, label) for stage_dump."""
|
| 173 |
+
from datasets import load_dataset
|
| 174 |
+
|
| 175 |
+
if calib_source == "longbench-hotpotqa":
|
| 176 |
+
ds = _load_longbench_hotpotqa()
|
| 177 |
+
n_total = len(ds)
|
| 178 |
+
# Last n_calib rows avoid overlap with longbench_eval.py (range(0, n_eval)).
|
| 179 |
+
start = max(0, n_total - n_calib)
|
| 180 |
+
ds = ds.select(range(start, n_total))
|
| 181 |
+
label = f"LongBench hotpotqa (rows {start}–{n_total - 1})"
|
| 182 |
+
return ds, flatten_longbench, label
|
| 183 |
+
|
| 184 |
+
ds = load_dataset(CALIB_DATASET, split=CALIB_SPLIT)
|
| 185 |
+
label = f"{CALIB_DATASET} split={CALIB_SPLIT}"
|
| 186 |
+
return ds, lambda ex: flatten_trace(ex, tok), label
|
| 187 |
+
|
| 188 |
+
|
| 189 |
+
# STAGE: dump -- run real traces, capture post-RoPE K/V from full-attn layers
|
| 190 |
+
def stage_dump(n_calib=16, max_len=32768, calib_source: str | None = None,
|
| 191 |
+
min_len=2048):
|
| 192 |
+
from transformers.cache_utils import DynamicCache
|
| 193 |
+
|
| 194 |
+
calib_source = calib_source or CALIB_SOURCE
|
| 195 |
+
model, tok, meta = load_model_and_meta()
|
| 196 |
+
full = set(meta["full_layers"])
|
| 197 |
+
|
| 198 |
+
# device_map="auto" can leave model.device as 'meta'; always use cuda:0.
|
| 199 |
+
input_device = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
| 200 |
+
|
| 201 |
+
class DumpingCache(DynamicCache):
|
| 202 |
+
def __init__(self, *a, **k):
|
| 203 |
+
super().__init__(*a, **k)
|
| 204 |
+
self.dump = {i: {"k": [], "v": []} for i in full}
|
| 205 |
+
def update(self, key_states, value_states, layer_idx, cache_kwargs=None):
|
| 206 |
+
if layer_idx in full:
|
| 207 |
+
self.dump[layer_idx]["k"].append(
|
| 208 |
+
key_states.detach()[0].permute(1, 0, 2).float().cpu())
|
| 209 |
+
self.dump[layer_idx]["v"].append(
|
| 210 |
+
value_states.detach()[0].permute(1, 0, 2).float().cpu())
|
| 211 |
+
return super().update(key_states, value_states, layer_idx, cache_kwargs)
|
| 212 |
+
|
| 213 |
+
ds, get_text, source_label = _load_calib_dump_dataset(n_calib, calib_source, tok)
|
| 214 |
+
print(f"[dump] source={calib_source} {source_label} rows={len(ds)} "
|
| 215 |
+
f"schema={list(ds[0].keys())}")
|
| 216 |
+
|
| 217 |
+
agg = {i: {"k": [], "v": []} for i in full}
|
| 218 |
+
used = 0
|
| 219 |
+
for ex in ds:
|
| 220 |
+
if used >= n_calib:
|
| 221 |
+
break
|
| 222 |
+
text = get_text(ex)
|
| 223 |
+
ids = tok(text, return_tensors="pt", truncation=True,
|
| 224 |
+
max_length=max_len).to(input_device)
|
| 225 |
+
if ids["input_ids"].shape[1] < min_len:
|
| 226 |
+
continue
|
| 227 |
+
cache = DumpingCache(config=model.config) # fresh per trace
|
| 228 |
+
with torch.no_grad():
|
| 229 |
+
model.model(**ids, past_key_values=cache, use_cache=True) # skip lm_head
|
| 230 |
+
for i in full:
|
| 231 |
+
agg[i]["k"].append(torch.cat(cache.dump[i]["k"]))
|
| 232 |
+
agg[i]["v"].append(torch.cat(cache.dump[i]["v"]))
|
| 233 |
+
used += 1
|
| 234 |
+
print(f"[dump] trace {used}/{n_calib} len={ids['input_ids'].shape[1]}")
|
| 235 |
+
|
| 236 |
+
calib = {i: {"k": torch.cat(agg[i]["k"]), "v": torch.cat(agg[i]["v"])}
|
| 237 |
+
for i in full}
|
| 238 |
+
for i in list(full)[:3]:
|
| 239 |
+
k = calib[i]["k"].reshape(-1, meta["head_dim"])
|
| 240 |
+
cut = meta["head_dim"] // 2
|
| 241 |
+
print(f"[dump] layer {i}: {tuple(calib[i]['k'].shape)} | "
|
| 242 |
+
f"rope-half std {k[:, :cut].std():.3f} "
|
| 243 |
+
f"pass-half std {k[:, cut:].std():.3f} "
|
| 244 |
+
f"max|ch| {k.abs().amax(0).max():.2f}")
|
| 245 |
+
path = os.path.join(ARTIFACT_DIR, "calib_caches.pt")
|
| 246 |
+
torch.save({"calib": calib, "meta": meta}, path)
|
| 247 |
+
print(f"[dump] saved -> {path}")
|
| 248 |
+
|
| 249 |
+
|
| 250 |
+
# STAGE: fit -- per-layer codebooks for every (data-dependent) config
|
| 251 |
+
def stage_fit(only: list[str] | None = None):
|
| 252 |
+
"""Fit quantizers and write artifacts/codebooks.pt.
|
| 253 |
+
|
| 254 |
+
By default fits every config in cache_configs(). Pass ``only=["sign-1bit", ...]``
|
| 255 |
+
to fit just those names and merge into an existing codebooks.pt (skips the rest).
|
| 256 |
+
Tuning-free quantizers (Sign, Ternary, Scalar, KIVI) finish in seconds; only
|
| 257 |
+
ProductVQ / RoPE-split need calib_caches.pt.
|
| 258 |
+
"""
|
| 259 |
+
codebooks_path = os.path.join(ARTIFACT_DIR, "codebooks.pt")
|
| 260 |
+
calib_path = os.path.join(ARTIFACT_DIR, "calib_caches.pt")
|
| 261 |
+
|
| 262 |
+
if only and os.path.exists(codebooks_path):
|
| 263 |
+
existing = torch.load(codebooks_path, weights_only=False)
|
| 264 |
+
fitted = existing["fitted"]
|
| 265 |
+
meta = existing["meta"]
|
| 266 |
+
print(f"[fit] merging into existing {codebooks_path} ({len(fitted)} configs)")
|
| 267 |
+
else:
|
| 268 |
+
if not os.path.exists(calib_path):
|
| 269 |
+
raise FileNotFoundError(
|
| 270 |
+
f"{calib_path} not found; run --stage dump first, or use "
|
| 271 |
+
f"--only with an existing codebooks.pt for tuning-free configs")
|
| 272 |
+
blob = torch.load(calib_path)
|
| 273 |
+
calib, meta = blob["calib"], blob["meta"]
|
| 274 |
+
fitted = {}
|
| 275 |
+
|
| 276 |
+
calib = None
|
| 277 |
+
if os.path.exists(calib_path):
|
| 278 |
+
calib = torch.load(calib_path)["calib"]
|
| 279 |
+
|
| 280 |
+
hd = meta["head_dim"]
|
| 281 |
+
layer_ids = meta["full_layers"]
|
| 282 |
+
|
| 283 |
+
configs = [(n, f) for n, f in cache_configs() if f is not None]
|
| 284 |
+
if only is not None:
|
| 285 |
+
only_set = set(only)
|
| 286 |
+
configs = [(n, f) for n, f in configs if n in only_set]
|
| 287 |
+
unknown = only_set - {n for n, _ in configs}
|
| 288 |
+
if unknown:
|
| 289 |
+
raise ValueError(f"unknown --only config(s): {sorted(unknown)}")
|
| 290 |
+
|
| 291 |
+
# GPU fitting: LBG is pure torch; moving calib to CUDA makes bmm/argmin ~20x
|
| 292 |
+
# faster. Serial over layers on GPU (CUDA is already async; threading adds no
|
| 293 |
+
# benefit). Parallel over layers on CPU (BLAS releases GIL; real concurrency).
|
| 294 |
+
fit_device = "cuda" if torch.cuda.is_available() else "cpu"
|
| 295 |
+
if fit_device == "cuda":
|
| 296 |
+
print(f"[fit] GPU available ({torch.cuda.get_device_name()}) -- fitting LBG on CUDA")
|
| 297 |
+
n_workers = 1 if fit_device == "cuda" else min(len(layer_ids), os.cpu_count() or 1)
|
| 298 |
+
|
| 299 |
+
for name, factory in configs:
|
| 300 |
+
t0 = time.time()
|
| 301 |
+
|
| 302 |
+
def _fit_layer(i, _factory=factory, _device=fit_device):
|
| 303 |
+
if calib is not None and i in calib:
|
| 304 |
+
kf = calib[i]["k"].reshape(-1, hd)[:200_000].to(_device)
|
| 305 |
+
vf = calib[i]["v"].reshape(-1, hd)[:200_000].to(_device)
|
| 306 |
+
else:
|
| 307 |
+
kf = torch.zeros(1, hd, device=_device)
|
| 308 |
+
vf = torch.zeros(1, hd, device=_device)
|
| 309 |
+
q = _factory().fit(kf, vf)
|
| 310 |
+
# Codebooks are saved to disk as CPU tensors; move back before returning.
|
| 311 |
+
if _device != "cpu" and hasattr(q, "to"):
|
| 312 |
+
q.to("cpu")
|
| 313 |
+
return i, q
|
| 314 |
+
|
| 315 |
+
with ThreadPoolExecutor(max_workers=n_workers) as ex:
|
| 316 |
+
per_layer = dict(ex.map(_fit_layer, layer_ids))
|
| 317 |
+
|
| 318 |
+
fitted[name] = per_layer
|
| 319 |
+
print(f"[fit] {name}: {len(per_layer)} layer-codebooks in {time.time()-t0:.1f}s")
|
| 320 |
+
|
| 321 |
+
torch.save({"fitted": fitted, "meta": meta}, codebooks_path)
|
| 322 |
+
print(f"[fit] saved -> {codebooks_path} ({len(fitted)} configs total)")
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
# A drop-in cache that applies a per-layer quantizer to the target layers only.
|
| 326 |
+
def make_vq_cache_class(per_layer_quantizers, target_layers, model_config, device=None):
|
| 327 |
+
"""Build a VQCache class with codebooks pre-moved to `device`.
|
| 328 |
+
|
| 329 |
+
Pass device="cuda" (or the model's device) so the roundtrip runs entirely
|
| 330 |
+
on GPU with no CPU<->GPU transfers. Without this, every update() call
|
| 331 |
+
implicitly transfers key_states to CPU and back, making decode very slow.
|
| 332 |
+
"""
|
| 333 |
+
from transformers.cache_utils import DynamicCache
|
| 334 |
+
|
| 335 |
+
if device is not None:
|
| 336 |
+
for q in per_layer_quantizers.values():
|
| 337 |
+
if hasattr(q, "to"):
|
| 338 |
+
q.to(device)
|
| 339 |
+
|
| 340 |
+
class VQCache(DynamicCache):
|
| 341 |
+
def __init__(self, *a, **k):
|
| 342 |
+
super().__init__(*a, **k)
|
| 343 |
+
self.q = per_layer_quantizers
|
| 344 |
+
self.target = set(target_layers)
|
| 345 |
+
def update(self, key_states, value_states, layer_idx, cache_kwargs=None):
|
| 346 |
+
if layer_idx in self.target and layer_idx in self.q:
|
| 347 |
+
q = self.q[layer_idx]
|
| 348 |
+
b, h, s, d = key_states.shape
|
| 349 |
+
kf = key_states[0].transpose(0, 1).reshape(-1, d).float()
|
| 350 |
+
vf = value_states[0].transpose(0, 1).reshape(-1, d).float()
|
| 351 |
+
# See stage_cheap: per-channel-key quantizers reduce the key
|
| 352 |
+
# along the token axis and need a per-head block, not a flatten.
|
| 353 |
+
per_channel_key = (
|
| 354 |
+
isinstance(q, KIVIScalarKV)
|
| 355 |
+
or getattr(q, "per_channel_key", False)
|
| 356 |
+
)
|
| 357 |
+
if per_channel_key:
|
| 358 |
+
kk = key_states[0].permute(1, 0, 2).float() # (s, h, d)
|
| 359 |
+
k_hat = torch.stack([q.roundtrip_k(kk[:, hh, :]) for hh in range(h)], 1)
|
| 360 |
+
vv = value_states[0].permute(1, 0, 2).float()
|
| 361 |
+
v_hat = torch.stack([q.roundtrip_v(vv[:, hh, :]) for hh in range(h)], 1)
|
| 362 |
+
k_hat = k_hat.permute(1, 0, 2)[None]
|
| 363 |
+
v_hat = v_hat.permute(1, 0, 2)[None]
|
| 364 |
+
else:
|
| 365 |
+
k_hat = q.roundtrip_k(kf).reshape(s, h, d).permute(1, 0, 2)[None]
|
| 366 |
+
v_hat = q.roundtrip_v(vf).reshape(s, h, d).permute(1, 0, 2)[None]
|
| 367 |
+
key_states = k_hat.to(key_states.dtype).to(key_states.device)
|
| 368 |
+
value_states = v_hat.to(value_states.dtype).to(value_states.device)
|
| 369 |
+
return super().update(key_states, value_states, layer_idx, cache_kwargs)
|
| 370 |
+
return VQCache
|
| 371 |
+
|
| 372 |
+
|
| 373 |
+
# STAGE: cheap -- tier-1/2 metrics on held-out trace windows (no test suites)
|
| 374 |
+
def stage_cheap(n_eval=64, max_len=16384):
|
| 375 |
+
import collections
|
| 376 |
+
from datasets import load_dataset
|
| 377 |
+
blob = torch.load(os.path.join(ARTIFACT_DIR, "codebooks.pt"), weights_only=False)
|
| 378 |
+
fitted, meta = blob["fitted"], blob["meta"]
|
| 379 |
+
hd, full = meta["head_dim"], meta["full_layers"]
|
| 380 |
+
n_q = meta.get("n_q_heads", 48) # fallback for codebooks.pt written before this field
|
| 381 |
+
|
| 382 |
+
# Window for O(T²) attention metrics. 512 tokens keeps peak mem <200MB on GPU.
|
| 383 |
+
ATTN_WIN = 512
|
| 384 |
+
|
| 385 |
+
model, tok, _ = load_model_and_meta()
|
| 386 |
+
from tqdm import tqdm
|
| 387 |
+
from transformers.cache_utils import DynamicCache
|
| 388 |
+
|
| 389 |
+
# Move every fitted codebook to the model device ONCE. The roundtrip then
|
| 390 |
+
# runs entirely on-GPU against the on-GPU eval caches (see EvalDump below).
|
| 391 |
+
# ScalarKV/KIVI are tuning-free and have no .to(); they follow their input.
|
| 392 |
+
for per_layer in fitted.values():
|
| 393 |
+
for q in per_layer.values():
|
| 394 |
+
if hasattr(q, "to"):
|
| 395 |
+
q.to(model.device)
|
| 396 |
+
|
| 397 |
+
class EvalDump(DynamicCache):
|
| 398 |
+
def __init__(self):
|
| 399 |
+
super().__init__(); self.d = {i: {} for i in full}
|
| 400 |
+
def update(self, ks, vs, li, ck=None):
|
| 401 |
+
if li in set(full):
|
| 402 |
+
# Keep on-GPU: the quantizer roundtrip is a nearest-neighbour
|
| 403 |
+
# search -- a GPU job. Moving to CPU here dominated runtime.
|
| 404 |
+
self.d[li]["k"] = ks.detach()[0].permute(1, 0, 2).float()
|
| 405 |
+
self.d[li]["v"] = vs.detach()[0].permute(1, 0, 2).float()
|
| 406 |
+
return super().update(ks, vs, li, ck)
|
| 407 |
+
|
| 408 |
+
ds = load_dataset(CALIB_DATASET, split=CALIB_SPLIT)
|
| 409 |
+
|
| 410 |
+
# Per-trace rows (not saved to disk; aggregated below)
|
| 411 |
+
trace_rows = []
|
| 412 |
+
|
| 413 |
+
# Held-out slice. Start at 500 to safely clear any rows the dump stage
|
| 414 |
+
# consumed (dump skips short traces, so actual rows used >> n_calib=16).
|
| 415 |
+
for ex in tqdm(ds.select(range(500, 500 + n_eval))):
|
| 416 |
+
text = flatten_trace(ex, tok)
|
| 417 |
+
ids = tok(text, return_tensors="pt", truncation=True, max_length=max_len).to(model.device)
|
| 418 |
+
cache = EvalDump()
|
| 419 |
+
with torch.no_grad():
|
| 420 |
+
# model.model() skips lm_head: avoids a ~3 GB allocation per trace
|
| 421 |
+
# (max_len * vocab_size * 2 bytes) that is not needed for KV metrics.
|
| 422 |
+
model.model(**ids, past_key_values=cache, use_cache=True)
|
| 423 |
+
|
| 424 |
+
# Synthetic Q for attention/IP metrics: generated once per (trace, layer)
|
| 425 |
+
# and reused across all configs so the comparison is apples-to-apples.
|
| 426 |
+
# Unit-normalised so inner-product scale doesn't swamp the bias signal.
|
| 427 |
+
synth_q = {}
|
| 428 |
+
for i in full:
|
| 429 |
+
s = cache.d[i]["k"].shape[0]
|
| 430 |
+
win = min(s, ATTN_WIN)
|
| 431 |
+
q_rand = torch.randn(win, n_q, hd, device=cache.d[i]["k"].device)
|
| 432 |
+
synth_q[i] = q_rand / q_rand.norm(dim=-1, keepdim=True).clamp_min(1e-8)
|
| 433 |
+
|
| 434 |
+
for name, _ in cache_configs():
|
| 435 |
+
if name == "fp16 (baseline)":
|
| 436 |
+
continue
|
| 437 |
+
per_layer = fitted[name]
|
| 438 |
+
acc = collections.defaultdict(float)
|
| 439 |
+
nL = 0
|
| 440 |
+
for i in full:
|
| 441 |
+
k = cache.d[i]["k"] # (s, h, d)
|
| 442 |
+
v = cache.d[i]["v"]
|
| 443 |
+
q = per_layer[i]
|
| 444 |
+
s, h, d = k.shape
|
| 445 |
+
|
| 446 |
+
# Per-channel-key quantizers (KIVI, Sign, Ternary with
|
| 447 |
+
# per_channel_key=True) reduce the KEY along the TOKEN axis
|
| 448 |
+
# (dim=0). They must see one (s, d) block PER HEAD; flattening
|
| 449 |
+
# (s,h,d)->(s*h,d) would mix tokens across heads into one scale
|
| 450 |
+
# and corrupt the key metric. VQ/scalar-per-token quantizers
|
| 451 |
+
# reduce along dim=-1 and are safe to flatten.
|
| 452 |
+
per_channel_key = (
|
| 453 |
+
isinstance(q, KIVIScalarKV)
|
| 454 |
+
or getattr(q, "per_channel_key", False)
|
| 455 |
+
)
|
| 456 |
+
if per_channel_key:
|
| 457 |
+
k_hat = torch.stack([q.roundtrip_k(k[:, hh, :]) for hh in range(h)], 1)
|
| 458 |
+
v_hat = torch.stack([q.roundtrip_v(v[:, hh, :]) for hh in range(h)], 1)
|
| 459 |
+
else:
|
| 460 |
+
k_hat = q.roundtrip_k(k.reshape(-1, d)).reshape(s, h, d)
|
| 461 |
+
v_hat = q.roundtrip_v(v.reshape(-1, d)).reshape(s, h, d)
|
| 462 |
+
|
| 463 |
+
acc["key_cos"] += key_cosine(k, k_hat)
|
| 464 |
+
acc["val_cos"] += key_cosine(v, v_hat)
|
| 465 |
+
acc["key_mse"] += cache_mse(k, k_hat)
|
| 466 |
+
acc["val_mse"] += cache_mse(v, v_hat)
|
| 467 |
+
|
| 468 |
+
# Windowed attention/IP metrics on last ATTN_WIN tokens
|
| 469 |
+
win = min(s, ATTN_WIN)
|
| 470 |
+
kw, kw_hat = k[-win:], k_hat[-win:]
|
| 471 |
+
vw, vw_hat = v[-win:], v_hat[-win:]
|
| 472 |
+
q_syn = synth_q[i] # (win, n_q, d)
|
| 473 |
+
|
| 474 |
+
out_ref, _ = attention_output(q_syn, kw, vw, n_q)
|
| 475 |
+
out_hat, _ = attention_output(q_syn, kw_hat, vw_hat, n_q)
|
| 476 |
+
acc["attn_cos"] += attn_output_cosine(out_ref, out_hat)
|
| 477 |
+
acc["attn_output_error"] += attn_output_error(out_ref, out_hat)
|
| 478 |
+
|
| 479 |
+
ip = inner_product_distortion(q_syn, kw, kw_hat)
|
| 480 |
+
acc["ip_rel"] += ip["ip_rel_err"]
|
| 481 |
+
acc["ip_bias"] += ip["ip_bias"]
|
| 482 |
+
|
| 483 |
+
nL += 1
|
| 484 |
+
|
| 485 |
+
trace_rows.append({
|
| 486 |
+
"trace_len": ids["input_ids"].shape[1],
|
| 487 |
+
"config": name,
|
| 488 |
+
"key_cos": acc["key_cos"] / nL,
|
| 489 |
+
"val_cos": acc["val_cos"] / nL,
|
| 490 |
+
"key_mse": acc["key_mse"] / nL,
|
| 491 |
+
"val_mse": acc["val_mse"] / nL,
|
| 492 |
+
"attn_cos": acc["attn_cos"] / nL,
|
| 493 |
+
"attn_output_error": acc["attn_output_error"] / nL,
|
| 494 |
+
"ip_rel": acc["ip_rel"] / nL,
|
| 495 |
+
"ip_bias": acc["ip_bias"] / nL,
|
| 496 |
+
})
|
| 497 |
+
|
| 498 |
+
# Aggregate across traces: one summary row per config (what gets saved)
|
| 499 |
+
agg = collections.defaultdict(lambda: collections.defaultdict(list))
|
| 500 |
+
for r in trace_rows:
|
| 501 |
+
for col in ("key_cos", "val_cos", "key_mse", "val_mse",
|
| 502 |
+
"attn_cos", "attn_output_error", "ip_rel", "ip_bias"):
|
| 503 |
+
agg[r["config"]][col].append(r[col])
|
| 504 |
+
|
| 505 |
+
COLS = ("key_cos", "val_cos", "key_mse", "val_mse",
|
| 506 |
+
"attn_cos", "attn_output_error", "ip_rel", "ip_bias")
|
| 507 |
+
|
| 508 |
+
summary = []
|
| 509 |
+
for name, _ in cache_configs():
|
| 510 |
+
if name not in agg:
|
| 511 |
+
continue
|
| 512 |
+
cols = agg[name]
|
| 513 |
+
n = len(cols["key_cos"])
|
| 514 |
+
q0 = next(iter(fitted[name].values()))
|
| 515 |
+
row = {"config": name, "bits_per_elt": round(q0.bits_per_element(hd), 4), "n_traces": n}
|
| 516 |
+
for col in COLS:
|
| 517 |
+
row[col] = round(sum(cols[col]) / n, 5)
|
| 518 |
+
summary.append(row)
|
| 519 |
+
|
| 520 |
+
print(f"\n[cheap] mean metrics over {n_eval} held-out traces:")
|
| 521 |
+
print(f" {'config':24s} {'bpe':>5} {'key_cos':>8} {'val_cos':>8} "
|
| 522 |
+
f"{'key_mse':>9} {'val_mse':>9} {'attn_cos':>9} {'attn_err':>9} "
|
| 523 |
+
f"{'ip_rel':>8} {'ip_bias':>9}")
|
| 524 |
+
for row in summary:
|
| 525 |
+
print(f" {row['config']:24s} {row['bits_per_elt']:5.2f} "
|
| 526 |
+
f"{row['key_cos']:8.4f} {row['val_cos']:8.4f} "
|
| 527 |
+
f"{row['key_mse']:9.5f} {row['val_mse']:9.5f} "
|
| 528 |
+
f"{row['attn_cos']:9.4f} {row['attn_output_error']:9.4f} "
|
| 529 |
+
f"{row['ip_rel']:8.5f} {row['ip_bias']:9.6f}")
|
| 530 |
+
|
| 531 |
+
out_path = os.path.join(ARTIFACT_DIR, "cheap_metrics.json")
|
| 532 |
+
json.dump(summary, open(out_path, "w"), indent=2)
|
| 533 |
+
print(f"[cheap] saved -> {out_path}")
|
| 534 |
+
|
| 535 |
+
|
| 536 |
+
# Optional SWE-bench Verified eval (requires Docker + swebench).
|
| 537 |
+
_AGENT_SYSTEM = (
|
| 538 |
+
"You are an expert software engineer fixing a GitHub issue. "
|
| 539 |
+
"You have a bash shell inside the repository checked out at the failing commit. "
|
| 540 |
+
"Use <bash>command</bash> tags to run shell commands. "
|
| 541 |
+
"Explore the code, implement the fix, then output <submit> when done."
|
| 542 |
+
)
|
| 543 |
+
|
| 544 |
+
|
| 545 |
+
def _agent_loop(model, tok, task: dict, cache_factory, max_turns: int,
|
| 546 |
+
max_new: int = 1024) -> str:
|
| 547 |
+
"""Run a minimal ReAct-bash loop on one SWE-bench task.
|
| 548 |
+
|
| 549 |
+
Clones the repo at base_commit into a temp dir, runs the model in a
|
| 550 |
+
generate→bash→observe loop, and returns the final `git diff HEAD` patch.
|
| 551 |
+
Each generate call rebuilds the full context from scratch so the VQCache
|
| 552 |
+
sees the compounding long-context pressure that the project targets.
|
| 553 |
+
"""
|
| 554 |
+
import re
|
| 555 |
+
import shutil
|
| 556 |
+
import subprocess
|
| 557 |
+
import tempfile
|
| 558 |
+
|
| 559 |
+
repo_dir = tempfile.mkdtemp(prefix="sweagent_")
|
| 560 |
+
try:
|
| 561 |
+
subprocess.run(
|
| 562 |
+
["git", "clone", f"https://github.com/{task['repo']}.git", repo_dir],
|
| 563 |
+
check=True, capture_output=True, timeout=120,
|
| 564 |
+
)
|
| 565 |
+
subprocess.run(
|
| 566 |
+
["git", "checkout", task["base_commit"]],
|
| 567 |
+
check=True, capture_output=True, cwd=repo_dir, timeout=30,
|
| 568 |
+
)
|
| 569 |
+
except Exception as exc:
|
| 570 |
+
print(f" [agent] clone/checkout failed for {task['instance_id']}: {exc}")
|
| 571 |
+
shutil.rmtree(repo_dir, ignore_errors=True)
|
| 572 |
+
return ""
|
| 573 |
+
|
| 574 |
+
try:
|
| 575 |
+
messages = [
|
| 576 |
+
{"role": "system", "content": _AGENT_SYSTEM},
|
| 577 |
+
{"role": "user", "content": (
|
| 578 |
+
f"Repository: {task['repo']}\n\n"
|
| 579 |
+
f"Issue:\n{task['problem_statement']}"
|
| 580 |
+
)},
|
| 581 |
+
]
|
| 582 |
+
|
| 583 |
+
for _ in range(max_turns):
|
| 584 |
+
prompt = tok.apply_chat_template(
|
| 585 |
+
messages, tokenize=False, add_generation_prompt=True)
|
| 586 |
+
ids = tok(prompt, return_tensors="pt", truncation=True,
|
| 587 |
+
max_length=32768).to(model.device)
|
| 588 |
+
|
| 589 |
+
cache = cache_factory()
|
| 590 |
+
with torch.no_grad():
|
| 591 |
+
out = model.generate(
|
| 592 |
+
**ids, max_new_tokens=max_new, do_sample=False,
|
| 593 |
+
past_key_values=cache, use_cache=True,
|
| 594 |
+
)
|
| 595 |
+
gen = tok.decode(out[0, ids["input_ids"].shape[1]:],
|
| 596 |
+
skip_special_tokens=True)
|
| 597 |
+
messages.append({"role": "assistant", "content": gen})
|
| 598 |
+
|
| 599 |
+
if re.search(r"<submit\s*/?>", gen, re.I):
|
| 600 |
+
break
|
| 601 |
+
|
| 602 |
+
cmds = re.findall(r"<bash>(.*?)</bash>", gen, re.DOTALL)
|
| 603 |
+
if not cmds:
|
| 604 |
+
break # model stopped issuing commands; take whatever diff we have
|
| 605 |
+
|
| 606 |
+
obs_parts = []
|
| 607 |
+
for cmd in cmds:
|
| 608 |
+
try:
|
| 609 |
+
r = subprocess.run(
|
| 610 |
+
cmd, shell=True, capture_output=True, text=True,
|
| 611 |
+
timeout=30, cwd=repo_dir,
|
| 612 |
+
)
|
| 613 |
+
obs_parts.append(
|
| 614 |
+
f"$ {cmd.strip()}\n{(r.stdout + r.stderr)[:2000]}")
|
| 615 |
+
except subprocess.TimeoutExpired:
|
| 616 |
+
obs_parts.append(f"$ {cmd.strip()}\n[timeout after 30s]")
|
| 617 |
+
messages.append({"role": "user", "content": "\n\n".join(obs_parts)})
|
| 618 |
+
|
| 619 |
+
diff = subprocess.run(
|
| 620 |
+
["git", "diff", "HEAD"],
|
| 621 |
+
capture_output=True, text=True, cwd=repo_dir,
|
| 622 |
+
)
|
| 623 |
+
return diff.stdout
|
| 624 |
+
finally:
|
| 625 |
+
shutil.rmtree(repo_dir, ignore_errors=True)
|
| 626 |
+
|
| 627 |
+
|
| 628 |
+
def run_swebench_subset(model, tok, cache_factory_per_layer, target_layers,
|
| 629 |
+
task_ids, max_turns=100):
|
| 630 |
+
"""Run the official SWE-bench Verified harness over `task_ids`.
|
| 631 |
+
|
| 632 |
+
Builds per-config VQCache (or None for fp16), runs the mini-bash agent on
|
| 633 |
+
each task, writes predictions.jsonl, evaluates with the swebench harness,
|
| 634 |
+
and returns resolve rate in [0, 1]. Keep `task_ids` FIXED across configs.
|
| 635 |
+
"""
|
| 636 |
+
import glob
|
| 637 |
+
from datasets import load_dataset
|
| 638 |
+
|
| 639 |
+
# Build cache factory: VQCache for quantized configs, None for fp16.
|
| 640 |
+
if cache_factory_per_layer is not None:
|
| 641 |
+
VQCls = make_vq_cache_class(cache_factory_per_layer, target_layers,
|
| 642 |
+
model.config, device=model.device)
|
| 643 |
+
def cache_factory(): return VQCls()
|
| 644 |
+
else:
|
| 645 |
+
def cache_factory(): return None # model allocates DynamicCache internally
|
| 646 |
+
|
| 647 |
+
# Load task metadata indexed by instance_id.
|
| 648 |
+
verified = load_dataset("princeton-nlp/SWE-bench_Verified", split="test")
|
| 649 |
+
tasks = {r["instance_id"]: r for r in verified
|
| 650 |
+
if r["instance_id"] in set(task_ids)}
|
| 651 |
+
|
| 652 |
+
# Generate patches with the agent.
|
| 653 |
+
run_id = f"vqkv_{int(time.time())}"
|
| 654 |
+
predictions = []
|
| 655 |
+
for task_id in task_ids:
|
| 656 |
+
task = tasks.get(task_id)
|
| 657 |
+
if task is None:
|
| 658 |
+
print(f"[swebench] {task_id}: not in Verified dataset, skipping")
|
| 659 |
+
continue
|
| 660 |
+
print(f"[swebench] {task_id} ({len(predictions)+1}/{len(task_ids)})")
|
| 661 |
+
patch = _agent_loop(model, tok, task, cache_factory, max_turns=max_turns)
|
| 662 |
+
predictions.append({
|
| 663 |
+
"instance_id": task_id,
|
| 664 |
+
"model_patch": patch,
|
| 665 |
+
"model_name_or_path": "laguna-vqkv",
|
| 666 |
+
})
|
| 667 |
+
|
| 668 |
+
preds_path = os.path.join(ARTIFACT_DIR, f"{run_id}.jsonl")
|
| 669 |
+
with open(preds_path, "w") as f:
|
| 670 |
+
for p in predictions:
|
| 671 |
+
f.write(json.dumps(p) + "\n")
|
| 672 |
+
print(f"[swebench] wrote {len(predictions)} predictions -> {preds_path}")
|
| 673 |
+
|
| 674 |
+
# Run the official harness (needs Docker daemon).
|
| 675 |
+
# pip install swebench
|
| 676 |
+
from swebench.harness.run_evaluation import main as run_evaluation
|
| 677 |
+
run_evaluation(
|
| 678 |
+
dataset_name_or_path="princeton-nlp/SWE-bench_Verified",
|
| 679 |
+
split="test",
|
| 680 |
+
instance_ids=task_ids,
|
| 681 |
+
predictions_path=preds_path,
|
| 682 |
+
max_workers=4,
|
| 683 |
+
force_rebuild=False,
|
| 684 |
+
cache_level="env",
|
| 685 |
+
clean=False,
|
| 686 |
+
open_file_limit=4096,
|
| 687 |
+
run_id=run_id,
|
| 688 |
+
timeout=1800,
|
| 689 |
+
)
|
| 690 |
+
|
| 691 |
+
# Parse results. swebench writes a JSON summary; location varies by version.
|
| 692 |
+
result_files = (
|
| 693 |
+
glob.glob(os.path.join(ARTIFACT_DIR, f"{run_id}*.json"))
|
| 694 |
+
+ glob.glob(f"{run_id}*.json") # also check cwd
|
| 695 |
+
)
|
| 696 |
+
if not result_files:
|
| 697 |
+
print(f"[swebench] WARNING: no results file found for run_id={run_id}")
|
| 698 |
+
return 0.0
|
| 699 |
+
|
| 700 |
+
with open(result_files[0]) as f:
|
| 701 |
+
results = json.load(f)
|
| 702 |
+
|
| 703 |
+
if isinstance(results, list):
|
| 704 |
+
n_resolved = sum(1 for r in results if r.get("resolved", False))
|
| 705 |
+
elif isinstance(results, dict):
|
| 706 |
+
# some harness versions use {instance_id: {resolved: bool, ...}}
|
| 707 |
+
n_resolved = sum(1 for v in results.values()
|
| 708 |
+
if (v.get("resolved") if isinstance(v, dict) else v))
|
| 709 |
+
else:
|
| 710 |
+
n_resolved = 0
|
| 711 |
+
|
| 712 |
+
return n_resolved / len(task_ids)
|
| 713 |
+
|
| 714 |
+
|
| 715 |
+
def stage_swebench(n_tasks=50, seed=0):
|
| 716 |
+
import random
|
| 717 |
+
from datasets import load_dataset
|
| 718 |
+
blob = torch.load(os.path.join(ARTIFACT_DIR, "codebooks.pt"))
|
| 719 |
+
fitted, meta = blob["fitted"], blob["meta"]
|
| 720 |
+
model, tok, _ = load_model_and_meta()
|
| 721 |
+
|
| 722 |
+
verified = load_dataset("princeton-nlp/SWE-bench_Verified", split="test")
|
| 723 |
+
rng = random.Random(seed)
|
| 724 |
+
task_ids = [verified[i]["instance_id"]
|
| 725 |
+
for i in rng.sample(range(len(verified)), n_tasks)]
|
| 726 |
+
json.dump(task_ids, open(os.path.join(ARTIFACT_DIR, "task_subset.json"), "w"))
|
| 727 |
+
print(f"[swebench] fixed subset of {n_tasks} tasks (seed={seed}) saved.")
|
| 728 |
+
|
| 729 |
+
results = []
|
| 730 |
+
for name, _ in cache_configs():
|
| 731 |
+
quantizers = None if name == "fp16 (baseline)" else fitted[name]
|
| 732 |
+
try:
|
| 733 |
+
rate = run_swebench_subset(model, tok, quantizers,
|
| 734 |
+
meta["full_layers"], task_ids)
|
| 735 |
+
except NotImplementedError as e:
|
| 736 |
+
print(f"[swebench] {name}: STUB -- {e}")
|
| 737 |
+
rate = None
|
| 738 |
+
results.append({"config": name, "resolve_rate": rate})
|
| 739 |
+
print(f"[swebench] {name}: resolve_rate={rate}")
|
| 740 |
+
|
| 741 |
+
# report DELTAS vs fp16 (robust to absolute-score contamination)
|
| 742 |
+
base = next((r["resolve_rate"] for r in results
|
| 743 |
+
if r["config"] == "fp16 (baseline)"), None)
|
| 744 |
+
print("\n[swebench] resolve rate on fixed subset (delta vs fp16):")
|
| 745 |
+
for r in results:
|
| 746 |
+
d = (None if (r["resolve_rate"] is None or base is None)
|
| 747 |
+
else round(r["resolve_rate"] - base, 4))
|
| 748 |
+
print(f" {r['config']:24s} {r['resolve_rate']} (Δ {d})")
|
| 749 |
+
json.dump(results, open(os.path.join(ARTIFACT_DIR, "swebench_results.json"), "w"),
|
| 750 |
+
indent=2)
|
| 751 |
+
|
| 752 |
+
|
| 753 |
+
def main():
|
| 754 |
+
ap = argparse.ArgumentParser()
|
| 755 |
+
ap.add_argument("--stage", required=True,
|
| 756 |
+
choices=["dump", "fit", "cheap", "swebench"])
|
| 757 |
+
ap.add_argument("--n_calib", type=int, default=16)
|
| 758 |
+
ap.add_argument(
|
| 759 |
+
"--calib_source", type=str, default=None,
|
| 760 |
+
choices=["swesmith", "longbench-hotpotqa"],
|
| 761 |
+
help="dump stage: calibration corpus (default: CALIB_SOURCE env or swesmith)")
|
| 762 |
+
ap.add_argument("--n_eval", type=int, default=64)
|
| 763 |
+
ap.add_argument("--n_tasks", type=int, default=50)
|
| 764 |
+
ap.add_argument(
|
| 765 |
+
"--only", type=str, default=None,
|
| 766 |
+
help="fit stage: comma-separated config names to fit/merge (e.g. "
|
| 767 |
+
"sign-1bit,ternary-bitnet). Skips refitting other configs.")
|
| 768 |
+
args = ap.parse_args()
|
| 769 |
+
|
| 770 |
+
if args.stage == "dump":
|
| 771 |
+
stage_dump(n_calib=args.n_calib, calib_source=args.calib_source)
|
| 772 |
+
elif args.stage == "fit":
|
| 773 |
+
only = [s.strip() for s in args.only.split(",")] if args.only else None
|
| 774 |
+
stage_fit(only=only)
|
| 775 |
+
elif args.stage == "cheap":
|
| 776 |
+
stage_cheap(n_eval=args.n_eval)
|
| 777 |
+
elif args.stage == "swebench":
|
| 778 |
+
stage_swebench(n_tasks=args.n_tasks)
|
| 779 |
+
|
| 780 |
+
|
| 781 |
+
if __name__ == "__main__":
|
| 782 |
+
main()
|
generate.py
ADDED
|
@@ -0,0 +1,25 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""AttnVQ: VQQuantizedCache wired into model.generate()."""
|
| 2 |
+
import torch
|
| 3 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 4 |
+
from vqkv.compressed_cache import VQQuantizedCache
|
| 5 |
+
|
| 6 |
+
|
| 7 |
+
# load model
|
| 8 |
+
tok = AutoTokenizer.from_pretrained("poolside/Laguna-XS.2", trust_remote_code=True, fix_mistral_regex=True)
|
| 9 |
+
model = AutoModelForCausalLM.from_pretrained("poolside/Laguna-XS.2", torch_dtype=torch.bfloat16, device_map="cuda", trust_remote_code=True).eval()
|
| 10 |
+
|
| 11 |
+
# load codebooks or fit and use your own
|
| 12 |
+
CODEBOOKS_PATH = "artifacts/codebooks.pt"
|
| 13 |
+
codebooks = torch.load(CODEBOOKS_PATH, map_location="cuda", weights_only=False)
|
| 14 |
+
|
| 15 |
+
# build cache
|
| 16 |
+
quantizers, layers = codebooks["fitted"]["productvq-32x256-2b"], blob["meta"]["full_layers"]
|
| 17 |
+
cache = VQQuantizedCache(quantizers, layers) # persists uint8 codebook indices
|
| 18 |
+
|
| 19 |
+
# generate
|
| 20 |
+
ids = tok("Hello", return_tensors="pt").to(model.device)
|
| 21 |
+
out = model.generate(**ids, max_new_tokens=32, past_key_values=cache, use_cache=True)
|
| 22 |
+
print(tok.decode(out[0, ids["input_ids"].shape[1]:], skip_special_tokens=True))
|
| 23 |
+
|
| 24 |
+
# print memory footprint
|
| 25 |
+
print(cache.memory_footprint())
|
longbench_eval.py
ADDED
|
@@ -0,0 +1,763 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
longbench_eval.py — LongBench v1 evaluation for AttnVQ and baselines.
|
| 3 |
+
|
| 4 |
+
Stages:
|
| 5 |
+
cheap proxy KV metrics on task contexts (fast, default)
|
| 6 |
+
generate end-to-end task scoring via model.generate (slow)
|
| 7 |
+
|
| 8 |
+
Tasks: qasper, 2wikimqa, hotpotqa, passage_retrieval_en, repobench-p
|
| 9 |
+
|
| 10 |
+
Usage:
|
| 11 |
+
python longbench_eval.py --stage cheap --n_eval 50
|
| 12 |
+
python longbench_eval.py --stage generate --tasks hotpotqa qasper
|
| 13 |
+
"""
|
| 14 |
+
|
| 15 |
+
from __future__ import annotations
|
| 16 |
+
|
| 17 |
+
import argparse
|
| 18 |
+
import collections
|
| 19 |
+
import json
|
| 20 |
+
import os
|
| 21 |
+
import re
|
| 22 |
+
import string
|
| 23 |
+
from difflib import SequenceMatcher
|
| 24 |
+
|
| 25 |
+
import torch
|
| 26 |
+
from tqdm import tqdm
|
| 27 |
+
|
| 28 |
+
from turbo_benchmark import TurboQuantMSE, QJLResidualIP, turbo_configs # noqa: F401 — unpickle
|
| 29 |
+
from vqkv.quantizers import KIVIScalarKV # noqa: F401
|
| 30 |
+
from vqkv.metrics import (key_cosine, cache_mse, inner_product_distortion,
|
| 31 |
+
attention_output, attn_output_cosine, attn_output_error)
|
| 32 |
+
|
| 33 |
+
MODEL_ID = os.environ.get("LAGUNA_ID", "poolside/Laguna-XS.2")
|
| 34 |
+
ARTIFACT_DIR = os.environ.get("VQKV_ARTIFACTS", "./artifacts")
|
| 35 |
+
|
| 36 |
+
ATTN_WIN = 512
|
| 37 |
+
CHEAP_COLS = ("key_cos", "val_cos", "key_mse", "val_mse",
|
| 38 |
+
"attn_cos", "attn_output_error", "ip_rel", "ip_bias")
|
| 39 |
+
|
| 40 |
+
# Scorers (referenced from TASK_CONFIGS)
|
| 41 |
+
def _normalize(s: str) -> str:
|
| 42 |
+
s = s.lower()
|
| 43 |
+
s = s.translate(str.maketrans("", "", string.punctuation))
|
| 44 |
+
return " ".join(s.split())
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def qa_f1_score(prediction: str, ground_truths: list[str], **_) -> float:
|
| 48 |
+
"""Max token-level F1 over all reference answers (QA tasks)."""
|
| 49 |
+
pred_toks = _normalize(prediction).split()
|
| 50 |
+
best = 0.0
|
| 51 |
+
for ref in ground_truths:
|
| 52 |
+
ref_toks = _normalize(ref).split()
|
| 53 |
+
if not pred_toks or not ref_toks:
|
| 54 |
+
best = max(best, float(pred_toks == ref_toks))
|
| 55 |
+
continue
|
| 56 |
+
common = collections.Counter(pred_toks) & collections.Counter(ref_toks)
|
| 57 |
+
n_common = sum(common.values())
|
| 58 |
+
if n_common == 0:
|
| 59 |
+
continue
|
| 60 |
+
p = n_common / len(pred_toks)
|
| 61 |
+
r = n_common / len(ref_toks)
|
| 62 |
+
best = max(best, 2 * p * r / (p + r))
|
| 63 |
+
return best
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def retrieval_score(prediction: str, ground_truths: list[str], **_) -> float:
|
| 67 |
+
"""Passage-retrieval accuracy: extract digit from prediction, match gold.
|
| 68 |
+
|
| 69 |
+
Ground truths are strings like 'Paragraph 3'; we extract the number and
|
| 70 |
+
check whether it appears among the first 10 digits in the prediction.
|
| 71 |
+
Mirrors the LongBench retrieval_score implementation.
|
| 72 |
+
"""
|
| 73 |
+
gt_ids = set()
|
| 74 |
+
for ref in ground_truths:
|
| 75 |
+
m = re.findall(r"\d+", ref)
|
| 76 |
+
gt_ids.add(m[0] if m else _normalize(ref))
|
| 77 |
+
pred_nums = re.findall(r"\d+", prediction)
|
| 78 |
+
if not pred_nums:
|
| 79 |
+
return 0.0
|
| 80 |
+
right = sum(1 for n in pred_nums[:10] if n in gt_ids)
|
| 81 |
+
return right / len(pred_nums[:10])
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def classification_score(prediction: str, ground_truths: list[str],
|
| 85 |
+
all_classes: list[str] | None = None, **_) -> float:
|
| 86 |
+
"""Classification accuracy (TREC).
|
| 87 |
+
|
| 88 |
+
If all_classes is provided (from the dataset example), restrict matches
|
| 89 |
+
to valid class labels to avoid spurious substring hits — matches the
|
| 90 |
+
LongBench classification_score behaviour.
|
| 91 |
+
"""
|
| 92 |
+
if all_classes:
|
| 93 |
+
matched = [c for c in all_classes if c.lower() in prediction.lower()]
|
| 94 |
+
return float(any(ref.lower() in [m.lower() for m in matched]
|
| 95 |
+
for ref in ground_truths))
|
| 96 |
+
# Fallback: normalised substring match
|
| 97 |
+
pred_norm = _normalize(prediction)
|
| 98 |
+
return float(any(_normalize(ref) in pred_norm for ref in ground_truths))
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
def edit_similarity_score(prediction: str, ground_truths: list[str], **_) -> float:
|
| 102 |
+
"""Character-level edit similarity for code completion (RepoBench-P).
|
| 103 |
+
|
| 104 |
+
Uses difflib.SequenceMatcher.ratio() — equivalent to
|
| 105 |
+
fuzz.ratio() (not partial_ratio) and zero-dep.
|
| 106 |
+
"""
|
| 107 |
+
pred = prediction.strip()
|
| 108 |
+
best = 0.0
|
| 109 |
+
for ref in ground_truths:
|
| 110 |
+
ref_s = ref.strip()
|
| 111 |
+
if not pred and not ref_s:
|
| 112 |
+
best = 1.0
|
| 113 |
+
elif pred and ref_s:
|
| 114 |
+
best = max(best, SequenceMatcher(None, pred, ref_s).ratio())
|
| 115 |
+
return best
|
| 116 |
+
|
| 117 |
+
|
| 118 |
+
# ============================================================================
|
| 119 |
+
# Task configs
|
| 120 |
+
# ============================================================================
|
| 121 |
+
# QA tasks: increasing context length (~3.6K → ~18K tokens) directly probes
|
| 122 |
+
# the compounding-error-vs-length axis from RUNBOOK Step 6.
|
| 123 |
+
# Three extra tasks cover retrieval accuracy, classification, and code completion.
|
| 124 |
+
# ============================================================================
|
| 125 |
+
TASK_CONFIGS: dict[str, dict] = {
|
| 126 |
+
"qasper": dict(
|
| 127 |
+
score_fn=qa_f1_score,
|
| 128 |
+
max_new_tokens=20,
|
| 129 |
+
max_len=16384,
|
| 130 |
+
prompt_template=(
|
| 131 |
+
"Answer the question based on the given passages. "
|
| 132 |
+
"Only give me the answer and do not output any other words.\n\n"
|
| 133 |
+
"The following are given passages.\n{context}\n\n"
|
| 134 |
+
"Answer the question based on the given passages. "
|
| 135 |
+
"Only give me the answer and do not output any other words.\n\n"
|
| 136 |
+
"Question: {input}\nAnswer:"
|
| 137 |
+
),
|
| 138 |
+
),
|
| 139 |
+
"2wikimqa": dict(
|
| 140 |
+
score_fn=qa_f1_score,
|
| 141 |
+
max_new_tokens=10,
|
| 142 |
+
max_len=16384,
|
| 143 |
+
prompt_template=(
|
| 144 |
+
"Answer the question based on the given passages. "
|
| 145 |
+
"Only give me the answer and do not output any other words.\n\n"
|
| 146 |
+
"The following are given passages.\n{context}\n\n"
|
| 147 |
+
"Answer the question based on the given passages. "
|
| 148 |
+
"Only give me the answer and do not output any other words.\n\n"
|
| 149 |
+
"Question: {input}\nAnswer:"
|
| 150 |
+
),
|
| 151 |
+
),
|
| 152 |
+
"hotpotqa": dict(
|
| 153 |
+
score_fn=qa_f1_score,
|
| 154 |
+
max_new_tokens=32,
|
| 155 |
+
max_len=32768,
|
| 156 |
+
prompt_template=(
|
| 157 |
+
"Answer the question based on the given passages. "
|
| 158 |
+
"Only give me the answer and do not output any other words.\n\n"
|
| 159 |
+
"The following are given passages.\n{context}\n\n"
|
| 160 |
+
"Answer the question based on the given passages. "
|
| 161 |
+
"Only give me the answer and do not output any other words.\n\n"
|
| 162 |
+
"Question: {input}\nAnswer:"
|
| 163 |
+
),
|
| 164 |
+
),
|
| 165 |
+
"passage_retrieval_en": dict(
|
| 166 |
+
score_fn=retrieval_score,
|
| 167 |
+
max_new_tokens=10,
|
| 168 |
+
max_len=32768,
|
| 169 |
+
prompt_template=(
|
| 170 |
+
"Below is a record of a series of paragraphs, each from different "
|
| 171 |
+
"documents. Tell me which paragraph the given passage is from.\n\n"
|
| 172 |
+
"{context}\n\n"
|
| 173 |
+
"Based on the above paragraphs, which paragraph does the following "
|
| 174 |
+
"passage come from? Only output the paragraph number. "
|
| 175 |
+
"Do not output any other characters.\n\n"
|
| 176 |
+
"{input}\nAnswer:"
|
| 177 |
+
),
|
| 178 |
+
),
|
| 179 |
+
"repobench-p": dict(
|
| 180 |
+
score_fn=edit_similarity_score,
|
| 181 |
+
max_new_tokens=64,
|
| 182 |
+
max_len=16384,
|
| 183 |
+
prompt_template=(
|
| 184 |
+
"Please complete the code given below.\n{context}\n{input}\n"
|
| 185 |
+
),
|
| 186 |
+
),
|
| 187 |
+
}
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
# ============================================================================
|
| 191 |
+
# Model loading
|
| 192 |
+
# ============================================================================
|
| 193 |
+
def load_model_and_meta():
|
| 194 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 195 |
+
tok = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True, fix_mistral_regex=True)
|
| 196 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 197 |
+
MODEL_ID, torch_dtype=torch.bfloat16, device_map="cuda",
|
| 198 |
+
trust_remote_code=True)
|
| 199 |
+
model.eval()
|
| 200 |
+
cfg = model.config
|
| 201 |
+
full_layers = [i for i, t in enumerate(cfg.layer_types) if t == "full_attention"]
|
| 202 |
+
meta = {
|
| 203 |
+
"full_layers": full_layers,
|
| 204 |
+
"n_kv_heads": cfg.num_key_value_heads,
|
| 205 |
+
"n_q_heads": cfg.num_attention_heads,
|
| 206 |
+
"head_dim": cfg.head_dim,
|
| 207 |
+
}
|
| 208 |
+
print(f"[meta] full-attention layers ({len(full_layers)}): {full_layers}")
|
| 209 |
+
print(f"[meta] kv_heads={meta['n_kv_heads']} head_dim={meta['head_dim']}")
|
| 210 |
+
return model, tok, meta
|
| 211 |
+
|
| 212 |
+
|
| 213 |
+
# ============================================================================
|
| 214 |
+
# Generic VQCache
|
| 215 |
+
# ============================================================================
|
| 216 |
+
def make_cache_class(per_layer_fns: dict, target_layers: list):
|
| 217 |
+
"""Build a DynamicCache subclass that round-trips K/V through the given fns.
|
| 218 |
+
|
| 219 |
+
per_layer_fns[layer_idx] = {
|
| 220 |
+
"k_fn": callable (N, d) -> (N, d),
|
| 221 |
+
"v_fn": callable (N, d) -> (N, d),
|
| 222 |
+
"per_channel": bool, # True for KIVI/Sign/Ternary (key dim=0 reduction)
|
| 223 |
+
}
|
| 224 |
+
|
| 225 |
+
For non-per-channel quantizers, K and V are flattened to (s*h, d) before
|
| 226 |
+
calling k_fn/v_fn. For per-channel, each head's (s, d) block is passed
|
| 227 |
+
separately so the quantizer's token-axis statistics are per-head (not
|
| 228 |
+
cross-head), matching the KIVI / benchmark.py convention.
|
| 229 |
+
"""
|
| 230 |
+
from transformers.cache_utils import DynamicCache
|
| 231 |
+
_target = set(target_layers)
|
| 232 |
+
|
| 233 |
+
class VQCache(DynamicCache):
|
| 234 |
+
def update(self, key_states, value_states, layer_idx, cache_kwargs=None):
|
| 235 |
+
if layer_idx in _target and layer_idx in per_layer_fns:
|
| 236 |
+
entry = per_layer_fns[layer_idx]
|
| 237 |
+
k_fn = entry["k_fn"]
|
| 238 |
+
v_fn = entry["v_fn"]
|
| 239 |
+
per_channel = entry.get("per_channel", False)
|
| 240 |
+
|
| 241 |
+
_b, h, s, d = key_states.shape
|
| 242 |
+
|
| 243 |
+
if per_channel:
|
| 244 |
+
# key_states[0]: (h, s, d) -> (s, h, d)
|
| 245 |
+
kk = key_states[0].permute(1, 0, 2).float()
|
| 246 |
+
k_hat = torch.stack([k_fn(kk[:, hh, :]) for hh in range(h)], dim=1)
|
| 247 |
+
vv = value_states[0].permute(1, 0, 2).float()
|
| 248 |
+
v_hat = torch.stack([v_fn(vv[:, hh, :]) for hh in range(h)], dim=1)
|
| 249 |
+
# (s, h, d) -> (h, s, d) -> (1, h, s, d)
|
| 250 |
+
k_hat = k_hat.permute(1, 0, 2).unsqueeze(0)
|
| 251 |
+
v_hat = v_hat.permute(1, 0, 2).unsqueeze(0)
|
| 252 |
+
else:
|
| 253 |
+
# key_states[0]: (h, s, d) -> (s, h, d) -> (s*h, d)
|
| 254 |
+
kf = key_states[0].permute(1, 0, 2).reshape(-1, d).float()
|
| 255 |
+
vf = value_states[0].permute(1, 0, 2).reshape(-1, d).float()
|
| 256 |
+
# (s*h, d) -> (s, h, d) -> (h, s, d) -> (1, h, s, d)
|
| 257 |
+
k_hat = k_fn(kf).reshape(s, h, d).permute(1, 0, 2).unsqueeze(0)
|
| 258 |
+
v_hat = v_fn(vf).reshape(s, h, d).permute(1, 0, 2).unsqueeze(0)
|
| 259 |
+
|
| 260 |
+
key_states = k_hat.to(key_states.dtype).to(key_states.device)
|
| 261 |
+
value_states = v_hat.to(value_states.dtype).to(value_states.device)
|
| 262 |
+
|
| 263 |
+
return super().update(key_states, value_states, layer_idx, cache_kwargs)
|
| 264 |
+
|
| 265 |
+
return VQCache
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
# ============================================================================
|
| 269 |
+
# Build unified config list from both codebook files
|
| 270 |
+
# ============================================================================
|
| 271 |
+
def build_all_configs(meta: dict, device, only: list[str] | None = None):
|
| 272 |
+
"""Return list of (name, bpe, cache_cls_or_None).
|
| 273 |
+
|
| 274 |
+
cache_cls_or_None: class (not instance) to call as cls() each generation,
|
| 275 |
+
or None for the fp16 baseline (DynamicCache allocated internally by model).
|
| 276 |
+
"""
|
| 277 |
+
hd = meta["head_dim"]
|
| 278 |
+
layers = meta["full_layers"]
|
| 279 |
+
configs: list[tuple[str, float, type | None]] = []
|
| 280 |
+
|
| 281 |
+
configs.append(("fp16", 16.0, None))
|
| 282 |
+
|
| 283 |
+
# -- Regular codebooks (ProductVQ, RoPESplit, Scalar, KIVI, Sign, Ternary) --
|
| 284 |
+
cb_path = os.path.join(ARTIFACT_DIR, "codebooks.pt")
|
| 285 |
+
if os.path.exists(cb_path):
|
| 286 |
+
blob = torch.load(cb_path, weights_only=False)
|
| 287 |
+
fitted = blob["fitted"]
|
| 288 |
+
for name, per_layer in fitted.items():
|
| 289 |
+
for q in per_layer.values():
|
| 290 |
+
if hasattr(q, "to"):
|
| 291 |
+
q.to(device)
|
| 292 |
+
q0 = next(iter(per_layer.values()))
|
| 293 |
+
bpe = round(q0.bits_per_element(hd), 4)
|
| 294 |
+
per_channel = (isinstance(q0, KIVIScalarKV)
|
| 295 |
+
or getattr(q0, "per_channel_key", False))
|
| 296 |
+
per_layer_fns = {
|
| 297 |
+
i: {"k_fn": per_layer[i].roundtrip_k,
|
| 298 |
+
"v_fn": per_layer[i].roundtrip_v,
|
| 299 |
+
"per_channel": per_channel}
|
| 300 |
+
for i in layers if i in per_layer
|
| 301 |
+
}
|
| 302 |
+
configs.append((name, bpe, make_cache_class(per_layer_fns, layers)))
|
| 303 |
+
else:
|
| 304 |
+
print(f"[warn] {cb_path} not found — skipping ProductVQ / scalar configs")
|
| 305 |
+
|
| 306 |
+
# -- TurboQuant codebooks --
|
| 307 |
+
tc_path = os.path.join(ARTIFACT_DIR, "turbo_codebooks.pt")
|
| 308 |
+
if os.path.exists(tc_path):
|
| 309 |
+
blob = torch.load(tc_path, weights_only=False)
|
| 310 |
+
fitted = blob["fitted"]
|
| 311 |
+
bits_list = blob["bits"]
|
| 312 |
+
cfg_lookup = {n: (b, use_qjl) for n, b, use_qjl in turbo_configs(bits_list)}
|
| 313 |
+
for name, per_layer in fitted.items():
|
| 314 |
+
b, use_qjl = cfg_lookup[name]
|
| 315 |
+
bpe = round(b + (1 if use_qjl else 0) + 16.0 / hd, 4)
|
| 316 |
+
for entry in per_layer.values():
|
| 317 |
+
entry["kq"].to(device)
|
| 318 |
+
entry["vq"].to(device)
|
| 319 |
+
if entry["qjl"] is not None:
|
| 320 |
+
entry["qjl"].to(device)
|
| 321 |
+
per_layer_fns = {
|
| 322 |
+
i: {"k_fn": per_layer[i]["kq"].roundtrip,
|
| 323 |
+
"v_fn": per_layer[i]["vq"].roundtrip,
|
| 324 |
+
"per_channel": False}
|
| 325 |
+
for i in layers if i in per_layer
|
| 326 |
+
}
|
| 327 |
+
configs.append((name, bpe, make_cache_class(per_layer_fns, layers)))
|
| 328 |
+
else:
|
| 329 |
+
print(f"[warn] {tc_path} not found — skipping TurboQuant configs")
|
| 330 |
+
|
| 331 |
+
if only is not None:
|
| 332 |
+
only_set = set(only)
|
| 333 |
+
configs = [(n, b, c) for n, b, c in configs if n in only_set]
|
| 334 |
+
unknown = only_set - {n for n, _, _ in configs}
|
| 335 |
+
if unknown:
|
| 336 |
+
print(f"[warn] unknown --configs names: {sorted(unknown)}")
|
| 337 |
+
|
| 338 |
+
print(f"[build] {len(configs)} configs loaded")
|
| 339 |
+
for n, b, _ in configs:
|
| 340 |
+
print(f" {n:<32} {b:.3f} bpe")
|
| 341 |
+
return configs
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
# ============================================================================
|
| 345 |
+
# Per-task generation + scoring
|
| 346 |
+
# ============================================================================
|
| 347 |
+
def _load_longbench(task_name: str):
|
| 348 |
+
"""Load a LongBench task, compatible with datasets >= 3.0.
|
| 349 |
+
|
| 350 |
+
datasets >= 3.0 dropped custom dataset-script support and raises
|
| 351 |
+
'Dataset scripts are no longer supported' for THUDM/LongBench.
|
| 352 |
+
We load the underlying JSONL files from the HF Hub directly instead.
|
| 353 |
+
Tries the English-suffixed file first (_e.jsonl), then the plain name.
|
| 354 |
+
"""
|
| 355 |
+
from datasets import load_dataset as _ld
|
| 356 |
+
# Candidates in priority order:
|
| 357 |
+
# {task}_e.jsonl – English-suffixed bilingual tasks
|
| 358 |
+
# {task}.jsonl – single-language tasks
|
| 359 |
+
# {task_underscored}.jsonl – hyphenated names (repobench-p → repobench_p)
|
| 360 |
+
slug = task_name.replace("-", "_")
|
| 361 |
+
candidates = [f"{task_name}_e.jsonl", f"{task_name}.jsonl",
|
| 362 |
+
f"{slug}_e.jsonl", f"{slug}.jsonl"]
|
| 363 |
+
# deduplicate while preserving order
|
| 364 |
+
seen: set[str] = set()
|
| 365 |
+
for fname in [c for c in candidates if not (c in seen or seen.add(c))]:
|
| 366 |
+
try:
|
| 367 |
+
return _ld(
|
| 368 |
+
"json",
|
| 369 |
+
data_files=f"hf://datasets/THUDM/LongBench/data/{fname}",
|
| 370 |
+
split="train",
|
| 371 |
+
)
|
| 372 |
+
except Exception:
|
| 373 |
+
continue
|
| 374 |
+
# Fallback for older datasets versions that still support scripts
|
| 375 |
+
return _ld("THUDM/LongBench", name=task_name, split="test")
|
| 376 |
+
|
| 377 |
+
|
| 378 |
+
def prompt_text(task_cfg: dict, example: dict) -> str:
|
| 379 |
+
return task_cfg["prompt_template"].format(
|
| 380 |
+
context=example["context"], input=example["input"])
|
| 381 |
+
|
| 382 |
+
|
| 383 |
+
def load_vqkv_fitted(device):
|
| 384 |
+
path = os.path.join(ARTIFACT_DIR, "codebooks.pt")
|
| 385 |
+
if not os.path.exists(path):
|
| 386 |
+
raise FileNotFoundError(f"{path} not found; run benchmark.py --stage fit")
|
| 387 |
+
blob = torch.load(path, weights_only=False)
|
| 388 |
+
fitted, meta = blob["fitted"], blob["meta"]
|
| 389 |
+
for per_layer in fitted.values():
|
| 390 |
+
for q in per_layer.values():
|
| 391 |
+
if hasattr(q, "to"):
|
| 392 |
+
q.to(device)
|
| 393 |
+
return fitted, meta
|
| 394 |
+
|
| 395 |
+
|
| 396 |
+
def load_turbo_fitted(device):
|
| 397 |
+
path = os.path.join(ARTIFACT_DIR, "turbo_codebooks.pt")
|
| 398 |
+
if not os.path.exists(path):
|
| 399 |
+
print(f"[warn] {path} not found — skipping TurboQuant configs")
|
| 400 |
+
return {}, {}
|
| 401 |
+
blob = torch.load(path, weights_only=False)
|
| 402 |
+
fitted = blob["fitted"]
|
| 403 |
+
cfg_lookup = {n: (b, use_qjl) for n, b, use_qjl in turbo_configs(blob["bits"])}
|
| 404 |
+
for per_layer in fitted.values():
|
| 405 |
+
for entry in per_layer.values():
|
| 406 |
+
entry["kq"].to(device)
|
| 407 |
+
entry["vq"].to(device)
|
| 408 |
+
if entry["qjl"] is not None:
|
| 409 |
+
entry["qjl"].to(device)
|
| 410 |
+
return fitted, cfg_lookup
|
| 411 |
+
|
| 412 |
+
|
| 413 |
+
def _aggregate_cheap_rows(trace_rows: list[dict], bpe_lookup: dict) -> list[dict]:
|
| 414 |
+
agg = collections.defaultdict(lambda: collections.defaultdict(list))
|
| 415 |
+
for r in trace_rows:
|
| 416 |
+
for col in CHEAP_COLS:
|
| 417 |
+
agg[r["config"]][col].append(r[col])
|
| 418 |
+
summary = []
|
| 419 |
+
for name in bpe_lookup:
|
| 420 |
+
if name not in agg:
|
| 421 |
+
continue
|
| 422 |
+
cols = agg[name]
|
| 423 |
+
n = len(cols["key_cos"])
|
| 424 |
+
row = {"config": name, "bits_per_elt": bpe_lookup[name], "n_traces": n}
|
| 425 |
+
for col in CHEAP_COLS:
|
| 426 |
+
row[col] = round(sum(cols[col]) / n, 5)
|
| 427 |
+
summary.append(row)
|
| 428 |
+
return summary
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
def _print_cheap_table(task_name: str, summary: list[dict]):
|
| 432 |
+
print(f"\n[cheap/{task_name}] mean metrics:")
|
| 433 |
+
print(f" {'config':32s} {'bpe':>5} {'key_cos':>8} {'val_cos':>8} "
|
| 434 |
+
f"{'key_mse':>9} {'val_mse':>9} {'attn_cos':>9} {'attn_err':>9} "
|
| 435 |
+
f"{'ip_rel':>8} {'ip_bias':>9}")
|
| 436 |
+
for row in summary:
|
| 437 |
+
print(f" {row['config']:32s} {row['bits_per_elt']:5.2f} "
|
| 438 |
+
f"{row['key_cos']:8.4f} {row['val_cos']:8.4f} "
|
| 439 |
+
f"{row['key_mse']:9.5f} {row['val_mse']:9.5f} "
|
| 440 |
+
f"{row['attn_cos']:9.4f} {row['attn_output_error']:9.4f} "
|
| 441 |
+
f"{row['ip_rel']:8.5f} {row['ip_bias']:9.6f}")
|
| 442 |
+
|
| 443 |
+
|
| 444 |
+
def run_cheap_task(model, tok, meta, task_name: str, task_cfg: dict,
|
| 445 |
+
vqkv_fitted: dict, turbo_fitted: dict,
|
| 446 |
+
turbo_cfg_lookup: dict, bpe_lookup: dict,
|
| 447 |
+
config_filter: set[str] | None, n_eval: int,
|
| 448 |
+
min_len: int = 2048) -> list[dict]:
|
| 449 |
+
"""Dump fp16 caches on n_eval LongBench prompts; score all quantizer configs."""
|
| 450 |
+
from transformers.cache_utils import DynamicCache
|
| 451 |
+
|
| 452 |
+
ds = _load_longbench(task_name)
|
| 453 |
+
subset = ds.select(range(min(n_eval, len(ds))))
|
| 454 |
+
max_len = task_cfg["max_len"]
|
| 455 |
+
full = meta["full_layers"]
|
| 456 |
+
hd = meta["head_dim"]
|
| 457 |
+
n_q = meta.get("n_q_heads", 48)
|
| 458 |
+
dev = model.device
|
| 459 |
+
if str(dev) == "meta":
|
| 460 |
+
dev = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
| 461 |
+
|
| 462 |
+
print(f"\n[cheap/{task_name}] {len(subset)} examples, max_len={max_len}")
|
| 463 |
+
|
| 464 |
+
class EvalDump(DynamicCache):
|
| 465 |
+
def __init__(self):
|
| 466 |
+
super().__init__()
|
| 467 |
+
self.d = {i: {} for i in full}
|
| 468 |
+
|
| 469 |
+
def update(self, ks, vs, li, ck=None):
|
| 470 |
+
if li in set(full):
|
| 471 |
+
self.d[li]["k"] = ks.detach()[0].permute(1, 0, 2).float()
|
| 472 |
+
self.d[li]["v"] = vs.detach()[0].permute(1, 0, 2).float()
|
| 473 |
+
return super().update(ks, vs, li, ck)
|
| 474 |
+
|
| 475 |
+
trace_rows: list[dict] = []
|
| 476 |
+
n_used = 0
|
| 477 |
+
|
| 478 |
+
for ex in tqdm(subset, desc=f"cheap/{task_name}"):
|
| 479 |
+
text = prompt_text(task_cfg, ex)
|
| 480 |
+
ids = tok(text, return_tensors="pt", truncation=True,
|
| 481 |
+
max_length=max_len).to(dev)
|
| 482 |
+
if ids["input_ids"].shape[1] < min_len:
|
| 483 |
+
continue
|
| 484 |
+
|
| 485 |
+
cache = EvalDump()
|
| 486 |
+
with torch.no_grad():
|
| 487 |
+
model.model(**ids, past_key_values=cache, use_cache=True)
|
| 488 |
+
n_used += 1
|
| 489 |
+
|
| 490 |
+
synth_q = {}
|
| 491 |
+
for i in full:
|
| 492 |
+
s = cache.d[i]["k"].shape[0]
|
| 493 |
+
win = min(s, ATTN_WIN)
|
| 494 |
+
q_rand = torch.randn(win, n_q, hd, device=dev)
|
| 495 |
+
synth_q[i] = q_rand / q_rand.norm(dim=-1, keepdim=True).clamp_min(1e-8)
|
| 496 |
+
|
| 497 |
+
# -- vqkv configs (ProductVQ, scalar, KIVI, …) --
|
| 498 |
+
for name, per_layer in vqkv_fitted.items():
|
| 499 |
+
if config_filter and name not in config_filter:
|
| 500 |
+
continue
|
| 501 |
+
acc = collections.defaultdict(float)
|
| 502 |
+
nL = 0
|
| 503 |
+
for i in full:
|
| 504 |
+
k = cache.d[i]["k"]
|
| 505 |
+
v = cache.d[i]["v"]
|
| 506 |
+
q = per_layer[i]
|
| 507 |
+
s, h, d = k.shape
|
| 508 |
+
per_channel = (
|
| 509 |
+
isinstance(q, KIVIScalarKV)
|
| 510 |
+
or getattr(q, "per_channel_key", False)
|
| 511 |
+
)
|
| 512 |
+
if per_channel:
|
| 513 |
+
k_hat = torch.stack([q.roundtrip_k(k[:, hh, :]) for hh in range(h)], 1)
|
| 514 |
+
v_hat = torch.stack([q.roundtrip_v(v[:, hh, :]) for hh in range(h)], 1)
|
| 515 |
+
else:
|
| 516 |
+
k_hat = q.roundtrip_k(k.reshape(-1, d)).reshape(s, h, d)
|
| 517 |
+
v_hat = q.roundtrip_v(v.reshape(-1, d)).reshape(s, h, d)
|
| 518 |
+
|
| 519 |
+
acc["key_cos"] += key_cosine(k, k_hat)
|
| 520 |
+
acc["val_cos"] += key_cosine(v, v_hat)
|
| 521 |
+
acc["key_mse"] += cache_mse(k, k_hat)
|
| 522 |
+
acc["val_mse"] += cache_mse(v, v_hat)
|
| 523 |
+
|
| 524 |
+
win = min(s, ATTN_WIN)
|
| 525 |
+
kw, kw_hat = k[-win:], k_hat[-win:]
|
| 526 |
+
vw, vw_hat = v[-win:], v_hat[-win:]
|
| 527 |
+
q_syn = synth_q[i]
|
| 528 |
+
|
| 529 |
+
out_ref, _ = attention_output(q_syn, kw, vw, n_q)
|
| 530 |
+
out_hat, _ = attention_output(q_syn, kw_hat, vw_hat, n_q)
|
| 531 |
+
acc["attn_cos"] += attn_output_cosine(out_ref, out_hat)
|
| 532 |
+
acc["attn_output_error"] += attn_output_error(out_ref, out_hat)
|
| 533 |
+
|
| 534 |
+
ip = inner_product_distortion(q_syn, kw, kw_hat)
|
| 535 |
+
acc["ip_rel"] += ip["ip_rel_err"]
|
| 536 |
+
acc["ip_bias"] += ip["ip_bias"]
|
| 537 |
+
nL += 1
|
| 538 |
+
|
| 539 |
+
trace_rows.append({
|
| 540 |
+
"task": task_name,
|
| 541 |
+
"trace_len": ids["input_ids"].shape[1],
|
| 542 |
+
"config": name,
|
| 543 |
+
**{col: acc[col] / nL for col in CHEAP_COLS},
|
| 544 |
+
})
|
| 545 |
+
|
| 546 |
+
# -- TurboQuant configs --
|
| 547 |
+
for name, per_layer in turbo_fitted.items():
|
| 548 |
+
if config_filter and name not in config_filter:
|
| 549 |
+
continue
|
| 550 |
+
acc = collections.defaultdict(float)
|
| 551 |
+
nL = 0
|
| 552 |
+
for i in full:
|
| 553 |
+
k = cache.d[i]["k"]
|
| 554 |
+
v = cache.d[i]["v"]
|
| 555 |
+
e = per_layer[i]
|
| 556 |
+
s, h, d = k.shape
|
| 557 |
+
k_hat = e["kq"].roundtrip(k.reshape(-1, d)).reshape(s, h, d)
|
| 558 |
+
v_hat = e["vq"].roundtrip(v.reshape(-1, d)).reshape(s, h, d)
|
| 559 |
+
|
| 560 |
+
acc["key_cos"] += key_cosine(k, k_hat)
|
| 561 |
+
acc["val_cos"] += key_cosine(v, v_hat)
|
| 562 |
+
acc["key_mse"] += cache_mse(k, k_hat)
|
| 563 |
+
acc["val_mse"] += cache_mse(v, v_hat)
|
| 564 |
+
|
| 565 |
+
win = min(s, ATTN_WIN)
|
| 566 |
+
kw, kw_hat = k[-win:], k_hat[-win:]
|
| 567 |
+
vw, vw_hat = v[-win:], v_hat[-win:]
|
| 568 |
+
q_syn = synth_q[i]
|
| 569 |
+
|
| 570 |
+
out_ref, _ = attention_output(q_syn, kw, vw, n_q)
|
| 571 |
+
out_hat, _ = attention_output(q_syn, kw_hat, vw_hat, n_q)
|
| 572 |
+
acc["attn_cos"] += attn_output_cosine(out_ref, out_hat)
|
| 573 |
+
acc["attn_output_error"] += attn_output_error(out_ref, out_hat)
|
| 574 |
+
|
| 575 |
+
q0 = q_syn[:, 0, :]
|
| 576 |
+
kr0 = kw[:, 0, :]
|
| 577 |
+
kh0 = kw_hat[:, 0, :]
|
| 578 |
+
qi = torch.randint(0, win, (4096,), device=dev)
|
| 579 |
+
ki = torch.randint(0, win, (4096,), device=dev)
|
| 580 |
+
ip_ref = (q0[qi] * kr0[ki]).sum(-1)
|
| 581 |
+
if e["qjl"] is not None:
|
| 582 |
+
ip_hat = e["qjl"].estimate_ip(q0[qi], kr0[ki])
|
| 583 |
+
else:
|
| 584 |
+
ip_hat = (q0[qi] * kh0[ki]).sum(-1)
|
| 585 |
+
acc["ip_bias"] += (ip_hat - ip_ref).mean().item()
|
| 586 |
+
acc["ip_rel"] += ((ip_hat - ip_ref).abs() /
|
| 587 |
+
ip_ref.abs().clamp_min(1e-6)).mean().item()
|
| 588 |
+
nL += 1
|
| 589 |
+
|
| 590 |
+
trace_rows.append({
|
| 591 |
+
"task": task_name,
|
| 592 |
+
"trace_len": ids["input_ids"].shape[1],
|
| 593 |
+
"config": name,
|
| 594 |
+
**{col: acc[col] / nL for col in CHEAP_COLS},
|
| 595 |
+
})
|
| 596 |
+
|
| 597 |
+
print(f"[cheap/{task_name}] used {n_used}/{len(subset)} traces (min_len={min_len})")
|
| 598 |
+
return trace_rows
|
| 599 |
+
|
| 600 |
+
|
| 601 |
+
def stage_cheap(tasks: list[str], n_eval: int, config_filter: set[str] | None):
|
| 602 |
+
model, tok, model_meta = load_model_and_meta()
|
| 603 |
+
dev = model.device
|
| 604 |
+
if str(dev) == "meta":
|
| 605 |
+
dev = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
| 606 |
+
|
| 607 |
+
vqkv_fitted, cb_meta = load_vqkv_fitted(dev)
|
| 608 |
+
turbo_fitted, turbo_cfg_lookup = load_turbo_fitted(dev)
|
| 609 |
+
meta = cb_meta if cb_meta else model_meta
|
| 610 |
+
hd = meta["head_dim"]
|
| 611 |
+
|
| 612 |
+
bpe_lookup: dict[str, float] = {}
|
| 613 |
+
for name, per_layer in vqkv_fitted.items():
|
| 614 |
+
q0 = next(iter(per_layer.values()))
|
| 615 |
+
bpe_lookup[name] = round(q0.bits_per_element(hd), 4)
|
| 616 |
+
for name in turbo_cfg_lookup:
|
| 617 |
+
if name in turbo_fitted:
|
| 618 |
+
b, use_qjl = turbo_cfg_lookup[name]
|
| 619 |
+
bpe_lookup[name] = round(b + (1 if use_qjl else 0) + 16.0 / hd, 4)
|
| 620 |
+
|
| 621 |
+
if config_filter:
|
| 622 |
+
bpe_lookup = {k: v for k, v in bpe_lookup.items() if k in config_filter}
|
| 623 |
+
print(f"[cheap] config filter: {len(bpe_lookup)} configs")
|
| 624 |
+
|
| 625 |
+
all_trace_rows: list[dict] = []
|
| 626 |
+
all_summary: list[dict] = []
|
| 627 |
+
|
| 628 |
+
for task_name in tasks:
|
| 629 |
+
task_cfg = TASK_CONFIGS[task_name]
|
| 630 |
+
# trec few-shot prompts can be shorter than agentic traces
|
| 631 |
+
min_len = 512 if task_name == "trec" else 2048
|
| 632 |
+
rows = run_cheap_task(
|
| 633 |
+
model, tok, meta, task_name, task_cfg,
|
| 634 |
+
vqkv_fitted, turbo_fitted, turbo_cfg_lookup, bpe_lookup,
|
| 635 |
+
config_filter, n_eval, min_len=min_len,
|
| 636 |
+
)
|
| 637 |
+
all_trace_rows.extend(rows)
|
| 638 |
+
summary = _aggregate_cheap_rows(rows, bpe_lookup)
|
| 639 |
+
for row in summary:
|
| 640 |
+
row["task"] = task_name
|
| 641 |
+
all_summary.extend(summary)
|
| 642 |
+
_print_cheap_table(task_name, summary)
|
| 643 |
+
|
| 644 |
+
out_path = os.path.join(ARTIFACT_DIR, "longbench_cheap_metrics.json")
|
| 645 |
+
with open(out_path, "w") as fh:
|
| 646 |
+
json.dump(all_summary, fh, indent=2)
|
| 647 |
+
print(f"\n[cheap] saved -> {out_path} ({len(all_summary)} task×config rows)")
|
| 648 |
+
|
| 649 |
+
|
| 650 |
+
# ============================================================================
|
| 651 |
+
# Per-task generation + scoring (--stage generate)
|
| 652 |
+
# ============================================================================
|
| 653 |
+
def run_task(model, tok, task_name: str, task_cfg: dict,
|
| 654 |
+
configs: list, n_eval: int) -> dict[str, float]:
|
| 655 |
+
"""Run every config on n_eval examples. Returns {config_name: mean score}."""
|
| 656 |
+
ds = _load_longbench(task_name)
|
| 657 |
+
subset = ds.select(range(min(n_eval, len(ds))))
|
| 658 |
+
print(f"\n[{task_name}] {len(subset)} examples, "
|
| 659 |
+
f"max_len={task_cfg['max_len']}, max_new={task_cfg['max_new_tokens']}")
|
| 660 |
+
|
| 661 |
+
score_fn = task_cfg.get("score_fn", qa_f1_score)
|
| 662 |
+
max_new = task_cfg["max_new_tokens"]
|
| 663 |
+
max_len = task_cfg["max_len"]
|
| 664 |
+
scores: dict[str, list[float]] = {name: [] for name, _, _ in configs}
|
| 665 |
+
|
| 666 |
+
for ex in tqdm(subset, desc=task_name):
|
| 667 |
+
prompt = prompt_text(task_cfg, ex)
|
| 668 |
+
refs = (ex["answers"] if isinstance(ex["answers"], list)
|
| 669 |
+
else [ex["answers"]])
|
| 670 |
+
# all_classes is present in TREC examples; ignored by other scorers
|
| 671 |
+
all_classes = ex.get("all_classes") or None
|
| 672 |
+
ids = tok(prompt, return_tensors="pt", truncation=True,
|
| 673 |
+
max_length=max_len).to(model.device)
|
| 674 |
+
|
| 675 |
+
for name, _bpe, cache_cls in configs:
|
| 676 |
+
cache = cache_cls() if cache_cls is not None else None
|
| 677 |
+
with torch.no_grad():
|
| 678 |
+
out = model.generate(
|
| 679 |
+
**ids,
|
| 680 |
+
max_new_tokens=max_new,
|
| 681 |
+
do_sample=False,
|
| 682 |
+
past_key_values=cache,
|
| 683 |
+
use_cache=True,
|
| 684 |
+
)
|
| 685 |
+
pred = tok.decode(out[0, ids["input_ids"].shape[1]:],
|
| 686 |
+
skip_special_tokens=True).strip()
|
| 687 |
+
scores[name].append(score_fn(pred, refs, all_classes=all_classes))
|
| 688 |
+
|
| 689 |
+
return {name: sum(vs) / len(vs) for name, vs in scores.items() if vs}
|
| 690 |
+
|
| 691 |
+
|
| 692 |
+
# ============================================================================
|
| 693 |
+
# Main
|
| 694 |
+
# ============================================================================
|
| 695 |
+
def stage_generate(tasks: list[str], n_eval: int, config_filter: list[str] | None):
|
| 696 |
+
model, tok, meta = load_model_and_meta()
|
| 697 |
+
device = model.device
|
| 698 |
+
if str(device) == "meta":
|
| 699 |
+
device = torch.device("cuda:0")
|
| 700 |
+
|
| 701 |
+
configs = build_all_configs(meta, device, only=config_filter)
|
| 702 |
+
print(f"\n[generate] {len(configs)} configs × {len(tasks)} tasks × "
|
| 703 |
+
f"{n_eval} examples each")
|
| 704 |
+
|
| 705 |
+
all_results: dict[str, dict[str, float]] = {}
|
| 706 |
+
for task_name in tasks:
|
| 707 |
+
all_results[task_name] = run_task(
|
| 708 |
+
model, tok, task_name, TASK_CONFIGS[task_name], configs, n_eval)
|
| 709 |
+
|
| 710 |
+
records = []
|
| 711 |
+
print(f"\n{'task':<22} {'config':<32} {'bpe':>5} {'score':>6} {'Δ':>8}")
|
| 712 |
+
print("-" * 78)
|
| 713 |
+
for task_name in tasks:
|
| 714 |
+
task_scores = all_results[task_name]
|
| 715 |
+
fp16_score = task_scores.get("fp16")
|
| 716 |
+
for name, bpe, _ in configs:
|
| 717 |
+
score = task_scores.get(name)
|
| 718 |
+
delta = (None if (score is None or fp16_score is None)
|
| 719 |
+
else round(score - fp16_score, 4))
|
| 720 |
+
delta_str = f"{delta:+.4f}" if delta is not None else " —"
|
| 721 |
+
print(f"{task_name:<22} {name:<32} {bpe:5.2f} "
|
| 722 |
+
f"{score:6.4f} {delta_str}")
|
| 723 |
+
records.append({
|
| 724 |
+
"task": task_name,
|
| 725 |
+
"config": name,
|
| 726 |
+
"bpe": bpe,
|
| 727 |
+
"score": round(score, 4) if score is not None else None,
|
| 728 |
+
"delta": delta,
|
| 729 |
+
})
|
| 730 |
+
print()
|
| 731 |
+
|
| 732 |
+
out_path = os.path.join(ARTIFACT_DIR, "longbench_results.json")
|
| 733 |
+
with open(out_path, "w") as fh:
|
| 734 |
+
json.dump(records, fh, indent=2)
|
| 735 |
+
print(f"[generate] saved -> {out_path}")
|
| 736 |
+
|
| 737 |
+
|
| 738 |
+
def main():
|
| 739 |
+
ap = argparse.ArgumentParser(
|
| 740 |
+
description=__doc__,
|
| 741 |
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
| 742 |
+
ap.add_argument("--stage", choices=["cheap", "generate"], default="cheap",
|
| 743 |
+
help="cheap: KV proxy metrics (default); generate: task scores")
|
| 744 |
+
ap.add_argument("--tasks", nargs="+", default=list(TASK_CONFIGS),
|
| 745 |
+
choices=list(TASK_CONFIGS),
|
| 746 |
+
help="LongBench tasks to run (default: all 6)")
|
| 747 |
+
ap.add_argument("--n_eval", type=int, default=50,
|
| 748 |
+
help="Examples per task (default: 50)")
|
| 749 |
+
ap.add_argument("--configs", nargs="+", default=None,
|
| 750 |
+
help="Config names to include (default: all). "
|
| 751 |
+
"E.g. --configs productvq-32x256-2b turbo-mse-2b")
|
| 752 |
+
args = ap.parse_args()
|
| 753 |
+
|
| 754 |
+
config_filter = set(args.configs) if args.configs else None
|
| 755 |
+
|
| 756 |
+
if args.stage == "cheap":
|
| 757 |
+
stage_cheap(args.tasks, args.n_eval, config_filter)
|
| 758 |
+
else:
|
| 759 |
+
stage_generate(args.tasks, args.n_eval, args.configs)
|
| 760 |
+
|
| 761 |
+
|
| 762 |
+
if __name__ == "__main__":
|
| 763 |
+
main()
|
turbo_benchmark.py
ADDED
|
@@ -0,0 +1,531 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
turbo_benchmark.py — faithful TurboQuant baseline (Zandieh et al., ICLR 2026).
|
| 3 |
+
|
| 4 |
+
Haar rotation + per-coordinate Lloyd-Max codebooks + optional QJL residual for
|
| 5 |
+
unbiased inner-product estimation. Separate from benchmark.py; writes
|
| 6 |
+
artifacts/turbo_codebooks.pt.
|
| 7 |
+
|
| 8 |
+
Usage:
|
| 9 |
+
python benchmark.py --stage dump # or use existing calib_caches.pt
|
| 10 |
+
python turbo_benchmark.py --stage fit
|
| 11 |
+
python turbo_benchmark.py --stage cheap
|
| 12 |
+
"""
|
| 13 |
+
|
| 14 |
+
from __future__ import annotations
|
| 15 |
+
|
| 16 |
+
import argparse
|
| 17 |
+
import json
|
| 18 |
+
import os
|
| 19 |
+
import time
|
| 20 |
+
|
| 21 |
+
import torch
|
| 22 |
+
|
| 23 |
+
from vqkv.metrics import (key_cosine, cache_mse, inner_product_distortion,
|
| 24 |
+
attention_output, attn_output_cosine, attn_output_error)
|
| 25 |
+
|
| 26 |
+
ARTIFACT_DIR = os.environ.get("VQKV_ARTIFACTS", "./artifacts")
|
| 27 |
+
MODEL_ID = os.environ.get("LAGUNA_ID", "poolside/Laguna-XS.2")
|
| 28 |
+
CALIB_DATASET = os.environ.get("CALIB_DATASET", "SWE-bench/SWE-smith-trajectories")
|
| 29 |
+
CALIB_SPLIT = os.environ.get("CALIB_SPLIT", "tool")
|
| 30 |
+
EVAL_SOURCE = os.environ.get("EVAL_SOURCE", "swesmith")
|
| 31 |
+
|
| 32 |
+
_HOTPOTQA_PROMPT = (
|
| 33 |
+
"Answer the question based on the given passages. "
|
| 34 |
+
"Only give me the answer and do not output any other words.\n\n"
|
| 35 |
+
"The following are given passages.\n{context}\n\n"
|
| 36 |
+
"Answer the question based on the given passages. "
|
| 37 |
+
"Only give me the answer and do not output any other words.\n\n"
|
| 38 |
+
"Question: {input}\nAnswer:"
|
| 39 |
+
)
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
# ============================================================================
|
| 43 |
+
# Lloyd-Max 1-D scalar quantizer (the MSE-optimal levels for a given sample)
|
| 44 |
+
# ============================================================================
|
| 45 |
+
def lloyd_max_1d(samples: torch.Tensor, n_levels: int, iters: int = 30,
|
| 46 |
+
seed: int = 0) -> torch.Tensor:
|
| 47 |
+
"""Optimal scalar (Lloyd-Max) codebook for a 1-D distribution given samples.
|
| 48 |
+
|
| 49 |
+
samples: (M,) 1-D values drawn from the (concentrated) coordinate dist.
|
| 50 |
+
returns: (n_levels,) sorted reconstruction levels.
|
| 51 |
+
|
| 52 |
+
This is 1-D k-means; for the rotated unit-norm coordinates the distribution
|
| 53 |
+
is the same across coordinates in high dim, so one shared codebook per
|
| 54 |
+
bit-width suffices (matching the paper's single precomputed Beta codebook).
|
| 55 |
+
"""
|
| 56 |
+
s = samples.flatten()
|
| 57 |
+
s = s[torch.isfinite(s)]
|
| 58 |
+
# init levels at quantiles so empty bins are rare
|
| 59 |
+
qs = torch.linspace(0.0, 1.0, n_levels + 2, device=s.device)[1:-1]
|
| 60 |
+
levels = torch.quantile(s, qs)
|
| 61 |
+
for _ in range(iters):
|
| 62 |
+
# assign each sample to nearest level
|
| 63 |
+
idx = torch.bucketize(s, (levels[1:] + levels[:-1]) / 2)
|
| 64 |
+
new = levels.clone()
|
| 65 |
+
for j in range(n_levels):
|
| 66 |
+
m = idx == j
|
| 67 |
+
if m.any():
|
| 68 |
+
new[j] = s[m].mean()
|
| 69 |
+
shift = (new - levels).abs().max()
|
| 70 |
+
levels = new
|
| 71 |
+
if shift < 1e-6:
|
| 72 |
+
break
|
| 73 |
+
return torch.sort(levels).values
|
| 74 |
+
|
| 75 |
+
|
| 76 |
+
def _haar_rotation(d: int, seed: int = 0, device=None) -> torch.Tensor:
|
| 77 |
+
g = torch.Generator().manual_seed(seed)
|
| 78 |
+
a = torch.randn(d, d, generator=g)
|
| 79 |
+
q, r = torch.linalg.qr(a)
|
| 80 |
+
# sign-correct so Q is Haar-distributed (QR sign ambiguity)
|
| 81 |
+
q = q * torch.sign(torch.diag(r)).unsqueeze(0)
|
| 82 |
+
if device is not None:
|
| 83 |
+
q = q.to(device)
|
| 84 |
+
return q
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
# ============================================================================
|
| 88 |
+
# TurboQuant-MSE : rotation -> per-coordinate Lloyd-Max -> norm rescale
|
| 89 |
+
# ============================================================================
|
| 90 |
+
class TurboQuantMSE:
|
| 91 |
+
def __init__(self, nbits: int = 3, seed: int = 0):
|
| 92 |
+
self.nbits = nbits
|
| 93 |
+
self.seed = seed
|
| 94 |
+
self._rot = None # (d, d)
|
| 95 |
+
self._levels = None # (K,) shared Lloyd-Max levels
|
| 96 |
+
|
| 97 |
+
def fit(self, calib: torch.Tensor):
|
| 98 |
+
"""calib: (N, d). Fit rotation + shared per-coordinate level set."""
|
| 99 |
+
d = calib.shape[-1]
|
| 100 |
+
self._rot = _haar_rotation(d, self.seed, calib.device)
|
| 101 |
+
xn = calib / calib.norm(dim=-1, keepdim=True).clamp_min(1e-8)
|
| 102 |
+
r = xn @ self._rot # (N, d) rotated unit-norm coords
|
| 103 |
+
# all coordinates share the same concentrated dist -> pool them
|
| 104 |
+
pool = r.flatten()
|
| 105 |
+
if pool.numel() > 2_000_000: # cap for speed
|
| 106 |
+
pool = pool[torch.randperm(pool.numel(), device=pool.device)[:2_000_000]]
|
| 107 |
+
self._levels = lloyd_max_1d(pool, 1 << self.nbits)
|
| 108 |
+
return self
|
| 109 |
+
|
| 110 |
+
def _quantize(self, x):
|
| 111 |
+
norms = x.norm(dim=-1, keepdim=True).clamp_min(1e-8)
|
| 112 |
+
xn = x / norms
|
| 113 |
+
y = xn @ self._rot
|
| 114 |
+
edges = (self._levels[1:] + self._levels[:-1]) / 2
|
| 115 |
+
idx = torch.bucketize(y, edges).clamp(0, self._levels.numel() - 1)
|
| 116 |
+
return idx, norms
|
| 117 |
+
|
| 118 |
+
def roundtrip(self, x):
|
| 119 |
+
idx, norms = self._quantize(x)
|
| 120 |
+
y_hat = self._levels[idx]
|
| 121 |
+
x_hat = y_hat @ self._rot.T
|
| 122 |
+
return x_hat * norms
|
| 123 |
+
|
| 124 |
+
def to(self, device):
|
| 125 |
+
self._rot = self._rot.to(device)
|
| 126 |
+
self._levels = self._levels.to(device)
|
| 127 |
+
return self
|
| 128 |
+
|
| 129 |
+
|
| 130 |
+
# ============================================================================
|
| 131 |
+
# QJL inner-product estimator (faithful, ASYMMETRIC) -- NOT a reconstruction.
|
| 132 |
+
#
|
| 133 |
+
# Important: QJL does not reconstruct a vector. It estimates <q, k> directly,
|
| 134 |
+
# with the query JL-transformed but UNQUANTIZED and only the key residual
|
| 135 |
+
# sign-quantized (the asymmetric estimator of Zandieh et al.). It therefore
|
| 136 |
+
# cannot be expressed as roundtrip(x)->x_hat without reintroducing the very
|
| 137 |
+
# bias it removes. We expose it as a standalone estimator used only in the
|
| 138 |
+
# inner-product-bias evaluation, not in the cosine/MSE reconstruction metrics.
|
| 139 |
+
#
|
| 140 |
+
# <q, k> ~= <q, k_hat_mse> + (sqrt(pi/2) * ||r|| / m) * sum_i (g_i . q) * sign(g_i . r)
|
| 141 |
+
#
|
| 142 |
+
# where r = k_n - k_hat_mse_n is the residual in unit-norm space, g_i are the
|
| 143 |
+
# rows of a gaussian JL matrix, and ||r|| is the stored residual norm.
|
| 144 |
+
# ============================================================================
|
| 145 |
+
class QJLResidualIP:
|
| 146 |
+
def __init__(self, mse: "TurboQuantMSE", qjl_rows: int = None, seed: int = 0):
|
| 147 |
+
self.mse = mse
|
| 148 |
+
self.seed = seed
|
| 149 |
+
self.qjl_rows = qjl_rows
|
| 150 |
+
self._G = None
|
| 151 |
+
|
| 152 |
+
def fit(self, calib: torch.Tensor):
|
| 153 |
+
d = calib.shape[-1]
|
| 154 |
+
m = self.qjl_rows or d
|
| 155 |
+
g = torch.Generator().manual_seed(self.seed + 7)
|
| 156 |
+
self._G = torch.randn(m, d, generator=g).to(calib.device)
|
| 157 |
+
return self
|
| 158 |
+
|
| 159 |
+
def estimate_ip(self, q, k):
|
| 160 |
+
"""Unbiased estimate of rowwise <q, k> using MSE stage + QJL residual.
|
| 161 |
+
q, k: (N, d). Returns (N,) inner-product estimates."""
|
| 162 |
+
m = self._G.shape[0]
|
| 163 |
+
# stage-1 reconstruction in unit-norm space
|
| 164 |
+
knorm = k.norm(dim=-1, keepdim=True).clamp_min(1e-8)
|
| 165 |
+
kn = k / knorm
|
| 166 |
+
idx, _ = self.mse._quantize(k)
|
| 167 |
+
kn_hat = (self.mse._levels[idx]) @ self.mse._rot.T
|
| 168 |
+
score1 = (q * (kn_hat * knorm)).sum(-1) # <q, k_hat_mse>
|
| 169 |
+
# stage-2 residual correction (asymmetric: q unquantized, residual signed)
|
| 170 |
+
r = (kn - kn_hat)
|
| 171 |
+
rnorm = r.norm(dim=-1, keepdim=True).clamp_min(1e-12)
|
| 172 |
+
rn = r / rnorm
|
| 173 |
+
qg = q @ self._G.T # (N, m) query JL, unquantized
|
| 174 |
+
rs = torch.sign(rn @ self._G.T) # (N, m) residual sign bits
|
| 175 |
+
score2 = ((torch.pi / 2) ** 0.5 / m) * (qg * rs).sum(-1) * rnorm.squeeze(-1) * knorm.squeeze(-1)
|
| 176 |
+
return score1 + score2
|
| 177 |
+
|
| 178 |
+
def to(self, device):
|
| 179 |
+
self.mse.to(device)
|
| 180 |
+
self._G = self._G.to(device)
|
| 181 |
+
return self
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
# ============================================================================
|
| 185 |
+
# Config table. Each TurboQuant variant uses TurboQuant-MSE for RECONSTRUCTION
|
| 186 |
+
# (the metric that feeds cosine/MSE). For keys we ALSO build a QJL inner-product
|
| 187 |
+
# estimator (the faithful Prod second stage), reported only in the ip_bias
|
| 188 |
+
# column since QJL is an IP estimator, not a reconstruction.
|
| 189 |
+
#
|
| 190 |
+
# `qjl=True` marks variants that add the QJL key estimator. 1-bit is included
|
| 191 |
+
# as the aggressive floor; at 1 bit the MSE stage is effectively sign, so the
|
| 192 |
+
# QJL residual carries most of the IP fidelity there.
|
| 193 |
+
# ============================================================================
|
| 194 |
+
def turbo_configs(bits_list):
|
| 195 |
+
cfgs = []
|
| 196 |
+
for b in bits_list:
|
| 197 |
+
cfgs.append((f"turbo-mse-{b}b", b, False))
|
| 198 |
+
cfgs.append((f"turbo-prod-{b}b (K:+qjl)", b, True))
|
| 199 |
+
return cfgs
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
# ============================================================================
|
| 203 |
+
# STAGE: fit -- per-layer codebooks from calib_caches.pt
|
| 204 |
+
# ============================================================================
|
| 205 |
+
def stage_fit(bits_list):
|
| 206 |
+
path = os.path.join(ARTIFACT_DIR, "calib_caches.pt")
|
| 207 |
+
if not os.path.exists(path):
|
| 208 |
+
raise FileNotFoundError(
|
| 209 |
+
f"{path} not found. Run `python benchmark.py --stage dump` first "
|
| 210 |
+
f"to produce the calibration caches this script reuses.")
|
| 211 |
+
blob = torch.load(path, weights_only=False)
|
| 212 |
+
calib, meta = blob["calib"], blob["meta"]
|
| 213 |
+
hd = meta["head_dim"]
|
| 214 |
+
dev = "cuda" if torch.cuda.is_available() else "cpu"
|
| 215 |
+
|
| 216 |
+
fitted = {}
|
| 217 |
+
for name, b, use_qjl in turbo_configs(bits_list):
|
| 218 |
+
per_layer = {}
|
| 219 |
+
t0 = time.time()
|
| 220 |
+
for i, c in calib.items():
|
| 221 |
+
kf = c["k"].reshape(-1, hd)[:200_000].to(dev)
|
| 222 |
+
vf = c["v"].reshape(-1, hd)[:200_000].to(dev)
|
| 223 |
+
kq = TurboQuantMSE(nbits=b).fit(kf)
|
| 224 |
+
vq = TurboQuantMSE(nbits=b).fit(vf)
|
| 225 |
+
qjl = QJLResidualIP(kq).fit(kf) if use_qjl else None
|
| 226 |
+
per_layer[i] = {"kq": kq, "vq": vq, "qjl": qjl}
|
| 227 |
+
fitted[name] = per_layer
|
| 228 |
+
print(f"[fit] {name}: {len(per_layer)} layer-codebooks in {time.time()-t0:.1f}s")
|
| 229 |
+
torch.save({"fitted": fitted, "meta": meta, "bits": bits_list},
|
| 230 |
+
os.path.join(ARTIFACT_DIR, "turbo_codebooks.pt"))
|
| 231 |
+
print(f"[fit] saved -> {ARTIFACT_DIR}/turbo_codebooks.pt")
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
# ============================================================================
|
| 235 |
+
# Trace flattening -- minimal copy (kept independent of benchmark.py)
|
| 236 |
+
# ============================================================================
|
| 237 |
+
def flatten_trace(example, tok) -> str:
|
| 238 |
+
raw = (example.get("messages") or example.get("trajectory")
|
| 239 |
+
or example.get("conversations"))
|
| 240 |
+
if raw is None:
|
| 241 |
+
return json.dumps(example)[:200_000]
|
| 242 |
+
if isinstance(raw, str):
|
| 243 |
+
try:
|
| 244 |
+
raw = json.loads(raw)
|
| 245 |
+
except json.JSONDecodeError:
|
| 246 |
+
return raw[:200_000]
|
| 247 |
+
norm = []
|
| 248 |
+
for m in raw:
|
| 249 |
+
role = m.get("role") or m.get("from") or "user"
|
| 250 |
+
content = m.get("content") or m.get("value") or ""
|
| 251 |
+
if isinstance(content, list):
|
| 252 |
+
content = "\n".join(
|
| 253 |
+
it.get("text", str(it)) if isinstance(it, dict) else str(it)
|
| 254 |
+
for it in content)
|
| 255 |
+
tc = m.get("tool_calls")
|
| 256 |
+
if tc:
|
| 257 |
+
tct = json.dumps(tc, ensure_ascii=False)
|
| 258 |
+
content = (content + "\n" + tct).strip() if content else tct
|
| 259 |
+
role = {"human": "user", "gpt": "assistant", "tool": "user"}.get(role, role)
|
| 260 |
+
if not content.strip():
|
| 261 |
+
continue
|
| 262 |
+
norm.append({"role": role, "content": content})
|
| 263 |
+
merged = []
|
| 264 |
+
for m in norm:
|
| 265 |
+
if merged and merged[-1]["role"] == m["role"]:
|
| 266 |
+
merged[-1]["content"] += "\n\n" + m["content"]
|
| 267 |
+
else:
|
| 268 |
+
merged.append(dict(m))
|
| 269 |
+
try:
|
| 270 |
+
return tok.apply_chat_template(merged, tokenize=False,
|
| 271 |
+
add_generation_prompt=False)
|
| 272 |
+
except Exception:
|
| 273 |
+
return "\n\n".join(f"{m['role']}: {m['content']}" for m in merged)
|
| 274 |
+
|
| 275 |
+
|
| 276 |
+
def flatten_longbench(example) -> str:
|
| 277 |
+
"""Format a LongBench hotpotqa example (context + input) as a plain string."""
|
| 278 |
+
return _HOTPOTQA_PROMPT.format(
|
| 279 |
+
context=example["context"], input=example["input"])
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
def _load_longbench_hotpotqa():
|
| 283 |
+
"""Load THUDM/LongBench hotpotqa, bypassing the deprecated dataset script."""
|
| 284 |
+
from datasets import load_dataset as _ld
|
| 285 |
+
for fname in ("hotpotqa_e.jsonl", "hotpotqa.jsonl"):
|
| 286 |
+
try:
|
| 287 |
+
return _ld(
|
| 288 |
+
"json",
|
| 289 |
+
data_files=f"hf://datasets/THUDM/LongBench/data/{fname}",
|
| 290 |
+
split="train",
|
| 291 |
+
)
|
| 292 |
+
except Exception:
|
| 293 |
+
continue
|
| 294 |
+
return _ld("THUDM/LongBench", name="hotpotqa", split="test")
|
| 295 |
+
|
| 296 |
+
|
| 297 |
+
def _load_cheap_eval_dataset(n_eval: int, eval_source: str, tok):
|
| 298 |
+
"""Return (dataset_slice, get_text, max_len, label) for stage_cheap."""
|
| 299 |
+
from datasets import load_dataset
|
| 300 |
+
|
| 301 |
+
if eval_source == "longbench-hotpotqa":
|
| 302 |
+
ds = _load_longbench_hotpotqa()
|
| 303 |
+
# First n_eval rows — disjoint from dump calib (last n_calib rows).
|
| 304 |
+
end = min(n_eval, len(ds))
|
| 305 |
+
ds = ds.select(range(0, end))
|
| 306 |
+
label = f"LongBench hotpotqa (rows 0–{end - 1})"
|
| 307 |
+
return ds, flatten_longbench, 32768, label
|
| 308 |
+
|
| 309 |
+
ds = load_dataset(CALIB_DATASET, split=CALIB_SPLIT)
|
| 310 |
+
start = 500 # held-out offset (matches benchmark.py --stage cheap)
|
| 311 |
+
end = min(start + n_eval, len(ds))
|
| 312 |
+
ds = ds.select(range(start, end))
|
| 313 |
+
label = f"{CALIB_DATASET} split={CALIB_SPLIT} (rows {start}–{end - 1})"
|
| 314 |
+
return ds, lambda ex: flatten_trace(ex, tok), 16384, label
|
| 315 |
+
|
| 316 |
+
|
| 317 |
+
def load_model_and_meta():
|
| 318 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer
|
| 319 |
+
tok = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
|
| 320 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 321 |
+
MODEL_ID, dtype=torch.bfloat16, device_map="cuda", trust_remote_code=True)
|
| 322 |
+
model.eval()
|
| 323 |
+
cfg = model.config
|
| 324 |
+
full = [i for i, t in enumerate(cfg.layer_types) if t == "full_attention"]
|
| 325 |
+
meta = {"full_layers": full, "n_kv_heads": cfg.num_key_value_heads,
|
| 326 |
+
"n_q_heads": cfg.num_attention_heads,
|
| 327 |
+
"head_dim": cfg.head_dim, "n_layers": cfg.num_hidden_layers}
|
| 328 |
+
print(f"[meta] full-attention layers ({len(full)}): {full}")
|
| 329 |
+
return model, tok, meta
|
| 330 |
+
|
| 331 |
+
|
| 332 |
+
# ============================================================================
|
| 333 |
+
# STAGE: cheap -- full metric set on held-out traces (mirrors benchmark.py)
|
| 334 |
+
# ============================================================================
|
| 335 |
+
def stage_cheap(n_eval=64, max_len=None, eval_source: str | None = None,
|
| 336 |
+
min_len=2048):
|
| 337 |
+
import collections
|
| 338 |
+
from tqdm import tqdm
|
| 339 |
+
from transformers.cache_utils import DynamicCache
|
| 340 |
+
|
| 341 |
+
eval_source = eval_source or EVAL_SOURCE
|
| 342 |
+
blob = torch.load(os.path.join(ARTIFACT_DIR, "turbo_codebooks.pt"),
|
| 343 |
+
weights_only=False)
|
| 344 |
+
fitted, meta = blob["fitted"], blob["meta"]
|
| 345 |
+
full = meta["full_layers"]
|
| 346 |
+
hd = meta["head_dim"]
|
| 347 |
+
n_q = meta.get("n_q_heads", 48) # fallback for turbo_codebooks.pt without this field
|
| 348 |
+
|
| 349 |
+
# Window for O(T²) attention metrics (matches benchmark.py)
|
| 350 |
+
ATTN_WIN = 512
|
| 351 |
+
|
| 352 |
+
# Lookup: config name -> (nbits, use_qjl) for bits_per_elt reporting
|
| 353 |
+
cfg_lookup = {name: (b, use_qjl) for name, b, use_qjl in turbo_configs(blob["bits"])}
|
| 354 |
+
|
| 355 |
+
model, tok, _ = load_model_and_meta()
|
| 356 |
+
dev = model.device
|
| 357 |
+
if str(dev) == "meta":
|
| 358 |
+
dev = torch.device("cuda:0" if torch.cuda.is_available() else "cpu")
|
| 359 |
+
|
| 360 |
+
ds, get_text, default_max_len, source_label = _load_cheap_eval_dataset(
|
| 361 |
+
n_eval, eval_source, tok)
|
| 362 |
+
if max_len is None:
|
| 363 |
+
max_len = default_max_len
|
| 364 |
+
print(f"[turbo-cheap] eval_source={eval_source} {source_label} "
|
| 365 |
+
f"rows={len(ds)} max_len={max_len}")
|
| 366 |
+
|
| 367 |
+
for per_layer in fitted.values():
|
| 368 |
+
for entry in per_layer.values():
|
| 369 |
+
entry["kq"].to(dev); entry["vq"].to(dev)
|
| 370 |
+
if entry["qjl"] is not None:
|
| 371 |
+
entry["qjl"].to(dev)
|
| 372 |
+
|
| 373 |
+
class EvalDump(DynamicCache):
|
| 374 |
+
def __init__(self):
|
| 375 |
+
super().__init__(); self.d = {i: {} for i in full}
|
| 376 |
+
def update(self, ks, vs, li, ck=None):
|
| 377 |
+
if li in set(full):
|
| 378 |
+
self.d[li]["k"] = ks.detach()[0].permute(1, 0, 2).float()
|
| 379 |
+
self.d[li]["v"] = vs.detach()[0].permute(1, 0, 2).float()
|
| 380 |
+
return super().update(ks, vs, li, ck)
|
| 381 |
+
|
| 382 |
+
trace_rows = []
|
| 383 |
+
n_used = 0
|
| 384 |
+
|
| 385 |
+
for ex in tqdm(ds, desc=f"turbo-cheap/{eval_source}"):
|
| 386 |
+
text = get_text(ex)
|
| 387 |
+
ids = tok(text, return_tensors="pt", truncation=True,
|
| 388 |
+
max_length=max_len).to(dev)
|
| 389 |
+
if ids["input_ids"].shape[1] < min_len:
|
| 390 |
+
continue
|
| 391 |
+
cache = EvalDump()
|
| 392 |
+
with torch.no_grad():
|
| 393 |
+
model.model(**ids, past_key_values=cache, use_cache=True)
|
| 394 |
+
n_used += 1
|
| 395 |
+
|
| 396 |
+
# Synthetic Q: generated once per (trace, layer), reused across configs.
|
| 397 |
+
# Using synthetic Q for the QJL estimator is more faithful than the
|
| 398 |
+
# previous keys-as-proxy approach: q0[qi] are proper query vectors,
|
| 399 |
+
# kr0[ki] are the keys being estimated.
|
| 400 |
+
synth_q = {}
|
| 401 |
+
for i in full:
|
| 402 |
+
s = cache.d[i]["k"].shape[0]
|
| 403 |
+
win = min(s, ATTN_WIN)
|
| 404 |
+
q_rand = torch.randn(win, n_q, hd, device=dev)
|
| 405 |
+
synth_q[i] = q_rand / q_rand.norm(dim=-1, keepdim=True).clamp_min(1e-8)
|
| 406 |
+
|
| 407 |
+
for name, per_layer in fitted.items():
|
| 408 |
+
acc = collections.defaultdict(float)
|
| 409 |
+
nL = 0
|
| 410 |
+
for i in full:
|
| 411 |
+
k = cache.d[i]["k"]; v = cache.d[i]["v"] # (s, h, d)
|
| 412 |
+
s, h, d = k.shape
|
| 413 |
+
e = per_layer[i]
|
| 414 |
+
k_hat = e["kq"].roundtrip(k.reshape(-1, d)).reshape(s, h, d)
|
| 415 |
+
v_hat = e["vq"].roundtrip(v.reshape(-1, d)).reshape(s, h, d)
|
| 416 |
+
|
| 417 |
+
acc["key_cos"] += key_cosine(k, k_hat)
|
| 418 |
+
acc["val_cos"] += key_cosine(v, v_hat)
|
| 419 |
+
acc["key_mse"] += cache_mse(k, k_hat)
|
| 420 |
+
acc["val_mse"] += cache_mse(v, v_hat)
|
| 421 |
+
|
| 422 |
+
# Windowed attention/IP metrics on last ATTN_WIN tokens
|
| 423 |
+
win = min(s, ATTN_WIN)
|
| 424 |
+
kw, kw_hat = k[-win:], k_hat[-win:]
|
| 425 |
+
vw, vw_hat = v[-win:], v_hat[-win:]
|
| 426 |
+
q_syn = synth_q[i] # (win, n_q, d)
|
| 427 |
+
|
| 428 |
+
out_ref, _ = attention_output(q_syn, kw, vw, n_q)
|
| 429 |
+
out_hat, _ = attention_output(q_syn, kw_hat, vw_hat, n_q)
|
| 430 |
+
acc["attn_cos"] += attn_output_cosine(out_ref, out_hat)
|
| 431 |
+
acc["attn_output_error"] += attn_output_error(out_ref, out_hat)
|
| 432 |
+
|
| 433 |
+
# IP metrics. For Prod configs, use the faithful QJL asymmetric
|
| 434 |
+
# estimator (designed for unbiased <q,k> estimation). For MSE
|
| 435 |
+
# configs, use the plain reconstructed inner product.
|
| 436 |
+
q0 = q_syn[:, 0, :] # (win, d) — head 0 of synthetic Q
|
| 437 |
+
kr0 = kw[:, 0, :] # (win, d) — head 0 of reference K
|
| 438 |
+
kh0 = kw_hat[:, 0, :] # (win, d) — head 0 of reconstructed K
|
| 439 |
+
qi = torch.randint(0, win, (4096,), device=dev)
|
| 440 |
+
ki = torch.randint(0, win, (4096,), device=dev)
|
| 441 |
+
ip_ref = (q0[qi] * kr0[ki]).sum(-1)
|
| 442 |
+
if e["qjl"] is not None:
|
| 443 |
+
ip_hat = e["qjl"].estimate_ip(q0[qi], kr0[ki])
|
| 444 |
+
else:
|
| 445 |
+
ip_hat = (q0[qi] * kh0[ki]).sum(-1)
|
| 446 |
+
acc["ip_bias"] += (ip_hat - ip_ref).mean().item()
|
| 447 |
+
acc["ip_rel"] += ((ip_hat - ip_ref).abs() /
|
| 448 |
+
ip_ref.abs().clamp_min(1e-6)).mean().item()
|
| 449 |
+
nL += 1
|
| 450 |
+
|
| 451 |
+
trace_rows.append({
|
| 452 |
+
"trace_len": ids["input_ids"].shape[1],
|
| 453 |
+
"config": name,
|
| 454 |
+
"key_cos": acc["key_cos"] / nL,
|
| 455 |
+
"val_cos": acc["val_cos"] / nL,
|
| 456 |
+
"key_mse": acc["key_mse"] / nL,
|
| 457 |
+
"val_mse": acc["val_mse"] / nL,
|
| 458 |
+
"attn_cos": acc["attn_cos"] / nL,
|
| 459 |
+
"attn_output_error": acc["attn_output_error"] / nL,
|
| 460 |
+
"ip_rel": acc["ip_rel"] / nL,
|
| 461 |
+
"ip_bias": acc["ip_bias"] / nL,
|
| 462 |
+
})
|
| 463 |
+
|
| 464 |
+
# Aggregate across traces: one summary row per config
|
| 465 |
+
agg = collections.defaultdict(lambda: collections.defaultdict(list))
|
| 466 |
+
for r in trace_rows:
|
| 467 |
+
for col in ("key_cos", "val_cos", "key_mse", "val_mse",
|
| 468 |
+
"attn_cos", "attn_output_error", "ip_rel", "ip_bias"):
|
| 469 |
+
agg[r["config"]][col].append(r[col])
|
| 470 |
+
|
| 471 |
+
COLS = ("key_cos", "val_cos", "key_mse", "val_mse",
|
| 472 |
+
"attn_cos", "attn_output_error", "ip_rel", "ip_bias")
|
| 473 |
+
|
| 474 |
+
summary = []
|
| 475 |
+
for name in cfg_lookup:
|
| 476 |
+
if name not in agg:
|
| 477 |
+
continue
|
| 478 |
+
b, use_qjl = cfg_lookup[name]
|
| 479 |
+
# bpe: nbits/coord + 1 bit/coord for QJL signs (m=d rows) + fp16 norm
|
| 480 |
+
bpe = b + (1 if use_qjl else 0) + 16.0 / hd
|
| 481 |
+
cols = agg[name]
|
| 482 |
+
n = len(cols["key_cos"])
|
| 483 |
+
row = {"config": name, "bits_per_elt": round(bpe, 4),
|
| 484 |
+
"n_traces": n, "eval_source": eval_source}
|
| 485 |
+
for col in COLS:
|
| 486 |
+
row[col] = round(sum(cols[col]) / n, 5)
|
| 487 |
+
summary.append(row)
|
| 488 |
+
|
| 489 |
+
print(f"\n[turbo-cheap] mean metrics over {n_used} traces ({eval_source}):")
|
| 490 |
+
print(f" {'config':30s} {'bpe':>5} {'key_cos':>8} {'val_cos':>8} "
|
| 491 |
+
f"{'key_mse':>9} {'val_mse':>9} {'attn_cos':>9} {'attn_err':>9} "
|
| 492 |
+
f"{'ip_rel':>8} {'ip_bias':>9}")
|
| 493 |
+
for row in summary:
|
| 494 |
+
print(f" {row['config']:30s} {row['bits_per_elt']:5.2f} "
|
| 495 |
+
f"{row['key_cos']:8.4f} {row['val_cos']:8.4f} "
|
| 496 |
+
f"{row['key_mse']:9.5f} {row['val_mse']:9.5f} "
|
| 497 |
+
f"{row['attn_cos']:9.4f} {row['attn_output_error']:9.4f} "
|
| 498 |
+
f"{row['ip_rel']:8.5f} {row['ip_bias']:9.6f}")
|
| 499 |
+
|
| 500 |
+
out_name = ("turbo_cheap_metrics_hotpotqa.json"
|
| 501 |
+
if eval_source == "longbench-hotpotqa"
|
| 502 |
+
else "turbo_cheap_metrics.json")
|
| 503 |
+
out_path = os.path.join(ARTIFACT_DIR, out_name)
|
| 504 |
+
json.dump(summary, open(out_path, "w"), indent=2)
|
| 505 |
+
print(f"[turbo-cheap] saved -> {out_path}")
|
| 506 |
+
|
| 507 |
+
|
| 508 |
+
def main():
|
| 509 |
+
ap = argparse.ArgumentParser(description=__doc__,
|
| 510 |
+
formatter_class=argparse.RawDescriptionHelpFormatter)
|
| 511 |
+
ap.add_argument("--stage", required=True, choices=["fit", "cheap"])
|
| 512 |
+
ap.add_argument("--bits", type=int, nargs="+", default=[4, 2, 1],
|
| 513 |
+
help="bit-widths to sweep (default: 4 2 1)")
|
| 514 |
+
ap.add_argument("--n_eval", type=int, default=64)
|
| 515 |
+
ap.add_argument(
|
| 516 |
+
"--eval_source", type=str, default=None,
|
| 517 |
+
choices=["swesmith", "longbench-hotpotqa"],
|
| 518 |
+
help="cheap stage: eval corpus (default: EVAL_SOURCE env or swesmith)")
|
| 519 |
+
ap.add_argument("--max_len", type=int, default=None,
|
| 520 |
+
help="cheap stage: max prompt tokens (default: 16384 swesmith, "
|
| 521 |
+
"32768 longbench-hotpotqa)")
|
| 522 |
+
args = ap.parse_args()
|
| 523 |
+
if args.stage == "fit":
|
| 524 |
+
stage_fit(args.bits)
|
| 525 |
+
elif args.stage == "cheap":
|
| 526 |
+
stage_cheap(n_eval=args.n_eval, max_len=args.max_len,
|
| 527 |
+
eval_source=args.eval_source)
|
| 528 |
+
|
| 529 |
+
|
| 530 |
+
if __name__ == "__main__":
|
| 531 |
+
main()
|
vqkv/__init__.py
ADDED
|
File without changes
|
vqkv/compressed_cache.py
ADDED
|
@@ -0,0 +1,245 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
vqkv.compressed_cache — inference-ready KV cache with real memory savings.
|
| 3 |
+
|
| 4 |
+
VQQuantizedCache persists uint8 codebook indices for target layers (not bf16
|
| 5 |
+
reconstructions). Dequantizes transiently per layer on each attention read.
|
| 6 |
+
Without a fused kernel this saves memory but not wall-clock; see memory_footprint().
|
| 7 |
+
"""
|
| 8 |
+
|
| 9 |
+
from __future__ import annotations
|
| 10 |
+
|
| 11 |
+
import math
|
| 12 |
+
|
| 13 |
+
import torch
|
| 14 |
+
|
| 15 |
+
try:
|
| 16 |
+
from vqkv.quantizers import ProductVQKV, ScalarKV, KIVIScalarKV
|
| 17 |
+
except ModuleNotFoundError: # flat layout (files beside this one)
|
| 18 |
+
from quantizers import ProductVQKV, ScalarKV, KIVIScalarKV
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
# ----------------------------------------------------------------------------
|
| 22 |
+
# encode / decode for a *fitted* ProductVQKV (no change to quantizers.py)
|
| 23 |
+
# ----------------------------------------------------------------------------
|
| 24 |
+
# These reach into the already-built batched stacks (cb, mu, sd) that
|
| 25 |
+
# ProductVQKV._ensure_stacked() prepares, so encode(x) then decode(idx) is
|
| 26 |
+
# bit-identical to the existing q.roundtrip_k(x). Promote them to methods on
|
| 27 |
+
# ProductVQKV if you prefer; kept as free functions to leave your file untouched.
|
| 28 |
+
|
| 29 |
+
def _stacked(q: ProductVQKV, which: str):
|
| 30 |
+
q._ensure_stacked()
|
| 31 |
+
return q._k_stacked if which == "k" else q._v_stacked
|
| 32 |
+
|
| 33 |
+
|
| 34 |
+
def pvq_encode(q: ProductVQKV, x: torch.Tensor, which: str = "k") -> torch.Tensor:
|
| 35 |
+
"""x: (N, head_dim) -> idx: (N, n_sub) uint8 (nearest codeword per sub-block)."""
|
| 36 |
+
cb, mu, sd = _stacked(q, which) # cb: (n_sub, K, sub_dim)
|
| 37 |
+
n_sub, K, sub_dim = cb.shape
|
| 38 |
+
if K > 256:
|
| 39 |
+
raise ValueError(f"n_codes={K} > 256 needs >1 byte/index; this path is uint8. "
|
| 40 |
+
f"Use K<=256 (all headline configs do) or add bit-packing.")
|
| 41 |
+
N = x.shape[0]
|
| 42 |
+
c_sq = (cb * cb).sum(-1).unsqueeze(1) # (n_sub, 1, K)
|
| 43 |
+
mu_b = mu.permute(1, 0, 2) if mu is not None else None
|
| 44 |
+
sd_b = sd.permute(1, 0, 2) if sd is not None else None
|
| 45 |
+
chunk = max(1, (256 * 1024 * 1024) // (n_sub * K * 4))
|
| 46 |
+
out = []
|
| 47 |
+
for s0 in range(0, N, chunk):
|
| 48 |
+
xc = x[s0:s0 + chunk]
|
| 49 |
+
c = xc.shape[0]
|
| 50 |
+
xb = xc.reshape(c, n_sub, sub_dim).permute(1, 0, 2).contiguous()
|
| 51 |
+
if mu_b is not None:
|
| 52 |
+
xb = (xb - mu_b) / sd_b
|
| 53 |
+
x_sq = (xb * xb).sum(-1, keepdim=True) # (n_sub, c, 1)
|
| 54 |
+
cross = torch.bmm(xb, cb.transpose(1, 2)) # (n_sub, c, K)
|
| 55 |
+
idx = (x_sq - 2 * cross + c_sq).argmin(-1) # (n_sub, c)
|
| 56 |
+
out.append(idx.to(torch.uint8))
|
| 57 |
+
return torch.cat(out, dim=1).T.contiguous() # (N, n_sub) uint8
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def pvq_decode(q: ProductVQKV, idx: torch.Tensor, which: str = "k") -> torch.Tensor:
|
| 61 |
+
"""idx: (N, n_sub) uint8 -> x_hat: (N, head_dim) (codebook dtype, e.g. fp32)."""
|
| 62 |
+
cb, mu, sd = _stacked(q, which)
|
| 63 |
+
n_sub, K, sub_dim = cb.shape
|
| 64 |
+
mu_b = mu.permute(1, 0, 2) if mu is not None else None
|
| 65 |
+
sd_b = sd.permute(1, 0, 2) if sd is not None else None
|
| 66 |
+
idxT = idx.T.long() # (n_sub, N)
|
| 67 |
+
rec = torch.gather(cb, 1, idxT.unsqueeze(-1).expand(-1, -1, sub_dim)) # (n_sub,N,sub_dim)
|
| 68 |
+
if mu_b is not None:
|
| 69 |
+
rec = rec * sd_b + mu_b
|
| 70 |
+
N = idxT.shape[1]
|
| 71 |
+
return rec.permute(1, 0, 2).reshape(N, n_sub * sub_dim)
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
def pvq_codebook_bytes(q: ProductVQKV) -> int:
|
| 75 |
+
"""Fixed per-layer codebook overhead (bytes), amortized over all tokens."""
|
| 76 |
+
cb_k, _, _ = _stacked(q, "k")
|
| 77 |
+
cb_v, _, _ = _stacked(q, "v")
|
| 78 |
+
return cb_k.numel() * cb_k.element_size() + cb_v.numel() * cb_v.element_size()
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
# ----------------------------------------------------------------------------
|
| 82 |
+
# Inference-ready cache: compressed store for target layers, native for rest
|
| 83 |
+
# ----------------------------------------------------------------------------
|
| 84 |
+
try:
|
| 85 |
+
from transformers.cache_utils import DynamicCache
|
| 86 |
+
_HAVE_TF = True
|
| 87 |
+
except Exception: # let the file import for the standalone memory harness
|
| 88 |
+
DynamicCache = object
|
| 89 |
+
_HAVE_TF = False
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class VQQuantizedCache(DynamicCache):
|
| 93 |
+
"""Drop-in cache that persists ProductVQ indices for ``target_layers`` and
|
| 94 |
+
leaves all other layers in native precision (Laguna's sliding-window layers
|
| 95 |
+
are bounded at 512 tokens and don't dominate, so we don't touch them).
|
| 96 |
+
|
| 97 |
+
Memory model: ``key_cache``/``value_cache`` for target layers are never
|
| 98 |
+
populated with full tensors. We keep ``k_codes[layer]`` / ``v_codes[layer]``
|
| 99 |
+
as (seq, n_kv_heads, n_sub) uint8 and dequantize the whole buffer transiently
|
| 100 |
+
each time the layer runs attention.
|
| 101 |
+
|
| 102 |
+
NOTE ON transformers VERSIONS: signatures around DynamicCache shift between
|
| 103 |
+
releases. This targets the modern
|
| 104 |
+
``update(key_states, value_states, layer_idx, cache_kwargs=None) -> (k, v)``
|
| 105 |
+
interface and overrides ``get_seq_length``. If your pinned version calls
|
| 106 |
+
additional hooks (``reorder_cache`` for beam search, ``crop`` for assisted
|
| 107 |
+
decoding), forward them to the code buffers the same way ``update`` does.
|
| 108 |
+
"""
|
| 109 |
+
|
| 110 |
+
def __init__(self, per_layer_quantizers: dict, target_layers, *a, **k):
|
| 111 |
+
super().__init__(*a, **k)
|
| 112 |
+
self.q = per_layer_quantizers
|
| 113 |
+
self.target = set(int(i) for i in target_layers)
|
| 114 |
+
self.k_codes: dict[int, torch.Tensor] = {}
|
| 115 |
+
self.v_codes: dict[int, torch.Tensor] = {}
|
| 116 |
+
self._dtype = None
|
| 117 |
+
self._device = None
|
| 118 |
+
|
| 119 |
+
# -- the hot path --------------------------------------------------------
|
| 120 |
+
def update(self, key_states, value_states, layer_idx, cache_kwargs=None):
|
| 121 |
+
if layer_idx not in self.target or layer_idx not in self.q:
|
| 122 |
+
# native path for sliding-window / non-targeted layers
|
| 123 |
+
return super().update(key_states, value_states, layer_idx, cache_kwargs)
|
| 124 |
+
|
| 125 |
+
self._dtype = key_states.dtype
|
| 126 |
+
self._device = key_states.device
|
| 127 |
+
q = self.q[layer_idx]
|
| 128 |
+
b, h, s, d = key_states.shape # b == 1 in this harness
|
| 129 |
+
|
| 130 |
+
# encode the NEW tokens (token-major, head-minor rows -> (s, h, n_sub))
|
| 131 |
+
kf = key_states[0].transpose(0, 1).reshape(-1, d).float()
|
| 132 |
+
vf = value_states[0].transpose(0, 1).reshape(-1, d).float()
|
| 133 |
+
kc = pvq_encode(q, kf, "k").reshape(s, h, -1)
|
| 134 |
+
vc = pvq_encode(q, vf, "v").reshape(s, h, -1)
|
| 135 |
+
|
| 136 |
+
# append to the persistent compressed buffer
|
| 137 |
+
if layer_idx in self.k_codes:
|
| 138 |
+
self.k_codes[layer_idx] = torch.cat([self.k_codes[layer_idx], kc], dim=0)
|
| 139 |
+
self.v_codes[layer_idx] = torch.cat([self.v_codes[layer_idx], vc], dim=0)
|
| 140 |
+
else:
|
| 141 |
+
self.k_codes[layer_idx] = kc
|
| 142 |
+
self.v_codes[layer_idx] = vc
|
| 143 |
+
|
| 144 |
+
# transiently dequantize the FULL buffer for this layer's attention
|
| 145 |
+
allk, allv = self.k_codes[layer_idx], self.v_codes[layer_idx]
|
| 146 |
+
S = allk.shape[0]
|
| 147 |
+
kfull = pvq_decode(q, allk.reshape(-1, allk.shape[-1]), "k").reshape(S, h, d)
|
| 148 |
+
vfull = pvq_decode(q, allv.reshape(-1, allv.shape[-1]), "v").reshape(S, h, d)
|
| 149 |
+
kfull = kfull.permute(1, 0, 2)[None].to(self._dtype).to(self._device)
|
| 150 |
+
vfull = vfull.permute(1, 0, 2)[None].to(self._dtype).to(self._device)
|
| 151 |
+
return kfull, vfull
|
| 152 |
+
|
| 153 |
+
def get_seq_length(self, layer_idx: int = 0) -> int:
|
| 154 |
+
for li in sorted(self.target):
|
| 155 |
+
if li in self.k_codes:
|
| 156 |
+
return self.k_codes[li].shape[0]
|
| 157 |
+
return super().get_seq_length(layer_idx) if _HAVE_TF else 0
|
| 158 |
+
|
| 159 |
+
# -- live memory readout for the demo ------------------------------------
|
| 160 |
+
def memory_footprint(self) -> dict:
|
| 161 |
+
"""Persistent bytes actually held on device, split by source."""
|
| 162 |
+
code_bytes = sum(t.numel() for t in self.k_codes.values()) \
|
| 163 |
+
+ sum(t.numel() for t in self.v_codes.values()) # uint8 = 1 B
|
| 164 |
+
cb_bytes = sum(pvq_codebook_bytes(self.q[li]) for li in self.k_codes)
|
| 165 |
+
native_bytes = 0
|
| 166 |
+
if _HAVE_TF:
|
| 167 |
+
for kc in getattr(self, "key_cache", []):
|
| 168 |
+
if isinstance(kc, torch.Tensor):
|
| 169 |
+
native_bytes += kc.numel() * kc.element_size()
|
| 170 |
+
for vc in getattr(self, "value_cache", []):
|
| 171 |
+
if isinstance(vc, torch.Tensor):
|
| 172 |
+
native_bytes += vc.numel() * vc.element_size()
|
| 173 |
+
return {"compressed_indices_B": code_bytes,
|
| 174 |
+
"codebooks_B": cb_bytes,
|
| 175 |
+
"native_layers_B": native_bytes,
|
| 176 |
+
"total_B": code_bytes + cb_bytes + native_bytes}
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
# ----------------------------------------------------------------------------
|
| 180 |
+
# Honest byte-accounting (model-free) -- drives the memory demo & the harness
|
| 181 |
+
# ----------------------------------------------------------------------------
|
| 182 |
+
class LagunaGeom:
|
| 183 |
+
"""Laguna-XS.2 cache geometry (from the config / proposal)."""
|
| 184 |
+
n_layers = 40
|
| 185 |
+
full_layers = 10 # full_attention layers that hold the growing cache
|
| 186 |
+
sliding_layers = 30
|
| 187 |
+
sliding_window = 512
|
| 188 |
+
n_kv_heads = 8
|
| 189 |
+
head_dim = 128
|
| 190 |
+
|
| 191 |
+
|
| 192 |
+
def kv_cache_bytes(context_len: int, bits_per_elt_full: float,
|
| 193 |
+
geom: LagunaGeom = LagunaGeom(),
|
| 194 |
+
bits_per_elt_sliding: float = 16.0) -> dict:
|
| 195 |
+
"""Total KV-cache bytes at a context length.
|
| 196 |
+
|
| 197 |
+
Only the full-attention layers carry the growing cache; sliding layers are
|
| 198 |
+
capped at ``sliding_window`` tokens. ``bits_per_elt_full`` is the rate the
|
| 199 |
+
quantizer reports for the compressed layers (use 16.0 for the fp16 baseline).
|
| 200 |
+
The K and V tensors are both counted.
|
| 201 |
+
"""
|
| 202 |
+
elts_per_token = geom.n_kv_heads * geom.head_dim * 2 # K and V
|
| 203 |
+
full_tokens = context_len * geom.full_layers
|
| 204 |
+
slide_tokens = min(context_len, geom.sliding_window) * geom.sliding_layers
|
| 205 |
+
full_B = full_tokens * elts_per_token * bits_per_elt_full / 8
|
| 206 |
+
slide_B = slide_tokens * elts_per_token * bits_per_elt_sliding / 8
|
| 207 |
+
return {"full_B": full_B, "sliding_B": slide_B, "total_B": full_B + slide_B}
|
| 208 |
+
|
| 209 |
+
|
| 210 |
+
def compression_vs_fp16(context_len: int, bits_per_elt_full: float,
|
| 211 |
+
geom: LagunaGeom = LagunaGeom()) -> float:
|
| 212 |
+
base = kv_cache_bytes(context_len, 16.0, geom)["total_B"]
|
| 213 |
+
comp = kv_cache_bytes(context_len, bits_per_elt_full, geom)["total_B"]
|
| 214 |
+
return base / comp
|
| 215 |
+
|
| 216 |
+
|
| 217 |
+
# ----------------------------------------------------------------------------
|
| 218 |
+
# self-test: verify encode/decode == roundtrip and show the real byte win
|
| 219 |
+
# ----------------------------------------------------------------------------
|
| 220 |
+
if __name__ == "__main__":
|
| 221 |
+
torch.manual_seed(0)
|
| 222 |
+
N, hd = 4096, 128
|
| 223 |
+
k = torch.randn(N, hd)
|
| 224 |
+
v = torch.randn(N, hd)
|
| 225 |
+
|
| 226 |
+
q = ProductVQKV(n_sub=32, n_codes=256, iters=10).fit(k, v) # 2 bits/elt
|
| 227 |
+
idx = pvq_encode(q, k, "k")
|
| 228 |
+
k_hat = pvq_decode(q, idx, "k")
|
| 229 |
+
rt = q.roundtrip_k(k)
|
| 230 |
+
|
| 231 |
+
print(f"idx dtype/shape : {idx.dtype} {tuple(idx.shape)}")
|
| 232 |
+
print(f"encode->decode == roundtrip : "
|
| 233 |
+
f"{torch.allclose(k_hat, rt, atol=1e-4)} "
|
| 234 |
+
f"(max abs diff {(k_hat - rt).abs().max():.2e})")
|
| 235 |
+
|
| 236 |
+
raw_B = k.numel() * 2 # fp16
|
| 237 |
+
comp_B = idx.numel() * 1 # uint8 indices
|
| 238 |
+
print(f"stored bytes fp16={raw_B} vq-2b={comp_B} ratio={raw_B / comp_B:.1f}x")
|
| 239 |
+
print(f"reported bits/elt: {q.bits_per_element(hd):.3f}")
|
| 240 |
+
|
| 241 |
+
for L in (4096, 32768, 131072):
|
| 242 |
+
r = compression_vs_fp16(L, q.bits_per_element(hd))
|
| 243 |
+
gb = kv_cache_bytes(L, q.bits_per_element(hd))["total_B"] / 1e9
|
| 244 |
+
base = kv_cache_bytes(L, 16.0)["total_B"] / 1e9
|
| 245 |
+
print(f"context {L:>7}: fp16={base:6.2f} GB vq-2b={gb:6.2f} GB ({r:.1f}x)")
|
vqkv/metrics.py
ADDED
|
@@ -0,0 +1,138 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
vqkv.metrics — attention-aware distortion metrics for KV-cache quantization.
|
| 3 |
+
|
| 4 |
+
Reports key cosine, inner-product bias, and attention-output error (not cache
|
| 5 |
+
MSE alone). attention_output() matches Laguna GQA geometry (8 KV / 48 Q heads).
|
| 6 |
+
"""
|
| 7 |
+
|
| 8 |
+
from __future__ import annotations
|
| 9 |
+
|
| 10 |
+
import torch
|
| 11 |
+
import torch.nn.functional as F
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def attention_output(q, k, v, n_q_heads, gate=None, scaling=None):
|
| 15 |
+
"""Single forward of GQA attention for one layer.
|
| 16 |
+
|
| 17 |
+
q: (T, n_q_heads, head_dim)
|
| 18 |
+
k: (T, n_kv_heads, head_dim)
|
| 19 |
+
v: (T, n_kv_heads, head_dim)
|
| 20 |
+
Returns: (T, n_q_heads, head_dim) attention output (pre o_proj).
|
| 21 |
+
"""
|
| 22 |
+
T, n_kv_heads, head_dim = k.shape
|
| 23 |
+
groups = n_q_heads // n_kv_heads
|
| 24 |
+
if scaling is None:
|
| 25 |
+
scaling = head_dim ** -0.5
|
| 26 |
+
|
| 27 |
+
# expand kv heads to q heads (GQA)
|
| 28 |
+
k_exp = k.repeat_interleave(groups, dim=1) # (T, n_q_heads, d)
|
| 29 |
+
v_exp = v.repeat_interleave(groups, dim=1)
|
| 30 |
+
|
| 31 |
+
# (n_q_heads, T, d)
|
| 32 |
+
qh = q.transpose(0, 1)
|
| 33 |
+
kh = k_exp.transpose(0, 1)
|
| 34 |
+
vh = v_exp.transpose(0, 1)
|
| 35 |
+
|
| 36 |
+
scores = torch.matmul(qh, kh.transpose(-1, -2)) * scaling # (H, T, T)
|
| 37 |
+
causal = torch.tril(torch.ones(T, T, dtype=torch.bool, device=q.device))
|
| 38 |
+
scores = scores.masked_fill(~causal, float("-inf"))
|
| 39 |
+
attn = F.softmax(scores, dim=-1)
|
| 40 |
+
out = torch.matmul(attn, vh) # (H, T, d)
|
| 41 |
+
out = out.transpose(0, 1) # (T, H, d)
|
| 42 |
+
|
| 43 |
+
if gate is not None: # Laguna per-head softplus gate
|
| 44 |
+
out = out * F.softplus(gate).unsqueeze(-1)
|
| 45 |
+
return out, attn
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def cache_mse(x, x_hat):
|
| 49 |
+
return F.mse_loss(x_hat, x).item()
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def attn_output_error(out_ref, out_hat):
|
| 53 |
+
"""Relative L2 error in attention output (magnitude + direction combined)."""
|
| 54 |
+
num = (out_hat - out_ref).norm()
|
| 55 |
+
den = out_ref.norm().clamp_min(1e-8)
|
| 56 |
+
return (num / den).item()
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def attn_output_cosine(out_ref, out_hat, per_head=True):
|
| 60 |
+
"""Cosine similarity between reference and quantized attention outputs.
|
| 61 |
+
|
| 62 |
+
This isolates DIRECTION, which is what propagates through o_proj into the
|
| 63 |
+
residual stream. A quantizer can preserve direction while distorting
|
| 64 |
+
magnitude (or vice versa); relative-L2 conflates the two. On Laguna the
|
| 65 |
+
per-head softplus gate (g_proj) rescales each head AFTER attention, so a
|
| 66 |
+
magnitude error is partly re-absorbed by the gate -- which makes the
|
| 67 |
+
directional (cosine) error the more faithful quality signal here.
|
| 68 |
+
|
| 69 |
+
out_*: (T, H, d). Returns mean cosine over (valid token, head) pairs.
|
| 70 |
+
If per_head=False, flattens heads and measures the full per-token vector.
|
| 71 |
+
"""
|
| 72 |
+
if per_head:
|
| 73 |
+
a = out_ref.reshape(-1, out_ref.shape[-1])
|
| 74 |
+
b = out_hat.reshape(-1, out_hat.shape[-1])
|
| 75 |
+
else:
|
| 76 |
+
a = out_ref.reshape(out_ref.shape[0], -1)
|
| 77 |
+
b = out_hat.reshape(out_hat.shape[0], -1)
|
| 78 |
+
cos = torch.nn.functional.cosine_similarity(a, b, dim=-1, eps=1e-8)
|
| 79 |
+
return cos.mean().item()
|
| 80 |
+
|
| 81 |
+
|
| 82 |
+
def magnitude_direction_split(out_ref, out_hat):
|
| 83 |
+
"""Decompose attention-output error into a directional part (1 - cosine)
|
| 84 |
+
and a magnitude part (relative norm error per vector). Reporting both shows
|
| 85 |
+
WHICH kind of error a quantizer makes -- the discriminating diagnostic when
|
| 86 |
+
comparing a rotation-based scalar method (TurboQuant) against product VQ.
|
| 87 |
+
"""
|
| 88 |
+
a = out_ref.reshape(-1, out_ref.shape[-1])
|
| 89 |
+
b = out_hat.reshape(-1, out_hat.shape[-1])
|
| 90 |
+
cos = torch.nn.functional.cosine_similarity(a, b, dim=-1, eps=1e-8)
|
| 91 |
+
na, nb = a.norm(dim=-1).clamp_min(1e-8), b.norm(dim=-1)
|
| 92 |
+
mag_rel = ((nb - na).abs() / na).mean().item()
|
| 93 |
+
return {"dir_err (1-cos)": round((1 - cos.mean()).item(), 5),
|
| 94 |
+
"mag_rel_err": round(mag_rel, 5)}
|
| 95 |
+
|
| 96 |
+
|
| 97 |
+
def attn_kl(attn_ref, attn_hat):
|
| 98 |
+
"""Mean KL(attn_ref || attn_hat) over query positions and heads."""
|
| 99 |
+
a = attn_ref.clamp_min(1e-9)
|
| 100 |
+
b = attn_hat.clamp_min(1e-9)
|
| 101 |
+
kl = (a * (a.log() - b.log())).sum(dim=-1) # (H, T)
|
| 102 |
+
# only count valid (non-masked) rows: those with > 0 mass already normalized
|
| 103 |
+
return kl.mean().item()
|
| 104 |
+
|
| 105 |
+
|
| 106 |
+
def key_cosine(k_ref, k_hat):
|
| 107 |
+
"""Mean cosine similarity between original and reconstructed KEY vectors.
|
| 108 |
+
|
| 109 |
+
The attention logit is q.k, which depends on the DIRECTION of k. Key cosine
|
| 110 |
+
is a cheap, query-free proxy for logit fidelity: if directions are preserved,
|
| 111 |
+
logits are preserved up to per-key magnitude. k_*: (T, H, d)."""
|
| 112 |
+
a = k_ref.reshape(-1, k_ref.shape[-1])
|
| 113 |
+
b = k_hat.reshape(-1, k_ref.shape[-1])
|
| 114 |
+
return torch.nn.functional.cosine_similarity(a, b, dim=-1, eps=1e-8).mean().item()
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def inner_product_distortion(q, k_ref, k_hat, n_pairs=4096):
|
| 118 |
+
"""Relative error in the q.k inner products that drive attention logits.
|
| 119 |
+
|
| 120 |
+
This is the objective TurboQuant (ICLR 2026) argues for over cache MSE: it
|
| 121 |
+
shows MSE-optimal quantizers are BIASED for inner-product estimation. We
|
| 122 |
+
measure |<q,k_hat> - <q,k_ref>| / |<q,k_ref>| over sampled query-key pairs.
|
| 123 |
+
|
| 124 |
+
Also returns the signed mean (bias): a method can be unbiased (mean ~0) yet
|
| 125 |
+
high-variance, or biased -- the distinction TurboQuant's QJL stage targets.
|
| 126 |
+
q: (T, Hq, d); k_*: (T, Hk, d). Uses head 0 of each for a cheap estimate.
|
| 127 |
+
"""
|
| 128 |
+
qd = q[:, 0, :]
|
| 129 |
+
kr = k_ref[:, 0, :]
|
| 130 |
+
kh = k_hat[:, 0, :]
|
| 131 |
+
T = qd.shape[0]
|
| 132 |
+
qi = torch.randint(0, T, (n_pairs,), device=qd.device)
|
| 133 |
+
ki = torch.randint(0, T, (n_pairs,), device=qd.device)
|
| 134 |
+
ip_ref = (qd[qi] * kr[ki]).sum(-1)
|
| 135 |
+
ip_hat = (qd[qi] * kh[ki]).sum(-1)
|
| 136 |
+
rel = ((ip_hat - ip_ref).abs() / ip_ref.abs().clamp_min(1e-6)).mean().item()
|
| 137 |
+
bias = (ip_hat - ip_ref).mean().item()
|
| 138 |
+
return {"ip_rel_err": round(rel, 5), "ip_bias": round(bias, 6)}
|
vqkv/quantizers.py
ADDED
|
@@ -0,0 +1,618 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
vqkv.quantizers — KV-cache quantizers for AttnVQ.
|
| 3 |
+
|
| 4 |
+
ScalarKV, KIVIScalarKV, ProductVQKV (LBG product VQ), RoPESplitVQKV,
|
| 5 |
+
SignScalarKV, TernaryScalarKV. Each exposes fit() + roundtrip_k/v() and
|
| 6 |
+
bits_per_element(). Distortion is evaluated via vqkv.metrics (attention-output
|
| 7 |
+
error, not cache MSE alone).
|
| 8 |
+
"""
|
| 9 |
+
|
| 10 |
+
from __future__ import annotations
|
| 11 |
+
|
| 12 |
+
import math
|
| 13 |
+
from dataclasses import dataclass, field
|
| 14 |
+
|
| 15 |
+
import torch
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
# ----------------------------------------------------------------------------
|
| 19 |
+
# Utilities
|
| 20 |
+
# ----------------------------------------------------------------------------
|
| 21 |
+
def _affine_quantize(x: torch.Tensor, nbits: int, dim: int):
|
| 22 |
+
"""Symmetric-range affine quantization along `dim`. Returns (q, scale, zero)."""
|
| 23 |
+
qmax = (1 << nbits) - 1
|
| 24 |
+
xmin = x.amin(dim=dim, keepdim=True)
|
| 25 |
+
xmax = x.amax(dim=dim, keepdim=True)
|
| 26 |
+
scale = (xmax - xmin).clamp_min(1e-8) / qmax
|
| 27 |
+
zero = torch.round(-xmin / scale)
|
| 28 |
+
q = torch.clamp(torch.round(x / scale) + zero, 0, qmax)
|
| 29 |
+
return q, scale, zero
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def _affine_dequantize(q, scale, zero):
|
| 33 |
+
return (q - zero) * scale
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
# ----------------------------------------------------------------------------
|
| 37 |
+
# Scalar baselines
|
| 38 |
+
# ----------------------------------------------------------------------------
|
| 39 |
+
@dataclass
|
| 40 |
+
class ScalarKV:
|
| 41 |
+
"""Per-token affine quantization of both K and V (the transformers default)."""
|
| 42 |
+
nbits: int = 4
|
| 43 |
+
|
| 44 |
+
def fit(self, k_calib, v_calib): # scalar quant is tuning-free
|
| 45 |
+
return self
|
| 46 |
+
|
| 47 |
+
def roundtrip_k(self, k):
|
| 48 |
+
# k: (..., head_dim) -> quantize along head_dim (per-token, per-head)
|
| 49 |
+
q, s, z = _affine_quantize(k, self.nbits, dim=-1)
|
| 50 |
+
return _affine_dequantize(q, s, z)
|
| 51 |
+
|
| 52 |
+
def roundtrip_v(self, v):
|
| 53 |
+
q, s, z = _affine_quantize(v, self.nbits, dim=-1)
|
| 54 |
+
return _affine_dequantize(q, s, z)
|
| 55 |
+
|
| 56 |
+
def bits_per_element(self, head_dim):
|
| 57 |
+
# nbits per element + scale/zero (fp16 each) amortized over head_dim
|
| 58 |
+
return self.nbits + (2 * 16) / head_dim
|
| 59 |
+
|
| 60 |
+
|
| 61 |
+
@dataclass
|
| 62 |
+
class KIVIScalarKV:
|
| 63 |
+
"""KIVI-style: keys quantized per-channel, values per-token.
|
| 64 |
+
|
| 65 |
+
Keys are quantized along the TOKEN axis (per channel); values along the
|
| 66 |
+
head_dim axis (per token). Requires a token-axis view, so fit/roundtrip
|
| 67 |
+
operate on a full (N_tokens, head_dim) block per head.
|
| 68 |
+
"""
|
| 69 |
+
nbits: int = 4
|
| 70 |
+
|
| 71 |
+
def fit(self, k_calib, v_calib):
|
| 72 |
+
return self
|
| 73 |
+
|
| 74 |
+
def roundtrip_k(self, k):
|
| 75 |
+
# k: (N_tokens, head_dim). per-channel == quantize along token axis (dim=0)
|
| 76 |
+
q, s, z = _affine_quantize(k, self.nbits, dim=0)
|
| 77 |
+
return _affine_dequantize(q, s, z)
|
| 78 |
+
|
| 79 |
+
def roundtrip_v(self, v):
|
| 80 |
+
q, s, z = _affine_quantize(v, self.nbits, dim=-1)
|
| 81 |
+
return _affine_dequantize(q, s, z)
|
| 82 |
+
|
| 83 |
+
def bits_per_element(self, head_dim):
|
| 84 |
+
return self.nbits + (2 * 16) / head_dim
|
| 85 |
+
|
| 86 |
+
|
| 87 |
+
# ----------------------------------------------------------------------------
|
| 88 |
+
# LBG / k-means codebook (the 1980 algorithm, the engine of the project)
|
| 89 |
+
# ----------------------------------------------------------------------------
|
| 90 |
+
def lbg_codebook(data: torch.Tensor, n_codes: int, iters: int = 25,
|
| 91 |
+
seed: int = 0) -> torch.Tensor:
|
| 92 |
+
"""Linde-Buzo-Gray vector quantizer design (== Lloyd / k-means for MSE).
|
| 93 |
+
|
| 94 |
+
data: (N, d) training vectors
|
| 95 |
+
returns: (n_codes, d) codebook
|
| 96 |
+
"""
|
| 97 |
+
g = torch.Generator().manual_seed(seed)
|
| 98 |
+
N, d = data.shape
|
| 99 |
+
dev = data.device
|
| 100 |
+
idx = torch.randperm(N, generator=g)[:n_codes].to(dev)
|
| 101 |
+
cb = data[idx].clone()
|
| 102 |
+
ones = torch.ones(N, dtype=data.dtype, device=dev)
|
| 103 |
+
for _ in range(iters):
|
| 104 |
+
# Assignment: BLAS GEMM path avoids the O(N*K*d) intermediate that
|
| 105 |
+
# torch.cdist allocates for small d (e.g. d=8 for n_sub=16).
|
| 106 |
+
assign = _sq_l2(data, cb).argmin(dim=1)
|
| 107 |
+
|
| 108 |
+
# Update: vectorized scatter_add replaces the Python loop over n_codes.
|
| 109 |
+
# Was: 256 Python iterations; now: 3 torch ops.
|
| 110 |
+
new_cb = torch.zeros_like(cb)
|
| 111 |
+
counts = torch.zeros(n_codes, dtype=data.dtype, device=dev)
|
| 112 |
+
new_cb.scatter_add_(0, assign.unsqueeze(1).expand(-1, d), data)
|
| 113 |
+
counts.scatter_add_(0, assign, ones)
|
| 114 |
+
live = counts > 0
|
| 115 |
+
new_cb[live] /= counts[live].unsqueeze(1)
|
| 116 |
+
# Re-seed dead centroids (LBG splitting heuristic)
|
| 117 |
+
dead = (~live).nonzero(as_tuple=True)[0]
|
| 118 |
+
if dead.numel() > 0:
|
| 119 |
+
ri = torch.randint(0, N, (dead.numel(),), generator=g).to(dev)
|
| 120 |
+
new_cb[dead] = data[ri]
|
| 121 |
+
|
| 122 |
+
shift = (new_cb - cb).norm()
|
| 123 |
+
cb = new_cb
|
| 124 |
+
if shift < 1e-5:
|
| 125 |
+
break
|
| 126 |
+
return cb
|
| 127 |
+
|
| 128 |
+
|
| 129 |
+
def lbg_codebook_batched(xb: torch.Tensor, n_codes: int, iters: int = 25,
|
| 130 |
+
seed: int = 0) -> torch.Tensor:
|
| 131 |
+
"""Fit n_sub independent LBG codebooks in one batched pass.
|
| 132 |
+
|
| 133 |
+
Mirrors the structure of ProductVQKV._roundtrip: replaces the Python loop
|
| 134 |
+
over sub-blocks with a single bmm-based assignment and a scatter_add-based
|
| 135 |
+
update, so n_sub sub-block fits become one operation at each Lloyd step.
|
| 136 |
+
|
| 137 |
+
xb: (n_sub, N, sub_dim) training vectors, pre-normalized if needed
|
| 138 |
+
returns: (n_sub, n_codes, sub_dim) codebooks, one per sub-block
|
| 139 |
+
"""
|
| 140 |
+
g = torch.Generator().manual_seed(seed)
|
| 141 |
+
n_sub, N, sub_dim = xb.shape
|
| 142 |
+
dev = xb.device
|
| 143 |
+
|
| 144 |
+
# Initialization: random subset per sub-block (same RNG sequence as serial)
|
| 145 |
+
idx = torch.stack([torch.randperm(N, generator=g)[:n_codes]
|
| 146 |
+
for _ in range(n_sub)]).to(dev) # (n_sub, K)
|
| 147 |
+
cb = xb[torch.arange(n_sub, device=dev).unsqueeze(1), idx].clone() # (n_sub, K, sub_dim)
|
| 148 |
+
|
| 149 |
+
ones = torch.ones(N, dtype=xb.dtype, device=dev)
|
| 150 |
+
|
| 151 |
+
for _ in range(iters):
|
| 152 |
+
# Assignment — one bmm replaces n_sub independent GEMMs
|
| 153 |
+
x_sq = (xb * xb).sum(-1, keepdim=True) # (n_sub, N, 1)
|
| 154 |
+
c_sq = (cb * cb).sum(-1).unsqueeze(1) # (n_sub, 1, K)
|
| 155 |
+
cross = torch.bmm(xb, cb.transpose(1, 2)) # (n_sub, N, K)
|
| 156 |
+
assign = (x_sq - 2 * cross + c_sq).argmin(dim=-1) # (n_sub, N)
|
| 157 |
+
|
| 158 |
+
# Update — batched scatter_add
|
| 159 |
+
new_cb = torch.zeros_like(cb)
|
| 160 |
+
counts = torch.zeros(n_sub, n_codes, dtype=xb.dtype, device=dev)
|
| 161 |
+
assign_exp = assign.unsqueeze(-1).expand(-1, -1, sub_dim)
|
| 162 |
+
new_cb.scatter_add_(1, assign_exp, xb)
|
| 163 |
+
counts.scatter_add_(1, assign, ones.unsqueeze(0).expand(n_sub, -1))
|
| 164 |
+
|
| 165 |
+
live = counts > 0
|
| 166 |
+
new_cb[live] /= counts[live].unsqueeze(-1)
|
| 167 |
+
|
| 168 |
+
# Re-seed dead centroids per sub-block (rare; loop is fine)
|
| 169 |
+
dead_any = ~live
|
| 170 |
+
if dead_any.any():
|
| 171 |
+
for s in range(n_sub):
|
| 172 |
+
dead = dead_any[s].nonzero(as_tuple=True)[0]
|
| 173 |
+
if dead.numel():
|
| 174 |
+
ri = torch.randint(0, N, (dead.numel(),), generator=g).to(dev)
|
| 175 |
+
new_cb[s, dead] = xb[s, ri]
|
| 176 |
+
|
| 177 |
+
shift = (new_cb - cb).norm()
|
| 178 |
+
cb = new_cb
|
| 179 |
+
if shift < 1e-5:
|
| 180 |
+
break
|
| 181 |
+
|
| 182 |
+
return cb
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def _sq_l2(x: torch.Tensor, cb: torch.Tensor) -> torch.Tensor:
|
| 186 |
+
"""Squared L2 distance matrix (N, K) via BLAS GEMM.
|
| 187 |
+
|
| 188 |
+
torch.cdist for small d (e.g. d=8 for n_sub=16) falls back to a naive
|
| 189 |
+
expand-and-subtract path that allocates an (N, K, d) intermediate.
|
| 190 |
+
For N=131072, K=256, d=8 that is 1.07 GB per call, causing massive
|
| 191 |
+
CPU allocation pressure and 100x slowdowns versus the BLAS path.
|
| 192 |
+
|
| 193 |
+
||x-c||^2 = ||x||^2 - 2*(x @ c.T) + ||c||^2 uses only (N,K) memory.
|
| 194 |
+
"""
|
| 195 |
+
return ((x * x).sum(1, keepdim=True)
|
| 196 |
+
+ (cb * cb).sum(1)
|
| 197 |
+
- 2 * (x @ cb.T))
|
| 198 |
+
|
| 199 |
+
|
| 200 |
+
def vq_encode(x: torch.Tensor, cb: torch.Tensor) -> torch.Tensor:
|
| 201 |
+
"""Nearest-codeword indices. x:(N,d) cb:(K,d) -> (N,) long."""
|
| 202 |
+
return _sq_l2(x, cb).argmin(dim=1)
|
| 203 |
+
|
| 204 |
+
|
| 205 |
+
# ----------------------------------------------------------------------------
|
| 206 |
+
# Product VQ
|
| 207 |
+
# ----------------------------------------------------------------------------
|
| 208 |
+
@dataclass
|
| 209 |
+
class ProductVQKV:
|
| 210 |
+
"""Product vector quantization of head-dim sub-blocks.
|
| 211 |
+
|
| 212 |
+
Each head's `head_dim` vector is split into `n_sub` contiguous sub-vectors
|
| 213 |
+
of length `sub_dim = head_dim / n_sub`; each sub-vector is quantized
|
| 214 |
+
against its own codebook of size `n_codes`.
|
| 215 |
+
|
| 216 |
+
If `normalize=True`, each sub-vector is standardized by its per-dimension
|
| 217 |
+
calibration mean/std before codebook design and restored after decode.
|
| 218 |
+
This decorrelates the scale variation RoPE introduces in the rotated half
|
| 219 |
+
of the key, and is the mechanism that lets RoPE-split specialize.
|
| 220 |
+
|
| 221 |
+
Rate (bits/element) = n_sub * log2(n_codes) / head_dim.
|
| 222 |
+
e.g. head_dim=128, n_sub=8, n_codes=256 -> 8*8/128 = 0.5 bits/element.
|
| 223 |
+
"""
|
| 224 |
+
n_sub: int = 8
|
| 225 |
+
n_codes: int = 256
|
| 226 |
+
iters: int = 25
|
| 227 |
+
normalize: bool = False
|
| 228 |
+
k_codebooks: list = field(default_factory=list)
|
| 229 |
+
v_codebooks: list = field(default_factory=list)
|
| 230 |
+
_k_stats: list = field(default_factory=list)
|
| 231 |
+
_v_stats: list = field(default_factory=list)
|
| 232 |
+
_k_stacked: tuple = None # lazily-built (cb, mu, sd) batched tensors
|
| 233 |
+
_v_stacked: tuple = None
|
| 234 |
+
|
| 235 |
+
def _split(self, x):
|
| 236 |
+
# x: (N, head_dim) -> list of (N, sub_dim)
|
| 237 |
+
return list(torch.chunk(x, self.n_sub, dim=-1))
|
| 238 |
+
|
| 239 |
+
def _fit_one(self, x):
|
| 240 |
+
N, head_dim = x.shape
|
| 241 |
+
sub_dim = head_dim // self.n_sub
|
| 242 |
+
# (n_sub, N, sub_dim) -- same layout _roundtrip uses, so batching mirrors inference
|
| 243 |
+
xb = x.reshape(N, self.n_sub, sub_dim).permute(1, 0, 2).contiguous()
|
| 244 |
+
|
| 245 |
+
if self.normalize:
|
| 246 |
+
mu = xb.mean(dim=1, keepdim=True) # (n_sub, 1, sub_dim)
|
| 247 |
+
sd = xb.std(dim=1, keepdim=True).clamp_min(1e-6)
|
| 248 |
+
xb = (xb - mu) / sd
|
| 249 |
+
# unstack into (1, sub_dim) tuples so _stack / to() are unchanged
|
| 250 |
+
stats = [(mu[s], sd[s]) for s in range(self.n_sub)]
|
| 251 |
+
else:
|
| 252 |
+
stats = [None] * self.n_sub
|
| 253 |
+
|
| 254 |
+
cb_batched = lbg_codebook_batched(xb, self.n_codes, self.iters)
|
| 255 |
+
cbs = list(cb_batched.unbind(dim=0)) # n_sub x (K, sub_dim)
|
| 256 |
+
return cbs, stats
|
| 257 |
+
|
| 258 |
+
def fit(self, k_calib, v_calib):
|
| 259 |
+
self.k_codebooks, self._k_stats = self._fit_one(k_calib)
|
| 260 |
+
self.v_codebooks, self._v_stats = self._fit_one(v_calib)
|
| 261 |
+
return self
|
| 262 |
+
|
| 263 |
+
def _stack(self, codebooks, stats):
|
| 264 |
+
"""Lazily stack per-sub-block codebooks/stats into batched tensors so the
|
| 265 |
+
whole product-VQ encode is a few large ops instead of n_sub small ones.
|
| 266 |
+
|
| 267 |
+
Returns:
|
| 268 |
+
cb_stacked: (n_sub, K, sub_dim)
|
| 269 |
+
mu_stacked: (1, n_sub, sub_dim) or None (None => no normalization)
|
| 270 |
+
sd_stacked: (1, n_sub, sub_dim) or None
|
| 271 |
+
Requires uniform sub_dim and n_codes across sub-blocks, which ProductVQ
|
| 272 |
+
guarantees (torch.chunk into equal pieces, single n_codes).
|
| 273 |
+
"""
|
| 274 |
+
cb_stacked = torch.stack(codebooks, dim=0) # (n_sub, K, sub_dim)
|
| 275 |
+
if any(st is not None for st in stats):
|
| 276 |
+
mu = torch.stack([st[0].reshape(-1) for st in stats], dim=0) # (n_sub, sub_dim)
|
| 277 |
+
sd = torch.stack([st[1].reshape(-1) for st in stats], dim=0)
|
| 278 |
+
mu_stacked = mu.unsqueeze(0) # (1, n_sub, sub_dim)
|
| 279 |
+
sd_stacked = sd.unsqueeze(0)
|
| 280 |
+
else:
|
| 281 |
+
mu_stacked = sd_stacked = None
|
| 282 |
+
return cb_stacked, mu_stacked, sd_stacked
|
| 283 |
+
|
| 284 |
+
def _ensure_stacked(self):
|
| 285 |
+
if getattr(self, "_k_stacked", None) is None:
|
| 286 |
+
self._k_stacked = self._stack(self.k_codebooks, self._k_stats)
|
| 287 |
+
if getattr(self, "_v_stacked", None) is None:
|
| 288 |
+
self._v_stacked = self._stack(self.v_codebooks, self._v_stats)
|
| 289 |
+
|
| 290 |
+
def _roundtrip(self, x, stacked):
|
| 291 |
+
"""Batched product-VQ round-trip.
|
| 292 |
+
|
| 293 |
+
x: (N, head_dim). Splits into (N, n_sub, sub_dim), then does ONE batched
|
| 294 |
+
squared-L2 (n_sub, N, K), one argmin, one gather -- replacing the Python
|
| 295 |
+
loop over sub-blocks and its 3*n_sub small kernels.
|
| 296 |
+
|
| 297 |
+
Chunks over N so peak memory (the (n_sub, chunk, K) distance tensor)
|
| 298 |
+
stays bounded: at N=131072, n_sub=16, K=256 the un-chunked tensor is
|
| 299 |
+
~2 GB in fp32. The batched path is a GPU optimization -- on CPU it is
|
| 300 |
+
roughly on par with the per-sub-block loop (no launch overhead to hide).
|
| 301 |
+
"""
|
| 302 |
+
cb, mu, sd = stacked # cb: (n_sub, K, sub_dim)
|
| 303 |
+
n_sub, K, sub_dim = cb.shape
|
| 304 |
+
N = x.shape[0]
|
| 305 |
+
c_sq = (cb * cb).sum(-1).unsqueeze(1) # (n_sub, 1, K)
|
| 306 |
+
if mu is not None:
|
| 307 |
+
mu_b = mu.permute(1, 0, 2) # (n_sub, 1, sub_dim)
|
| 308 |
+
sd_b = sd.permute(1, 0, 2)
|
| 309 |
+
|
| 310 |
+
# cap the distance tensor at ~256 MB fp32: n_sub * chunk * K * 4 bytes
|
| 311 |
+
chunk = max(1, (256 * 1024 * 1024) // (n_sub * K * 4))
|
| 312 |
+
out_chunks = []
|
| 313 |
+
for start in range(0, N, chunk):
|
| 314 |
+
xc = x[start:start + chunk] # (c, head_dim)
|
| 315 |
+
c = xc.shape[0]
|
| 316 |
+
xb = xc.reshape(c, n_sub, sub_dim).permute(1, 0, 2).contiguous()
|
| 317 |
+
if mu is not None:
|
| 318 |
+
xb = (xb - mu_b) / sd_b
|
| 319 |
+
x_sq = (xb * xb).sum(-1, keepdim=True) # (n_sub, c, 1)
|
| 320 |
+
cross = torch.bmm(xb, cb.transpose(1, 2)) # (n_sub, c, K)
|
| 321 |
+
d2 = x_sq - 2 * cross + c_sq
|
| 322 |
+
idx = d2.argmin(dim=-1) # (n_sub, c)
|
| 323 |
+
idx_exp = idx.unsqueeze(-1).expand(-1, -1, sub_dim)
|
| 324 |
+
rec = torch.gather(cb, 1, idx_exp) # (n_sub, c, sub_dim)
|
| 325 |
+
if mu is not None:
|
| 326 |
+
rec = rec * sd_b + mu_b
|
| 327 |
+
out_chunks.append(rec.permute(1, 0, 2).reshape(c, n_sub * sub_dim))
|
| 328 |
+
return torch.cat(out_chunks, dim=0)
|
| 329 |
+
|
| 330 |
+
def roundtrip_k(self, k):
|
| 331 |
+
self._ensure_stacked()
|
| 332 |
+
return self._roundtrip(k, self._k_stacked)
|
| 333 |
+
|
| 334 |
+
def roundtrip_v(self, v):
|
| 335 |
+
self._ensure_stacked()
|
| 336 |
+
return self._roundtrip(v, self._v_stacked)
|
| 337 |
+
|
| 338 |
+
def to(self, device):
|
| 339 |
+
self.k_codebooks = [cb.to(device) for cb in self.k_codebooks]
|
| 340 |
+
self.v_codebooks = [cb.to(device) for cb in self.v_codebooks]
|
| 341 |
+
self._k_stats = [(st[0].to(device), st[1].to(device)) if st is not None else None
|
| 342 |
+
for st in self._k_stats]
|
| 343 |
+
self._v_stats = [(st[0].to(device), st[1].to(device)) if st is not None else None
|
| 344 |
+
for st in self._v_stats]
|
| 345 |
+
# invalidate cached stacks; they rebuild on next roundtrip on the new device
|
| 346 |
+
self._k_stacked = None
|
| 347 |
+
self._v_stacked = None
|
| 348 |
+
return self
|
| 349 |
+
|
| 350 |
+
def bits_per_element(self, head_dim):
|
| 351 |
+
sub_dim = head_dim / self.n_sub
|
| 352 |
+
return math.log2(self.n_codes) / sub_dim
|
| 353 |
+
|
| 354 |
+
|
| 355 |
+
@dataclass
|
| 356 |
+
class TurboQuantKV:
|
| 357 |
+
"""Data-oblivious rotation + per-coordinate scalar quantization, in the
|
| 358 |
+
spirit of TurboQuant (Zandieh et al., ICLR 2026).
|
| 359 |
+
|
| 360 |
+
Pipeline: random rotation Pi (so coordinates become near-iid in high dim),
|
| 361 |
+
then a per-coordinate scalar codebook. We use a uniform Lloyd-Max-style
|
| 362 |
+
codebook on the rotated coordinates as a faithful stand-in for their
|
| 363 |
+
precomputed Beta-optimal codebook (the exact codebook is an implementation
|
| 364 |
+
detail; the rotation is the load-bearing idea). We store the per-vector L2
|
| 365 |
+
norm in fp16 and rescale on dequant, as the paper specifies.
|
| 366 |
+
|
| 367 |
+
This is included so we can compare a rotation-based SCALAR method against
|
| 368 |
+
product VQ on the attention-output COSINE metric -- the comparison the
|
| 369 |
+
TurboQuant paper does not make (it optimizes cache-vector MSE / inner prod).
|
| 370 |
+
"""
|
| 371 |
+
nbits: int = 4
|
| 372 |
+
seed: int = 0
|
| 373 |
+
_rot: torch.Tensor = None
|
| 374 |
+
_levels: torch.Tensor = None
|
| 375 |
+
|
| 376 |
+
def _make_rotation(self, d):
|
| 377 |
+
g = torch.Generator().manual_seed(self.seed)
|
| 378 |
+
a = torch.randn(d, d, generator=g)
|
| 379 |
+
q, _ = torch.linalg.qr(a)
|
| 380 |
+
return q
|
| 381 |
+
|
| 382 |
+
def fit(self, k_calib, v_calib):
|
| 383 |
+
d = k_calib.shape[-1]
|
| 384 |
+
self._rot = self._make_rotation(d)
|
| 385 |
+
# Fit levels on UNIT-NORMALIZED, rotated coordinates -- the same domain
|
| 386 |
+
# the round-trip quantizes in. (Earlier bug: levels were fit on
|
| 387 |
+
# un-normalized rotated data, so the per-vector-normalized values fell
|
| 388 |
+
# outside the level range and collapsed to one bin.)
|
| 389 |
+
kn = k_calib / k_calib.norm(dim=-1, keepdim=True).clamp_min(1e-8)
|
| 390 |
+
rk = kn @ self._rot
|
| 391 |
+
lo = torch.quantile(rk.flatten()[:200000], 0.001)
|
| 392 |
+
hi = torch.quantile(rk.flatten()[:200000], 0.999)
|
| 393 |
+
self._levels = torch.linspace(lo.item(), hi.item(), (1 << self.nbits))
|
| 394 |
+
return self
|
| 395 |
+
|
| 396 |
+
def _roundtrip(self, x):
|
| 397 |
+
norms = x.norm(dim=-1, keepdim=True).clamp_min(1e-8)
|
| 398 |
+
xn = x / norms
|
| 399 |
+
y = xn @ self._rot # rotate
|
| 400 |
+
idx = torch.bucketize(y, self._levels)
|
| 401 |
+
idx = idx.clamp(0, self._levels.numel() - 1)
|
| 402 |
+
y_hat = self._levels[idx] # per-coord scalar quant
|
| 403 |
+
x_hat = y_hat @ self._rot.T # un-rotate
|
| 404 |
+
return x_hat * norms # rescale by stored norm
|
| 405 |
+
|
| 406 |
+
def roundtrip_k(self, k):
|
| 407 |
+
return self._roundtrip(k)
|
| 408 |
+
|
| 409 |
+
def roundtrip_v(self, v):
|
| 410 |
+
return self._roundtrip(v)
|
| 411 |
+
|
| 412 |
+
def to(self, device):
|
| 413 |
+
self._rot = self._rot.to(device)
|
| 414 |
+
self._levels = self._levels.to(device)
|
| 415 |
+
return self
|
| 416 |
+
|
| 417 |
+
def bits_per_element(self, head_dim):
|
| 418 |
+
# nbits/coord + fp16 norm amortized over head_dim
|
| 419 |
+
return self.nbits + 16 / head_dim
|
| 420 |
+
|
| 421 |
+
|
| 422 |
+
@dataclass
|
| 423 |
+
class RoPESplitVQKV:
|
| 424 |
+
"""ProductVQ with separate codebooks for the RoPE'd vs pass-through halves
|
| 425 |
+
of each KEY head.
|
| 426 |
+
|
| 427 |
+
Laguna full-attention layers use partial_rotary_factor=0.5: the first half
|
| 428 |
+
of head_dim is rotated (position-dependent, broad distribution), the second
|
| 429 |
+
half is identity (position-independent). A single codebook must straddle
|
| 430 |
+
two regimes; splitting lets each codebook specialize.
|
| 431 |
+
|
| 432 |
+
Values receive no RoPE, so V uses a plain ProductVQ.
|
| 433 |
+
"""
|
| 434 |
+
n_sub_half: int = 4 # sub-vectors PER HALF for keys
|
| 435 |
+
n_codes: int = 256
|
| 436 |
+
iters: int = 25
|
| 437 |
+
rotary_fraction: float = 0.5
|
| 438 |
+
_rope_vq: ProductVQKV = None
|
| 439 |
+
_pass_vq: ProductVQKV = None
|
| 440 |
+
_v_vq: ProductVQKV = None
|
| 441 |
+
|
| 442 |
+
def fit(self, k_calib, v_calib):
|
| 443 |
+
d = k_calib.shape[-1]
|
| 444 |
+
cut = int(d * self.rotary_fraction)
|
| 445 |
+
k_rope, k_pass = k_calib[..., :cut], k_calib[..., cut:]
|
| 446 |
+
self._cut = cut
|
| 447 |
+
self._rope_vq = ProductVQKV(self.n_sub_half, self.n_codes, self.iters,
|
| 448 |
+
normalize=True).fit(k_rope, k_rope)
|
| 449 |
+
self._pass_vq = ProductVQKV(self.n_sub_half, self.n_codes, self.iters,
|
| 450 |
+
normalize=False).fit(k_pass, k_pass)
|
| 451 |
+
self._v_vq = ProductVQKV(2 * self.n_sub_half, self.n_codes, self.iters,
|
| 452 |
+
normalize=True).fit(v_calib, v_calib)
|
| 453 |
+
return self
|
| 454 |
+
|
| 455 |
+
def roundtrip_k(self, k):
|
| 456 |
+
kr = self._rope_vq.roundtrip_k(k[..., :self._cut])
|
| 457 |
+
kp = self._pass_vq.roundtrip_k(k[..., self._cut:])
|
| 458 |
+
return torch.cat([kr, kp], dim=-1)
|
| 459 |
+
|
| 460 |
+
def roundtrip_v(self, v):
|
| 461 |
+
return self._v_vq.roundtrip_v(v)
|
| 462 |
+
|
| 463 |
+
def to(self, device):
|
| 464 |
+
self._rope_vq.to(device)
|
| 465 |
+
self._pass_vq.to(device)
|
| 466 |
+
self._v_vq.to(device)
|
| 467 |
+
return self
|
| 468 |
+
|
| 469 |
+
def bits_per_element(self, head_dim):
|
| 470 |
+
# both halves use n_sub_half codes over head_dim/2 elements
|
| 471 |
+
half = head_dim / 2
|
| 472 |
+
sub_dim = half / self.n_sub_half
|
| 473 |
+
return math.log2(self.n_codes) / sub_dim
|
| 474 |
+
|
| 475 |
+
|
| 476 |
+
# ----------------------------------------------------------------------------
|
| 477 |
+
# Data-oblivious baseline: a simplified TurboQuant-style quantizer
|
| 478 |
+
# ----------------------------------------------------------------------------
|
| 479 |
+
@dataclass
|
| 480 |
+
class RandomRotationScalarKV:
|
| 481 |
+
"""Simplified, data-OBLIVIOUS rotation-then-scalar quantizer in the spirit
|
| 482 |
+
of TurboQuant / PolarQuant (Zandieh et al., ICLR 2026).
|
| 483 |
+
|
| 484 |
+
NOT the full method: TurboQuant adds PolarQuant's normalization-free polar
|
| 485 |
+
transform and a 1-bit QJL residual correction for UNBIASED inner-product
|
| 486 |
+
estimation. This stand-in captures only the core data-oblivious idea --
|
| 487 |
+
apply a fixed random orthogonal rotation so coordinates concentrate (a
|
| 488 |
+
Beta/Gaussian-like distribution), then scalar-quantize each coordinate with
|
| 489 |
+
a fixed range. It exists so the harness can run the central scientific
|
| 490 |
+
comparison of this project:
|
| 491 |
+
|
| 492 |
+
data-OBLIVIOUS rotation+scalar vs. data-DEPENDENT product VQ
|
| 493 |
+
|
| 494 |
+
on real Laguna cache statistics. If you want the real thing, drop in the
|
| 495 |
+
unofficial impl (github.com/0xSero/turboquant or hackimov/turboquant-kv)
|
| 496 |
+
behind this same fit/roundtrip interface. The OpenReview discussion of the
|
| 497 |
+
paper is contested precisely on the oblivious-vs-data-dependent claim, so a
|
| 498 |
+
clean head-to-head on a NEW model is a genuine contribution either way.
|
| 499 |
+
"""
|
| 500 |
+
nbits: int = 3
|
| 501 |
+
seed: int = 0
|
| 502 |
+
_R: torch.Tensor = None # rotation
|
| 503 |
+
_Rk_range: tuple = None
|
| 504 |
+
_Rv_range: tuple = None
|
| 505 |
+
|
| 506 |
+
def _rotation(self, d):
|
| 507 |
+
g = torch.Generator().manual_seed(self.seed)
|
| 508 |
+
a = torch.randn(d, d, generator=g)
|
| 509 |
+
q, _ = torch.linalg.qr(a) # random orthogonal matrix
|
| 510 |
+
return q
|
| 511 |
+
|
| 512 |
+
def fit(self, k_calib, v_calib):
|
| 513 |
+
d = k_calib.shape[-1]
|
| 514 |
+
self._R = self._rotation(d)
|
| 515 |
+
# fixed (data-oblivious-ish) ranges from a robust quantile of rotated calib
|
| 516 |
+
rk = k_calib @ self._R
|
| 517 |
+
rv = v_calib @ self._R
|
| 518 |
+
self._Rk_range = (rk.quantile(0.001), rk.quantile(0.999))
|
| 519 |
+
self._Rv_range = (rv.quantile(0.001), rv.quantile(0.999))
|
| 520 |
+
return self
|
| 521 |
+
|
| 522 |
+
def _rt(self, x, rng):
|
| 523 |
+
xr = x @ self._R
|
| 524 |
+
lo, hi = rng
|
| 525 |
+
qmax = (1 << self.nbits) - 1
|
| 526 |
+
scale = (hi - lo).clamp_min(1e-8) / qmax
|
| 527 |
+
q = torch.clamp(torch.round((xr - lo) / scale), 0, qmax)
|
| 528 |
+
xr_hat = q * scale + lo
|
| 529 |
+
return xr_hat @ self._R.T # rotation is orthogonal, inverse == transpose
|
| 530 |
+
|
| 531 |
+
def roundtrip_k(self, k):
|
| 532 |
+
return self._rt(k, self._Rk_range)
|
| 533 |
+
|
| 534 |
+
def roundtrip_v(self, v):
|
| 535 |
+
return self._rt(v, self._Rv_range)
|
| 536 |
+
|
| 537 |
+
def bits_per_element(self, head_dim):
|
| 538 |
+
# rotation is a fixed matrix (no per-token overhead); ranges are global.
|
| 539 |
+
return float(self.nbits)
|
| 540 |
+
|
| 541 |
+
|
| 542 |
+
# ----------------------------------------------------------------------------
|
| 543 |
+
# 1-bit baselines (the floor of the scalar family; rate-neighbors to VQ)
|
| 544 |
+
# ----------------------------------------------------------------------------
|
| 545 |
+
@dataclass
|
| 546 |
+
class SignScalarKV:
|
| 547 |
+
"""Symmetric 1-bit (sign) quantization with a per-group scale.
|
| 548 |
+
|
| 549 |
+
x_hat = scale * sign(x), scale = mean(|x|) over the group.
|
| 550 |
+
|
| 551 |
+
This is the honest 1-bit floor of the KIVI/quanto scalar family: unlike the
|
| 552 |
+
affine `ScalarKV(nbits=1)`, it stores NO zero-point (cache K/V are ~zero-mean
|
| 553 |
+
after norm), so it is both cheaper and better-centered. Group axis matches
|
| 554 |
+
KIVI conventions: keys per-channel (token axis), values per-token.
|
| 555 |
+
`per_channel_key` toggles the key axis.
|
| 556 |
+
"""
|
| 557 |
+
per_channel_key: bool = True
|
| 558 |
+
group_dim_k: int = 0 # 0 = per-channel (token axis); -1 = per-token
|
| 559 |
+
bits: float = 1.0
|
| 560 |
+
|
| 561 |
+
def fit(self, k_calib, v_calib):
|
| 562 |
+
return self
|
| 563 |
+
|
| 564 |
+
def _sign_q(self, x, dim):
|
| 565 |
+
scale = x.abs().mean(dim=dim, keepdim=True).clamp_min(1e-8)
|
| 566 |
+
return torch.sign(x) * scale
|
| 567 |
+
|
| 568 |
+
def roundtrip_k(self, k):
|
| 569 |
+
dim = 0 if self.per_channel_key else -1
|
| 570 |
+
return self._sign_q(k, dim)
|
| 571 |
+
|
| 572 |
+
def roundtrip_v(self, v):
|
| 573 |
+
return self._sign_q(v, dim=-1)
|
| 574 |
+
|
| 575 |
+
def bits_per_element(self, head_dim):
|
| 576 |
+
# 1 bit/element + one fp16 scale per group amortized over the group.
|
| 577 |
+
# per-token value group = head_dim elements; per-channel key group is the
|
| 578 |
+
# token axis (large), so its scale overhead is negligible. Report ~1 +
|
| 579 |
+
# 16/head_dim as a conservative upper bound for the per-token case.
|
| 580 |
+
return 1.0 + 16.0 / head_dim
|
| 581 |
+
|
| 582 |
+
|
| 583 |
+
@dataclass
|
| 584 |
+
class TernaryScalarKV:
|
| 585 |
+
"""1.58-bit ternary quantization {-1, 0, +1} with a per-group scale, in the
|
| 586 |
+
style of BitNet b1.58 -- the "just go (almost) 1-bit" school that the
|
| 587 |
+
Bonsai/PrismML line popularized. Zeros are assigned by a threshold at a
|
| 588 |
+
fraction of the mean-abs, letting small-magnitude coordinates drop out.
|
| 589 |
+
|
| 590 |
+
thr = alpha * mean(|x|); x_hat = scale * {sign(x) if |x|>thr else 0}
|
| 591 |
+
|
| 592 |
+
Rate ~ log2(3) ~ 1.58 bits/element. Included so the table spans the full
|
| 593 |
+
aggressive regime and so a reviewer who knows BitNet sees the comparison.
|
| 594 |
+
"""
|
| 595 |
+
alpha: float = 0.7
|
| 596 |
+
per_channel_key: bool = True
|
| 597 |
+
|
| 598 |
+
def fit(self, k_calib, v_calib):
|
| 599 |
+
return self
|
| 600 |
+
|
| 601 |
+
def _tern_q(self, x, dim):
|
| 602 |
+
m = x.abs().mean(dim=dim, keepdim=True).clamp_min(1e-8)
|
| 603 |
+
thr = self.alpha * m
|
| 604 |
+
mask = (x.abs() > thr).to(x.dtype)
|
| 605 |
+
scale = (x.abs() * mask).sum(dim=dim, keepdim=True) / \
|
| 606 |
+
mask.sum(dim=dim, keepdim=True).clamp_min(1.0)
|
| 607 |
+
return torch.sign(x) * mask * scale
|
| 608 |
+
|
| 609 |
+
def roundtrip_k(self, k):
|
| 610 |
+
dim = 0 if self.per_channel_key else -1
|
| 611 |
+
return self._tern_q(k, dim)
|
| 612 |
+
|
| 613 |
+
def roundtrip_v(self, v):
|
| 614 |
+
return self._tern_q(v, dim=-1)
|
| 615 |
+
|
| 616 |
+
def bits_per_element(self, head_dim):
|
| 617 |
+
import math as _m
|
| 618 |
+
return _m.log2(3) + 16.0 / head_dim
|