kv-landlords / scripts /longctx_sweep.py
a1exxd0's picture
Upload kv-quant (INT4/NVFP4 KIVI) work + vLLM fork source
6e668dc verified
Raw
History Blame Contribute Delete
6.16 kB
"""Context-length SWEEP of HumanEval pass@1: BF16 vs INT4-KIVI on Laguna-XS.2.
Runs the long-prefix HumanEval protocol at increasing prefix lengths in ONE
process (model loaded once) and prints a context-vs-accuracy table. Reuses the
validated prefix builder, prompt construction, extraction and execution from
``scripts.longctx_code_bench`` / ``scripts.humaneval_bench`` -- this file only
orchestrates the sweep.
For each prefix size we report: BF16 pass@1, INT4-KIVI pass@1, per-problem
agreement, actual mean context length, INT4 cache compression ratio, mean gen
time, and (at the largest cell that ran) the INT4 vs BF16 KV memory.
Usage
-----
.venv/bin/python scripts/longctx_sweep.py --n 8 --max-new 128 \
--prefixes 8000,16000,32000,64000,128000
"""
from __future__ import annotations
import argparse
import sys
import time
import torch
from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer, DynamicCache
ROOT = "/home/alex/poolside-hackathon-kv-quant"
sys.path.insert(0, ROOT)
from scripts.humaneval_bench import extract_code, run_tests
from scripts.longctx_code_bench import build_prefix_text, build_input_ids
from int4_kivi.hf_cache import Int4KiviCache
MODEL = "poolside/Laguna-XS.2"
@torch.no_grad()
def generate(model, input_ids, max_new, mode, config):
cache = Int4KiviCache(config=config) if mode == "int4" else DynamicCache()
out = model.generate(
input_ids, max_new_tokens=max_new, past_key_values=cache,
use_cache=True, do_sample=False, num_beams=1,
)
return out[0, input_ids.shape[1]:].tolist(), cache
def main():
ap = argparse.ArgumentParser()
ap.add_argument("--n", type=int, default=8)
ap.add_argument("--max-new", type=int, default=128)
ap.add_argument("--prefixes", type=str, default="8000,16000,32000,64000,128000")
ap.add_argument("--n-large", type=int, default=0,
help="if >0, override --n for prefixes >= --large-thresh")
ap.add_argument("--large-thresh", type=int, default=100000)
args = ap.parse_args()
prefixes = [int(x) for x in args.prefixes.split(",") if x]
print(f"[load] {MODEL} ...", flush=True)
tok = AutoTokenizer.from_pretrained(MODEL)
model = AutoModelForCausalLM.from_pretrained(
MODEL, dtype=torch.bfloat16, device_map="auto"
).eval()
device = next(model.parameters()).device
full_ds = load_dataset("openai/openai_humaneval", split="test")
table = [] # (prefix_target, ctx_mean, n, bf, iq, agree, ratio, t_mean,
# int4_bytes, bf16_bytes)
for ptarget in prefixes:
n = args.n
if args.n_large and ptarget >= args.large_thresh:
n = args.n_large
ds = full_ds.select(range(n))
print(f"\n{'':=<92}")
print(f" PREFIX TARGET {ptarget} tokens (n={n}, max_new={args.max_new})")
print(f"{'':=<92}", flush=True)
prefix_text, prefix_tok = build_prefix_text(tok, ptarget)
print(f" [prefix] actual prefix length: {prefix_tok} tokens", flush=True)
print(f" {'#':>3} {'task_id':<16} {'BF16':>5} {'INT4':>5} {'ctx':>8} "
f"{'bf16_s':>7} {'int4_s':>7}", flush=True)
res = {"bf16": [], "int4": []}
ctxs, ratios, times = [], [], []
last_int4_bytes = last_bf16_bytes = 0
for idx, prob in enumerate(ds):
prompt, tests, entry = prob["prompt"], prob["test"], prob["entry_point"]
input_ids = build_input_ids(tok, prompt, prefix_text, device)
ctx = int(input_ids.shape[1]); ctxs.append(ctx)
row, tnote = {}, {}
for mode in ("bf16", "int4"):
t0 = time.time()
ids, cache = generate(model, input_ids, args.max_new, mode,
model.config)
dt = time.time() - t0; tnote[mode] = dt
resp = tok.decode(ids, skip_special_tokens=True)
code = extract_code(resp, prompt)
passed, _ = run_tests(code, tests, entry)
res[mode].append(passed); row[mode] = passed
if mode == "int4":
try:
ratios.append(cache.compression_ratio_vs_bf16())
last_int4_bytes = cache.nbytes()
last_bf16_bytes = cache.bf16_nbytes()
except Exception:
pass
times.append(dt)
print(f" {idx+1:>3} {prob['task_id']:<16} "
f"{'PASS' if row['bf16'] else 'fail':>5} "
f"{'PASS' if row['int4'] else 'fail':>5} {ctx:>8} "
f"{tnote['bf16']:>6.1f}s {tnote['int4']:>6.1f}s", flush=True)
bf, iq = sum(res["bf16"]), sum(res["int4"])
agree = sum(a == b for a, b in zip(res["bf16"], res["int4"]))
ctx_m = sum(ctxs) // len(ctxs)
ratio_m = (sum(ratios) / len(ratios)) if ratios else 0.0
t_m = (sum(times) / len(times)) if times else 0.0
print(f" -> ctx~{ctx_m} BF16={bf}/{n} INT4={iq}/{n} "
f"agree={agree}/{n} ratio={ratio_m:.2f}x "
f"int4_KV={last_int4_bytes/2**20:.0f}MiB "
f"bf16_KV={last_bf16_bytes/2**20:.0f}MiB", flush=True)
table.append((ptarget, ctx_m, n, bf, iq, agree, ratio_m, t_m,
last_int4_bytes, last_bf16_bytes))
# ---- final table ----
print(f"\n{'':#<100}")
print(" CONTEXT-LENGTH SWEEP -- HumanEval pass@1, BF16 vs INT4-KIVI "
"(executed, greedy, batch-1)")
print(f"{'':#<100}")
print(f" {'ctx_tok':>8} {'n':>3} {'BF16':>10} {'INT4-KIVI':>12} "
f"{'agree':>8} {'ratio':>7} {'int4_KV':>9} {'bf16_KV':>9} {'t/gen':>7}")
print(f"{'':-<100}")
for (pt, ctx, n, bf, iq, ag, ratio, tm, ib, bb) in table:
print(f" {ctx:>8} {n:>3} {bf:>3}/{n:<3}({100*bf/n:>3.0f}%) "
f"{iq:>3}/{n:<3}({100*iq/n:>3.0f}%) {ag:>3}/{n:<3} "
f"{ratio:>6.2f}x {ib/2**20:>7.0f}M {bb/2**20:>7.0f}M {tm:>6.1f}s")
print(f"{'':-<100}")
print("\n[done]", flush=True)
if __name__ == "__main__":
main()