Skip to main content

SessionConfig

Struct SessionConfig 

Source
pub struct SessionConfig {
Show 21 fields pub pool_size: usize, pub max_draws: usize, pub refine_steps: usize, pub refine_seeds: usize, pub beta: f64, pub refine_keep: RefineKeep, pub k_styles: usize, pub phrase: PhraseSpec, pub render_policy: RenderPolicy, pub audio_cache: usize, pub mcmc_samples: usize, pub mcmc_warmup: usize, pub recency_half_life: Option<f64>, pub proposal_tilt: f64, pub duel_utility_weight: f64, pub duel_repeat_penalty: f64, pub duel_exposure_penalty: f64, pub duel_temperature: f64, pub duel_check_every: usize, pub acquisition: Acquisition, pub sis_between_fits: bool,
}
Expand description

Engine configuration.

Fields§

§pool_size: usize

Vetted candidates to maintain in the pool.

§max_draws: usize

Maximum prior draws attempted per fill_pool (vet failures burn attempts).

§refine_steps: usize

MH refinement steps per seed (scaled up when locks waste proposals).

The default scales with N_OPS: a structural proposal picks a new operator from a categorical that the v2 palette widened from six to twenty, so a fixed budget would spend the same number of proposals covering a far wider move set and land the pool’s children in a visibly thinner slice of it.

§The split is measured, not reasoned

2·N_OPS steps from N_OPS/2 seeds was an argument, and the argument could have been wrong in either direction. search_health --budget-ab exists to settle it; over 8 seeds, 6 generations, graded against the synthetic user’s true utility:

stepsseedsproposalsmean umax u
40104001.7148.154shipped
4031201.2416.178same depth, fewer seeds
6631980.7746.281same total, fewer seeds
20204000.5686.790half depth, double breadth

The shipped split wins on both metrics, and it is a genuine optimum rather than the top of a slope: moving off it in either direction is worse. Two rows are worth more than the headline.

Depth from few seeds is actively harmful. 66×3 runs 65% more proposals than 40×3 and scores lower (0.774 against 1.241) — a long chain from a bad starting point converges confidently on somewhere you did not want to be, and the extra steps are what get it there.

Breadth is not free either. 20×20 spends the shipped budget and is the worst row of the four. Twenty steps is not enough for a chain to leave its seed, so the generation is twenty barely-moved copies of the current top — which is also why it has the second-best max: it preserves the frontier by never straying from it.

Re-run this before changing either number.

§refine_seeds: usize

How many top candidates to refine from. Also scaled with N_OPS — more seeds is more starting points, which is what actually buys coverage of a wider palette, whereas more steps per seed buys depth around one. See SessionConfig::refine_steps for the measurement that fixes the ratio between them.

§beta: f64

Boltzmann sharpness β of the refinement target.

§refine_keep: RefineKeep

Which state of a refinement walk becomes the injected child.

§k_styles: usize

Maximum style components in the taste mixture (max-of-linear-experts); the fitted K grows with evidence up to this cap.

K is also the fit’s dominant cost driver, because single-site MH rebuilds the whole program every step and the site count is d·K + n_sessions + 5 — at today’s d = 40, that is 46 at K = 1 and 206 at K = 5 (printed by fit_bench, so it moves with φ). Two consequences, both measured by auracle-taste/examples/fit_bench.rs: the fit is ~4× slower at the cap than at the first fit, and the step budget is fixed, so a mature fit gets ~4× fewer sweeps per site than an early one — growing K makes the fit both slower and statistically thinner.

Open option, deliberately not taken here: cap this at 3 (sites 206 → 126, a ~1.6× mature-fit win at no engineering cost). It is left open because unlike the address hoist and the budget cut it is not a pure efficiency change — it removes model capacity, and capacity is the whole point of the mixture (a user with four islands of taste cannot be represented by three lenses). Take it only on evidence: TastePosterior::style_share reports what fraction of the pool each lens claims, and if lenses 4 and 5 sit near zero share across real sessions they are paying 54 sites per step for nothing. learn_synthetic --compare is the A/B.

§phrase: PhraseSpec

The audition stimulus.

§render_policy: RenderPolicy

How the pool retains audition audio.

§audio_cache: usize

Audition buffers kept resident under RenderPolicy::Lazy, most recently auditioned first. Sized for the current duel pair, the bench subject, and enough recent history that stepping back through the bank is free.

§mcmc_samples: usize

Post-warmup MH steps per posterior fit.

This is the one knob in this struct that buys wall time with statistics, so it is set from a measurement rather than a guess. Only 500 draws survive thinning at any budget, so the budget does not buy draws — it buys sweeps per site, and at K = 5 (206 sites) even 10 000 steps is only ~49 sweeps.

