arudradey commited on
Commit
fbfdf21
·
verified ·
1 Parent(s): 0006899

Add IFM/K2-Horizon-7B-Uno 9B CPU inference engine with SIMD & LoRA fusion

Browse files
Files changed (5) hide show
  1. app.py +44 -0
  2. model_runner.py +180 -0
  3. requirements.txt +7 -0
  4. run_k2_uno_cpu.py +54 -0
  5. static/index.html +213 -2
app.py CHANGED
@@ -151,6 +151,50 @@ async def remove_file(path: str = Query(...)):
151
  except Exception as e:
152
  raise HTTPException(status_code=400, detail=str(e))
153
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
154
  if __name__ == "__main__":
155
  import uvicorn
156
  uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)
 
 
151
  except Exception as e:
152
  raise HTTPException(status_code=400, detail=str(e))
153
 
154
+ class ModelInferenceRequest(BaseModel):
155
+ prompt: str
156
+ max_tokens: Optional[int] = 128
157
+ temperature: Optional[float] = 0.7
158
+ top_p: Optional[float] = 0.9
159
+
160
+ @app.get("/api/model/status")
161
+ async def model_status_endpoint():
162
+ from model_runner import get_model_status
163
+ return get_model_status()
164
+
165
+ @app.post("/api/model/generate")
166
+ async def generate_model_response(req: ModelInferenceRequest):
167
+ if not req.prompt or not req.prompt.strip():
168
+ raise HTTPException(status_code=400, detail="Prompt cannot be empty")
169
+
170
+ from model_runner import stream_generate
171
+ import json
172
+
173
+ def event_generator():
174
+ try:
175
+ yield f"event: start\ndata: {json.dumps({'message': 'Generation started'})}\n\n"
176
+ for token in stream_generate(
177
+ prompt=req.prompt.strip(),
178
+ max_new_tokens=req.max_tokens or 128,
179
+ temperature=req.temperature or 0.7,
180
+ top_p=req.top_p or 0.9
181
+ ):
182
+ yield f"event: token\ndata: {json.dumps({'token': token})}\n\n"
183
+ yield f"event: done\ndata: {json.dumps({'status': 'complete'})}\n\n"
184
+ except Exception as e:
185
+ yield f"event: error\ndata: {json.dumps({'error': str(e)})}\n\n"
186
+
187
+ return StreamingResponse(
188
+ event_generator(),
189
+ media_type="text/event-stream",
190
+ headers={
191
+ "Cache-Control": "no-cache",
192
+ "Connection": "keep-alive",
193
+ "X-Accel-Buffering": "no"
194
+ }
195
+ )
196
+
197
  if __name__ == "__main__":
198
  import uvicorn
199
  uvicorn.run("app:app", host="0.0.0.0", port=7860, reload=True)
