kv-landlords / scripts /quant_longctx.py
a1exxd0's picture
Upload kv-quant (INT4/NVFP4 KIVI) work + vLLM fork source
6e668dc verified
Raw
History Blame Contribute Delete
6.21 kB
"""Long-context test: does the INT4-KIVI vs NVFP4-baseline gap grow with context?
Scores long in-distribution sequences (real source code, in-distribution for a
code model) in a SINGLE forward per scheme. KV quantization is injected by
patching scaled_dot_product_attention so position t attends to a quantized
prefix. Per-position KL(bf16||scheme) and top-1 agreement are binned by sequence
position, revealing whether quantization error compounds as context lengthens.
All cached K/V are quantized (the <16-token bf16 hot page is omitted — negligible
at long context, and a slightly conservative choice).
Usage:
python -m scripts.quant_longctx [--ctx 8000] [--windows 3]
"""
from __future__ import annotations
import argparse
import glob
import sys
import torch
import torch.nn.functional as F
from transformers import AutoModelForCausalLM, AutoTokenizer
sys.path.insert(0, "/home/alex/poolside-hackathon-kv-quant")
from kv_quant import roundtrip
MODEL = "poolside/Laguna-XS.2"
N_ALPHAS = 32
SCHEMES = {
"nvfp4-baseline": {"k": ("nvfp4", "headdim", "absmax"), "v": ("nvfp4", "headdim", "absmax")},
"int4-kivi": {"k": ("int4", "channel", "mse"), "v": ("int4", "headdim", "mse")},
"int3-kivi": {"k": ("int3", "channel", "mse"), "v": ("int3", "headdim", "mse")},
"int3-naive": {"k": ("int3", "headdim", "absmax"), "v": ("int3", "headdim", "absmax")},
}
BASELINE = "nvfp4-baseline"
BINS = [(0, 512), (512, 1024), (1024, 2048), (2048, 4096), (4096, 8192)]
_ORIG_SDPA = F.scaled_dot_product_attention
_SCHEME = None
_HITS = 0
def _q_per_head(x, cell):
"""Quantize [H, S, D] head-by-head (caps the MSE-search peak memory)."""
return torch.stack([roundtrip(x[h:h + 1], *cell, n_alphas=N_ALPHAS)[0]
for h in range(x.shape[0])])
def _patched_sdpa(query, key, value, *a, **kw):
global _HITS
if _SCHEME is not None:
_HITS += 1
key = _q_per_head(key[0], _SCHEME["k"]).unsqueeze(0)
value = _q_per_head(value[0], _SCHEME["v"]).unsqueeze(0)
return _ORIG_SDPA(query, key, value, *a, **kw)
def token_pool(tok):
files = sorted(glob.glob(
"/home/alex/poolside-hackathon-kv-quant/.venv/**/transformers/**/modeling_*.py",
recursive=True))
texts, total = [], 0
for f in files:
try:
t = open(f).read()
except OSError:
continue
texts.append(t)
total += len(t)
if total > 600_000:
break
ids = tok("\n\n".join(texts), return_tensors="pt").input_ids[0]
return ids
@torch.no_grad()
def logits_of(model, ids):
return model(input_ids=ids.unsqueeze(0)).logits[0] # [S, V], bf16
def kl_top1(ref, lg, ref_arg, chunk=512):
"""Per-position KL(bf16||scheme) and top-1 match. ref/lg bf16 [S,V]."""
S = ref.shape[0]
kls, t1 = [], []
for i in range(0, S, chunk):
r, q = ref[i:i + chunk].float(), lg[i:i + chunk].float()
rlp, qlp = torch.log_softmax(r, -1), torch.log_softmax(q, -1)
kls.append((rlp.exp() * (rlp - qlp)).sum(-1))
t1.append((q.argmax(-1) == ref_arg[i:i + chunk]).float())
return torch.cat(kls), torch.cat(t1)
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--ctx", type=int, default=8000)
ap.add_argument("--windows", type=int, default=3)
args = ap.parse_args()
global _SCHEME, _HITS
F.scaled_dot_product_attention = _patched_sdpa
torch.nn.functional.scaled_dot_product_attention = _patched_sdpa
print(f"[load] {MODEL}", flush=True)
tok = AutoTokenizer.from_pretrained(MODEL)
model = AutoModelForCausalLM.from_pretrained(
MODEL, torch_dtype=torch.bfloat16, device_map="auto", attn_implementation="sdpa")
model.eval()
device = next(model.parameters()).device
pool = token_pool(tok)
ctx = (args.ctx // 16) * 16
nwin = min(args.windows, pool.shape[0] // ctx)
print(f"[seq] {nwin} windows x {ctx} tokens (pool={pool.shape[0]})", flush=True)
bins = [(lo, hi) for lo, hi in BINS if lo < ctx]
acc = {n: {b: {"kl": [], "t1": []} for b in bins} for n in SCHEMES}
for w in range(nwin):
ids = pool[w * ctx:(w + 1) * ctx].to(device)
_SCHEME = None
ref = logits_of(model, ids)
ref_arg = ref.argmax(-1)
for name, sch in SCHEMES.items():
_HITS = 0
_SCHEME = sch
lg = logits_of(model, ids)
_SCHEME = None
assert _HITS > 0, f"SDPA patch never fired for {name} — wrong attn path"
kl, t1 = kl_top1(ref, lg, ref_arg)
for lo, hi in bins:
acc[name][(lo, hi)]["kl"].append(kl[lo:min(hi, ctx)].mean().item())
acc[name][(lo, hi)]["t1"].append(t1[lo:min(hi, ctx)].mean().item())
del lg
print(f"[window {w}] done", flush=True)
avg = lambda xs: sum(xs) / max(len(xs), 1)
print("\n" + "=" * 74)
print(f"PER-POSITION KL(bf16||scheme), avg over {nwin} windows")
print(f" {'position':<12}" + "".join(f"{n[:13]:>14}" for n in SCHEMES))
print(f" {'-'*12}" + "".join(f" {'-'*13}" for _ in SCHEMES))
for lo, hi in bins:
row = f" {f'{lo}-{min(hi,ctx)}':<12}"
for n in SCHEMES:
row += f"{avg(acc[n][(lo,hi)]['kl']):>14.5f}"
print(row)
print(f"\nKIVI vs baseline: KL reduction by position (does the win grow?)")
print(f" {'position':<12}{'int4-kivi':>14}{'int3-kivi':>14}")
print(f" {'-'*12}{' '+'-'*13}{' '+'-'*13}")
for lo, hi in bins:
b = avg(acc[BASELINE][(lo, hi)]["kl"])
i4 = avg(acc["int4-kivi"][(lo, hi)]["kl"])
i3 = avg(acc["int3-kivi"][(lo, hi)]["kl"])
row = f" {f'{lo}-{min(hi,ctx)}':<12}"
row += f"{100*(b-i4)/max(b,1e-12):>13.0f}%"
row += f"{100*(b-i3)/max(b,1e-12):>13.0f}%"
print(row)
print(f"\nTOP-1 agreement vs bf16 by position")
print(f" {'position':<12}" + "".join(f"{n[:13]:>14}" for n in SCHEMES))
for lo, hi in bins:
row = f" {f'{lo}-{min(hi,ctx)}':<12}"
for n in SCHEMES:
row += f"{100*avg(acc[n][(lo,hi)]['t1']):>13.1f}%"
print(row)
if __name__ == "__main__":
main()