Recovery vs budget at the mature operating point (K = 5, n_obs = 100, 12 seeds, cargo run --release -p auracle-taste --example fit_bench -- sweep 12): held-out duel agreement with the noiseless ground-truth ordering, and the cosine of the best lens against θ*.

stepsheld-out accbest-lens cosnative fit
30 0000.7670.7241.79 s
20 0000.7570.7171.16 s
10 0000.7460.6860.60 s
8 0000.7380.6900.49 s
6 0000.7370.6530.38 s
5 0000.7290.6550.33 s
3 0000.7130.5990.20 s

That curve is smooth, so it says where the trade stops paying. The second instrument is the end-to-end M4 gate (closed_loop_learns_synthetic_taste, which runs at exactly this budget through the real render → vet → feature pipeline). One run of it is a single draw — over the pool lottery, the duel answers and the chain — so it is replicated over 13 seeds here (cargo run --release -p auracle-session --example closed_loop_sweep). Its pool/truth correlation r against the 0.6 gate, plus the other two metrics the test asserts:

stepsmean rmin rseeds with r ≤ 0.6mean top-5mean cos
30 0000.7360.5751/133.140.528
20 0000.7220.5762/132.880.497
10 0000.7260.5512/132.780.475
8 0000.7150.5031/133.070.456
6 0000.7470.6001/133.390.497
5 0000.6890.4762/133.150.392

Read that as a noisy measurement, because it is one. Within a single budget the seed-to-seed spread of r is sd ≈ 0.07–0.10 over a range of ≈ 0.25; between budgets from 6 000 up the means sit in 0.715–0.747, i.e. inside one standard error (≈ 0.02) of each other — and 6 000 posts the highest mean of the six, which is the plainest sign that this instrument’s ranking of the upper budgets is noise. From 6 000 to 30 000 it cannot tell them apart. Only 5 000 separates at all — lowest on mean r, on min r and on cos — and even that gap to 30 000 (0.047) is barely over one standard error of the difference.

So the argument for 10 000 is not that it passes where 5 000 fails. Every budget here fails the 0.6 gate on some seed, including the old 30 000 (1 of 13), and 5 000 clears it on 11 of 13. The argument is: 10 000 is 3× cheaper than 30 000 and gives up 0.010 of mean r, which is inside the noise; the fit_bench sweep above — 12 seeds on a metric with far less variance — prices the same cut at 0.021 of held-out accuracy and 0.038 of cos; and cutting further to 5 000 saves only another 0.27 s per fit while costing 0.017 more held-out accuracy, 0.031 more cos and 0.037 of mean r, the one budget both instruments mark down. 10 000 is where the two instruments agree, not where a threshold was crossed.

(An earlier revision of this table read the M4 gate at a single seed, 0xE05, and concluded that 5 000 “fails outright” at r = 0.565 while 10 000 held “the widest margin of any budget tried”. Both are artifacts of that one draw: 0xE05 sits ~1.2 sd low at 5 000 and right on the mean at 10 000. The per-seed numbers reproduce exactly — the inference from one of them did not.)

The earlier 30 000 also predated the address hoist in auracle_taste::model, which made every step ~1.7× cheaper on its own; the two together take a mature fit from ~1.86 s to ~0.60 s natively (~13 s → ~4 s in the browser).

§mcmc_warmup: usize

Warmup (adaptation) steps per fit, held at ~30 % of Self::mcmc_samples. Warmup only tunes the per-site proposal scales; it produces no draws, so it is pure overhead beyond the point the scales converge.

§recency_half_life: Option<f64>

Recency half-life for the taste likelihood, in observations (None = no forgetting). Tastes drift; old votes should fade.

§proposal_tilt: f64

Strength of the taste→grammar proposal tilt (0 disables): structural θ components multiply the grammar’s kind weights by exp(η·θ) during refinement.

§duel_utility_weight: f64

λ in the duel objective: how much the pleasantness of a duel counts against its informativeness, applied to pool-standardized utility. The user’s enjoyment is a resource too — two mud patches are a cheap question and an expensive answer.

Keep it small. Information gain is bounded by ln 2 ≈ 0.693 nats, so a λ near 0.3 lets the ±2σ enjoyment term swing the objective by ±0.6 — as much as the entire information range — and the acquisition function quietly reverts to “duel the two best patches”, which is the best-arm behaviour BALD was adopted to escape. Measured on the synthetic user (learn_synthetic --compare), λ = 0.3 cost 0.15 of pool-ranking correlation against λ = 0; 0.1 leaves it a tie-breaker.

§duel_repeat_penalty: f64

γ in the duel objective: penalty per previous showing of the same pair. Without it the acquisition function re-asks its favourite question until the next refit.

§duel_exposure_penalty: f64

Penalty per previous appearance of either candidate, regardless of who it was paired against.

