MaduRox commited on
Commit
ae7c56f
·
1 Parent(s): 095ce37

feat: add real empirical benchmark harness with /run_benchmark endpoint

Browse files
Files changed (3) hide show
  1. app.py +83 -9
  2. benchmark_real.py +480 -0
  3. requirements.txt +1 -0
app.py CHANGED
@@ -3,6 +3,7 @@ import time
3
  import traceback
4
  import base64
5
  import threading
 
6
 
7
  # Set HF token
8
  _VALID_HF_TOKEN = base64.b64decode('aGZfU09rZ0JjR1NvdXZRRVNEZ09Xbnl5dk9BRWFablREeFZX').decode('utf-8')
@@ -104,11 +105,53 @@ def _ui_predict(prompt: str, max_tokens: float, temperature: float):
104
  f"{res['layers_intercepted']}/24 Layers"
105
  )
106
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
107
  # ── Gradio 5 ZeroGPU-Native UI & API ──────────────────────────────────────────
108
  with gr.Blocks(title="Kalpanā API — ZeroGPU NVIDIA A100", theme=gr.themes.Soft()) as demo:
109
  gr.Markdown(
110
  "# ⚡ Kalpanā RIF O(1) Memory API & Inference Server\n"
111
- "Production constant-memory neural inference powered by **NVIDIA A100 80GB GPU** (`zero-a10g`).\n\n"
112
  "👉 **Visual Studio Frontend:** [Kalpana RIF Studio](https://huggingface.co/spaces/MaduRox/Kalpana-RIF-Studio)"
113
  )
114
  with gr.Tabs():
@@ -134,6 +177,34 @@ with gr.Blocks(title="Kalpanā API — ZeroGPU NVIDIA A100", theme=gr.themes.Sof
134
  api_name="generate"
135
  )
136
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
137
  with gr.TabItem("📖 Swagger & REST API Reference"):
138
  gr.Markdown(
139
  """
@@ -209,15 +280,18 @@ console.log("Output:", text);
209
 
210
  ---
211
 
212
- #### 🐧 5. Linux / macOS cURL (2-Step Flow)
213
- ```bash
214
- # Step 1: POST prompt (returns event_id)
215
- EVENT_ID=$(curl -s -X POST https://madurox-kalpana-api-gpu.hf.space/gradio_api/call/generate \\
216
- -H "Content-Type: application/json" \\
217
- -d '{"data": ["What is cricket?", 128, 0.7]}' | jq -r .event_id)
218
 
219
- # Step 2: GET generated answer
220
- curl https://madurox-kalpana-api-gpu.hf.space/gradio_api/call/generate/$EVENT_ID
 
 
 
 
 
 
221
  ```
222
  """
223
  )
 
3
  import traceback
4
  import base64
5
  import threading
6
+ import json
7
 
8
  # Set HF token
9
  _VALID_HF_TOKEN = base64.b64decode('aGZfU09rZ0JjR1NvdXZRRVNEZ09Xbnl5dk9BRWFablREeFZX').decode('utf-8')
 
105
  f"{res['layers_intercepted']}/24 Layers"
106
  )
107
 
108
+ # ── Benchmark endpoint ─────────────────────────────────────────────────────
109
+ @spaces.GPU(duration=300)
110
+ def run_benchmark_endpoint(context_lengths_str: str = "128,256,512,1024,2048") -> str:
111
+ """
112
+ Run the real empirical benchmark harness.
113
+ Returns JSON with all measured metrics.
114
+ """
115
+ try:
116
+ from benchmark_real import run_benchmark
117
+
118
+ # Parse context lengths
119
+ ctx_lengths = [int(x.strip()) for x in context_lengths_str.split(",") if x.strip()]
120
+ if not ctx_lengths:
121
+ ctx_lengths = [128, 256, 512, 1024, 2048]
122
+
123
+ # Cap at 4096 for safety on T4
124
+ ctx_lengths = [c for c in ctx_lengths if c <= 4096]
125
+
126
+ # Only run fidelity on short lengths
127
+ fidelity_lengths = [c for c in ctx_lengths if c <= 512]
128
+
129
+ result = run_benchmark(
130
+ context_lengths=ctx_lengths,
131
+ num_gen_tokens=10,
132
+ run_fidelity=True,
133
+ fidelity_lengths=fidelity_lengths,
134
+ )
135
+
136
+ # Save to space
137
+ out_path = os.path.join(os.path.dirname(__file__), "benchmark_results.json")
138
+ with open(out_path, "w") as f:
139
+ json.dump(result, f, indent=2, default=str)
140
+
141
+ return json.dumps(result, indent=2, default=str)
142
+
143
+ except Exception as e:
144
+ return json.dumps({
145
+ "error": str(e),
146
+ "traceback": traceback.format_exc()
147
+ }, indent=2)
148
+
149
+
150
  # ── Gradio 5 ZeroGPU-Native UI & API ──────────────────────────────────────────
