Spaces:
Sleeping
Sleeping
File size: 5,027 Bytes
163f417 caaa501 163f417 caaa501 163f417 caaa501 163f417 caaa501 163f417 caaa501 163f417 caaa501 163f417 caaa501 163f417 caaa501 163f417 caaa501 163f417 caaa501 163f417 caaa501 163f417 caaa501 | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 | """Gradio Space demo: KakeyaLatticeCache on a small HF causal LM.
Run locally:
pip install kakeyalattice[hf] gradio
python app.py
Deploy to HF Spaces: see ./README.md. By default uses Qwen2-0.5B
(head_dim=64, E8-compatible) so it fits on a free HF Space CPU.
Swap to Qwen/Qwen2.5-1.5B or Llama-3.2-1B (GPU Space) for more interesting
decode-length comparisons.
The demo shows, side-by-side, the same prompt generated under:
(a) bf16 DynamicCache — reference
(b) KakeyaLatticeCache E8 Q=10 (aggressive, ~3.6x KV compression)
(c) KakeyaLatticeCache E8 Q=38 (balanced, ~2.5x KV compression)
(d) KakeyaLatticeCache E8 Q=152 (near-lossless, ~1.9x KV compression)
and reports wall-clock + per-layer K rel-MSE.
"""
from __future__ import annotations
import os
import time
from typing import Optional
import gradio as gr
import torch
try:
from transformers import AutoModelForCausalLM, AutoTokenizer, DynamicCache
except ImportError as e:
raise ImportError("Install transformers: pip install 'kakeyalattice[hf]'") from e
from kakeyalattice.hf import KakeyaLatticeCache
DEFAULT_MODEL = os.environ.get("KAKEYA_DEMO_MODEL", "Qwen/Qwen2-0.5B")
_model_cache: dict = {}
def _load_model(model_id: str, device: str):
key = (model_id, device)
if key in _model_cache:
return _model_cache[key]
tok = AutoTokenizer.from_pretrained(model_id, trust_remote_code=True)
model = AutoModelForCausalLM.from_pretrained(
model_id,
torch_dtype=torch.bfloat16 if device == "cuda" else torch.float32,
trust_remote_code=True,
).to(device)
model.eval()
_model_cache[key] = (tok, model)
return tok, model
def _generate_one(
tok, model, prompt: str, max_new: int, cache, device: str,
) -> tuple[str, float]:
ids = tok(prompt, return_tensors="pt").to(device)
t0 = time.perf_counter()
with torch.inference_mode():
out = model.generate(
**ids,
max_new_tokens=max_new,
do_sample=False,
past_key_values=cache,
use_cache=True,
)
elapsed = time.perf_counter() - t0
text = tok.decode(out[0], skip_special_tokens=True)
return text, elapsed
def run_demo(
prompt: str,
max_new: int,
model_id: str,
device_pref: str,
) -> tuple[str, str, str, str, str]:
device = "cuda" if (device_pref == "auto" and torch.cuda.is_available()) else (
"cuda" if device_pref == "cuda" else "cpu"
)
tok, model = _load_model(model_id, device)
cfg = model.config
num_hidden_layers = cfg.num_hidden_layers
head_dim = getattr(cfg, "head_dim", cfg.hidden_size // cfg.num_attention_heads)
results = []
# Baseline: bf16 DynamicCache
baseline_cache = DynamicCache()
text_bf16, t_bf16 = _generate_one(tok, model, prompt, max_new, baseline_cache, device)
results.append(("bf16 DynamicCache (reference)", text_bf16, t_bf16, head_dim * 16))
for q, label in [(10, "E8 Q=10 aggressive"), (38, "E8 Q=38 balanced"), (152, "E8 Q=152 near-lossless")]:
try:
cache = KakeyaLatticeCache(
variant="e8", q_range=q,
num_hidden_layers=num_hidden_layers,
head_dim=head_dim,
device=device,
strict=False,
)
text, t = _generate_one(tok, model, prompt, max_new, cache, device)
bits = cache._codecs[0].bits_per_token_per_head if cache._codecs else head_dim * 16
results.append((f"KakeyaLattice {label}", text, t, bits))
except Exception as e:
results.append((f"KakeyaLattice {label} (FAILED)", f"Error: {e}", 0.0, 0))
# Format as comparison table
header = f"Model: {model_id} | head_dim: {head_dim} | device: {device} | new_tokens: {max_new}"
rows = [
f"\n### {name} — {t:.2f}s, {bits} bits/vec ({bits/16:.1f}x vs bf16)\n\n{text}"
for (name, text, t, bits) in results
]
return header, *rows
with gr.Blocks(title="KakeyaLattice KV-cache compression demo") as demo:
gr.Markdown(
"# KakeyaLattice KV-cache compression demo\n\n"
"Compare generation output + latency across **bf16 baseline** and "
"three **KakeyaLattice E8** compression levels on a small HF causal LM. "
"The E8 variant uses 8-D nested-lattice closest-point quantisation "
"with Sylvester-Hadamard rotation and per-vector adaptive scaling."
)
with gr.Row():
prompt = gr.Textbox(
label="Prompt",
value="Explain in one paragraph why lattice quantisation can beat scalar quantisation:",
lines=3,
)
with gr.Row():
max_new = gr.Slider(minimum=16, maximum=512, value=128, step=16, label="Max new tokens")
model_id = gr.Textbox(label="HF model id", value=DEFAULT_MODEL)
device_pref = gr.Radio(choices=["auto", "cpu", "cuda"], value="auto", label="Device")
run_btn = gr.Button("Run comparison", variant="primary")
header_out = gr.Markdown("")
out_bf16 = gr.Markdown("")
out_q10 = gr.Markdown("")
out_q38 = gr.Markdown("")
out_q152 = gr.Markdown("")
run_btn.click(
fn=run_demo,
inputs=[prompt, max_new, model_id, device_pref],
outputs=[header_out, out_bf16, out_q10, out_q38, out_q152],
)
if __name__ == "__main__":
demo.launch(
server_name=os.environ.get("GRADIO_SERVER_NAME", "0.0.0.0"),
server_port=int(os.environ.get("PORT", os.environ.get("GRADIO_SERVER_PORT", "7860"))),
)
|