Rafs-an09002 commited on
Commit
7017b3f
·
verified ·
1 Parent(s): 1561c06

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +124 -107
app.py CHANGED
@@ -1,144 +1,161 @@
 
 
 
 
 
1
  from fastapi import FastAPI, HTTPException
2
  from fastapi.middleware.cors import CORSMiddleware
3
  from pydantic import BaseModel, Field
4
- import onnxruntime as ort
5
- import numpy as np
6
- import chess
7
  import time
8
  import logging
9
- import os
10
- from typing import Optional, Tuple
 
11
 
12
- logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s')
 
 
 
 
13
  logger = logging.getLogger(__name__)
14
 
15
- class NexusNanoEngine:
16
- PIECE_VALUES = {chess.PAWN: 1, chess.KNIGHT: 3, chess.BISHOP: 3, chess.ROOK: 5, chess.QUEEN: 9, chess.KING: 0}
17
-
18
- def __init__(self, model_path: str):
19
- if not os.path.exists(model_path):
20
- raise FileNotFoundError(f"Model not found: {model_path}")
21
- logger.info(f"Loading model from {model_path}...")
22
- sess_options = ort.SessionOptions()
23
- sess_options.intra_op_num_threads = 2
24
- sess_options.graph_optimization_level = ort.GraphOptimizationLevel.ORT_ENABLE_ALL
25
- self.session = ort.InferenceSession(model_path, sess_options=sess_options, providers=['CPUExecutionProvider'])
26
- self.input_name = self.session.get_inputs()[0].name
27
- self.output_name = self.session.get_outputs()[0].name
28
- self.nodes = 0
29
- logger.info("✅ Nexus-Nano engine loaded")
30
-
31
- def fen_to_tensor(self, fen: str) -> np.ndarray:
32
- board = chess.Board(fen)
33
- tensor = np.zeros((1, 12, 8, 8), dtype=np.float32)
34
- piece_map = {chess.PAWN: 0, chess.KNIGHT: 1, chess.BISHOP: 2, chess.ROOK: 3, chess.QUEEN: 4, chess.KING: 5}
35
- for sq, piece in board.piece_map().items():
36
- r, f = divmod(sq, 8)
37
- ch = piece_map[piece.piece_type] + (6 if piece.color == chess.BLACK else 0)
38
- tensor[0, ch, r, f] = 1.0
39
- return tensor
40
-
41
- def evaluate(self, board: chess.Board) -> float:
42
- self.nodes += 1
43
- tensor = self.fen_to_tensor(board.fen())
44
- output = self.session.run([self.output_name], {self.input_name: tensor})
45
- score = float(output[0][0][0]) * 400.0
46
- return -score if board.turn == chess.BLACK else score
47
-
48
- def order_moves(self, board, moves):
49
- scored = []
50
- for m in moves:
51
- s = 0
52
- if board.is_capture(m):
53
- v, a = board.piece_at(m.to_square), board.piece_at(m.from_square)
54
- if v and a: s = self.PIECE_VALUES.get(v.piece_type, 0) * 10 - self.PIECE_VALUES.get(a.piece_type, 0)
55
- if m.promotion == chess.QUEEN: s += 90
56
- scored.append((s, m))
57
- scored.sort(key=lambda x: x[0], reverse=True)
58
- return [m for _, m in scored]
59
-
60
- def alpha_beta(self, board, depth, alpha, beta):
61
- if board.is_game_over(): return (-10000 if board.is_checkmate() else 0), None
62
- if depth == 0: return self.evaluate(board), None
63
- moves = list(board.legal_moves)
64
- if not moves: return 0, None
65
- moves = self.order_moves(board, moves)
66
- best_move, best_score = moves[0], float('-inf')
67
- for move in moves:
68
- board.push(move)
69
- score, _ = self.alpha_beta(board, depth - 1, -beta, -alpha)
70
- score = -score
71
- board.pop()
72
- if score > best_score: best_score, best_move = score, move
73
- alpha = max(alpha, score)
74
- if alpha >= beta: break
75
- return best_score, best_move
76
-
77
- def search(self, fen: str, depth: int = 3):
78
- board = chess.Board(fen)
79
- self.nodes = 0
80
- moves = list(board.legal_moves)
81
- if not moves: return {'best_move': '0000', 'evaluation': 0.0, 'nodes': 0, 'depth': 0}
82
- if len(moves) == 1: return {'best_move': moves[0].uci(), 'evaluation': round(self.evaluate(board)/100, 2), 'nodes': 1, 'depth': 0}
83
- best_move, best_score, current_depth = moves[0], float('-inf'), 1
84
- for d in range(1, depth + 1):
85
- try:
86
- score, move = self.alpha_beta(board, d, float('-inf'), float('inf'))
87
- if move: best_move, best_score, current_depth = move, score, d
88
- except: break
89
- return {'best_move': best_move.uci(), 'evaluation': round(best_score/100, 2), 'depth': current_depth, 'nodes': self.nodes}
90
-
91
- app = FastAPI(title="Nexus-Nano API", version="1.0.0")
92
- app.add_middleware(CORSMiddleware, allow_origins=["*"], allow_credentials=True, allow_methods=["*"], allow_headers=["*"])
93
 
 
94
  engine = None