151
  with gr.Blocks(title="Kalpanā API — ZeroGPU NVIDIA A100", theme=gr.themes.Soft()) as demo:
152
  gr.Markdown(
153
  "# ⚡ Kalpanā RIF O(1) Memory API & Inference Server\n"
154
+ "Production constant-memory neural inference powered by **NVIDIA GPU** (dedicated).\n\n"
155
  "👉 **Visual Studio Frontend:** [Kalpana RIF Studio](https://huggingface.co/spaces/MaduRox/Kalpana-RIF-Studio)"
156
  )
157
  with gr.Tabs():
 
177
  api_name="generate"
178
  )
179
 
180
+ with gr.TabItem("🔬 Empirical Benchmark"):
181
+ gr.Markdown(
182
+ "## Real Empirical Benchmark Harness\n\n"
183
+ "Run genuine GPU measurements comparing **Standard DynamicCache** vs **KalpanaDynamicCache** vs **SinkCache (StreamingLLM)**.\n\n"
184
+ "Measures: persistent cache size, peak VRAM, prefill time, TTFT, per-token latency, "
185
+ "needle-in-a-haystack recall, and reconstruction fidelity (cosine similarity).\n\n"
186
+ "> ⚠️ **This takes 2-10 minutes depending on context lengths.**"
187
+ )
188
+ with gr.Row():
189
+ ctx_input = gr.Textbox(
190
+ label="Context Lengths (comma-separated)",
191
+ value="128,256,512,1024,2048",
192
+ info="Token counts to benchmark. Max 4096 on T4."
193
+ )
194
+ bench_btn = gr.Button("🔬 Run Real Benchmark", variant="primary")
195
+ bench_output = gr.Textbox(
196
+ label="Benchmark Results (JSON)",
197
+ lines=30,
198
+ interactive=False,
199
+ show_copy_button=True,
200
+ )
201
+ bench_btn.click(
202
+ run_benchmark_endpoint,
203
+ inputs=[ctx_input],
204
+ outputs=[bench_output],
205
+ api_name="run_benchmark"
206
+ )
207
+
208
  with gr.TabItem("📖 Swagger & REST API Reference"):
209
  gr.Markdown(
210
  """
 
280
 
281
  ---
282
 
283
+ #### 🔬 5. Run Real Benchmark (Python)
284
+ ```python
285
+ from gradio_client import Client
 
 
 
286
 
287
+ client = Client("MaduRox/Kalpana-API-GPU")
288
+ result = client.predict(
289
+ context_lengths_str="128,256,512,1024,2048",
290
+ api_name="/run_benchmark"
291
+ )
292
+ import json
293
+ data = json.loads(result)
294
+ print(json.dumps(data, indent=2))
295
  ```
296
  """
297
  )
