pub struct PsroConfig {
pub max_iterations: usize,
pub max_population_size: usize,
pub br_train_steps_per_iteration: usize,
pub payoff_eval_episodes: usize,
pub max_payoff_evals_per_iteration: Option<usize>,
pub br_reward_scale: f32,
pub seed: u64,
pub serialize_br_updates: bool,
}Expand description
PSRO trainer configuration.
Fields§
§max_iterations: usizeNumber of PSRO outer-loop iterations to run.
max_population_size: usizeMaximum population size per agent. Iteration is aborted with an
Err (not a panic) when this is reached.
br_train_steps_per_iteration: usizeNumber of joint-trainer updates spent training each new best-response policy against the sampled mixture.
payoff_eval_episodes: usizeNumber of payoff-evaluation episodes per (row, col) cell in
the empirical-payoff matrix.
max_payoff_evals_per_iteration: Option<usize>Optional cap on the number of fresh payoff-cell evaluations performed per outer iteration (issue #212).
PSRO grows each agent’s population by one policy per iteration,
so the only cells that need (re)evaluation are the boundary
slab: joint strategies in which at least one agent plays its
brand-new policy. Interior cells (between pre-existing policies)
are already cached across iterations and never recomputed — see
PayoffCache::resize_for_boundary. The boundary slab itself
still grows as (k+1)^N − k^N ≈ N·k^(N-1) cells, which for the
4-player bucket-brigade game (N = 4) is super-linear and
dominates long-run cost even with the rayon-parallel evaluation
(#203) — see the 2026-06-21 calibration in
docs/research/2026-06-bucket-brigade-validation.md.
When set to Some(cap) and an iteration’s boundary slab has more
than cap cells, the trainer deterministically subsamples
cap boundary cells to actually roll out (preserving the
rayon-parallel evaluation for those), and fills each un-sampled
boundary cell from the nearest already-evaluated sampled cell in
the deterministic flat ordering. This bounds per-iteration cost
at the price of an approximate meta-game on the subsampled
boundary.
None (the default) evaluates the entire boundary slab and is
therefore bit-identical to the pre-#212 behavior. The
subsampling path is purely opt-in; existing callers and the
determinism discipline (#201) are unaffected.
br_reward_scale: f32Optional reward scaling applied to per-step rewards before the
best-response (PPO) update, mirroring
NfspConfig::br_reward_scale
(issue #199 / #215).
PSRO trains each new best response with the same joint PPO update
as NFSP’s BR side, so it inherits the same numerical pathology on
the large-magnitude bucket-brigade payoff band ([−700, 0]): the
unscaled rewards drive the critic’s regression targets and the
per-minibatch advantage normalization into a range where the
value loss dominates the surrogate and the BR effectively stops
learning a meaningful response. Scaling rewards by a constant is
an affine transform of the return — it does not change the
optimal policy — but keeps the critic targets and advantage stats
numerically friendly. A value like 0.01 rescales the
bucket-brigade band to roughly [−7, 0].
1.0 (the default) is a no-op and preserves the pre-#215
behavior bit-for-bit.
seed: u64RNG seed for opponent sampling and deterministic tests.
serialize_br_updates: boolSerialize the per-agent best-response update (backward) calls to
avoid a concurrent-backward() deadlock in burn-autodiff 0.21 on
some many-core hosts (issue #307).
The #232 parallel path dispatches num_agents independent
best-response tasks via rayon, so all agents’ joint_loss.backward()
passes run concurrently on worker threads. burn-autodiff 0.21
guards its GraphLocator/GraphState singletons with parking_lot
mutexes acquired in an inconsistent order across the two backward
code paths, producing a lock-order inversion that deterministically
wedges on macOS arm64 (8-core M-series) with N = 4 concurrent
backward passes. Linux CI (2-core x86) rarely hits the race window.
When true (the default until the upstream burn issue is resolved
— no burn 0.22 exists on crates.io yet), the parallel par_iter
is replaced by a serial .map() that trains each agent’s BR in
fixed agent order, so at most one backward graph is live at a time.
When false, all agents’ backward passes run concurrently under
rayon (the original #232 path).
§Determinism
This flag does not affect results. All shared-mutable draws are
hoisted into the fixed-order pre-pass in
train_best_responses_parallel before any dispatch, and each
BR is a pure function of its pre-drawn inputs, collected by index.
Serial vs. parallel dispatch therefore yields bit-identical
PsroStats for a given seed; only wall-clock concurrency of the
backward passes changes. The #232 thread-count-invariance guarantee
is preserved in both modes.
Set this back to false once a burn version that fixes the
autodiff graph-lock inversion is adopted (tracked in #307).
Trait Implementations§
Source§impl Clone for PsroConfig
impl Clone for PsroConfig
Source§fn clone(&self) -> PsroConfig
fn clone(&self) -> PsroConfig
1.0.0 (const: unstable) · Source§fn clone_from(&mut self, source: &Self)
fn clone_from(&mut self, source: &Self)
source. Read moreSource§impl Debug for PsroConfig
impl Debug for PsroConfig
Auto Trait Implementations§
impl Freeze for PsroConfig
impl RefUnwindSafe for PsroConfig
impl Send for PsroConfig
impl Sync for PsroConfig
impl Unpin for PsroConfig
impl UnsafeUnpin for PsroConfig
impl UnwindSafe for PsroConfig
Blanket Implementations§
Source§impl<T> BorrowMut<T> for Twhere
T: ?Sized,
impl<T> BorrowMut<T> for Twhere
T: ?Sized,
Source§fn borrow_mut(&mut self) -> &mut T
fn borrow_mut(&mut self) -> &mut T
Source§impl<T> CloneToUninit for Twhere
T: Clone,
impl<T> CloneToUninit for Twhere
T: Clone,
Source§impl<T> Instrument for T
impl<T> Instrument for T
Source§fn instrument(self, span: Span) -> Instrumented<Self>
fn instrument(self, span: Span) -> Instrumented<Self>
Source§fn in_current_span(self) -> Instrumented<Self>
fn in_current_span(self) -> Instrumented<Self>
Source§impl<T> IntoEither for T
impl<T> IntoEither for T
Source§fn into_either(self, into_left: bool) -> Either<Self, Self>
fn into_either(self, into_left: bool) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left is true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read moreSource§fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
fn into_either_with<F>(self, into_left: F) -> Either<Self, Self>
self into a Left variant of Either<Self, Self>
if into_left(&self) returns true.
Converts self into a Right variant of Either<Self, Self>
otherwise. Read more