Dsxir. Optimizer. COPRO. Sampler
(dsxir v0.3.0)
Copy Markdown
Checkpointable, pure coordinate-ascent state for a Dsxir.Optimizer.COPRO
session.
This module holds the data a COPRO run needs to checkpoint and resume, and
the pure transitions over it. There is no IO here: proposer (LM) calls and
evaluator calls live in the Dsxir.Optimizer.COPRO wrapper, which feeds the
resulting scores into record_trial/3 and drives commit_round/1,
enqueue_round/2, and dequeue/1. This keeps the optimizer's pure-core
criterion intact.
COPRO is coordinate ascent over per-predictor instructions: each round proposes a breadth of candidate instructions per predictor, evaluates each candidate as a whole program (the other predictors held at their current committed override), and at the end of the round commits each predictor's strict round winner.
Serialized via :erlang.term_to_binary/2 with [:deterministic] so two
checkpoints over the same logical state are byte-identical, and deserialized
with [:safe] to refuse atom creation from untrusted blobs.
Summary
Functions
Whether more trials remain in the planned budget.
Commits the current round.
Pops the next work item off the queue, returning {item, sampler} with the
item removed, or :empty when the queue is exhausted.
Decodes a sampler blob produced by serialize_state/1. Uses the :safe term
decoder and validates the resulting struct shape.
Replaces the queue with items and increments the round index.
Builds a sampler from a keyword of pinned fields.
Records the result of evaluating a dequeued item with the whole-program
score.
Encodes the sampler deterministically. Returns {:ok, blob, version}.
Types
@type history_entry() :: %{ instruction: String.t() | nil, score: number() | nil, round: non_neg_integer() }
@type predictor_name() :: atom()
@type t() :: %Dsxir.Optimizer.COPRO.Sampler{ attempts: non_neg_integer(), best_overrides: %{required(predictor_name()) => String.t()}, best_score: nil | float(), decls: [Dsxir.Program.PredictorDecl.t()], degraded: boolean(), evalset: [Dsxir.Example.t()], history: %{required(predictor_name()) => [history_entry()]}, proposer_calls: non_neg_integer(), queue: [work_item()], round: non_neg_integer(), round_best: %{required(predictor_name()) => round_best_entry()}, seed_program: Dsxir.Program.t(), total_devset_evals: non_neg_integer(), total_planned_trials: non_neg_integer(), trial_records: [Dsxir.Optimizer.COPRO.Stats.Record.t()] }
@type work_item() :: %{ predictor: predictor_name(), instruction: String.t(), source: atom() }
Functions
Whether more trials remain in the planned budget.
@spec commit_round(t()) :: {t(), non_neg_integer()}
Commits the current round.
For each declared predictor, promotes its round winner into best_overrides
when the winner's instruction differs from the current override and the
winner has a numeric score. Returns {sampler, improved_count} where
improved_count is the number of predictors whose override changed.
best_score is recomputed as the max over the round's per-predictor winner
scores. Because each candidate is evaluated as a whole program (others held
fixed), the round's best whole-program score under the held-fixed others is
the value carried forward. Nil scores are rejected from the max; if every
winner score is nil (e.g. an entirely failed round) the prior best_score is
kept. round_best is reset to the committed baseline so the next round starts
from the freshly committed overrides.
Pops the next work item off the queue, returning {item, sampler} with the
item removed, or :empty when the queue is exhausted.
@spec deserialize_state(binary(), pos_integer()) :: {:ok, t()} | {:error, :version_mismatch | :corrupt_blob | {:bad_sampler_shape, term()}}
Decodes a sampler blob produced by serialize_state/1. Uses the :safe term
decoder and validates the resulting struct shape.
Replaces the queue with items and increments the round index.
Builds a sampler from a keyword of pinned fields.
Accepts :seed_program, :decls, :evalset, :total_planned_trials, and
optionally :best_overrides. Counters and accumulators default to their zero
values. round_best and history are seeded with one entry per declared
predictor so the transition functions never Map.fetch!/2 a missing key.
Records the result of evaluating a dequeued item with the whole-program
score.
Admits the candidate as the predictor's round best only on a strict
improvement (score > prev); a tie keeps the prior best. Prepends a history
entry for the predictor, bumps total_devset_evals by the eval-set size, and
increments attempts.
Encodes the sampler deterministically. Returns {:ok, blob, version}.