200
+
model_runner.py ADDED
@@ -0,0 +1,180 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import os
2
+ import sys
3
+ import time
4
+ import psutil
5
+ import threading
6
+ from pathlib import Path
7
+ from typing import Optional, Generator, Dict, Any
8
+
9
+ import torch
10
+ from storage_manager import STORAGE_DIR
11
+
12
+ # CPU Acceleration Optimization Settings
13
+ CPU_COUNT = os.cpu_count() or 4
14
+ torch.set_num_threads(CPU_COUNT)
15
+ torch.set_num_interop_threads(max(1, CPU_COUNT // 2))
16
+ if hasattr(torch.backends, "mkldnn"):
17
+ torch.backends.mkldnn.enabled = True
18
+
19
+ MODEL_STATE = {
20
+ "is_loaded": False,
21
+ "is_loading": False,
22
+ "model_id": "IFM/K2-Horizon-7B-Uno",
23
+ "base_model": "IFM/K2-Horizon-7B",
24
+ "adapter_path": str(STORAGE_DIR),
25
+ "device": "cpu",
26
+ "dtype": "bfloat16",
27
+ "quantized": False,
28
+ "threads": CPU_COUNT,
29
+ "load_time_seconds": 0.0,
30
+ "status_message": "Model not loaded",
31
+ }
32
+
33
+ _model = None
34
+ _tokenizer = None
35
+ _load_lock = threading.Lock()
36
+
37
+ def get_model_status() -> Dict[str, Any]:
38
+ mem = psutil.virtual_memory()
39
+ return {
40
+ **MODEL_STATE,
41
+ "system_ram_total_gb": round(mem.total / (1024**3), 2),
42
+ "system_ram_available_gb": round(mem.available / (1024**3), 2),
43
+ "adapter_present": (STORAGE_DIR / "adapter_model.safetensors").exists(),
44
+ }
45
+
46
+ def load_k2_horizon_uno(force_reload: bool = False):
47
+ global _model, _tokenizer, MODEL_STATE
48
+
49
+ with _load_lock:
50
+ if _model is not None and not force_reload:
51
+ return _model, _tokenizer
52
+
53
+ MODEL_STATE["is_loading"] = True
54
+ MODEL_STATE["status_message"] = "Initializing K2-Horizon-7B-Uno on CPU..."
55
+ start_time = time.time()
56
+
57
+ try:
58
+ from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
59
+ from peft import PeftModel
60
+
61
+ adapter_path = STORAGE_DIR
62
+ base_model_id = "IFM/K2-Horizon-7B"
63
+
64
+ # Check RAM: 9B BF16 needs ~18GB; if available RAM < 18GB, use dynamic INT8 quantization
65
+ mem = psutil.virtual_memory()
66
+ available_gb = mem.available / (1024**3)
67
+ use_int8_quant = available_gb < 20.0
68
+
69
+ MODEL_STATE["status_message"] = f"Loading tokenizer ({base_model_id})..."
70
+ tokenizer = AutoTokenizer.from_pretrained(
71
+ base_model_id,
72
+ trust_remote_code=True,
73
+ padding_side="left"
74
+ )
75
+ if tokenizer.pad_token is None:
76
+ tokenizer.pad_token = tokenizer.eos_token
77
+
78
+ MODEL_STATE["status_message"] = f"Loading 9B base model weights on CPU (dtype=bfloat16)..."
79
+ base_model = AutoModelForCausalLM.from_pretrained(
80
+ base_model_id,
81
+ torch_dtype=torch.bfloat16,
82
+ trust_remote_code=True,
83
+ low_cpu_mem_usage=True,
84
+ device_map="cpu"
85
+ )
86
+
87
+ # Check if adapter weights exist in the persistent bucket volume
88
+ adapter_file = adapter_path / "adapter_model.safetensors"
89
+ if adapter_file.exists():
90
+ MODEL_STATE["status_message"] = "Attaching Uno diffusion LoRA adapter from /data..."
91
+ peft_model = PeftModel.from_pretrained(
92
+ base_model,
93
+ model_id=str(adapter_path),
94
+ is_trainable=False
95
+ )
96
+ MODEL_STATE["status_message"] = "Merging LoRA weights into base model projections for zero-overhead CPU GEMM..."
97
+ # merge_and_unload fuses adapter into base weights, eliminating LoRA runtime overhead
98
+ base_model = peft_model.merge_and_unload()
99
+ del peft_model
100
+
101
+ if use_int8_quant:
102
+ MODEL_STATE["status_message"] = "Applying dynamic INT8 quantization for optimal CPU SIMD throughput and low memory footprint..."
103
+ base_model = torch.ao.quantization.quantize_dynamic(
104
+ base_model,
105
+ {torch.nn.Linear},
106
+ dtype=torch.qint8
107
+ )
108
+ MODEL_STATE["quantized"] = True
109
+ MODEL_STATE["dtype"] = "int8_dynamic"
110
+
111
+ base_model.eval()
112
+ _model = base_model
113
+ _tokenizer = tokenizer
114
+
115
+ load_duration = round(time.time() - start_time, 2)
116
+ MODEL_STATE["is_loaded"] = True
117
+ MODEL_STATE["is_loading"] = False
118
+ MODEL_STATE["load_time_seconds"] = load_duration
119
+ MODEL_STATE["status_message"] = f"Model loaded and ready in {load_duration}s ({CPU_COUNT} CPU threads)"
120
+
121
+ return _model, _tokenizer
122
+
123
+ except Exception as e:
124
+ MODEL_STATE["is_loaded"] = False
125
+ MODEL_STATE["is_loading"] = False
126
+ MODEL_STATE["status_message"] = f"Load error: {str(e)}"
127
+ raise e
128
+
129
+ def stream_generate(
130
+ prompt: str,
131
+ max_new_tokens: int = 128,
132
+ temperature: float = 0.7,
133
+ top_p: float = 0.9,
134
+ ) -> Generator[str, None, None]:
135
+ model, tokenizer = load_k2_horizon_uno()
136
+
137
+ from transformers import TextIteratorStreamer
138
+
139
+ # Format using chat template if applicable
140
+ messages = [{"role": "user", "content": prompt}]
141
+ try:
142
+ formatted_prompt = tokenizer.apply_chat_template(
143
+ messages,
144
+ tokenize=False,
145
+ add_generation_prompt=True
146
+ )
147
+ except Exception:
148
+ formatted_prompt = f"<|im_start|>user\n{prompt}<|im_end|>\n<|im_start|>assistant\n"
149
+
150
+ inputs = tokenizer(formatted_prompt, return_tensors="pt").to("cpu")
151
+
152
+ streamer = TextIteratorStreamer(
153
+ tokenizer,
154
+ skip_prompt=True,
155
+ skip_special_tokens=True,
156
+ timeout=120.0
157
+ )
158
+
159
+ gen_kwargs = dict(
160
+ inputs,
161
+ streamer=streamer,
162
+ max_new_tokens=max_new_tokens,
163
+ temperature=max(temperature, 0.05),
164
+ top_p=top_p,
165
+ do_sample=temperature > 0.1,
166
+ pad_token_id=tokenizer.pad_token_id,
167
+ eos_token_id=tokenizer.eos_token_id,
168
+ )
169
+
170
+ thread = threading.Thread(target=model.generate, kwargs=gen_kwargs)
171
+ thread.start()
172
+
173
+ token_count = 0
174
+ t0 = time.time()
175
+
176
+ for text_chunk in streamer:
177
+ token_count += 1
178
+ yield text_chunk
179
+
180
+ thread.join()
requirements.txt CHANGED
@@ -1,6 +1,13 @@
 
1
  fastapi>=0.110.0,<1.0.0
2
  uvicorn[standard]>=0.28.0,<1.0.0
3
  pydantic>=2.6.0,<3.0.0
4
  aiofiles>=23.2.1,<24.0.0
5
  httpx>=0.27.0,<1.0.0
6
  psutil>=5.9.8,<6.0.0
 
 
 
 
 
 
 
1
+ --extra-index-url https://download.pytorch.org/whl/cpu
2
  fastapi>=0.110.0,<1.0.0
3
  uvicorn[standard]>=0.28.0,<1.0.0
4
  pydantic>=2.6.0,<3.0.0
5
  aiofiles>=23.2.1,<24.0.0
6
  httpx>=0.27.0,<1.0.0
7
  psutil>=5.9.8,<6.0.0
8
+ torch
9
+ transformers>=4.40.0
10
+ peft>=0.10.0
11
+ accelerate>=0.29.0
12
+ safetensors>=0.4.0
13
+ sentencepiece>=0.2.0
run_k2_uno_cpu.py ADDED
@@ -0,0 +1,54 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ #!/usr/bin/env python3
2
+ """
3
+ CLI Runner for IFM/K2-Horizon-7B-Uno on CPU with maximum optimization.
4
+ """
5
+ import argparse
6
+ import sys
7
+ import time
8
+ from model_runner import load_k2_horizon_uno, stream_generate, get_model_status
9
+
10
+ def main():
11
+ parser = argparse.ArgumentParser(description="Run IFM/K2-Horizon-7B-Uno on CPU with highest speed")
12
+ parser.add_argument("--prompt", type=str, default="Hello! What are the key features of the K2 Horizon Uno architecture?", help="Input prompt")
13
+ parser.add_argument("--max-tokens", type=int, default=128, help="Maximum generated tokens")
14
+ parser.add_argument("--temp", type=float, default=0.7, help="Sampling temperature")
15
+ parser.add_argument("--info", action="store_true", help="Print model and system status only")
16
+ args = parser.parse_args()
17
+
18
+ status = get_model_status()
19
+ print("=" * 60)
20
+ print(" IFM/K2-Horizon-7B-Uno High-Speed CPU Inference Engine")
21
+ print("=" * 60)
22
+ print(f" Base Model: {status['base_model']}")
23
+ print(f" Adapter Location: {status['adapter_path']}")
24
+ print(f" Adapter Detected: {status['adapter_present']}")
25
+ print(f" CPU Threads: {status['threads']}")
26
+ print(f" System RAM: {status['system_ram_available_gb']} GB available / {status['system_ram_total_gb']} GB total")
27
+ print("=" * 60)
28
+
29
+ if args.info:
30
+ return
31
+
32
+ print("\n[1/2] Loading model weights into memory...")
33
+ t0 = time.time()
34
+ load_k2_horizon_uno()
35
+ load_time = time.time() - t0
36
+ print(f"✓ Model ready in {load_time:.2f}s\n")
37
+
38
+ print(f"[2/2] Generating response for prompt:\n> \"{args.prompt}\"\n")
39
+ print("-" * 60)
40
+
41
+ t_start = time.time()
42
+ generated_tokens = 0
43
+
44
+ for chunk in stream_generate(args.prompt, max_new_tokens=args.max_tokens, temperature=args.temp):
45
+ sys.stdout.write(chunk)
46
+ sys.stdout.flush()
47
+ generated_tokens += 1
48
+
49
+ t_gen = time.time() - t_start
50
+ print("\n" + "-" * 60)
51
+ print(f"✓ Completed in {t_gen:.2f}s (~{generated_tokens / max(t_gen, 0.01):.1f} tokens/s)\n")
52
+
53
+ if __name__ == "__main__":
54
+ main()
static/index.html CHANGED
@@ -86,11 +86,13 @@
86
  </div>
87
  </div>
88
 
89
- <!-- Navigation Tabs (Segmented Control) -->
90
  <nav class="flex items-center p-1 rounded-lg bg-zinc-900 border border-zinc-800 text-xs font-medium">
91
  <button onclick="switchTab('orchestrator')" id="btn-tab-orchestrator" class="px-3.5 py-1.5 rounded-md bg-white text-zinc-950 font-bold transition">
92
  Runner
93
  </button>
 
 
 
94
  <button onclick="switchTab('storage')" id="btn-tab-storage" class="px-3.5 py-1.5 rounded-md text-zinc-400 hover:text-white transition">
95
  Storage
96
  </button>
@@ -266,6 +268,114 @@ Waiting for execution cycle output...
266
 
267
  </div>
268
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
269
  <!-- ============================================================= -->
270
  <!-- TAB 2: STORAGE EXPLORER (/data) -->
271
  <!-- ============================================================= -->
@@ -489,7 +599,7 @@ with httpx.stream("POST", url, json=payload, timeout=60.0) as response:
489
  }
490
 
491
  function switchTab(tabId) {
492
- const tabs = ['orchestrator', 'storage', 'telemetry', 'api'];
493
  tabs.forEach(t => {
494
  const pane = document.getElementById('tab-' + t);
495
  const btn = document.getElementById('btn-tab-' + t);
@@ -501,11 +611,112 @@ with httpx.stream("POST", url, json=payload, timeout=60.0) as response:
501
  btn.className = 'px-3.5 py-1.5 rounded-md text-zinc-400 hover:text-white transition';
502
  }
503
  });
 
504
  if (tabId === 'storage') fetchFiles();
505
  if (tabId === 'telemetry') fetchTelemetry();
506
  initIcons();
507
  }
