Spaces:
Sleeping
Sleeping
| 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") | |
| async def root(): | |
| return FileResponse("static/index.html") | |
| async def health(): | |
| return {"status": model_status, "stage": load_stage, "error": model_error} | |
| # βββ Chat endpoint (SSE streaming) ββββββββββββββββββββββββββββββββββ | |
| 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 | |
| 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"}, | |
| ) |