95
 
 
 
96
  class MoveRequest(BaseModel):
97
- fen: str
98
- depth: Optional[int] = Field(3, ge=1, le=5)
 
 
99
 
100
  class MoveResponse(BaseModel):
101
  best_move: str
102
  evaluation: float
103
  depth_searched: int
 
104
  nodes_evaluated: int
105
  time_taken: int
 
 
 
 
106
 
 
 
 
 
 
 
 
 
107
  @app.on_event("startup")
108
- async def startup():
109
  global engine
110
- logger.info("🚀 Starting Nexus-Nano API...")
111
- model_path = "/app/nexus_nano.onnx"
 
112
  try:
113
- engine = NexusNanoEngine(model_path)
114
- logger.info("✅ Engine ready")
 
 
 
 
115
  except Exception as e:
116
- logger.error(f"❌ Failed to load engine: {e}")
117
  raise
118
 
119
- @app.get("/health")
120
- async def health():
121
- return {"status": "healthy" if engine else "unhealthy", "model_loaded": engine is not None, "version": "1.0.0"}
122
 
 
 
 
 
 
 
 
 
 
 
 
 
123
  @app.post("/get-move", response_model=MoveResponse)
124
- async def get_move(req: MoveRequest):
125
- if not engine: raise HTTPException(503, "Engine not loaded")
126
- try: chess.Board(req.fen)
127
- except: raise HTTPException(400, "Invalid FEN")
128
- start = time.time()
 
 
 
 
129
  try:
130
- result = engine.search(req.fen, req.depth)
131
- elapsed = int((time.time() - start) * 1000)
132
- logger.info(f"Move: {result['best_move']} | Eval: {result['evaluation']:+.2f} | Time: {elapsed}ms")
133
- return MoveResponse(best_move=result['best_move'], evaluation=result['evaluation'],
134
- depth_searched=result['depth'], nodes_evaluated=result['nodes'], time_taken=elapsed)
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
135
  except Exception as e:
136
  logger.error(f"Error: {e}")
137
- raise HTTPException(500, str(e))
138
 
 
 
139
  @app.get("/")
140
  async def root():
141
- return {"name": "Nexus-Nano", "version": "1.0.0", "status": "online" if engine else "starting"}
 
 
 
 
 
 
 
 
 
 
 
 
142
 
143
  if __name__ == "__main__":
144
  import uvicorn
 
1
+ """
2
+ Nexus-Nano Inference API
3
+ 2.8M parameter ultra-fast engine
4
+ """
5
+
6
  from fastapi import FastAPI, HTTPException
7
  from fastapi.middleware.cors import CORSMiddleware
8
  from pydantic import BaseModel, Field
 
 
 
9
  import time
10
  import logging
11
+ from typing import Optional, List
12
+
13
+ from engine import NexusNanoEngine
14
 
15
+ # Logging
16
+ logging.basicConfig(
17
+ level=logging.INFO,
18
+ format='%(asctime)s - %(name)s - %(levelname)s - %(message)s'
19
+ )
20
  logger = logging.getLogger(__name__)
21
 
22
+ # FastAPI
23
+ app = FastAPI(
24
+ title="Nexus-Nano Inference API",
25
+ description="Ultra-fast 2.8M parameter chess engine",
26
+ version="1.0.0"
27
+ )
28
+
29
+ # CORS
30
+ app.add_middleware(
31
+ CORSMiddleware,
32
+ allow_origins=["*"],
33
+ allow_credentials=True,
34
+ allow_methods=["*"],
35
+ allow_headers=["*"],
36
+ )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
37
 
