Spaces:
Runtime error
Runtime error
| import os | |
| import logging | |
| from contextlib import asynccontextmanager | |
| import torch | |
| from fastapi import FastAPI, HTTPException | |
| from fastapi.middleware.cors import CORSMiddleware | |
| from pydantic import BaseModel, Field | |
| from maia3_pkg.inference import build_model, predict_moves | |
| logging.basicConfig(level=logging.INFO) | |
| logger = logging.getLogger(__name__) | |
| model = None | |
| model_cfg = None | |
| class PredictRequest(BaseModel): | |
| fen: str = Field(..., description="FEN string of the chess position") | |
| self_elo: int = Field(1500, ge=0, le=5000, description="Side-to-move Elo rating") | |
| oppo_elo: int = Field(1500, ge=0, le=5000, description="Opponent Elo rating") | |
| temperature: float = Field(1.0, ge=0.0, le=5.0, description="Sampling temperature (0 = argmax)") | |
| top_p: float = Field(1.0, ge=0.0, le=1.0, description="Nucleus sampling threshold") | |
| multipv: int = Field(5, ge=1, le=50, description="Number of candidate moves to return") | |
| class PredictResponse(BaseModel): | |
| fen: str | |
| turn: str | |
| self_elo: int | |
| oppo_elo: int | |
| top_moves: list[dict] | |
| wdl: dict | |
| class HealthResponse(BaseModel): | |
| status: str | |
| device: str | |
| model_loaded: bool | |
| async def lifespan(app: FastAPI): | |
| global model, model_cfg | |
| logger.info("Starting Maia3 inference server...") | |
| try: | |
| ckpt_path = os.environ.get( | |
| "MAIA3_CHECKPOINT", | |
| "/app/maia3-3m.pt", | |
| ) | |
| device = os.environ.get("MAIA3_DEVICE", "cuda" if torch.cuda.is_available() else "cpu") | |
| use_amp = os.environ.get("MAIA3_USE_AMP", "1" if device.startswith("cuda") else "0") == "1" | |
| logger.info(f"Loading model from {ckpt_path} on {device} (amp={use_amp})...") | |
| model, model_cfg = build_model(ckpt_path, device=device, use_amp=use_amp) | |
| logger.info("Model loaded successfully") | |
| except Exception as e: | |
| logger.error(f"Failed to load model: {e}") | |
| model = None | |
| model_cfg = None | |
| yield | |
| app = FastAPI( | |
| title="Maia3 Chess Prediction API", | |
| description="Human-like chess move prediction using Maia3-3M (Chessformer)", | |
| version="1.0.0", | |
| lifespan=lifespan, | |
| ) | |
| app.add_middleware( | |
| CORSMiddleware, | |
| allow_origins=["*"], | |
| allow_credentials=True, | |
| allow_methods=["*"], | |
| allow_headers=["*"], | |
| ) | |
| async def health(): | |
| return HealthResponse( | |
| status="ok" if model is not None else "degraded", | |
| device=str(model_cfg.device) if model_cfg else "unknown", | |
| model_loaded=model is not None, | |
| ) | |
| async def predict(req: PredictRequest): | |
| if model is None or model_cfg is None: | |
| raise HTTPException(status_code=503, detail="Model not loaded") | |
| try: | |
| result = predict_moves( | |
| model=model, | |
| cfg=model_cfg, | |
| fen=req.fen, | |
| self_elo=req.self_elo, | |
| oppo_elo=req.oppo_elo, | |
| temperature=req.temperature, | |
| top_p=req.top_p, | |
| multipv=req.multipv, | |
| ) | |
| return PredictResponse(**result) | |
| except ValueError as e: | |
| raise HTTPException(status_code=400, detail=str(e)) | |
| except Exception as e: | |
| logger.exception("Prediction failed") | |
| raise HTTPException(status_code=500, detail=str(e)) | |
| async def root(): | |
| return { | |
| "service": "Maia3 Chess Prediction", | |
| "model": "Maia3-ablate-3M (Chessformer, 3M params)", | |
| "endpoints": { | |
| "health": "GET /health", | |
| "predict": "POST /predict", | |
| "docs": "GET /docs", | |
| }, | |
| } | |