Spaces:
Sleeping
Sleeping
File size: 5,852 Bytes
802006f d46761f 802006f f0e980a 802006f 49129a1 802006f 16b5c29 802006f 49129a1 f0e980a 802006f f0e980a 49129a1 802006f f276b69 802006f d46761f f276b69 802006f d46761f 802006f 16b5c29 802006f f0e980a d46761f 802006f d46761f 802006f d46761f 802006f d46761f 802006f f276b69 49129a1 802006f f276b69 802006f 16b5c29 802006f f276b69 802006f f276b69 d46761f f276b69 d46761f f276b69 802006f f0e980a 802006f d46761f f276b69 f0e980a 802006f d46761f 49129a1 802006f 16b5c29 802006f d46761f 802006f d46761f 16b5c29 802006f 16b5c29 802006f d46761f 16b5c29 802006f 16b5c29 802006f d46761f f276b69 d46761f f276b69 f0e980a d46761f 802006f | 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 | import os, json, threading, time
# βββ set thread env BEFORE torch import βββββββββββββββββββββββββββ
_nt = str(os.cpu_count() or 4)
os.environ.setdefault("OMP_NUM_THREADS", _nt)
os.environ.setdefault("MKL_NUM_THREADS", _nt)
import torch
from fastapi import FastAPI, Request
from fastapi.responses import StreamingResponse, JSONResponse, FileResponse
from fastapi.staticfiles import StaticFiles
from transformers import AutoModelForCausalLM, AutoTokenizer, TextIteratorStreamer
# βββ CPU thread opts ββββββββββββββββββββββββββββββββββββββββββββββββ
torch.set_num_threads(os.cpu_count() or 4)
try:
torch.set_num_interop_threads(max(1, (os.cpu_count() or 4) // 2))
except RuntimeError:
pass # can only set once per process
log = lambda m: print(m, flush=True)
# βββ Globals βββββββββββββββββββββββββββββββββββββββββββββββββββββββββ
MODEL_ID = "Smilyai-labs/Nova-1-Standard-1.3B-Preview"
model = None
tokenizer = None
model_status = "loading"
model_error = ""
load_stage = "starting"
# βββ Background loader ββββββββββββββββββββββββββββββββββββββββββββββ
def load_model():
global model, tokenizer, model_status, model_error, load_stage
try:
load_stage = "tokenizer"
log("[nova] loading tokenizer β¦")
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID, trust_remote_code=True)
log("[nova] tokenizer ready β")
load_stage = "weights"
log("[nova] loading model weights (float32, no quant) β¦")
model = AutoModelForCausalLM.from_pretrained(
MODEL_ID,
trust_remote_code=True,
dtype=torch.float32, # float32 β safest/fastest on CPU
low_cpu_mem_usage=True,
)
n_params = sum(p.numel() for p in model.parameters()) / 1e9
log(f"[nova] weights loaded β ({n_params:.2f}B params)")
# ββ NO int8 quant ββ
# For batch_size=1 on CPU the per-op quantize/dequantize overhead
# makes dynamic int8 SLOWER than plain float32.
# The bottleneck is compute (use_cache=False) not memory bandwidth,
# so quant doesn't help β it just adds latency.
log("[nova] skipping int8 quant (slower for bs=1 CPU inference)")
model.eval()
model_status = "ready"
load_stage = "ready"
log("[nova] β
MODEL READY β go ahead and chat!")
except Exception as e:
model_status = "error"
model_error = str(e)
load_stage = "error"
log(f"[nova] β FATAL: {e}")
threading.Thread(target=load_model, daemon=True).start()
# βββ FastAPI app ββββββββββββββββββββββββββββββββββββββββββββββββββββ
app = FastAPI()
app.mount("/static", StaticFiles(directory="static"), name="static")
@app.get("/")
async def root():
return FileResponse("static/index.html")
@app.get("/api/health")
async def health():
return {"status": model_status, "stage": load_stage, "error": model_error}
# βββ Chat endpoint (SSE streaming) ββββββββββββββββββββββββββββββββββ
@app.post("/api/chat")
async def chat(request: Request):
if model_status != "ready":
return JSONResponse(
{"error": f"Model not ready (stage={load_stage})"},
status_code=503,
)
body = await request.json()
# ββ BRUTAL context truncation ββ
# Nova-1 has use_cache=False baked in β every token recomputes the
# ENTIRE context. Shorter context = exponentially faster.
# Keep only last 2 user turns.
messages = body.get("messages", [])[-2:]
temperature = float(body.get("temperature", 0.7))
max_new_tokens = int(body.get("max_new_tokens", 128))
top_p = float(body.get("top_p", 0.9))
chat_messages = [{"role": "system",
"content": "You are Nova, a helpful, honest AI assistant. Keep answers concise."}]
chat_messages.extend(messages)
input_ids = tokenizer.apply_chat_template(
chat_messages,
add_generation_prompt=True,
return_tensors="pt",
)
streamer = TextIteratorStreamer(
tokenizer, skip_prompt=True, skip_special_tokens=True,
)
do_sample = temperature > 0
gen_kwargs = dict(
input_ids=input_ids, # β FIXED: was inputs= (caused AttributeError)
max_new_tokens=max_new_tokens,
streamer=streamer,
use_cache=False, # nova1 architecture β no KV cache possible
do_sample=do_sample,
num_beams=1, # greedy/sample only β no beam search
pad_token_id=tokenizer.eos_token_id,
)
if do_sample:
gen_kwargs["temperature"] = max(temperature, 0.01)
gen_kwargs["top_p"] = top_p
@torch.inference_mode()
def _generate():
model.generate(**gen_kwargs)
thread = threading.Thread(target=_generate, daemon=True)
thread.start()
def event_stream():
try:
for token in streamer:
if token:
yield f"data: {json.dumps({'token': token})}\n\n"
except Exception as e:
yield f"data: {json.dumps({'error': str(e)})}\n\n"
yield "data: [DONE]\n\n"
return StreamingResponse(
event_stream(),
media_type="text/event-stream",
headers={"Cache-Control": "no-cache", "X-Accel-Buffering": "no"},
) |