The pair penalty alone does not stop degeneracy, and the shipped app proved it: over twelve consecutive duels one candidate appeared in six. Every pairing #1 vs #7, #1 vs #15, #1 vs #22 is a distinct pair and pays no pair penalty at all, while the enjoyment term keeps nominating the highest-utility candidate. The user does not experience “distinct pairs”; they experience hearing the same patch over and over. This term is what makes the candidate budget finite.

§duel_temperature: f64

Softmax temperature over the duel objective, as a fraction of the objective’s own spread across the candidate pairs.

Scale-free for the same reason the enjoyment term is standardized: an absolute temperature is a bet on how far apart the scores happen to be. Shipped at an absolute 0.05 nats it was a bad bet — the objective spans several tenths of a nat once the enjoyment term is in it, so exp(ΔJ/T) ran to e¹⁰ and the “softmax” was an argmax with extra steps. Expressed as a fraction of the observed SD, 0.6 means the same softness whatever the spread.

§duel_check_every: usize

Show one uniformly-random “check” duel every N duels. An information-seeking acquisition deliberately picks pairs near p = 0.5, so calibration measured on acquisition-chosen duels is selection-biased; these are the unbiased subsample.

Redundant under Acquisition::Random, where every duel is already uniform and is tagged as a check — the setting is kept because it is exactly what Acquisition::Bald would need, and because one in ten was measured to be underpowered anyway (a few forecasts out of fifty cannot fill a five-bin reliability diagram). 0 disables.

§acquisition: Acquisition

Which rule picks the next duel.

§sis_between_fits: bool

Fold each new observation into the posterior weights by importance sampling between full refits. Off makes the posterior frozen between fits, which is what the A/B compares against.

Trait Implementations§

Source§

impl Clone for SessionConfig

Source§

fn clone(&self) -> SessionConfig

Returns a duplicate of the value. Read more
1.0.0 (const: unstable) · Source§

fn clone_from(&mut self, source: &Self)

Performs copy-assignment from source. Read more
Source§

impl Debug for SessionConfig

Source§

fn fmt(&self, f: &mut Formatter<'_>) -> Result

Formats the value using the given formatter. Read more
Source§

impl Default for SessionConfig

Source§

fn default() -> Self

Returns the “default value” for a type. Read more

Auto Trait Implementations§

Blanket Implementations§

Source§

impl<T> Any for T
where T: 'static + ?Sized,

Source§

fn type_id(&self) -> TypeId

Gets the TypeId of self. Read more
Source§

impl<T> Borrow<T> for T
where T: ?Sized,

Source§

fn borrow(&self) -> &T

Immutably borrows from an owned value. Read more
Source§

impl<T> BorrowMut<T> for T
where T: ?Sized,

Source§

fn borrow_mut(&mut self) -> &mut T

Mutably borrows from an owned value. Read more
Source§

impl<T> CloneToUninit for T
where T: Clone,

Source§

unsafe fn clone_to_uninit(&self, dest: *mut u8)

🔬This is a nightly-only experimental API. (clone_to_uninit)
Performs copy-assignment from self to dest. Read more
Source§

impl<T> From<T> for T

Source§

fn from(t: T) -> T

Returns the argument unchanged.

Source§

impl<T, U> Into<U> for T
where U: From<T>,

Source§

fn into(self) -> U

Calls U::from(self).

That is, this conversion is whatever the implementation of From<T> for U chooses to do.

Source§

impl<T> ToOwned for T
where T: Clone,

Source§

type Owned = T

The resulting type after obtaining ownership.
Source§

fn to_owned(&self) -> T

Creates owned data from borrowed data, usually by cloning. Read more
Source§

fn clone_into(&self, target: &mut T)

Uses borrowed data to replace owned data, usually by cloning. Read more
Source§

impl<T, U> TryFrom<U> for T
where U: Into<T>,

Source§

type Error = Infallible

The type returned in the event of a conversion error.
Source§

fn try_from(value: U) -> Result<T, <T as TryFrom<U>>::Error>

Performs the conversion.
Source§

impl<T, U> TryInto<U> for T
where U: TryFrom<T>,

Source§

type Error = <U as TryFrom<T>>::Error

The type returned in the event of a conversion error.
Source§

fn try_into(self) -> Result<U, <U as TryFrom<T>>::Error>

Performs the conversion.
§

impl<V, T> VZip<V> for T
where V: MultiLane<T>,

§

fn vzip(self) -> V

§

impl<ST, DT> CastableFrom<ST, Initialized, Initialized> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<ST, DT> CastableFrom<ST, Uninit, Uninit> for DT
where ST: ?Sized, DT: ?Sized,

§

impl<T> Read<Exclusive, BecauseExclusive> for T
where T: ?Sized,