| """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] |
|
|
|
|
| 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() |
|
|