508
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
509
  function selectStrategy(strat) {
510
  activeStrategy = strat;
511
  const strats = ['react', 'plan_solve', 'reflexion', 'explorer'];
 
86
  </div>
87
  </div>
88
 
 
89
  <nav class="flex items-center p-1 rounded-lg bg-zinc-900 border border-zinc-800 text-xs font-medium">
90
  <button onclick="switchTab('orchestrator')" id="btn-tab-orchestrator" class="px-3.5 py-1.5 rounded-md bg-white text-zinc-950 font-bold transition">
91
  Runner
92
  </button>
93
+ <button onclick="switchTab('model')" id="btn-tab-model" class="px-3.5 py-1.5 rounded-md text-zinc-400 hover:text-white transition">
94
+ K2 Uno (9B)
95
+ </button>
96
  <button onclick="switchTab('storage')" id="btn-tab-storage" class="px-3.5 py-1.5 rounded-md text-zinc-400 hover:text-white transition">
97
  Storage
98
  </button>
 
268
 
269
  </div>
270
 
271
+ <!-- ============================================================= -->
272
+ <!-- TAB: K2 HORIZON UNO (9B MODEL) -->
273
+ <!-- ============================================================= -->
274
+ <div id="tab-model" class="space-y-6 hidden">
275
+
276
+ <!-- Top Overview Card -->
277
+ <div class="rounded-2xl border border-zinc-800 bg-zinc-925/80 p-6 sm:p-8 space-y-6 shadow-2xl">
278
+ <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-3">
279
+ <div>
280
+ <div class="flex items-center space-x-2">
281
+ <span class="px-2 py-0.5 rounded text-[10px] font-mono font-bold bg-white text-black uppercase">9 Billion Parameters</span>
282
+ <span class="px-2 py-0.5 rounded text-[10px] font-mono font-bold bg-emerald-500/10 text-emerald-400 border border-emerald-500/20 uppercase">CPU Optimized SIMD</span>
283
+ </div>
284
+ <h1 class="text-2xl sm:text-3xl font-black tracking-tight text-white uppercase mt-2">
285
+ K2-Horizon-7B-Uno
286
+ </h1>
287
+ <p class="text-xs sm:text-sm text-zinc-400 mt-1">
288
+ Diffusion-Augmented LoRA adapter loaded directly from persistent volume (<code class="text-zinc-300 font-mono">/data/adapter_model.safetensors</code>), merged into base projections for zero-overhead multi-threaded CPU inference.
289
+ </p>
290
+ </div>
291
+ <div class="text-right font-mono text-xs text-zinc-500">
292
+ <div>Threads: <span id="model-cpu-threads" class="text-white font-bold">--</span></div>
293
+ <div>Dtype: <span id="model-dtype" class="text-emerald-400 font-bold">bfloat16 / int8</span></div>
294
+ </div>
295
+ </div>
296
+
297
+ <!-- Spec Badges -->
298
+ <div class="grid grid-cols-2 sm:grid-cols-4 gap-3 text-xs font-mono">
299
+ <div class="p-3 rounded-xl border border-zinc-800 bg-zinc-950">
300
+ <div class="text-zinc-500 text-[10px] uppercase">Base Weights</div>
301
+ <div class="text-white font-bold truncate mt-0.5">IFM/K2-Horizon-7B</div>
302
+ </div>
303
+ <div class="p-3 rounded-xl border border-zinc-800 bg-zinc-950">
304
+ <div class="text-zinc-500 text-[10px] uppercase">Diffusion Adapter</div>
305
+ <div class="text-white font-bold truncate mt-0.5">/data (1.4 GB LoRA)</div>
306
+ </div>
307
+ <div class="p-3 rounded-xl border border-zinc-800 bg-zinc-950">
308
+ <div class="text-zinc-500 text-[10px] uppercase">LoRA Merging</div>
309
+ <div class="text-emerald-400 font-bold truncate mt-0.5">Fused in-place</div>
310
+ </div>
311
+ <div class="p-3 rounded-xl border border-zinc-800 bg-zinc-950">
312
+ <div class="text-zinc-500 text-[10px] uppercase">RAM Available</div>
313
+ <div id="model-ram-avail" class="text-white font-bold truncate mt-0.5">-- GB</div>
314
+ </div>
315
+ </div>
316
+
317
+ <!-- Prompt Area -->
318
+ <div class="space-y-2">
319
+ <div class="flex items-center justify-between text-xs font-mono">
320
+ <span class="text-zinc-400 uppercase font-semibold">Inference Prompt:</span>
321
+ <div class="flex gap-2">
322
+ <button onclick="setModelPrompt('explain')" class="text-[10px] px-2 py-0.5 rounded bg-zinc-900 border border-zinc-800 text-zinc-400 hover:text-white">Architecture</button>
323
+ <button onclick="setModelPrompt('quantum')" class="text-[10px] px-2 py-0.5 rounded bg-zinc-900 border border-zinc-800 text-zinc-400 hover:text-white">Quantum Computing</button>
324
+ <button onclick="setModelPrompt('python')" class="text-[10px] px-2 py-0.5 rounded bg-zinc-900 border border-zinc-800 text-zinc-400 hover:text-white">Parallel Code</button>
325
+ </div>
326
+ </div>
327
+ <textarea id="model-prompt" rows="3" class="w-full rounded-xl bg-zinc-950 border border-zinc-800 focus:border-zinc-500 focus:ring-1 focus:ring-zinc-500 p-4 text-sm font-medium text-white placeholder-zinc-600 focus:outline-none transition resize-none custom-scroll leading-relaxed" placeholder="Ask K2-Horizon-7B-Uno anything...">Explain the dual-pathway discrete diffusion mechanism in K2-Horizon-7B-Uno and how it achieves lossless multi-token generation speedups.</textarea>
328
+ </div>
329
+
330
+ <!-- Controls & Action Button -->
331
+ <div class="flex flex-col sm:flex-row sm:items-center justify-between gap-4 pt-2">
332
+ <div class="flex items-center space-x-4 text-xs font-mono">
333
+ <div class="flex items-center space-x-2">
334
+ <span class="text-zinc-500">Max Tokens:</span>
335
+ <select id="model-max-tokens" class="bg-zinc-900 border border-zinc-800 text-white font-bold rounded-lg px-2 py-1 focus:outline-none">
336
+ <option value="64">64</option>
337
+ <option value="128" selected>128</option>
338
+ <option value="256">256</option>
339
+ <option value="512">512</option>
340
+ </select>
341
+ </div>
342
+ <div class="flex items-center space-x-2">
343
+ <span class="text-zinc-500">Temp:</span>
344
+ <select id="model-temperature" class="bg-zinc-900 border border-zinc-800 text-white font-bold rounded-lg px-2 py-1 focus:outline-none">
345
+ <option value="0.2">0.2 (Precise)</option>
346
+ <option value="0.7" selected>0.7 (Balanced)</option>
347
+ <option value="0.9">0.9 (Creative)</option>
348
+ </select>
349
+ </div>
350
+ </div>
351
+
352
+ <button id="btn-model-run" onclick="generateFromModel()" class="px-6 py-2.5 rounded-lg bg-white hover:bg-zinc-200 text-black font-extrabold text-xs uppercase tracking-wider transition flex items-center space-x-2 shadow-lg active:scale-95">
353
+ <i data-lucide="zap" class="w-4 h-4 fill-black"></i>
354
+ <span>Run on CPU</span>
355
+ </button>
356
+ </div>
357
+
358
+ </div>
359
+
360
+ <!-- Real-Time Generation Output Card -->
361
+ <div class="rounded-2xl border border-zinc-800 bg-zinc-925/50 p-6 space-y-4">
362
+ <div class="flex items-center justify-between border-b border-zinc-800 pb-3">
363
+ <div class="flex items-center space-x-2 text-xs font-mono font-bold text-zinc-200 uppercase">
364
+ <i data-lucide="cpu" class="w-4 h-4 text-emerald-400"></i>
365
+ <span>Live Token Stream</span>
366
+ </div>
367
+ <div id="model-metrics" class="text-xs font-mono text-zinc-400">
368
+ Tokens: <span id="metric-tokens" class="text-white font-bold">0</span> &bull; Speed: <span id="metric-speed" class="text-emerald-400 font-bold">0.0 t/s</span>
369
+ </div>
370
+ </div>
371
+
372
+ <div id="model-output-box" class="p-4 rounded-xl bg-zinc-950 border border-zinc-900 text-zinc-200 font-mono text-xs whitespace-pre-wrap leading-relaxed min-h-[220px] max-h-[480px] overflow-y-auto custom-scroll">
373
+ Enter a prompt above and click "Run on CPU" to begin token generation.
374
+ </div>
375
+ </div>
376
+
377
+ </div>
378
+
379
  <!-- ============================================================= -->
380
  <!-- TAB 2: STORAGE EXPLORER (/data) -->
381
  <!-- ============================================================= -->
 
599
  }
