Files
wehub-resource-sync e64161ec32
CI / ci (3.11) (push) Has been cancelled
CI / ci (3.10) (push) Has been cancelled
CI / dependabot (push) Has been cancelled
Release / release_and_publish (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:36:15 +08:00

141 lines
4.7 KiB
Python

"""
RL Runner - 执行训练代码并提交 Grading Server 评测
作为 autorl_bench agent 运行:
- 训练代码在本地执行($WORKSPACE/code/ 下)
- 评测通过 HTTP POST $GRADING_SERVER_URL/submit
"""
import json
import os
import subprocess
import time
from pathlib import Path
import requests
from rdagent.core.developer import Developer
from rdagent.core.experiment import Experiment
from rdagent.core.scenario import Scenario
from rdagent.log import rdagent_logger as logger
class RLPostTrainingRunner(Developer):
"""RL Runner - 本地执行训练 + HTTP API 评测"""
def __init__(self, scen: Scenario, timeout: int = 360000) -> None:
self.scen = scen
self.timeout = timeout
def develop(self, exp: Experiment) -> Experiment:
"""
执行训练代码并提交评测
流程:
1. 将生成的代码写入 $WORKSPACE/code/
2. 本地执行 main.py
3. POST $GRADING_SERVER_URL/submit 提交评测
"""
workspace = exp.experiment_workspace
if workspace is None or "main.py" not in workspace.file_dict:
logger.warning("No main.py in experiment workspace, skipping")
exp.result = {"exit_code": -1, "stdout": "No main.py generated"}
return exp
# 从 env var 读取路径(run.py 已设置)
ws_dir = os.environ.get("WORKSPACE", "")
output_dir = os.environ.get("OUTPUT_DIR", "")
grading_url = os.environ.get("GRADING_SERVER_URL", "")
if not ws_dir:
logger.error("WORKSPACE env var not set")
exp.result = {"exit_code": -1, "stdout": "WORKSPACE not set"}
return exp
code_dir = Path(ws_dir) / "code"
code_dir.mkdir(parents=True, exist_ok=True)
# 1. 将生成的代码写入 code/
for filename, content in workspace.file_dict.items():
dst = code_dir / filename
dst.parent.mkdir(parents=True, exist_ok=True)
dst.write_text(content)
logger.info(f" Wrote {dst}")
# 2. 本地执行 main.py
main_py = code_dir / "main.py"
logger.info(f"=== Executing {main_py} ===")
start_time = time.time()
try:
proc = subprocess.run(
["python", str(main_py)],
cwd=str(code_dir),
capture_output=True,
text=True,
timeout=self.timeout,
env={**os.environ, "PYTHONUNBUFFERED": "1"},
)
exit_code = proc.returncode
stdout = proc.stdout + proc.stderr
except subprocess.TimeoutExpired as e:
exit_code = -1
stdout = f"Timeout after {self.timeout}s\n{e.stdout or ''}"
logger.warning(f"Training timed out after {self.timeout}s")
elapsed = time.time() - start_time
logger.info(f"Training finished: exit_code={exit_code}, time={elapsed:.1f}s")
if exit_code != 0:
logger.warning(f"Training failed:\n{stdout[:2000]}")
exp.result = {
"exit_code": exit_code,
"stdout": stdout,
"running_time": elapsed,
"benchmark": None,
}
# 3. 提交 Grading Server 评测
if exit_code != 0 or not grading_url or not output_dir:
return exp
output_path = Path(output_dir)
if not output_path.exists() or not any(output_path.iterdir()):
logger.info("No model output found, skipping evaluation")
return exp
# 找到 output/ 下最新的模型目录(可能有 v1/, v2/ 等子目录)
model_path = self._find_latest_model(output_path)
logger.info(f"=== Submitting to Grading Server: {model_path} ===")
try:
resp = requests.post(
f"{grading_url}/submit",
json={"model_path": str(model_path)},
timeout=600,
)
result = resp.json()
exp.result["benchmark"] = result
logger.info(
f" Score: {result.get('score')}, "
f"Improvement: {result.get('improvement')}, "
f"Best: {result.get('best', {}).get('score')}"
)
except Exception as e:
logger.error(f"Grading server submission failed: {e}")
return exp
@staticmethod
def _find_latest_model(output_dir: Path) -> Path:
"""找到 output/ 下的模型路径。
如果有子目录(v1/, v2/ 等),返回最新修改的那个;
否则返回 output/ 本身。
"""
subdirs = [d for d in output_dir.iterdir() if d.is_dir() and not d.name.startswith(".")]
if subdirs:
return max(subdirs, key=lambda d: d.stat().st_mtime)
return output_dir