benchmark_real.py ADDED
@@ -0,0 +1,480 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Kalpana RIF — Real Empirical Benchmark Harness
3
+ ================================================
4
+ Measures ACTUAL GPU memory, latency, and recall at multiple context lengths.
5
+ Compares: Standard DynamicCache vs KalpanaDynamicCache vs SinkCache (StreamingLLM).
6
+
7
+ All numbers are measured, not estimated.
8
+
9
+ CRITICAL NOTE on what is measured:
10
+ - persistent_cache_mb: The stored cache state size (O(1) for Kalpana)
11
+ - peak_vram_mb: PEAK GPU allocation including intermediate tensors during
12
+ forward pass — this includes reconstruction intermediates for Kalpana
13
+ - prefill_time_s: Wall clock to process all input tokens
14
+ - ttft_ms: Time to generate the FIRST output token after prefill
15
+ - avg_token_ms: Average time per generated token
16
+ - reconstruction_cosine_sim: Cosine similarity of Kalpana's reconstructed K/V
17
+ vs ground-truth standard cache K/V (measures information loss)
18
+ """
19
+
20
+ import torch
21
+ import torch.nn.functional as F
22
+ import time
23
+ import json
24
+ import gc
25
+ import os
26
+ import traceback
27
+
28
+ MODEL_NAME = "Qwen/Qwen2.5-0.5B-Instruct"
29
+
30
+ # ---------------------------------------------------------------------------
31
+ # Haystack builder: long filler text with a planted "needle" fact
32
+ # ---------------------------------------------------------------------------
33
+ FILLER = (
34
+ "System telemetry block {i}: harmonic sensor reading at {f:.4f} MHz "
35
+ "with phase offset {p} degrees in monitoring sector {s}. "
36
+ "All parameters within nominal operating range. "
37
+ )
38
+
39
+ NEEDLE_TEMPLATE = (
40
+ "CRITICAL CLASSIFIED FINDING: The secret authorization passkey "
41
+ "for Project Nightingale is {code}. This information is top-secret. "
42
+ )
43
+
44
+ NEEDLE_QUERY = (
45
+ "What is the secret authorization passkey for Project Nightingale? "
46
+ "Reply with ONLY the passkey code, nothing else."
47
+ )
48
+
49
+
50
+ def build_haystack(tokenizer, target_tokens, needle_code, needle_depth_pct=0.5):
51
+ """Build input_ids with a needle fact embedded at specified depth percentage."""
52
+ # Generate filler chunks
53
+ chunks = []
54
+ for i in range(30000):
55
+ chunks.append(FILLER.format(i=i, f=i * 0.31416, p=(i * 37) % 360, s=i % 16))
56
+
57
+ # Estimate tokens per filler chunk
58
+ sample_enc = tokenizer.encode(chunks[0], add_special_tokens=False)
59
+ toks_per_chunk = max(1, len(sample_enc))
60
+
61
+ # Calculate chunks needed (leave room for needle + query + template)
62
+ overhead_tokens = 120 # chat template + query + needle
63
+ content_tokens = max(10, target_tokens - overhead_tokens)
64
+ n_chunks = max(1, content_tokens // toks_per_chunk)
65
+
66
+ # Insert needle at target depth
67
+ needle_idx = max(0, int(n_chunks * needle_depth_pct))
68
+ needle_text = NEEDLE_TEMPLATE.format(code=needle_code)
69
+ chunks_to_use = chunks[:n_chunks]
70
+ chunks_to_use.insert(needle_idx, needle_text)
71
+
72
+ context = " ".join(chunks_to_use)
73
+ full_prompt = context + "\n\nQuestion: " + NEEDLE_QUERY
74
+
75
+ messages = [{"role": "user", "content": full_prompt}]
76
+ formatted = tokenizer.apply_chat_template(
77
+ messages, tokenize=False, add_generation_prompt=True
78
+ )
79
+ input_ids = tokenizer(
80
+ formatted, return_tensors="pt", truncation=True, max_length=target_tokens
81
+ ).input_ids
82
+
83
+ return input_ids
84
+
85
+
86
+ # ---------------------------------------------------------------------------
87
+ # Core measurement function
88
+ # ---------------------------------------------------------------------------
89
+ def measure_one(model, tokenizer, input_ids, cache, cache_name, device, num_gen=10):
90
+ """
91
+ Measure one benchmark point: prefill + generation.
92
+ Returns dict with all measured metrics.
93
+ """
94
+ N = input_ids.shape[1]
95
+
96
+ # Clean slate
97
+ gc.collect()
98
+ torch.cuda.empty_cache()
99
+ torch.cuda.reset_peak_memory_stats(device)
100
+ baseline_vram = torch.cuda.memory_allocated(device)
101
+
102
+ # === PREFILL ===
103
+ t_prefill_start = time.perf_counter()
104
+ try:
105
+ with torch.inference_mode():
106
+ out = model(
107
+ input_ids.to(device), past_key_values=cache, use_cache=True
108
+ )
109
+ torch.cuda.synchronize()
110
+ except Exception as e:
111
+ gc.collect()
112
+ torch.cuda.empty_cache()
113
+ return {
114
+ "cache_type": cache_name,
115
+ "context_length": N,
116
+ "error": f"Prefill failed: {type(e).__name__}: {e}",
117
+ }
118
+ t_prefill_end = time.perf_counter()
119
+
120
+ peak_vram_prefill = torch.cuda.max_memory_allocated(device)
121
+ alloc_after_prefill = torch.cuda.memory_allocated(device)
122
+
123
+ # Persistent cache size
124
+ if hasattr(cache, "get_total_memory_mb"):
125
+ persist_mb = cache.get_total_memory_mb()
126
+ elif hasattr(cache, "key_cache"):
127
+ b = 0
128
+ for t in getattr(cache, "key_cache", []):
129
+ if isinstance(t, torch.Tensor):
130
+ b += t.nelement() * t.element_size()
131
+ for t in getattr(cache, "value_cache", []):
132
+ if isinstance(t, torch.Tensor):
133
+ b += t.nelement() * t.element_size()
134
+ persist_mb = b / (1024 * 1024)
135
+ else:
136
+ persist_mb = -1
137
+
138
+ # === GENERATION (token by token) ===
139
+ torch.cuda.reset_peak_memory_stats(device)
140
+ nxt = out.logits[:, -1:, :].argmax(dim=-1)
141
+ generated_ids = []
142
+ gen_times = []
143
+
144
+ for _ in range(num_gen):
145
+ t0g = time.perf_counter()
146
+ try:
147
+ with torch.inference_mode():
148
+ out = model(nxt, past_key_values=cache, use_cache=True)
149
+ torch.cuda.synchronize()
150
+ except Exception:
151
+ break
152
+ gen_times.append(time.perf_counter() - t0g)
153
+ nxt = out.logits[:, -1:, :].argmax(dim=-1)
154
+ generated_ids.append(nxt.item())
155
+
156
+ peak_vram_gen = torch.cuda.max_memory_allocated(device)
157
+ gen_text = tokenizer.decode(generated_ids, skip_special_tokens=True)
158
+
159
+ del out, nxt
160
+
161
+ return {
162
+ "cache_type": cache_name,
163
+ "context_length": N,
164
+ "persistent_cache_mb": round(persist_mb, 3),
165
+ "peak_vram_prefill_mb": round(peak_vram_prefill / (1024 ** 2), 2),
166
+ "peak_vram_generation_mb": round(peak_vram_gen / (1024 ** 2), 2),
167
+ "vram_delta_after_prefill_mb": round(
168
+ (alloc_after_prefill - baseline_vram) / (1024 ** 2), 2
169
+ ),
170
+ "prefill_time_s": round(t_prefill_end - t_prefill_start, 4),
171
+ "prefill_tok_per_s": round(N / max(1e-6, t_prefill_end - t_prefill_start), 1),
172
+ "ttft_ms": round(gen_times[0] * 1000, 2) if gen_times else None,
173
+ "avg_token_ms": round(
174
+ sum(gen_times) / max(1, len(gen_times)) * 1000, 2
175
+ )
176
+ if gen_times
177
+ else None,
178
+ "tokens_generated": len(generated_ids),
179
+ "generated_text": gen_text[:300],
180
+ }
181
+
182
+
183
+ # ---------------------------------------------------------------------------
184
+ # Reconstruction fidelity: compare Kalpana K/V vs ground-truth
185
+ # ---------------------------------------------------------------------------
186
+ def measure_reconstruction_fidelity(model, tokenizer, input_ids, device, num_layers):
187
+ """
188
+ Compare K/V tensors from standard DynamicCache vs KalpanaDynamicCache.
189
+ Returns per-layer cosine similarity.
190
+ """
191
+ from transformers import DynamicCache
192
+ from kalpana_embed_to_kv import KalpanaDynamicCache
193
+
194
+ N = input_ids.shape[1]
195
+
196
+ # Run standard
197
+ gc.collect()
198
+ torch.cuda.empty_cache()
199
+ std_cache = DynamicCache()
200
+ with torch.inference_mode():
201
+ model(input_ids.to(device), past_key_values=std_cache, use_cache=True)
202
+ torch.cuda.synchronize()
203
+
204
+ # Capture standard K/V
205
+ std_keys = [k.detach().clone() for k in std_cache.key_cache]
206
+ std_vals = [v.detach().clone() for v in std_cache.value_cache]
207
+
208
+ del std_cache
209
+ gc.collect()
210
+ torch.cuda.empty_cache()
211
+
212
+ # Run Kalpana
213
+ kal_cache = KalpanaDynamicCache(
214
+ num_layers=num_layers, bands=2048, sliding_window=128
215
+ )
216
+ with torch.inference_mode():
217
+ model(input_ids.to(device), past_key_values=kal_cache, use_cache=True)
218
+ torch.cuda.synchronize()
219
+
220
+ kal_keys = [k.detach().clone() for k in kal_cache.key_cache]
221
+ kal_vals = [v.detach().clone() for v in kal_cache.value_cache]
222
+
223
+ del kal_cache
224
+ gc.collect()
225
+ torch.cuda.empty_cache()
226
+
227
+ # Compare
228
+ layer_sims = []
229
+ for layer_idx in range(min(len(std_keys), len(kal_keys))):
230
+ sk = std_keys[layer_idx].float().flatten()
231
+ kk = kal_keys[layer_idx].float().flatten()
232
+ sv = std_vals[layer_idx].float().flatten()
233
+ kv = kal_vals[layer_idx].float().flatten()
234
+
235
+ # Shapes might differ if Kalpana hybrid has window + prefix
236
+ min_len_k = min(sk.shape[0], kk.shape[0])
237
+ min_len_v = min(sv.shape[0], kv.shape[0])
238
+
239
+ key_sim = F.cosine_similarity(sk[:min_len_k].unsqueeze(0), kk[:min_len_k].unsqueeze(0)).item()
240
+ val_sim = F.cosine_similarity(sv[:min_len_v].unsqueeze(0), kv[:min_len_v].unsqueeze(0)).item()
241
+
242
+ layer_sims.append({
243
+ "layer": layer_idx,
244
+ "key_cosine_sim": round(key_sim, 6),
245
+ "val_cosine_sim": round(val_sim, 6),
246
+ "std_key_shape": list(std_keys[layer_idx].shape),
247
+ "kal_key_shape": list(kal_keys[layer_idx].shape),
248
+ })
249
+
250
+ del std_keys, std_vals, kal_keys, kal_vals
251
+ gc.collect()
252
+ torch.cuda.empty_cache()
253
+
254
+ avg_key_sim = sum(l["key_cosine_sim"] for l in layer_sims) / max(1, len(layer_sims))
255
+ avg_val_sim = sum(l["val_cosine_sim"] for l in layer_sims) / max(1, len(layer_sims))
256
+
257
+ return {
258
+ "context_length": N,
259
+ "avg_key_cosine_sim": round(avg_key_sim, 6),
260
+ "avg_val_cosine_sim": round(avg_val_sim, 6),
261
+ "per_layer": layer_sims,
262
+ }
263
+
264
+
265
+ # ---------------------------------------------------------------------------
266
+ # Main benchmark runner
267
+ # ---------------------------------------------------------------------------
268
+ def run_benchmark(
269
+ context_lengths=None,
270
+ num_gen_tokens=10,
271
+ run_fidelity=True,
272
+ fidelity_lengths=None,
273
+ ):
274
+ """
275
+ Run the full benchmark suite.
276
+
277
+ Args:
278
+ context_lengths: list of int, token counts to test (default: [128..4096])
279
+ num_gen_tokens: how many tokens to generate per test
280
+ run_fidelity: whether to run reconstruction fidelity comparison
281
+ fidelity_lengths: context lengths for fidelity test (default: [128, 256, 512])
282
+
283
+ Returns:
284
+ dict with metadata and results
285
+ """
286
+ if context_lengths is None:
287
+ context_lengths = [128, 256, 512, 1024, 2048, 4096]
288
+ if fidelity_lengths is None:
289
+ fidelity_lengths = [128, 256, 512]
290
+
291
+ device = "cuda" if torch.cuda.is_available() else "cpu"
292
+ dtype = torch.float16 if device == "cuda" else torch.float32
293
+
294
+ gpu_name = torch.cuda.get_device_name(0) if device == "cuda" else "CPU"
295
+ total_vram = (
296
+ torch.cuda.get_device_properties(0).total_mem / (1024 ** 3)
297
+ if device == "cuda"
298
+ else 0
299
+ )
300
+
301
+ from transformers import AutoModelForCausalLM, AutoTokenizer
302
+
303
+ print(f"[Benchmark] Loading {MODEL_NAME}...")
304
+ tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
305
+ if tokenizer.pad_token_id is None:
306
+ tokenizer.pad_token_id = tokenizer.eos_token_id
307
+
308
+ model = AutoModelForCausalLM.from_pretrained(
309
+ MODEL_NAME, torch_dtype=dtype, low_cpu_mem_usage=True
310
+ ).to(device)
311
+ model.eval()
312
+
313
+ model_vram = (
314
+ torch.cuda.memory_allocated(device) / (1024 ** 2) if device == "cuda" else 0
315
+ )
316
+ num_layers = getattr(model.config, "num_hidden_layers", 24)
317
+ num_kv_heads = getattr(model.config, "num_key_value_heads", 2)
318
+ head_dim = getattr(model.config, "head_dim", 64)
319
+ elem_bytes = 2 if dtype == torch.float16 else 4
320
+
321
+ # Theoretical KV bytes per token for standard cache
322
+ kv_bytes_per_token = num_layers * num_kv_heads * head_dim * 2 * elem_bytes
323
+
324
+ # Theoretical Kalpana persistent state size
325
+ # layers * (K+V) * heads * bands * dim * (real+imag) * fp32
326
+ kalpana_state_bytes = num_layers * 2 * num_kv_heads * 2048 * head_dim * 2 * 4
327
+ kalpana_state_mb = kalpana_state_bytes / (1024 ** 2)
328
+
329
+ meta = {
330
+ "gpu": gpu_name,
331
+ "total_vram_gb": round(total_vram, 1),
332
+ "model": MODEL_NAME,
333
+ "model_vram_mb": round(model_vram, 1),
334
+ "num_layers": num_layers,
335
+ "num_kv_heads": num_kv_heads,
336
+ "head_dim": head_dim,
337
+ "dtype": str(dtype),
338
+ "kv_bytes_per_token_standard": kv_bytes_per_token,
339
+ "kalpana_theoretical_state_mb": round(kalpana_state_mb, 2),
340
+ "timestamp": time.strftime("%Y-%m-%d %H:%M:%S UTC", time.gmtime()),
341
+ }
342
+
343
+ print(f"[Benchmark] GPU: {gpu_name}, VRAM: {total_vram:.1f} GB")
344
+ print(f"[Benchmark] Model VRAM: {model_vram:.1f} MB")
345
+ print(f"[Benchmark] KV bytes/token (standard): {kv_bytes_per_token}")
346
+ print(f"[Benchmark] Kalpana theoretical state: {kalpana_state_mb:.2f} MB")
347
+
348
+ needle_code = "NIGHTINGALE-7749"
349
+ results = []
350
+
351
+ # ── Main scaling benchmark ──
352
+ for ctx_len in context_lengths:
353
+ print(f"\n{'=' * 60}")
354
+ print(f"CONTEXT LENGTH: {ctx_len} tokens")
355
+ print(f"{'=' * 60}")
356
+
357
+ input_ids = build_haystack(tokenizer, ctx_len, needle_code, needle_depth_pct=0.5)
358
+ actual = input_ids.shape[1]
359
+ print(f" Actual input tokens: {actual}")
360
+
361
+ # --- Standard DynamicCache ---
362
+ print(" [1/3] Standard DynamicCache...")
363
+ from transformers import DynamicCache
364
+
365
+ cache = DynamicCache()
366
+ r = measure_one(model, tokenizer, input_ids, cache, "Standard_DynamicCache", device, num_gen_tokens)
367
+ r["needle_code"] = needle_code
368
+ r["needle_found"] = needle_code.lower() in r.get("generated_text", "").lower()
369
+ r["theoretical_cache_mb"] = round(actual * kv_bytes_per_token / (1024 ** 2), 3)
370
+ results.append(r)
371
+ del cache
372
+ gc.collect()
373
+ torch.cuda.empty_cache()
374
+ print(f" prefill={r.get('prefill_time_s')}s peak={r.get('peak_vram_prefill_mb')}MB cache={r.get('persistent_cache_mb')}MB needle={r.get('needle_found')}")
375
+
376
+ # --- KalpanaDynamicCache ---
377
+ print(" [2/3] KalpanaDynamicCache (bands=2048, window=128)...")
378
+ try:
379
+ from kalpana_embed_to_kv import KalpanaDynamicCache
380
+
381
+ cache = KalpanaDynamicCache(
382
+ num_layers=num_layers, bands=2048, sliding_window=128
383
+ )
384
+ r = measure_one(model, tokenizer, input_ids, cache, "Kalpana_RIF", device, num_gen_tokens)
385
+ r["needle_code"] = needle_code
386
+ r["needle_found"] = needle_code.lower() in r.get("generated_text", "").lower()
387
+ r["kalpana_theoretical_state_mb"] = round(kalpana_state_mb, 3)
388
+ results.append(r)
389
+ del cache
390
+ gc.collect()
391
+ torch.cuda.empty_cache()
392
+ print(f" prefill={r.get('prefill_time_s')}s peak={r.get('peak_vram_prefill_mb')}MB persist={r.get('persistent_cache_mb')}MB needle={r.get('needle_found')}")
393
+ except Exception as e:
394
+ err_r = {
395
+ "cache_type": "Kalpana_RIF",
396
+ "context_length": actual,
397
+ "error": f"{type(e).__name__}: {e}",
398
+ }
399
+ results.append(err_r)
400
+ print(f" ERROR: {e}")
401
+ gc.collect()
402
+ torch.cuda.empty_cache()
403
+
404
+ # --- SinkCache (StreamingLLM) ---
405
+ print(" [3/3] SinkCache (StreamingLLM, window=128, sinks=4)...")
406
+ try:
407
+ from transformers import SinkCache
408
+
409
+ cache = SinkCache(window_length=128, num_sink_tokens=4)
410
+ r = measure_one(model, tokenizer, input_ids, cache, "SinkCache_StreamingLLM", device, num_gen_tokens)
411
+ r["needle_code"] = needle_code
412
+ r["needle_found"] = needle_code.lower() in r.get("generated_text", "").lower()
413
+ results.append(r)
414
+ del cache
415
+ gc.collect()
416
+ torch.cuda.empty_cache()
417
+ print(f" prefill={r.get('prefill_time_s')}s peak={r.get('peak_vram_prefill_mb')}MB cache={r.get('persistent_cache_mb')}MB needle={r.get('needle_found')}")
418
+ except ImportError:
419
+ results.append({
420
+ "cache_type": "SinkCache_StreamingLLM",
421
+ "context_length": actual,
422
+ "error": "SinkCache not available in this transformers version",
423
+ })
424
+ print(" SKIPPED (SinkCache not available)")
425
+ except Exception as e:
426
+ results.append({
427
+ "cache_type": "SinkCache_StreamingLLM",
428
+ "context_length": actual,
429
+ "error": f"{type(e).__name__}: {e}",
430
+ })
431
+ print(f" ERROR: {e}")
432
+ gc.collect()
433
+ torch.cuda.empty_cache()
434
+
435
+ # ── Reconstruction fidelity test ──
436
+ fidelity_results = []
437
+ if run_fidelity:
438
+ print(f"\n{'=' * 60}")
439
+ print("RECONSTRUCTION FIDELITY TEST")
440
+ print(f"{'=' * 60}")
441
+
442
+ for fl in fidelity_lengths:
443
+ if fl > max(context_lengths):
444
+ continue
445
+ print(f" Fidelity test at {fl} tokens...")
446
+ try:
447
+ input_ids = build_haystack(tokenizer, fl, needle_code)
448
+ fr = measure_reconstruction_fidelity(
449
+ model, tokenizer, input_ids, device, num_layers
450
+ )
451
+ fidelity_results.append(fr)
452
+ print(f" avg_key_sim={fr['avg_key_cosine_sim']:.6f} avg_val_sim={fr['avg_val_cosine_sim']:.6f}")
453
+ except Exception as e:
454
+ fidelity_results.append({
455
+ "context_length": fl,
456
+ "error": f"{type(e).__name__}: {e}",
457
+ })
458
+ print(f" ERROR: {e}")
459
+ gc.collect()
460
+ torch.cuda.empty_cache()
461
+
462
+ return {
463
+ "metadata": meta,
464
+ "scaling_results": results,
465
+ "fidelity_results": fidelity_results,
466
+ }
467
+
468
+
469
+ # ---------------------------------------------------------------------------
470
+ # Standalone entry point
471
+ # ---------------------------------------------------------------------------
472
+ if __name__ == "__main__":
473
+ import sys
474
+
475
+ result = run_benchmark()
476
+ out_path = os.path.join(os.path.dirname(__file__), "benchmark_results.json")
477
+ with open(out_path, "w") as f:
478
+ json.dump(result, f, indent=2, default=str)
479
+ print(f"\n\nResults saved to {out_path}")
480
+ print(json.dumps(result, indent=2, default=str))
requirements.txt CHANGED
@@ -2,3 +2,4 @@ transformers==5.8.0
2
  accelerate==1.8.1
3
  requests
4
  numpy
 
 
2
  accelerate==1.8.1
3
  requests
4
  numpy
5
+ psutil