Skip to content

fix(optimizers): fallback to seed_candidates in propose_new_candidates#41

Open
lelouar wants to merge 1 commit intoSynaLinks:mainfrom
lelouar:fix/omega-empty-best-candidates
Open

fix(optimizers): fallback to seed_candidates in propose_new_candidates#41
lelouar wants to merge 1 commit intoSynaLinks:mainfrom
lelouar:fix/omega-empty-best-candidates

Conversation

@lelouar
Copy link
Copy Markdown

@lelouar lelouar commented Apr 14, 2026

Summary

EvolutionaryOptimizer.propose_new_candidates calls select_candidate on best_candidates, which on the very first training step is still empty (it is populated later by maybe_add_candidate). The resulting None is then passed as selected_candidate into mutate_candidate / merge_candidate, crashing concrete subclasses such as OMEGA on selected_candidate.items() in omega.py.

The companion method on_batch_begin already handles this situation by falling back to a random seed candidate. This PR applies the exact same fallback in propose_new_candidates, keeping behaviour consistent across the class and letting OMEGA (and any future EvolutionaryOptimizer subclass) run fit() without pre-populating best_candidates.

Reproduction

program.compile(
    reward=synalinks.rewards.ExactMatch(in_mask=["answer"]),
    optimizer=synalinks.optimizers.OMEGA(
        language_model=lm,
        embedding_model=em,
    ),
)

await program.fit(x=x_train, y=y_train, epochs=1, batch_size=1, validation_split=0.2)

Before the fix this crashes at step 0 with:

AttributeError: 'NoneType' object has no attribute 'items'
  at omega.py:457 in mutate_candidate

Changes

  • synalinks/src/optimizers/evolutionary_optimizer.py: in propose_new_candidates, when select_candidate(best_candidates) returns None, fall back to a random seed candidate (mirroring on_batch_begin).
  • synalinks/src/optimizers/evolutionary_optimizer_test.py: add test_propose_new_candidates_falls_back_to_seed_when_best_empty. The test subclasses EvolutionaryOptimizer to record what mutate_candidate receives, and verifies it is the seed candidate (not None). The test fails on main and passes with this fix.

Test plan

  • New regression test passes with the fix applied
  • New regression test fails without the fix (verified by stashing the fix and re-running)
  • Full evolutionary_optimizer_test.py suite passes (15/15)

`EvolutionaryOptimizer.propose_new_candidates` calls `select_candidate`
on `best_candidates`, which on the very first training step is still
empty (it is populated later by `maybe_add_candidate`). The resulting
`None` was then passed as `selected_candidate` into `mutate_candidate`
/ `merge_candidate`, crashing subclasses such as OMEGA on
`selected_candidate.items()` in `omega.py`.

Apply the same seed-candidate fallback that `on_batch_begin` already
uses, so mutation/crossover always receives a valid starting point.

Add a regression test covering the empty-`best_candidates` path.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant