ffzeroHua commited on
Commit
f2f2fc1
·
verified ·
1 Parent(s): 03828ea

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +48 -37
app.py CHANGED
@@ -11,6 +11,7 @@ import gradio as gr
11
  import pandas as pd
12
  import matplotlib.pyplot as plt
13
  from huggingface_hub import snapshot_download, hf_hub_download, HfApi
 
14
 
15
  from riichienv import RiichiEnv, GameRule
16
  from model3pLOCAL import load_model
@@ -24,7 +25,7 @@ HF_TOKEN = os.getenv("HF_TOKEN")
24
 
25
  # 为当前节点生成唯一的 ID
26
  WORKER_ID = os.getenv("WORKER_ID", str(uuid.uuid4())[:6])
27
- REPORT_FILE_PREFIX = 'Step40800P42998_vs_9070_eval_report'
28
  REPORT_FILE = f"{REPORT_FILE_PREFIX}_{WORKER_ID}.txt"
29
 
30
  api = HfApi()
@@ -130,36 +131,48 @@ class MortalAgent:
130
  return action
131
 
132
  # ==========================================
133
- # 2. 核心对局任务
134
  # ==========================================
135
- def play_one_game(game_index):
136
- env = RiichiEnv(game_mode="3p-red-half", rule=GameRule.default_tenhou())
137
- new_seat = random.randrange(3)
 
 
 
138
 
139
- agents = {}
140
- for i in range(3):
141
- # 🚀 固定对抗:主角 1 vs 考官 2
142
- model_file = TEST_MODEL if i == new_seat else EXAMINER_MODEL
143
- agents[i] = MortalAgent(i, model_file)
144
-
145
- obs_dict = env.reset()
146
- while not env.done():
147
- actions = {pid: agents[pid].act(obs) for pid, obs in obs_dict.items()}
148
- obs_dict = env.step(actions)
 
 
 
 
 
 
 
 
149
 
150
- scores = env.scores()
151
- ranks = env.ranks()
152
- return ranks[new_seat], scores[new_seat]
 
153
 
154
  # ==========================================
155
  # 3. 后台独立评估线程
156
  # ==========================================
157
  def background_eval_loop():
158
- sync_models_from_hub() # 🚀 启动时从 Riichi-Model-Repo 拉取对战模型
159
- sync_data_from_hub() # 🚀 启动时从战绩仓库拉取历史战绩
160
 
161
- NUM_WORKERS = 2
162
- print(f"🚀 节点 [{WORKER_ID}] 后台对战线程已启动: {TEST_MODEL} 挑战双 {EXAMINER_MODEL}")
163
 
164
  if not os.path.exists(REPORT_FILE):
165
  open(REPORT_FILE, 'w').close()
@@ -167,7 +180,8 @@ def background_eval_loop():
167
  games_since_last_sync = 0
168
 
169
  with concurrent.futures.ProcessPoolExecutor(max_workers=NUM_WORKERS) as executor:
170
- futures = {executor.submit(play_one_game, i) for i in range(NUM_WORKERS * 2)}
 
171
  games_completed = 0
172
 
173
  while EVAL_RUNNING and futures:
@@ -178,19 +192,23 @@ def background_eval_loop():
178
  with open(REPORT_FILE, "a") as f:
179
  for future in done:
180
  try:
181
- rank, score = future.result()
182
- f.write(f"{rank} {score}\n")
 
 
 
 
 
183
  f.flush()
184
- games_completed += 1
185
- games_since_last_sync += 1
186
- print(f"[节点 {WORKER_ID}] 完成 {games_completed} 局: 顺位 {rank}, 得点 {score}")
187
  except Exception as e:
188
  print(f"对局异常: {e}")
189
 
190
  if EVAL_RUNNING:
191
- futures.add(executor.submit(play_one_game, games_completed))
 
192
 
193
- if games_since_last_sync >= 50:
 
194
  sync_data_to_hub()
195
  sync_data_from_hub()
196
  games_since_last_sync = 0
@@ -284,14 +302,7 @@ with gr.Blocks() as demo:
284
 
285
  refresh_btn.click(fn=read_and_analyze, inputs=None, outputs=[stats_output, plot_output])
286
 
287
- import multiprocessing
288
-
289
  if __name__ == "__main__":
290
- # 强制使用 spawn,解决 Rust FFI 多进程死锁问题
291
- multiprocessing.set_start_method('spawn', force=True)
292
-
293
- # 修复后,你可以安全地将 NUM_WORKERS 调大
294
- # HF Space 免费层通常有 2 vCPU,可以设为 2。如果是更高配置的机器,设为 os.cpu_count() - 1
295
  t = threading.Thread(target=background_eval_loop, daemon=True)
296
  t.start()
297
 
 
11
  import pandas as pd
