Skip to content

Commit 4866d2a

Browse files
authored
Merge pull request #5 from codelion/merged
Merged
2 parents 0560c84 + 456c07a commit 4866d2a

File tree

4 files changed

+83
-3
lines changed

4 files changed

+83
-3
lines changed

README.md

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,36 @@ OpenEvolve can also be run from the command line:
6161
python openevolve-run.py path/to/initial_program.py path/to/evaluator.py --config path/to/config.yaml --iterations 1000
6262
```
6363

64+
### Resuming from Checkpoints
65+
66+
OpenEvolve automatically saves checkpoints at intervals specified by the `checkpoint_interval` config parameter (default is 10 iterations). You can resume an evolution run from a saved checkpoint:
67+
68+
```bash
69+
python openevolve-run.py path/to/initial_program.py path/to/evaluator.py \
70+
--config path/to/config.yaml \
71+
--checkpoint path/to/checkpoint_directory \
72+
--iterations 50
73+
```
74+
75+
When resuming from a checkpoint:
76+
- The system loads all previously evolved programs and their metrics
77+
- Checkpoint numbering continues from where it left off (e.g., if loaded from checkpoint_50, the next checkpoint will be checkpoint_60)
78+
- All evolution state is preserved (best programs, feature maps, archives, etc.)
79+
80+
Example workflow with checkpoints:
81+
82+
```bash
83+
# Run for 50 iterations (creates checkpoints at iterations 10, 20, 30, 40, 50)
84+
python openevolve-run.py examples/function_minimization/initial_program.py \
85+
examples/function_minimization/evaluator.py \
86+
--iterations 50
87+
88+
# Resume from checkpoint 50 for another 50 iterations (creates checkpoints at 60, 70, 80, 90, 100)
89+
python openevolve-run.py examples/function_minimization/initial_program.py \
90+
examples/function_minimization/evaluator.py \
91+
--checkpoint examples/function_minimization/openevolve_output/checkpoints/checkpoint_50 \
92+
--iterations 50
93+
```
6494
### Docker
6595

6696
You can also install and execute via Docker:

openevolve/cli.py

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -45,6 +45,12 @@ def parse_args() -> argparse.Namespace:
4545
default="INFO",
4646
)
4747

48+
parser.add_argument(
49+
"--checkpoint",
50+
help="Path to checkpoint directory to resume from (e.g., openevolve_output/checkpoints/checkpoint_50)",
51+
default=None,
52+
)
53+
4854
parser.add_argument("--api-base", help="Base URL for the LLM API", default=None)
4955

5056
parser.add_argument("--primary-model", help="Primary LLM model name", default=None)
@@ -101,6 +107,17 @@ async def main_async() -> int:
101107
output_dir=args.output,
102108
)
103109

110+
# Load from checkpoint if specified
111+
if args.checkpoint:
112+
if not os.path.exists(args.checkpoint):
113+
print(f"Error: Checkpoint directory '{args.checkpoint}' not found")
114+
return 1
115+
print(f"Loading checkpoint from {args.checkpoint}")
116+
openevolve.database.load(args.checkpoint)
117+
print(
118+
f"Checkpoint loaded successfully (iteration {openevolve.database.last_iteration})"
119+
)
120+
104121
# Override log level if specified
105122
if args.log_level:
106123
logging.getLogger().setLevel(getattr(logging, args.log_level))
@@ -111,11 +128,29 @@ async def main_async() -> int:
111128
target_score=args.target_score,
112129
)
113130

131+
# Get the checkpoint path
132+
checkpoint_dir = os.path.join(openevolve.output_dir, "checkpoints")
133+
latest_checkpoint = None
134+
if os.path.exists(checkpoint_dir):
135+
checkpoints = [
136+
os.path.join(checkpoint_dir, d)
137+
for d in os.listdir(checkpoint_dir)
138+
if os.path.isdir(os.path.join(checkpoint_dir, d))
139+
]
140+
if checkpoints:
141+
latest_checkpoint = sorted(
142+
checkpoints, key=lambda x: int(x.split("_")[-1]) if "_" in x else 0
143+
)[-1]
144+
114145
print(f"\nEvolution complete!")
115146
print(f"Best program metrics:")
116147
for name, value in best_program.metrics.items():
117148
print(f" {name}: {value:.4f}")
118149

150+
if latest_checkpoint:
151+
print(f"\nLatest checkpoint saved at: {latest_checkpoint}")
152+
print(f"To resume, use: --checkpoint {latest_checkpoint}")
153+
119154
return 0
120155

121156
except Exception as e:

openevolve/controller.py

Lines changed: 9 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -154,7 +154,14 @@ async def run(
154154
self.database.add(initial_program)
155155

156156
# Main evolution loop
157-
for i in range(max_iterations):
157+
start_iteration = self.database.last_iteration
158+
total_iterations = start_iteration + max_iterations
159+
160+
logger.info(
161+
f"Starting evolution from iteration {start_iteration} for {max_iterations} iterations (total: {total_iterations})"
162+
)
163+
164+
for i in range(start_iteration, total_iterations):
158165
iteration_start = time.time()
159166

160167
# Sample parent and inspirations
@@ -348,7 +355,7 @@ def _save_checkpoint(self, iteration: int) -> None:
348355

349356
# Save the database
350357
checkpoint_path = os.path.join(checkpoint_dir, f"checkpoint_{iteration}")
351-
self.database.save(checkpoint_path)
358+
self.database.save(checkpoint_path, iteration)
352359

353360
logger.info(f"Saved checkpoint at iteration {iteration} to {checkpoint_path}")
354361

openevolve/database.py

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -81,6 +81,9 @@ def __init__(self, config: DatabaseConfig):
8181
# Track the absolute best program separately
8282
self.best_program_id: Optional[str] = None
8383

84+
# Track the last iteration number (for resuming)
85+
self.last_iteration: int = 0
86+
8487
# Load database from disk if path is provided
8588
if config.db_path and os.path.exists(config.db_path):
8689
self.load(config.db_path)
@@ -253,12 +256,13 @@ def get_top_programs(self, n: int = 10, metric: Optional[str] = None) -> List[Pr
253256

254257
return sorted_programs[:n]
255258

256-
def save(self, path: Optional[str] = None) -> None:
259+
def save(self, path: Optional[str] = None, iteration: int = 0) -> None:
257260
"""
258261
Save the database to disk
259262
260263
Args:
261264
path: Path to save to (uses config.db_path if None)
265+
iteration: Current iteration number
262266
"""
263267
save_path = path or self.config.db_path
264268
if not save_path:
@@ -278,6 +282,7 @@ def save(self, path: Optional[str] = None) -> None:
278282
"islands": [list(island) for island in self.islands],
279283
"archive": list(self.archive),
280284
"best_program_id": self.best_program_id,
285+
"last_iteration": iteration or self.last_iteration,
281286
}
282287

283288
with open(os.path.join(save_path, "metadata.json"), "w") as f:
@@ -306,6 +311,9 @@ def load(self, path: str) -> None:
306311
self.islands = [set(island) for island in metadata.get("islands", [])]
307312
self.archive = set(metadata.get("archive", []))
308313
self.best_program_id = metadata.get("best_program_id")
314+
self.last_iteration = metadata.get("last_iteration", 0)
315+
316+
logger.info(f"Loaded database metadata with last_iteration={self.last_iteration}")
309317

310318
# Load programs
311319
programs_dir = os.path.join(path, "programs")

0 commit comments

Comments
 (0)