Spaces:
Paused
Paused
Add IFM/K2-Horizon-7B-Uno 9B CPU inference engine with SIMD & LoRA fusion
Browse files- app.py +44 -0
- model_runner.py +180 -0
- requirements.txt +7 -0
- run_k2_uno_cpu.py +54 -0
- 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> • 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'];
|