Skip to content

Commit 869031a

Browse files
committed
allow resuming from checkpoints
1 parent 1f08698 commit 869031a

File tree

4 files changed

+75
-3
lines changed

4 files changed

+75
-3
lines changed

README.md

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

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

6596
OpenEvolve is highly configurable. You can specify configuration options in a YAML file:

openevolve/cli.py

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,12 @@ def parse_args() -> argparse.Namespace:
6666
default="INFO"
6767
)
6868

69+
parser.add_argument(
70+
"--checkpoint",
71+
help="Path to checkpoint directory to resume from (e.g., openevolve_output/checkpoints/checkpoint_50)",
72+
default=None
73+
)
74+
6975
parser.add_argument(
7076
"--api-base",
7177
help="Base URL for the LLM API",
@@ -134,6 +140,15 @@ async def main_async() -> int:
134140
output_dir=args.output,
135141
)
136142

143+
# Load from checkpoint if specified
144+
if args.checkpoint:
145+
if not os.path.exists(args.checkpoint):
146+
print(f"Error: Checkpoint directory '{args.checkpoint}' not found")
147+
return 1
148+
print(f"Loading checkpoint from {args.checkpoint}")
149+
openevolve.database.load(args.checkpoint)
150+
print(f"Checkpoint loaded successfully (iteration {openevolve.database.last_iteration})")
151+
137152
# Override log level if specified
138153
if args.log_level:
139154
logging.getLogger().setLevel(getattr(logging, args.log_level))
@@ -144,10 +159,23 @@ async def main_async() -> int:
144159
target_score=args.target_score,
145160
)
146161

162+
# Get the checkpoint path
163+
checkpoint_dir = os.path.join(openevolve.output_dir, "checkpoints")
164+
latest_checkpoint = None
165+
if os.path.exists(checkpoint_dir):
166+
checkpoints = [os.path.join(checkpoint_dir, d) for d in os.listdir(checkpoint_dir)
167+
if os.path.isdir(os.path.join(checkpoint_dir, d))]
168+
if checkpoints:
169+
latest_checkpoint = sorted(checkpoints, key=lambda x: int(x.split("_")[-1]) if "_" in x else 0)[-1]
170+
147171
print(f"\nEvolution complete!")
148172
print(f"Best program metrics:")
149173
for name, value in best_program.metrics.items():
150174
print(f" {name}: {value:.4f}")
175+
176+
if latest_checkpoint:
177+
print(f"\nLatest checkpoint saved at: {latest_checkpoint}")
178+
print(f"To resume, use: --checkpoint {latest_checkpoint}")
151179

152180
return 0
153181

openevolve/controller.py

Lines changed: 7 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -162,7 +162,12 @@ async def run(
162162
self.database.add(initial_program)
163163

164164
# Main evolution loop
165-
for i in range(max_iterations):
165+
start_iteration = self.database.last_iteration
166+
total_iterations = start_iteration + max_iterations
167+
168+
logger.info(f"Starting evolution from iteration {start_iteration} for {max_iterations} iterations (total: {total_iterations})")
169+
170+
for i in range(start_iteration, total_iterations):
166171
iteration_start = time.time()
167172

168173
# Sample parent and inspirations
@@ -347,7 +352,7 @@ def _save_checkpoint(self, iteration: int) -> None:
347352

348353
# Save the database
349354
checkpoint_path = os.path.join(checkpoint_dir, f"checkpoint_{iteration}")
350-
self.database.save(checkpoint_path)
355+
self.database.save(checkpoint_path, iteration)
351356

352357
logger.info(f"Saved checkpoint at iteration {iteration} to {checkpoint_path}")
353358

openevolve/database.py

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

83+
# Track the last iteration number (for resuming)
84+
self.last_iteration: int = 0
85+
8386
# Load database from disk if path is provided
8487
if config.db_path and os.path.exists(config.db_path):
8588
self.load(config.db_path)
@@ -251,12 +254,13 @@ def get_top_programs(
251254

252255
return sorted_programs[:n]
253256

254-
def save(self, path: Optional[str] = None) -> None:
257+
def save(self, path: Optional[str] = None, iteration: int = 0) -> None:
255258
"""
256259
Save the database to disk
257260
258261
Args:
259262
path: Path to save to (uses config.db_path if None)
263+
iteration: Current iteration number
260264
"""
261265
save_path = path or self.config.db_path
262266
if not save_path:
@@ -276,6 +280,7 @@ def save(self, path: Optional[str] = None) -> None:
276280
"islands": [list(island) for island in self.islands],
277281
"archive": list(self.archive),
278282
"best_program_id": self.best_program_id,
283+
"last_iteration": iteration or self.last_iteration,
279284
}
280285

281286
with open(os.path.join(save_path, "metadata.json"), "w") as f:
@@ -304,6 +309,9 @@ def load(self, path: str) -> None:
304309
self.islands = [set(island) for island in metadata.get("islands", [])]
305310
self.archive = set(metadata.get("archive", []))
306311
self.best_program_id = metadata.get("best_program_id")
312+
self.last_iteration = metadata.get("last_iteration", 0)
313+
314+
logger.info(f"Loaded database metadata with last_iteration={self.last_iteration}")
307315

308316
# Load programs
309317
programs_dir = os.path.join(path, "programs")

0 commit comments

Comments
 (0)