38
+ # Global
39
  engine = None
40
 
41
+
42
+ # Models
43
  class MoveRequest(BaseModel):
44
+ fen: str = Field(..., description="FEN notation")
45
+ depth: Optional[int] = Field(4, ge=1, le=6, description="Search depth (1-6)")
46
+ time_limit: Optional[int] = Field(2000, ge=500, le=10000, description="Time in ms")
47
+
48
 
49
  class MoveResponse(BaseModel):
50
  best_move: str
51
  evaluation: float
52
  depth_searched: int
53
+ seldepth: int
54
  nodes_evaluated: int
55
  time_taken: int
56
+ nps: int
57
+ pv: List[str]
58
+ tt_hit_rate: Optional[float] = None
59
+
60
 
61
+ class HealthResponse(BaseModel):
62
+ status: str
63
+ model_loaded: bool
64
+ version: str
65
+ model_size_mb: Optional[float] = None
66
+
67
+
68
+ # Startup
69
  @app.on_event("startup")
70
+ async def startup_event():
71
  global engine
72
+
73
+ logger.info("⚡ Starting Nexus-Nano API v1.0...")
74
+
75
  try:
76
+ engine = NexusNanoEngine(
77
+ model_path="/app/models/nexus_nano.onnx",
78
+ num_threads=1 # Single-threaded for speed
79
+ )
80
+ logger.info("✅ Engine loaded")
81
+
82
  except Exception as e:
83
+ logger.error(f"❌ Failed: {e}")
84
  raise
85
 
 
 
 
86
 
87
+ # Health
88
+ @app.get("/health", response_model=HealthResponse)
89
+ async def health_check():
90
+ return {
91
+ "status": "healthy" if engine else "unhealthy",
92
+ "model_loaded": engine is not None,
93
+ "version": "1.0.0",
94
+ "model_size_mb": engine.get_model_size() if engine else None
95
+ }
96
+
97
+
98
+ # Main
99
  @app.post("/get-move", response_model=MoveResponse)
100
+ async def get_move(request: MoveRequest):
101
+ if engine is None:
102
+ raise HTTPException(status_code=503, detail="Engine not loaded")
103
+
104
+ if not engine.validate_fen(request.fen):
105
+ raise HTTPException(status_code=400, detail="Invalid FEN")
106
+
107
+ start_time = time.time()
108
+
109
  try:
110
+ result = engine.get_best_move(
111
+ fen=request.fen,
112
+ depth=request.depth,
113
+ time_limit=request.time_limit
114
+ )
115
+
116
+ time_taken = int((time.time() - start_time) * 1000)
117
+
118
+ logger.info(
119
+ f"⚡ Move: {result['best_move']} | "
120
+ f"Eval: {result['evaluation']:+.2f} | "
121
+ f"Depth: {result['depth_searched']} | "
122
+ f"Time: {time_taken}ms | "
123
+ f"NPS: {result['nps']}"
124
+ )
125
+
126
+ return MoveResponse(
127
+ best_move=result['best_move'],
128
+ evaluation=result['evaluation'],
129
+ depth_searched=result['depth_searched'],
130
+ seldepth=result['seldepth'],
131
+ nodes_evaluated=result['nodes_evaluated'],
132
+ time_taken=time_taken,
133
+ nps=result['nps'],
134
+ pv=result['pv'],
135
+ tt_hit_rate=result['tt_stats']['hit_rate']
136
+ )
137
+
138
  except Exception as e:
139
  logger.error(f"Error: {e}")
140
+ raise HTTPException(status_code=500, detail=str(e))
141
 
142
+
143
+ # Root
144
  @app.get("/")
145
  async def root():
146
+ return {
147
+ "name": "Nexus-Nano Inference API",
148
+ "version": "1.0.0",
149
+ "model": "2.8M parameters (Lightweight CNN)",
150
+ "tagline": "Ultra-fast chess inference",
151
+ "search": "Alpha-Beta + Quiescence",
152
+ "endpoints": {
153
+ "POST /get-move": "Get best move (fast)",
154
+ "GET /health": "Health check",
155
+ "GET /docs": "API docs"
156
+ }
157
+ }
158
+
159
 
160
  if __name__ == "__main__":
161
  import uvicorn