shinka-backup / eval_agent /design_draft /RESULTS_DIR_FLOW.md
JustinTX's picture
Add files using upload-large-folder tool
3f6526a verified
# ShinkaEvolve: results_dir 传递流程详解
## 🎯 问题
当运行 ShinkaEvolve 时,评估脚本(evaluate.py)是如何知道要把结果保存到指定的 `results_dir` 的?
## 📊 完整数据流程
```
┌─────────────────────────────────────────────────────────────────────────┐
│ 第 1 步: 用户配置 (run_circle_packing_*.py) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ evo_config = EvolutionConfig( │
│ results_dir="examples/circle_packing/results/results_exp_20260129", │
│ ... │
│ ) │
│ │
└────────────────────────────┬────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 第 2 步: EvolutionRunner 初始化 (shinka/core/runner.py:90-108) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ class EvolutionRunner: │
│ def __init__(self, evo_config, ...): │
│ if evo_config.results_dir is None: │
│ timestamp = datetime.now().strftime("%Y%m%d_%H%M%S") │
│ self.results_dir = f"results_{timestamp}" │
│ else: │
│ self.results_dir = Path(evo_config.results_dir) ← 使用配置 │
│ │
│ # 创建目录 │
│ Path(self.results_dir).mkdir(parents=True, exist_ok=True) │
│ │
│ 结果: self.results_dir = "examples/.../results_exp_20260129" │
│ │
└────────────────────────────┬────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 第 3 步: 运行每一代 (shinka/core/runner.py:461-496) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ def _run_generation_0(self): │
│ # 构建当前代的目录路径 │
│ initial_dir = f"{self.results_dir}/{FOLDER_PREFIX}_0" │
│ # 即: "examples/.../results_exp_20260129/gen_0" │
│ │
│ Path(initial_dir).mkdir(parents=True, exist_ok=True) │
│ exec_fname = f"{initial_dir}/main.py" │
│ │
│ # 构建结果子目录 │
│ results_dir = f"{self.results_dir}/{FOLDER_PREFIX}_0/results" │
│ # 即: "examples/.../results_exp_20260129/gen_0/results" │
│ │
│ Path(results_dir).mkdir(parents=True, exist_ok=True) │
│ │
│ # 运行评估,传递 results_dir │
│ results, rtime = self.scheduler.run(exec_fname, results_dir) ←关键 │
│ │
└────────────────────────────┬────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 第 4 步: JobScheduler 构建命令 (shinka/launch/scheduler.py:98-148) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ def _build_command(self, exec_fname_t, results_dir_t): │
│ cmd = [ │
│ "python", │
│ f"{self.config.eval_program_path}", ← 评估脚本路径 │
│ "--program_path", │
│ f"{exec_fname_t}", ← 被评估的程序 │
│ "--results_dir", │
│ results_dir_t, ← 结果目录! │
│ ] │
│ │
│ 实际命令: │
│ python examples/circle_packing/evaluate_with_auxiliary.py \ │
│ --program_path examples/.../gen_0/main.py \ │
│ --results_dir examples/.../gen_0/results │
│ │
└────────────────────────────┬────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 第 5 步: 评估脚本接收参数 (evaluate_with_auxiliary.py:260-295) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ if __name__ == "__main__": │
│ parser = argparse.ArgumentParser(...) │
│ parser.add_argument( │
│ "--program_path", │
│ type=str, │
│ default="initial.py", │
│ ) │
│ parser.add_argument( │
│ "--results_dir", ← 接收命令行参数 │
│ type=str, │
│ default="results", │
│ ) │
│ │
│ args = parser.parse_args() │
│ │
│ main( │
│ program_path=args.program_path, │
│ results_dir=args.results_dir, ← 传递给 main 函数 │
│ ) │
│ │
└────────────────────────────┬────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 第 6 步: 评估执行和结果保存 (evaluate_with_auxiliary.py:161-256) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ def main(program_path, results_dir, ...): │
│ # 确保目录存在 │
│ os.makedirs(results_dir, exist_ok=True) │
│ │
│ # 运行评估 │
│ metrics, correct, error = run_shinka_eval( │
│ program_path=program_path, │
│ results_dir=results_dir, ← 传递给核心评估函数 │
│ ... │
│ ) │
│ │
└────────────────────────────┬────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────────────────┐
│ 第 7 步: 核心评估函数保存结果 (shinka/core/wrap_eval.py:54-193) │
├─────────────────────────────────────────────────────────────────────────┤
│ │
│ def run_shinka_eval(program_path, results_dir, ...): │
│ # ... 运行实验 ... │
│ │
│ # 保存额外数据 │
│ if "extra_data" in metrics: │
│ os.makedirs(results_dir, exist_ok=True) │
│ extra_file = os.path.join(results_dir, "extra.pkl") │
│ with open(extra_file, "wb") as f: │
│ pickle.dump(extra_data, f) │
│ │
│ # 保存主要结果 │
│ save_json_results(results_dir, metrics, correct, error) │
│ │
│ def save_json_results(results_dir, metrics, correct, error): │
│ os.makedirs(results_dir, exist_ok=True) │
│ metrics_file = os.path.join(results_dir, "metrics.json") │
│ correct_file = os.path.join(results_dir, "correct.json") │
│ │
│ # 保存文件 │
│ with open(metrics_file, "w") as f: │
│ json.dump(metrics, f, indent=4) │
│ with open(correct_file, "w") as f: │
│ json.dump({"correct": correct, "error": error}, f, indent=4) │
│ │
│ 结果文件: │
│ - examples/.../gen_0/results/metrics.json │
│ - examples/.../gen_0/results/correct.json │
│ - examples/.../gen_0/results/extra.pkl (如果有) │
│ │
└─────────────────────────────────────────────────────────────────────────┘
```
## 🔍 关键代码位置
### 1. EvolutionConfig 定义 (shinka/core/runner.py:38-64)
```python
@dataclass
class EvolutionConfig:
# ... 其他配置 ...
results_dir: Optional[str] = None # 第59行:results_dir 配置项
# ... 其他配置 ...
```
### 2. EvolutionRunner 使用配置 (shinka/core/runner.py:104-108)
```python
if evo_config.results_dir is None:
timestamp = datetime.now().strftime("%Y%m%d_%H%M%S")
self.results_dir = f"results_{timestamp}"
else:
self.results_dir = Path(evo_config.results_dir) # 使用用户配置
```
### 3. 构建每一代的路径 (shinka/core/runner.py:461-496)
```python
def _run_generation_0(self):
initial_dir = f"{self.results_dir}/{FOLDER_PREFIX}_0"
# ...
results_dir = f"{self.results_dir}/{FOLDER_PREFIX}_0/results"
Path(results_dir).mkdir(parents=True, exist_ok=True)
# 运行评估
results, rtime = self.scheduler.run(exec_fname, results_dir)
```
### 4. JobScheduler 构建命令 (shinka/launch/scheduler.py:98-148)
```python
def _build_command(self, exec_fname_t: str, results_dir_t: str) -> List[str]:
cmd = [
"python",
f"{self.config.eval_program_path}",
"--program_path",
f"{exec_fname_t}",
"--results_dir",
results_dir_t, # ← results_dir 作为命令行参数
]
return cmd
```
### 5. 评估脚本接收参数 (evaluate_with_auxiliary.py:260-295)
```python
if __name__ == "__main__":
parser = argparse.ArgumentParser(...)
parser.add_argument(
"--program_path",
type=str,
default="initial.py",
)
parser.add_argument(
"--results_dir", # ← 接收命令行参数
type=str,
default="results",
)
args = parser.parse_args()
main(
program_path=args.program_path,
results_dir=args.results_dir, # ← 传递给 main
)
```
### 6. 保存结果 (shinka/core/wrap_eval.py:33-51)
```python
def save_json_results(
results_dir: str, # ← 接收 results_dir
metrics: Dict[str, Any],
correct: bool,
error: Optional[str] = None,
) -> None:
os.makedirs(results_dir, exist_ok=True)
metrics_file = os.path.join(results_dir, "metrics.json")
correct_file = os.path.join(results_dir, "correct.json")
with open(metrics_file, "w", encoding="utf-8") as f:
json.dump(metrics, f, indent=4)
with open(correct_file, "w", encoding="utf-8") as f:
json.dump({"correct": correct, "error": error}, f, indent=4)
```
## 📁 实际目录结构示例
```
examples/circle_packing/results/results_exp_20260129_150530/
├── evolution_db_exp_20260129_150530.sqlite ← 数据库
├── evolution_run.log ← 日志
├── experiment_config.yaml ← 配置
├── gen_0/ ← 第 0 代
│ ├── main.py ← 被评估的程序
│ └── results/ ← 评估结果目录
│ ├── metrics.json ← 性能指标
│ ├── correct.json ← 正确性标志
│ ├── extra.npz ← 额外数据
│ ├── packing_viz.png ← 可视化
│ └── auxiliary_analysis.json ← 辅助分析
├── gen_1/
│ ├── main.py
│ └── results/
│ ├── metrics.json
│ └── ...
├── gen_2/
│ └── ...
└── best/ ← 最佳结果(符号链接)
└── results/
├── metrics.json
└── ...
```
## 🔧 关键设计特点
### 1. 层次化的目录结构
```
{results_dir}/
├── gen_0/results/ ← EvolutionRunner 创建
├── gen_1/results/ ← EvolutionRunner 创建
└── gen_2/results/ ← EvolutionRunner 创建
└── metrics.json ← 评估脚本保存
```
### 2. 参数传递链
```
用户配置 → EvolutionConfig.results_dir
→ EvolutionRunner.results_dir
→ JobScheduler.run(results_dir)
→ 命令行参数 --results_dir
→ argparse 解析
→ main(results_dir)
→ save_json_results(results_dir)
→ 文件保存
```
### 3. 职责分离
| 组件 | 职责 |
|-----|------|
| **EvolutionRunner** | 创建顶层目录和每代的子目录 |
| **JobScheduler** | 构建命令并传递 results_dir 参数 |
| **评估脚本** | 接收参数并执行评估 |
| **run_shinka_eval** | 保存评估结果到指定目录 |
## 💡 为什么这样设计?
### 优点
1. **解耦**: 评估脚本不需要知道 ShinkaEvolve 的目录结构
2. **灵活**: 可以独立运行评估脚本进行测试
3. **标准化**: 所有评估脚本使用相同的参数接口
4. **可追溯**: 每一代都有独立的结果目录
### 示例:独立运行评估脚本
```bash
# 可以完全独立于 ShinkaEvolve 运行评估
python examples/circle_packing/evaluate_with_auxiliary.py \
--program_path my_solution.py \
--results_dir my_custom_results/
# 结果会保存在 my_custom_results/ 而不是 ShinkaEvolve 的目录结构中
```
## 🎯 总结
**评估脚本通过命令行参数 `--results_dir` 知道保存位置!**
整个流程是:
1. 用户在 `EvolutionConfig` 中设置 `results_dir`
2. `EvolutionRunner` 读取配置并创建目录结构
3. 对于每一代,构建 `{results_dir}/gen_X/results` 路径
4. `JobScheduler` 将这个路径作为 `--results_dir` 参数传递给评估脚本
5. 评估脚本通过 `argparse` 解析参数
6. `run_shinka_eval` 和 `save_json_results` 使用这个路径保存结果
这是一个**标准的命令行参数传递机制**,而不是硬编码或环境变量。
---
**创建时间**: 2026-01-29
**相关文件**:
- `shinka/core/runner.py` - EvolutionRunner 和 EvolutionConfig
- `shinka/launch/scheduler.py` - JobScheduler 命令构建
- `shinka/core/wrap_eval.py` - run_shinka_eval 和结果保存
- `examples/circle_packing/evaluate_with_auxiliary.py` - 评估脚本示例