12
  import matplotlib.pyplot as plt
13
  from huggingface_hub import snapshot_download, hf_hub_download, HfApi
14
+ import secrets
15
 
16
  from riichienv import RiichiEnv, GameRule
17
  from model3pLOCAL import load_model
 
25
 
26
  # 为当前节点生成唯一的 ID
27
  WORKER_ID = os.getenv("WORKER_ID", str(uuid.uuid4())[:6])
28
+ REPORT_FILE_PREFIX = 'Step40800P42998_vs_9070_Fair_eval_report'
29
  REPORT_FILE = f"{REPORT_FILE_PREFIX}_{WORKER_ID}.txt"
30
 
31
  api = HfApi()
 
131
  return action
132
 
133
  # ==========================================
134
+ # 2. 核心对局任务 (复式评测:同种子 3 局轮换)
135
  # ==========================================
136
+ def play_three_games_with_seed(seed):
137
+ """
138
+ 接收一个全局唯一的随机种子,让测试模型在 0, 1, 2 三个位置各打一局。
139
+ 返回 3 局的成绩列表。
140
+ """
141
+ results = []
142
 
143
+ # 轮换测试模型的位置
144
+ for test_seat in range(3):
145
+ env = RiichiEnv(game_mode="3p-red-half", rule=GameRule.default_tenhou())
146
+ agents = {}
147
+ for i in range(3):
148
+ # 测试模型坐在 test_seat,其他位置是考官模型
149
+ model_file = TEST_MODEL if i == test_seat else EXAMINER_MODEL
150
+ agents[i] = MortalAgent(i, model_file)
151
+
152
+ # 🚀 关键:注入指定的种子!这样三次循环生成的牌山是完全一致的
153
+ obs_dict = env.reset(seed=seed)
154
+
155
+ while not env.done():
156
+ actions = {pid: agents[pid].act(obs) for pid, obs in obs_dict.items()}
157
+ obs_dict = env.step(actions)
158
+
159
+ scores = env.scores()
160
+ ranks = env.ranks()
161
 
162
+ # 记录测试模型在这一局的表现
163
+ results.append((ranks[test_seat], scores[test_seat]))
164
+
165
+ return results
166
 
167
  # ==========================================
168
  # 3. 后台独立评估线程
169
  # ==========================================
170
  def background_eval_loop():
171
+ sync_models_from_hub() # 启动时从 Riichi-Model-Repo 拉取对战模型
172
+ sync_data_from_hub() # 启动时从战绩仓库拉取历史战绩
173
 
174
+ NUM_WORKERS = 1
175
+ print(f"🚀 节点 [{WORKER_ID}] 后台对战线程已启动: {TEST_MODEL} 挑战双 {EXAMINER_MODEL} (复式打法)")
176
 
177
  if not os.path.exists(REPORT_FILE):
178
  open(REPORT_FILE, 'w').close()
 
180
  games_since_last_sync = 0
181
 
182
  with concurrent.futures.ProcessPoolExecutor(max_workers=NUM_WORKERS) as executor:
183
+ # 🚀 使用 secrets.randbits(31) 生成跨节点绝对不冲突的 31 位正整数种子
184
+ futures = {executor.submit(play_three_games_with_seed, secrets.randbits(31)) for _ in range(NUM_WORKERS * 2)}
185
  games_completed = 0
186
 
187
  while EVAL_RUNNING and futures:
 
192
  with open(REPORT_FILE, "a") as f:
193
  for future in done:
194
  try:
195
+ # 现在 future.result() 返回的是包含 3 局结果的列表
196
+ batch_results = future.result()
197
+ for rank, score in batch_results:
198
+ f.write(f"{rank} {score}\n")
199
+ games_completed += 1
200
+ games_since_last_sync += 1
201
+ print(f"[节点 {WORKER_ID}] 完成复式对局: 顺位 {rank}, 得点 {score}")
202
  f.flush()
 
 
 
203
  except Exception as e:
204
  print(f"对局异常: {e}")
205
 
206
  if EVAL_RUNNING:
207
+ # 完成了一轮复式,再投递一个全新的安全种子
208
+ futures.add(executor.submit(play_three_games_with_seed, secrets.randbits(31)))
209
 
210
+ # 由于一次产出 3 局,同步频率可以保持在累计完成 50 局左右时触发
211
+ if games_since_last_sync >= 60:
212
  sync_data_to_hub()
213
  sync_data_from_hub()
214
  games_since_last_sync = 0
 
302
 
303
  refresh_btn.click(fn=read_and_analyze, inputs=None, outputs=[stats_output, plot_output])
304
 
 
 
305
  if __name__ == "__main__":
 
 
 
 
 
306
  t = threading.Thread(target=background_eval_loop, daemon=True)
307
  t.start()
308