File size: 7,337 Bytes
163f417
 
 
292780d
 
163f417
292780d
23d974d
 
 
 
163f417
 
292780d
 
 
 
 
163f417
 
 
 
 
 
 
 
 
 
 
292780d
163f417
292780d
163f417
 
 
292780d
23d974d
292780d
163f417
 
292780d
163f417
292780d
 
 
 
 
 
 
 
 
 
 
 
 
163f417
 
292780d
163f417
292780d
 
 
 
 
 
 
 
 
 
 
 
 
 
163f417
 
292780d
 
 
 
163f417
292780d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
163f417
2b572cf
292780d
2b572cf
5c37c08
 
 
 
292780d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
23d974d
 
 
 
 
 
 
 
 
 
 
 
292780d
 
 
 
 
 
 
 
 
 
 
 
 
163f417
 
292780d
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
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
"""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 ./SPACE_README.md and ./HF_SPACE_DEPLOY.md.
By default uses Qwen3-0.6B (head_dim=128, GQA 16/8, E8-compatible) —
fits on a free HF Space CPU and is architecturally closer to production
LLMs than Qwen2-0.5B. Swap to Qwen/Qwen3-1.7B or Qwen/Qwen3-4B
(GPU Space) for faster / longer comparisons.

The demo shows, side-by-side, the same prompt generated under:
  (a) bf16 DynamicCache — reference
  (b) KakeyaLatticeCache E8 Q=10  (aggressive, highest KV compression)
  (c) KakeyaLatticeCache E8 Q=38  (balanced)
  (d) KakeyaLatticeCache E8 Q=152 (near-lossless)
and reports wall-clock + bits/vec vs bf16 baseline.
"""
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/Qwen3-0.6B")
DEFAULT_PROMPT = "List five countries in Africa:"
_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)
    bf16_bits = head_dim * 16  # reference: bits per token per head in bf16

    results = []

    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, bf16_bits))

    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 bf16_bits
            results.append((f"KakeyaLattice {label}", text, t, bits))
        except Exception as e:
            results.append((f"KakeyaLattice {label} (FAILED)", f"Error: {e}", 0.0, 0))

    header = (
        f"**Model:** `{model_id}` | **head_dim:** {head_dim} | "
        f"**device:** {device} | **new_tokens:** {max_new} | "
        f"**bf16 reference bits/vec:** {bf16_bits}"
    )

    rows = []
    for (name, text, t, bits) in results:
        if bits > 0:
            cr = bf16_bits / bits
            bit_saving = (1 - bits / bf16_bits) * 100
            cr_str = f"{cr:.2f}x"
            cr_detail = f"{bit_saving:+.0f}% bits vs bf16"
        else:
            cr_str = "n/a"
            cr_detail = "failed"
        rows.append(
            f"\n### {name}\n\n"
            f"- **latency:** {t:.2f}s\n"
            f"- **bits/vec:** {bits} (bf16 ref: {bf16_bits})\n"
            f"- **Compression:** {cr_str} ({cr_detail})\n\n"
            f"{text}"
        )
    return header, *rows


EXAMPLE_PROMPTS = [
    ["List five countries in Africa:"],
    ["Translate 'good morning' into French, Spanish, German, and Japanese:"],
    ["Write a two-sentence summary of what a transformer is in machine learning:"],
    ["What is 17 times 23? Show your work step by step."],
]


with gr.Blocks(title="KakeyaLattice KV-cache compression") as demo:
    gr.Markdown(
        "# KakeyaLattice KV-cache compression\n\n"
        "By dynamically adapting to the empirical non-Gaussian patterns and "
        "heavy-tail characteristics of real LLM KV activations, our solution "
        "achieves near-lossless compression and performance gains on models "
        "like Qwen3."
    )
    with gr.Row():
        prompt = gr.Textbox(
            label="Prompt",
            value=DEFAULT_PROMPT,
            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")

    gr.Examples(
        examples=EXAMPLE_PROMPTS,
        inputs=[prompt],
        label="Example prompts (click to fill)",
    )

    gr.Markdown(
        "### About the default model\n\n"
        f"The default model is **{DEFAULT_MODEL}** (0.6B params, head_dim=128, "
        "GQA 16/8). It runs on a free HF Space CPU in roughly 4–8 minutes per "
        "'Run comparison' click (four generations × ~128 tokens each on 2 "
        "cores). That is slow but deliberate: Qwen3's head_dim=128 + GQA is "
        "the same shape used by most production LLMs, so the E8 codec numbers "
        "you see here are representative.\n\n"
        "Small models can still fall into greedy-decode repetition loops on "
        "open-ended prompts — that is a property of the **model**, not the "
        "codec. If you see all four outputs repeating the same phrase, try a "
        "short, fact-shaped prompt (e.g. \"List five countries in Africa:\"). "
        "For faster decode / larger context, switch to a GPU Space and set "
        "`KAKEYA_DEMO_MODEL=Qwen/Qwen3-1.7B` or `Qwen/Qwen3-4B`."
    )

    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="0.0.0.0", server_port=7860)