| """ |
| Gemma 4 Supa-Fast Dual-API Server for HuggingFace Spaces (CPU, 16GB RAM) |
| Uses llama.cpp (GGUF) instead of transformers/PyTorch. |
| OpenAI: POST /v1/chat/completions |
| Anthropic: POST /v1/messages |
| """ |
|
|
| from __future__ import annotations |
|
|
| import os |
| import sys |
| import json |
| import time |
| import uuid |
| import asyncio |
| from contextlib import asynccontextmanager |
| from dataclasses import dataclass, field |
| from typing import AsyncGenerator, Dict, List, Literal, Optional, Union, Any |
|
|
| import huggingface_hub |
| from llama_cpp import Llama, CreateChatCompletionStreamResponse |
| from fastapi import FastAPI, HTTPException, Request, status |
| from fastapi.middleware.cors import CORSMiddleware |
| from fastapi.responses import JSONResponse, StreamingResponse |
| from pydantic import BaseModel, Field |
|
|
| |
|
|
| MODEL_REPO = os.getenv("MODEL_REPO", "ggml-org/gemma-4-E4B-it-GGUF") |
| QUANT = os.getenv("QUANT", "Q5_K_M") |
| MAX_CTX = int(os.getenv("MAX_CTX", "8192")) |
| N_THREADS = int(os.getenv("N_THREADS", str(os.cpu_count() or 2))) |
| N_BATCH = int(os.getenv("N_BATCH", "512")) |
| PORT = int(os.getenv("PORT", "7860")) |
|
|
| GGUF_FILENAME = f"gemma-4-E4B-it-{QUANT}.gguf" |
|
|
| |
|
|
| @dataclass |
| class ModelState: |
| llm: Optional[Llama] = None |
| ready: bool = False |
| load_error: Optional[str] = None |
| load_time_sec: float = 0.0 |
|
|
|
|
| STATE = ModelState() |
|
|
|
|
| |
|
|
| def load_model() -> None: |
| """Download GGUF (if needed) and load into llama.cpp.""" |
| global STATE |
| start = time.time() |
| print(f"[INIT] Repo: {MODEL_REPO}") |
| print(f"[INIT] Quant: {QUANT}") |
| print(f"[INIT] Threads: {N_THREADS} | Ctx: {MAX_CTX} | Batch: {N_BATCH}") |
|
|
| try: |
| print("[INIT] Downloading / verifying GGUF...") |
| model_path = huggingface_hub.hf_hub_download( |
| repo_id=MODEL_REPO, |
| filename=GGUF_FILENAME, |
| local_files_only=False, |
| ) |
| print(f"[INIT] GGUF path: {model_path}") |
|
|
| print("[INIT] Loading model into llama.cpp (mmap + mlock)...") |
| llm = Llama( |
| model_path=model_path, |
| n_ctx=MAX_CTX, |
| n_threads=N_THREADS, |
| n_batch=N_BATCH, |
| use_mmap=True, |
| use_mlock=True, |
| verbose=False, |
| ) |
|
|
| elapsed = time.time() - start |
| STATE.llm = llm |
| STATE.ready = True |
| STATE.load_time_sec = elapsed |
|
|
| print(f"[INIT] Ready in {elapsed:.1f}s") |
| print(f"[INIT] Model size: ~{os.path.getsize(model_path) / 1e9:.1f} GB on disk") |
|
|
| except Exception as e: |
| STATE.load_error = str(e) |
| STATE.ready = False |
| print(f"[ERROR] {e}") |
| import traceback |
| traceback.print_exc() |
|
|
|
|
| |
|
|
| class OAIChatMessage(BaseModel): |
| role: Literal["system", "user", "assistant", "tool"] = "user" |
| content: str = "" |
| name: Optional[str] = None |
|
|
|
|
| class OAIChatRequest(BaseModel): |
| model: str = "gemma-4" |
| messages: List[OAIChatMessage] |
| temperature: Optional[float] = Field(default=0.7, ge=0.0, le=2.0) |
| top_p: Optional[float] = Field(default=0.9, ge=0.0, le=1.0) |
| max_tokens: Optional[int] = Field(default=2048, ge=1, le=8192) |
| stream: bool = False |
| stop: Optional[Union[str, List[str]]] = None |
|
|
|
|
| class OAIChoice(BaseModel): |
| index: int = 0 |
| message: dict = Field(default_factory=dict) |
| finish_reason: Optional[str] = None |
|
|
|
|
| class OAIChunkChoice(BaseModel): |
| index: int = 0 |
| delta: dict = Field(default_factory=dict) |
| finish_reason: Optional[str] = None |
|
|
|
|
| class OAIUsage(BaseModel): |
| prompt_tokens: int = 0 |
| completion_tokens: int = 0 |
| total_tokens: int = 0 |
|
|
|
|
| class OAIChatResponse(BaseModel): |
| id: str = Field(default_factory=lambda: f"chatcmpl-{uuid.uuid4().hex[:12]}") |
| object: str = "chat.completion" |
| created: int = Field(default_factory=lambda: int(time.time())) |
| model: str = "gemma-4" |
| choices: List[OAIChoice] |
| usage: OAIUsage |
|
|
|
|
| class OAIChatStreamChunk(BaseModel): |
| id: str = "" |
| object: str = "chat.completion.chunk" |
| created: int = 0 |
| model: str = "gemma-4" |
| choices: List[OAIChunkChoice] |
|
|
|
|
| |
| class AnthropicMessageParam(BaseModel): |
| role: Literal["user", "assistant"] = "user" |
| content: str = "" |
|
|
|
|
| class AnthropicChatRequest(BaseModel): |
| model: str = "claude-sonnet-4" |
| messages: List[AnthropicMessageParam] |
| max_tokens: int = Field(default=4096, ge=1, le=8192) |
| system: Optional[str] = None |
| temperature: Optional[float] = Field(default=0.7, ge=0.0, le=1.0) |
| top_p: Optional[float] = Field(default=0.9, ge=0.0, le=1.0) |
| stream: bool = False |
| stop_sequences: Optional[List[str]] = None |
|
|
|
|
| class AnthropicUsage(BaseModel): |
| input_tokens: int = 0 |
| output_tokens: int = 0 |
|
|
|
|
| class AnthropicMessageResponse(BaseModel): |
| id: str = Field(default_factory=lambda: f"msg_{uuid.uuid4().hex[:24]}") |
| type: str = "message" |
| role: str = "assistant" |
| model: str = "gemma-4" |
| content: List[dict] |
| stop_reason: Optional[str] = "end_turn" |
| usage: AnthropicUsage |
|
|
|
|
| |
|
|
| def _to_llama_messages( |
| messages: List[OAIChatMessage], |
| system: Optional[str] = None, |
| ) -> List[Dict[str, str]]: |
| """Convert to llama.cpp chat format.""" |
| out: List[Dict[str, str]] = [] |
| if system: |
| out.append({"role": "system", "content": system}) |
| for m in messages: |
| out.append({"role": m.role, "content": m.content}) |
| return out |
|
|
|
|
| |
|
|
| @asynccontextmanager |
| async def lifespan(app: FastAPI): |
| print("[LIFESPAN] Starting up...") |
| loop = asyncio.get_event_loop() |
| await loop.run_in_executor(None, load_model) |
| yield |
| print("[LIFESPAN] Shutting down...") |
|
|
|
|
| app = FastAPI( |
| title="Gemma 4 Supa-Fast Server", |
| description="llama.cpp GGUF backend β OpenAI + Anthropic endpoints", |
| version="2.0.0", |
| lifespan=lifespan, |
| ) |
|
|
| app.add_middleware( |
| CORSMiddleware, |
| allow_origins=["*"], |
| allow_credentials=True, |
| allow_methods=["*"], |
| allow_headers=["*"], |
| ) |
|
|
|
|
| @app.get("/") |
| def root(): |
| return { |
| "status": "ok" if STATE.ready else "loading", |
| "model": MODEL_REPO, |
| "quant": QUANT, |
| "version": "2.0.0", |
| "endpoints": [ |
| "POST /v1/chat/completions", |
| "POST /v1/messages", |
| "GET /health", |
| ], |
| } |
|
|
|
|
| @app.get("/health") |
| def health(): |
| return { |
| "status": "healthy" if STATE.ready else "loading", |
| "model_loaded": STATE.ready, |
| "load_time_sec": STATE.load_time_sec, |
| "error": STATE.load_error, |
| } |
|
|
|
|
| @app.get("/v1/models") |
| def list_models(): |
| return { |
| "object": "list", |
| "data": [ |
| { |
| "id": "gemma-4", |
| "object": "model", |
| "created": int(time.time()), |
| "owned_by": "google", |
| } |
| ], |
| } |
|
|
|
|
| |
|
|
| @app.post("/v1/chat/completions") |
| async def openai_chat_completions(request: OAIChatRequest): |
| if not STATE.ready: |
| raise HTTPException( |
| status_code=503, |
| detail=f"Model not loaded. Error: {STATE.load_error}", |
| ) |
|
|
| messages = _to_llama_messages(request.messages) |
| stop = request.stop or [] |
| if isinstance(stop, str): |
| stop = [stop] |
|
|
| gen_kwargs = dict( |
| messages=messages, |
| max_tokens=request.max_tokens, |
| temperature=request.temperature, |
| top_p=request.top_p, |
| stop=stop, |
| stream=request.stream, |
| ) |
|
|
| if request.stream: |
| return StreamingResponse( |
| _openai_stream(gen_kwargs), |
| media_type="text/event-stream", |
| ) |
|
|
| |
| t0 = time.time() |
| output = STATE.llm.create_chat_completion(**gen_kwargs) |
| elapsed = time.time() - t0 |
|
|
| choice = output["choices"][0] |
| usage = output.get("usage", {}) |
|
|
| return OAIChatResponse( |
| choices=[ |
| OAIChoice( |
| index=0, |
| message=choice["message"], |
| finish_reason=choice.get("finish_reason", "stop"), |
| ) |
| ], |
| usage=OAIUsage( |
| prompt_tokens=usage.get("prompt_tokens", 0), |
| completion_tokens=usage.get("completion_tokens", 0), |
| total_tokens=usage.get("total_tokens", 0), |
| ), |
| ) |
|
|
|
|
| async def _openai_stream(gen_kwargs: dict): |
| """SSE stream for OpenAI format.""" |
| req_id = f"chatcmpl-{uuid.uuid4().hex[:12]}" |
| created = int(time.time()) |
|
|
| |
| first = OAIChatStreamChunk( |
| id=req_id, |
| created=created, |
| choices=[OAIChunkChoice(index=0, delta={"role": "assistant"}, finish_reason=None)], |
| ) |
| yield f"data: {first.model_dump_json()}\n\n" |
|
|
| content_buf = "" |
| prompt_tokens = 0 |
| completion_tokens = 0 |
|
|
| |
| stream = STATE.llm.create_chat_completion(**gen_kwargs) |
|
|
| for chunk in stream: |
| delta = chunk["choices"][0]["delta"] |
| finish = chunk["choices"][0].get("finish_reason") |
|
|
| if delta.get("content"): |
| content_buf += delta["content"] |
| completion_tokens += 1 |
| out_chunk = OAIChatStreamChunk( |
| id=req_id, |
| created=created, |
| choices=[OAIChunkChoice(index=0, delta={"content": delta["content"]}, finish_reason=None)], |
| ) |
| yield f"data: {out_chunk.model_dump_json()}\n\n" |
|
|
| if finish: |
| final = OAIChatStreamChunk( |
| id=req_id, |
| created=created, |
| choices=[OAIChunkChoice(index=0, delta={}, finish_reason=finish)], |
| ) |
| yield f"data: {final.model_dump_json()}\n\n" |
| break |
|
|
| usage = { |
| "id": req_id, |
| "object": "chat.completion.chunk", |
| "created": created, |
| "model": "gemma-4", |
| "choices": [], |
| "usage": { |
| "prompt_tokens": prompt_tokens, |
| "completion_tokens": completion_tokens, |
| "total_tokens": prompt_tokens + completion_tokens, |
| }, |
| } |
| yield f"data: {json.dumps(usage)}\n\n" |
| yield "data: [DONE]\n\n" |
|
|
|
|
| |
|
|
| @app.post("/v1/messages") |
| async def anthropic_messages(request: AnthropicChatRequest): |
| if not STATE.ready: |
| raise HTTPException(status_code=503, detail=f"Model not loaded. {STATE.load_error}") |
|
|
| |
| messages: List[Dict[str, str]] = [] |
| if request.system: |
| messages.append({"role": "system", "content": request.system}) |
| for m in request.messages: |
| messages.append({"role": m.role, "content": m.content}) |
|
|
| stop = request.stop_sequences or [] |
| gen_kwargs = dict( |
| messages=messages, |
| max_tokens=request.max_tokens, |
| temperature=request.temperature, |
| top_p=request.top_p, |
| stop=stop, |
| stream=request.stream, |
| ) |
|
|
| if request.stream: |
| return StreamingResponse( |
| _anthropic_stream(gen_kwargs), |
| media_type="text/event-stream", |
| headers={"x-api-version": "2023-06-01"}, |
| ) |
|
|
| output = STATE.llm.create_chat_completion(**gen_kwargs) |
| text = output["choices"][0]["message"]["content"] |
| usage = output.get("usage", {}) |
|
|
| return AnthropicMessageResponse( |
| content=[{"type": "text", "text": text}], |
| stop_reason="end_turn", |
| usage=AnthropicUsage( |
| input_tokens=usage.get("prompt_tokens", 0), |
| output_tokens=usage.get("completion_tokens", 0), |
| ), |
| ) |
|
|
|
|
| async def _anthropic_stream(gen_kwargs: dict): |
| msg_id = f"msg_{uuid.uuid4().hex[:24]}" |
|
|
| yield f"event: message_start\ndata: {json.dumps({'type': 'message_start', 'message': {'id': msg_id, 'type': 'message', 'role': 'assistant', 'model': 'gemma-4', 'content': [], 'stop_reason': None, 'stop_sequence': None, 'usage': {'input_tokens': 0, 'output_tokens': 0}}})}\n\n" |
| yield f"event: content_block_start\ndata: {json.dumps({'type': 'content_block_start', 'index': 0, 'content_block': {'type': 'text', 'text': ''}})}\n\n" |
|
|
| stream = STATE.llm.create_chat_completion(**gen_kwargs) |
| output_tokens = 0 |
|
|
| for chunk in stream: |
| delta = chunk["choices"][0]["delta"] |
| if delta.get("content"): |
| output_tokens += 1 |
| payload = { |
| "type": "content_block_delta", |
| "index": 0, |
| "delta": {"type": "text_delta", "text": delta["content"]}, |
| } |
| yield f"event: content_block_delta\ndata: {json.dumps(payload)}\n\n" |
|
|
| yield f"event: content_block_stop\ndata: {json.dumps({'type': 'content_block_stop', 'index': 0})}\n\n" |
| yield f"event: message_delta\ndata: {json.dumps({'type': 'message_delta', 'delta': {'stop_reason': 'end_turn', 'stop_sequence': None}, 'usage': {'output_tokens': output_tokens}})}\n\n" |
| yield f"event: message_stop\ndata: {json.dumps({'type': 'message_stop'})}\n\n" |
|
|
|
|
| |
|
|
| if __name__ == "__main__": |
| import uvicorn |
| print("=" * 60) |
| print(" Gemma 4 Supa-Fast Server (llama.cpp GGUF)") |
| print(" OpenAI: POST /v1/chat/completions") |
| print(" Anthropic: POST /v1/messages") |
| print(f" Repo: {MODEL_REPO} | Quant: {QUANT}") |
| print(f" Port: {PORT}") |
| print("=" * 60) |
| uvicorn.run("app:app", host="0.0.0.0", port=PORT, workers=1) |