Skip to content

Commit 8e8879f

Browse files
authored
Merge pull request #238 from codelion/feat-add-early-stopping
add early stopping
2 parents 69703e4 + 4a6fb6f commit 8e8879f

File tree

6 files changed

+104
-7
lines changed

6 files changed

+104
-7
lines changed

configs/default_config.yaml

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,11 @@ random_seed: 42 # Random seed for reproducibility (null =
1313
diff_based_evolution: true # Use diff-based evolution (true) or full rewrites (false)
1414
max_code_length: 10000 # Maximum allowed code length in characters
1515

16+
# Early stopping settings
17+
early_stopping_patience: null # Stop after N iterations without improvement (null = disabled)
18+
convergence_threshold: 0.001 # Minimum improvement required to reset patience counter
19+
early_stopping_metric: "combined_score" # Metric to track for early stopping
20+
1621
# LLM configuration
1722
llm:
1823
# Models for evolution
Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,38 @@
1+
# OpenEvolve Configuration with Early Stopping Example
2+
# This configuration demonstrates how to use the early stopping feature
3+
4+
# Basic settings
5+
max_iterations: 1000
6+
checkpoint_interval: 50
7+
log_level: "INFO"
8+
9+
# Early stopping configuration - stops evolution if no improvement for 30 iterations
10+
early_stopping_patience: 30 # Stop after 30 iterations without improvement
11+
convergence_threshold: 0.01 # Minimum improvement of 0.01 required to reset patience
12+
early_stopping_metric: "combined_score" # Track the combined_score metric
13+
14+
# LLM configuration
15+
llm:
16+
models:
17+
- name: "gpt-4o-mini"
18+
weight: 1.0
19+
20+
api_base: "https://api.openai.com/v1"
21+
temperature: 0.7
22+
max_tokens: 4096
23+
24+
# Database configuration
25+
database:
26+
population_size: 50
27+
num_islands: 3
28+
migration_interval: 20
29+
30+
# Evaluation settings
31+
evaluator:
32+
timeout: 60
33+
max_retries: 2
34+
parallel_evaluations: 2
35+
36+
# Evolution settings
37+
diff_based_evolution: true
38+
max_code_length: 8000

examples/attention_optimization/configs/failing_config.yaml

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -35,13 +35,16 @@ checkpoints:
3535
keep_best: true
3636
save_all_programs: false
3737

38+
# Early stopping settings (moved to top level)
39+
early_stopping_patience: 50 # Stop after 50 iterations without improvement
40+
convergence_threshold: 0.001 # Minimum improvement required
41+
early_stopping_metric: "speedup" # Track speedup metric
42+
3843
# Optimization targets
3944
optimization:
4045
target_metric: "speedup"
4146
target_value: 1.32 # 32% speedup like AlphaEvolve paper
4247
minimize: false
43-
convergence_threshold: 0.001
44-
early_stopping_patience: 50
4548

4649
# Logging
4750
logging:

openevolve/_version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,3 @@
11
"""Version information for openevolve package."""
22

3-
__version__ = "0.2.7"
3+
__version__ = "0.2.8"

openevolve/config.py

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -271,6 +271,11 @@ class Config:
271271
# Evolution settings
272272
diff_based_evolution: bool = True
273273
max_code_length: int = 10000
274+
275+
# Early stopping settings
276+
early_stopping_patience: Optional[int] = None
277+
convergence_threshold: float = 0.001
278+
early_stopping_metric: str = "combined_score"
274279

275280
@classmethod
276281
def from_yaml(cls, path: Union[str, Path]) -> "Config":
@@ -381,6 +386,10 @@ def to_dict(self) -> Dict[str, Any]:
381386
# Evolution settings
382387
"diff_based_evolution": self.diff_based_evolution,
383388
"max_code_length": self.max_code_length,
389+
# Early stopping settings
390+
"early_stopping_patience": self.early_stopping_patience,
391+
"convergence_threshold": self.convergence_threshold,
392+
"early_stopping_metric": self.early_stopping_metric,
384393
}
385394

386395
def to_yaml(self, path: Union[str, Path]) -> None:

openevolve/process_parallel.py

Lines changed: 46 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@
1515

1616
from openevolve.config import Config
1717
from openevolve.database import Program, ProgramDatabase
18+
from openevolve.utils.metrics_utils import safe_numeric_average
1819

1920
logger = logging.getLogger(__name__)
2021

@@ -145,8 +146,6 @@ def _run_iteration_worker(
145146
]
146147

147148
# Sort by metrics for top programs
148-
from openevolve.utils.metrics_utils import safe_numeric_average
149-
150149
island_programs.sort(
151150
key=lambda p: p.metrics.get("combined_score", safe_numeric_average(p.metrics)),
152151
reverse=True,
@@ -425,6 +424,17 @@ async def run_evolution(
425424
# Island management
426425
programs_per_island = max(1, max_iterations // (self.config.database.num_islands * 10))
427426
current_island_counter = 0
427+
428+
# Early stopping tracking
429+
early_stopping_enabled = self.config.early_stopping_patience is not None
430+
if early_stopping_enabled:
431+
best_score = float('-inf')
432+
iterations_without_improvement = 0
433+
logger.info(f"Early stopping enabled: patience={self.config.early_stopping_patience}, "
434+
f"threshold={self.config.convergence_threshold}, "
435+
f"metric={self.config.early_stopping_metric}")
436+
else:
437+
logger.info("Early stopping disabled")
428438

429439
# Process results as they complete
430440
while (
@@ -519,8 +529,6 @@ async def run_evolution(
519529
"combined_score" not in child_program.metrics
520530
and not self._warned_about_combined_score
521531
):
522-
from openevolve.utils.metrics_utils import safe_numeric_average
523-
524532
avg_score = safe_numeric_average(child_program.metrics)
525533
logger.warning(
526534
f"⚠️ No 'combined_score' metric found in evaluation results. "
@@ -563,6 +571,40 @@ async def run_evolution(
563571
)
564572
break
565573

574+
# Check early stopping
575+
if early_stopping_enabled and child_program.metrics:
576+
# Get the metric to track for early stopping
577+
current_score = None
578+
if self.config.early_stopping_metric in child_program.metrics:
579+
current_score = child_program.metrics[self.config.early_stopping_metric]
580+
elif self.config.early_stopping_metric == "combined_score":
581+
# Default metric not found, use safe average (standard pattern)
582+
current_score = safe_numeric_average(child_program.metrics)
583+
else:
584+
# User specified a custom metric that doesn't exist
585+
logger.warning(f"Early stopping metric '{self.config.early_stopping_metric}' not found, using safe numeric average")
586+
current_score = safe_numeric_average(child_program.metrics)
587+
588+
if current_score is not None and isinstance(current_score, (int, float)):
589+
# Check for improvement
590+
improvement = current_score - best_score
591+
if improvement >= self.config.convergence_threshold:
592+
best_score = current_score
593+
iterations_without_improvement = 0
594+
logger.debug(f"New best score: {best_score:.4f} (improvement: {improvement:+.4f})")
595+
else:
596+
iterations_without_improvement += 1
597+
logger.debug(f"No improvement: {iterations_without_improvement}/{self.config.early_stopping_patience}")
598+
599+
# Check if we should stop
600+
if iterations_without_improvement >= self.config.early_stopping_patience:
601+
logger.info(
602+
f"Early stopping triggered at iteration {completed_iteration}: "
603+
f"No improvement for {iterations_without_improvement} iterations "
604+
f"(best score: {best_score:.4f})"
605+
)
606+
break
607+
566608
except Exception as e:
567609
logger.error(f"Error processing result from iteration {completed_iteration}: {e}")
568610

0 commit comments

Comments
 (0)