600
 
601
  function switchTab(tabId) {
602
+ const tabs = ['orchestrator', 'model', 'storage', 'telemetry', 'api'];
603
  tabs.forEach(t => {
604
  const pane = document.getElementById('tab-' + t);
605
  const btn = document.getElementById('btn-tab-' + t);
 
611
  btn.className = 'px-3.5 py-1.5 rounded-md text-zinc-400 hover:text-white transition';
612
  }
613
  });
614
+ if (tabId === 'model') fetchModelStatus();
615
  if (tabId === 'storage') fetchFiles();
616
  if (tabId === 'telemetry') fetchTelemetry();
617
  initIcons();
618
  }
619
 
620
+ function setModelPrompt(type) {
621
+ const p = document.getElementById('model-prompt');
622
+ if (type === 'explain') {
623
+ p.value = "Explain the dual-pathway discrete diffusion mechanism in K2-Horizon-7B-Uno and how it achieves lossless multi-token generation speedups.";
624
+ } else if (type === 'quantum') {
625
+ p.value = "Explain the fundamental difference between classical and quantum computing, focusing on superposition and entanglement.";
626
+ } else if (type === 'python') {
627
+ p.value = "Write an optimized Python routine for multi-threaded batch token processing on CPU using PyTorch and bfloat16.";
628
+ }
629
+ }
630
+
631
+ async function fetchModelStatus() {
632
+ try {
633
+ const res = await fetch('/api/model/status');
634
+ const data = await res.json();
635
+ document.getElementById('model-cpu-threads').textContent = data.threads + ' cores';
636
+ document.getElementById('model-dtype').textContent = data.dtype || 'bfloat16';
637
+ document.getElementById('model-ram-avail').textContent = data.system_ram_available_gb + ' GB';
638
+ } catch (e) {
639
+ console.error(e);
640
+ }
641
+ }
642
+
643
+ async function generateFromModel() {
644
+ const prompt = document.getElementById('model-prompt').value.trim();
645
+ if (!prompt) return;
646
+
647
+ const maxTokens = parseInt(document.getElementById('model-max-tokens').value, 10);
648
+ const temp = parseFloat(document.getElementById('model-temperature').value);
649
+
650
+ const btn = document.getElementById('btn-model-run');
651
+ btn.disabled = true;
652
+ btn.className = "px-6 py-2.5 rounded-lg bg-zinc-700 text-zinc-400 font-extrabold text-xs uppercase tracking-wider transition flex items-center space-x-2 cursor-not-allowed";
653
+ btn.innerHTML = '<i data-lucide="loader-2" class="w-4 h-4 animate-spin"></i><span>Generating...</span>';
654
+ initIcons();
655
+
656
+ const outBox = document.getElementById('model-output-box');
657
+ outBox.textContent = "Initializing model inference pipeline on CPU...\n";
658
+
659
+ const tStart = Date.now();
660
+ let tokenCount = 0;
661
+
662
+ try {
663
+ const res = await fetch('/api/model/generate', {
664
+ method: 'POST',
665
+ headers: { 'Content-Type': 'application/json' },
666
+ body: JSON.stringify({
667
+ prompt: prompt,
668
+ max_tokens: maxTokens,
669
+ temperature: temp
670
+ })
671
+ });
672
+
673
+ const reader = res.body.getReader();
674
+ const decoder = new TextDecoder('utf-8');
675
+ let buffer = '';
676
+ outBox.textContent = '';
677
+
678
+ while (true) {
679
+ const { done, value } = await reader.read();
680
+ if (done) break;
681
+
682
+ buffer += decoder.decode(value, { stream: true });
683
+ const parts = buffer.split('\n\n');
684
+ buffer = parts.pop();
685
+
686
+ for (const p of parts) {
687
+ if (!p.trim()) continue;
688
+ let eventType = '';
689
+ let payload = '';
690
+ for (const line of p.split('\n')) {
691
+ if (line.startsWith('event: ')) eventType = line.substring(7).trim();
692
+ if (line.startsWith('data: ')) payload = line.substring(6).trim();
693
+ }
694
+ if (payload) {
695
+ const data = JSON.parse(payload);
696
+ if (eventType === 'token' && data.token) {
697
+ outBox.textContent += data.token;
698
+ outBox.scrollTop = outBox.scrollHeight;
699
+ tokenCount++;
700
+ const elapsed = (Date.now() - tStart) / 1000;
701
+ const tps = (tokenCount / Math.max(elapsed, 0.1)).toFixed(1);
702
+ document.getElementById('metric-tokens').textContent = tokenCount;
703
+ document.getElementById('metric-speed').textContent = tps + ' t/s';
704
+ } else if (eventType === 'error') {
705
+ outBox.textContent += "\n[Inference Error: " + data.error + "]";
706
+ }
707
+ }
708
+ }
709
+ }
710
+ } catch (err) {
711
+ outBox.textContent += "\n[Connection Error: " + err.message + "]";
712
+ } finally {
713
+ btn.disabled = false;
714
+ btn.className = "px-6 py-2.5 rounded-lg bg-white hover:bg-zinc-200 text-black font-extrabold text-xs uppercase tracking-wider transition flex items-center space-x-2 shadow-lg active:scale-95";
715
+ btn.innerHTML = '<i data-lucide="zap" class="w-4 h-4 fill-black"></i><span>Run on CPU</span>';
716
+ initIcons();
717
+ }
718
+ }
719
+
720
  function selectStrategy(strat) {
721
  activeStrategy = strat;
722
  const strats = ['react', 'plan_solve', 'reflexion', 'explorer'];