Amogh1221 commited on
Commit
51ba373
·
verified ·
1 Parent(s): 03eebe6

Update main.py

Browse files
Files changed (1) hide show
  1. main.py +52 -26
main.py CHANGED
@@ -69,6 +69,7 @@ app.add_middleware(
69
  allow_headers=["*"],
70
  )
71
 
 
72
  DEEPCASTLE_ENGINE_PATH = os.environ.get(
73
  "DEEPCASTLE_ENGINE_PATH",
74
  os.environ.get("ENGINE_PATH", "/app/engine_bin/deepcastle"),
@@ -125,10 +126,22 @@ def health():
125
 
126
  # Global engine instances to save memory and improve performance
127
  _GLOBAL_DEEPCASTLE_ENGINE = None
 
 
 
 
 
 
 
 
 
 
 
128
 
129
  async def _get_or_start_engine(engine_path: str, *, role: str, options: Optional[dict] = None):
130
  global _GLOBAL_DEEPCASTLE_ENGINE
131
 
 
132
  current_engine = _GLOBAL_DEEPCASTLE_ENGINE
133
  if current_engine is not None:
134
  try:
@@ -136,43 +149,57 @@ async def _get_or_start_engine(engine_path: str, *, role: str, options: Optional
136
  return current_engine
137
  except Exception:
138
  _GLOBAL_DEEPCASTLE_ENGINE = None
 
 
139
 
140
- if not os.path.exists(engine_path):
141
- raise HTTPException(status_code=500, detail=f"{role} binary NOT FOUND at {engine_path}")
 
 
 
 
 
 
 
 
142
 
143
- try:
144
- _, engine = await chess.engine.popen_uci(engine_path)
 
 
 
145
 
146
- if options:
147
- await engine.configure(options)
148
 
149
- if os.path.exists(NNUE_PATH):
150
- try:
151
- await engine.configure({"EvalFile": NNUE_PATH})
152
- except Exception as ne:
153
- print(f"[ERROR] EvalFile load failed: {str(ne)}")
154
- else:
155
- print(f"[WARNING] EvalFile not found at {NNUE_PATH}")
156
 
157
- if os.path.exists(NNUE_SMALL_PATH):
158
- try:
159
- await engine.configure({"EvalFileSmall": NNUE_SMALL_PATH})
160
- except Exception as ne:
161
- print(f"[ERROR] EvalFileSmall load failed: {str(ne)}")
162
- else:
163
- print(f"[WARNING] EvalFileSmall not found at {NNUE_SMALL_PATH}")
164
 
165
- _GLOBAL_DEEPCASTLE_ENGINE = engine
 
 
 
 
166
 
167
- return engine
168
- except Exception as e:
169
- raise HTTPException(status_code=500, detail=f"{role} crash: {str(e)}")
170
 
171
  async def get_deepcastle_engine():
172
  return await _get_or_start_engine(
173
  DEEPCASTLE_ENGINE_PATH,
174
  role="deepcastle",
175
- options={"Hash": 128, "Threads": 1},
176
  )
177
 
178
  async def get_stockfish_engine():
@@ -557,7 +584,6 @@ async def analyze_game(request: AnalyzeRequest):
557
  analysis_results.append(MoveAnalysis(
558
  move_num=i+1,
559
  san=san_move,
560
- fen=board.fen(),
561
  classification=cls,
562
  cpl=float(cpl),
563
  score_before=float(score_before / 100.0),
 
69
  allow_headers=["*"],
70
  )
71
 
72
+ # Paths relative to the Docker container
73
  DEEPCASTLE_ENGINE_PATH = os.environ.get(
74
  "DEEPCASTLE_ENGINE_PATH",
75
  os.environ.get("ENGINE_PATH", "/app/engine_bin/deepcastle"),
 
126
 
127
  # Global engine instances to save memory and improve performance
128
  _GLOBAL_DEEPCASTLE_ENGINE = None
129
+ _ENGINE_LOCK = asyncio.Lock()
130
+
131
+
132
+ def _engine_hash_mb() -> int:
133
+ """Transposition table size in MB; lower = less RAM (env ENGINE_HASH_MB, default 64)."""
134
+ try:
135
+ v = int(os.environ.get("ENGINE_HASH_MB", "64"))
136
+ except ValueError:
137
+ v = 64
138
+ return max(8, min(512, v))
139
+
140
 
141
  async def _get_or_start_engine(engine_path: str, *, role: str, options: Optional[dict] = None):
142
  global _GLOBAL_DEEPCASTLE_ENGINE
143
 
144
+ # Fast path: no lock when singleton is already healthy (avoids serializing every /move).
145
  current_engine = _GLOBAL_DEEPCASTLE_ENGINE
146
  if current_engine is not None:
147
  try:
 
149
  return current_engine
150
  except Exception:
151
  _GLOBAL_DEEPCASTLE_ENGINE = None
152
+ else:
153
+ _GLOBAL_DEEPCASTLE_ENGINE = None
154
 
155
+ async with _ENGINE_LOCK:
156
+ current_engine = _GLOBAL_DEEPCASTLE_ENGINE
157
+ if current_engine is not None:
158
+ try:
159
+ if not current_engine.is_terminated():
160
+ return current_engine
161
+ except Exception:
162
+ _GLOBAL_DEEPCASTLE_ENGINE = None
163
+ else:
164
+ _GLOBAL_DEEPCASTLE_ENGINE = None
165
 
166
+ if not os.path.exists(engine_path):
167
+ raise HTTPException(status_code=500, detail=f"{role} binary NOT FOUND at {engine_path}")
168
+
169
+ try:
170
+ _, engine = await chess.engine.popen_uci(engine_path)
171
 
172
+ if options:
173
+ await engine.configure(options)
174
 
175
+ if os.path.exists(NNUE_PATH):
176
+ try:
177
+ await engine.configure({"EvalFile": NNUE_PATH})
178
+ except Exception as ne:
179
+ print(f"[ERROR] EvalFile load failed: {str(ne)}")
180
+ else:
181
+ print(f"[WARNING] EvalFile not found at {NNUE_PATH}")
182
 
183
+ if os.path.exists(NNUE_SMALL_PATH):
184
+ try:
185
+ await engine.configure({"EvalFileSmall": NNUE_SMALL_PATH})
186
+ except Exception as ne:
187
+ print(f"[ERROR] EvalFileSmall load failed: {str(ne)}")
188
+ else:
189
+ print(f"[WARNING] EvalFileSmall not found at {NNUE_SMALL_PATH}")
190
 
191
+ _GLOBAL_DEEPCASTLE_ENGINE = engine
192
+
193
+ return engine
194
+ except Exception as e:
195
+ raise HTTPException(status_code=500, detail=f"{role} crash: {str(e)}")
196
 
 
 
 
197
 
198
  async def get_deepcastle_engine():
199
  return await _get_or_start_engine(
200
  DEEPCASTLE_ENGINE_PATH,
201
  role="deepcastle",
202
+ options={"Hash": _engine_hash_mb(), "Threads": 1},
203
  )
204
 
205
  async def get_stockfish_engine():
 
584
  analysis_results.append(MoveAnalysis(
585
  move_num=i+1,
586
  san=san_move,
 
587
  classification=cls,
588
  cpl=float(cpl),
589
  score_before=float(score_before / 100.0),