Skip to content

(removed) - #79

Closed
blackopsrepl wants to merge 9 commits into
mainfrom
feature/multicore
Closed

blackopsrepl wants to merge 9 commits into
mainfrom
feature/multicore

Conversation

@blackopsrepl

@blackopsrepl blackopsrepl commented Oct 22, 2026 •

Copy link
Copy Markdown
Collaborator

No description provided.

Parallel local-search evaluation needs independent score directors that start from the committed working solution. Director gains a defaulted fork_replica capability, and ScoreDirector implements it when it carries a constraint-set constructor (fn(&S) -> C): the replica clones the working solution, builds its own constraint set for that clone, and initializes it, so incremental state is never cloned or shared.

A constructor that receives the solution serves static models and dynamic host-language models alike, since a binding can rebuild its constraint set from the runtime model the solution carries. Directors without a constructor keep returning None.
… entry

The configured solve entry and the bridge's dynamic runner now take a constraint-set constructor, fn(&S) -> C, in place of a ready-made constraint set. The runtime builds the working solution's constraint set with it and records it on the score director, so every solve can fork evaluator replicas whose constraint sets are built the same way as the main one.

Generated solves wrap the model's zero-argument constraints function; bindings rebuild their host constraint set from the solution they receive, which is what lets dynamic models take part in parallel evaluation. The bridge documents that its score-family scope is per thread, so binding constraint sets that build DynamicScore values must enter their family themselves when replicas score on worker threads.
…ting

evaluate_candidate now composes two pieces: run_trial, which performs the pinned/doable check and the apply-score-undo-restore transaction against a director and nothing else, and account_trial, which records statistics and applies the hard- and score-improvement gates on the thread that owns the step scope.

Parallel evaluation needs exactly this boundary: trials run on evaluator replicas while all accounting stays on the coordinator in candidate order. Evaluated-move timing is now passed in as a Duration so the accounting side no longer needs the trial's start instant. Sequential behaviour is unchanged.
…mption

The step's candidate loop is now three explicit stages: record_generated_candidate records a pull (trace, generation telemetry, progress poll) and returns a WindowSlot, run_trial evaluates it, and consume_candidate feeds the outcome through accounting, the acceptor and the forager and releases what the step no longer needs.

Parallel evaluation produces several slots before consuming them in order, so these stages must be callable separately. Decisions, checks and their order are unchanged. The evaluated-move duration recorded for a scored candidate now covers the trial itself rather than also including the acceptor call, which is the quantity a trial run on another thread can report.
…e traces

Parallel evaluation evaluates a window of candidates before the coordinator consumes them in order, so a step can end with evaluated candidates that the sequential loop would never have reached. That work must be visible without distorting the logical counters that drive fixed-work limits.

SolverStats and SolverTelemetry gain moves_speculative, recorded through PhaseScope::record_move_speculative, and the candidate trace gains the terminal DiscardedSpeculative disposition. Sequential evaluation never produces either, so existing telemetry and traces are unchanged.
…search

Local search can now evaluate a step's candidates on several cores without changing the search. Each pass produces a window of candidates serially on the main director in canonical order, runs their trials on the main director plus persistent evaluator replicas inside one rayon::scope, and consumes the results in candidate order through the unchanged accounting, acceptor and forager. Before consuming each later slot the coordinator repeats the forager quit check and the interruption check the sequential loop makes, so with a window of one the loop is exactly the sequential one.

Replicas are forked from the working solution and resynchronised whenever its new working_solution_generation counter shows a write outside the loop's own commits; the counter never resets, unlike solution_revision. The selected move is applied to every replica by borrow before the owned commit, and each replica must reach the committed step score. Windows hold 32 candidates regardless of the evaluator count and never exceed the remaining in-phase move or score-calculation budget, so fixed-work runs are reproducible for every count of two or more and logical counters match sequential evaluation; discarded work is reported as speculative.

AGENTS.md records the replica copies as the same clone class as partition copies and the one-scope-per-window threading rule. The runtime still passes one evaluator; move_thread_count is wired next.
SolverConfig::move_thread_count was parsed but only documented, so there was no way to ask for parallel evaluation. The compiled runtime now resolves it into the evaluator count of every acceptor-forager local-search phase: an explicit count of two or more runs that many evaluators, capped by the threads of the Rayon pool the solve runs on; None, zero and one stay sequential.

Auto also stays sequential for now, so default behaviour and memory use do not change until benchmarks decide its policy. A config test pins the documented TOML forms.
… runtime

Pins the end-to-end contract through SolverManager and macro-generated solving: with move_thread_count 1, 2 and 4, a fixed-work solve (seeded construction plus 150 local-search steps) reaches the same assignment, score, step count, evaluated-move count and applied-move count. The plain-scalar default selectors keep no cross-step stream state, so sequential and parallel runs must match exactly.

A second test pins that Auto and an explicit count of one stay sequential and report no speculative work. Speculation under parallel evaluation is asserted in the solver's unit tests instead, because an explicit count is capped by the runner's thread count.
README's performance section now says how to turn on parallel local-search evaluation for Rust and Python models, that results are reproducible at fixed work, and that the auto default stays sequential for now. The architecture map adds the window evaluation on replicas to the search-phase box, regenerated from its SVG source.
@blackopsrepl
blackopsrepl deleted the feature/multicore branch October 22, 2026 18:35
@blackopsrepl blackopsrepl changed the title feat(solver): parallel local-search move evaluation (removed) Oct 22, 2026
@SolverForge SolverForge locked as resolved and limited conversation to collaborators Oct 22, 2026
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant