portfolio: VARAHA never-freeze default + freeze/allocation/clipping policy (gate-validated) - #28
Conversation
…r-import fix The portfolio balance heuristic gave a VARAHA/AV member weight ~0.01 before it had contracted (< portfolio_freeze_wt 0.05), freezing it from chunk 1 so it never became the workhorse; the portfolio then rode a stalling GMM member (S250114ax cold n_eff ~1.9 vs standalone AV ~100). Grace/revive (f2d51de) cannot fix this: a VARAHA member only contracts on the chunk it is updated, and it only earns allocation weight once it has contracted -- a feedback loop that reviving 1/N of the time never escapes (measured: the revive sweep stays at n_eff 1-3). Fix: make VARAHA/AV members freeze-EXEMPT by default (portfolio_varaha_never_freeze=True) -- update every chunk past their breakpoint, like standalone AV. The balance-heuristic mixture density q_mix keeps the estimate unbiased for any weights, so continuous VARAHA updates can only cost a few selfish-draw evals, never bias. Also add a plateau-aware revive (keep updating any low-weight member whose own per-chunk n_ess is still climbing) and a per-member n_ess history for diagnostics. Wire CLI flags into the driver and thread them into sampler.setup: --portfolio-grace-iters / --portfolio-revive-period / --portfolio-freeze-wt --portfolio-varaha-never-freeze (default) / --portfolio-varaha-can-freeze (disable) setup() now ignores None (unset) kwargs so the sampler keeps its defaults. Two correctness fixes surfaced by benchmarking: * --sampler-portfolio is now comma-split ('AV,GMM' was one bogus member -> 1-member portfolio). * portfolio plugin discovery no longer aborts the whole import when a plugin's optional deps are missing: the NF plugin does `import torch`, absent in the production GPU container, which made mcsamplerPortfolio unimportable there -> mcsampler_Portfolio_ok=False -> every portfolio run died with a NameError. Unusable plugins are now skipped, not fatal. Benchmarks (GPU A100), see RIFT/integrators/DESIGN_portfolio_freeze_policy.md: * S250114ax (hard, rho~82): never-freeze rescues AV n_eff 3.4 (frozen) -> 53, tracking standalone AV through the useful range; grace/revive tuning stays at 1-3; standalone GMM is NaN-unusable (works only inside the portfolio). * 4 typical O4 events (in-container, real SEOBNRv5PHM): portfolio REPLICATES the standalone-AV ln Z within MC error on every event and AV becomes the in-portfolio workhorse; on AV-favorable events the portfolio reaches a given n_eff in more evals (it still spends a share on GMM) -- an efficiency limit set by the DRAW-ALLOCATION heuristic, not the freeze policy, which is now solved. Harness: test/integrators/bench_portfolio_freeze.sh, bench_multi_event.py, run_multi_event.sh, parse_neff_traj.py. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Follow-on to the never-freeze fix. With never-freeze the portfolio is unbiased and AV is no
longer starved of UPDATES, but the DRAW allocation still split ~half the budget to GMM even when
AV was winning, so on AV-favorable events the portfolio needed more evals than standalone AV, and
on hard events it under-converged. Root cause is a draw catch-22 identical in structure to the
freeze bug: a member's per-chunk n_ess is suppressed while it has few draws, so the member that
should win is stuck under-observed and never earns more draws.
Fix (portfolio_adaptive_alloc, default on): decouple a per-member QUALITY estimate (EMA of n_ess,
updated ONLY from chunks where the member had a fair allocation) from the ALLOCATION
(quality^exponent above a small floor), and ROUND-ROBIN PROBE one member per probe_period chunks
at a raised share so a suppressed member gets a fair look and can prove itself. q_mix keeps every
allocation unbiased, so this only trades efficiency, never correctness. Knobs (setup + CLI):
portfolio_adaptive_alloc / --portfolio-static-alloc, portfolio_alloc_exponent (2.0) /
--portfolio-alloc-exponent, portfolio_probe_period (4) / --portfolio-probe-period,
portfolio_alloc_floor (0.05), portfolio_quality_decay (0.5), portfolio_probe_frac (0.6).
New test test/integrators/test_portfolio_adaptive_alloc.py (synthetic, GPU, no ILE), fixed budget,
standalone AV vs GMM vs portfolio:
* CORRELATED (compound-symmetric Gaussian): full-cov GMM beats AV's axis-aligned bins
(n_eff 390 vs 131); adaptive concentrates on GMM (weight 0.88) so the PORTFOLIO BEATS
standalone AV (381 vs 131) -- the correlated-problem regime where beating AV is expected.
* cold VARAHA/AV under-covers the Gaussian tails and is biased low (-0.6..-0.9); the portfolio
stays unbiased (-0.05) because the covering GMM enters q_mix.
The portfolio is now a "best-of" (tracks whichever member wins) rather than a fixed compromise.
test_portfolio_balance_heuristic.py: pin portfolio_adaptive_alloc=False there (it isolates the
q_mix ESTIMATOR under a fixed pathological allocation; adaptive alloc would change that scenario).
Also add a per-integration override: integrate_log now honors portfolio_adaptive_alloc in kwargs.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… real events) Real-event check flipped this from default-on to default-off. On S250114ax (a real high-SNR AV-favorable event) adaptive allocation drove the true AV workhorse to the 0.05 floor and rode a self-consistent-but-worse GMM member: measured n_eff 8 vs 53 for the legacy allocation -- a regression. Root cause: the quality signal is each member's per-chunk Kish n_ess, which rewards SELF-CONSISTENCY, not integral coverage. A warm GMM is instantly self-consistent (per-chunk n_ess ~120) while a warm VARAHA/AV member's per-chunk n_ess is genuinely ~1 during its slow, CUMULATIVE contraction (its value emerges over ~70 chunks). So the signal ranks the wrong member and the probe can't rescue AV (it still looks bad at high allocation until fully contracted). A correct default needs a GLOBAL-impact signal (a member's marginal contribution to the pooled q_mix n_eff), not per-member self-n_ess -- future work. So: portfolio_adaptive_alloc now defaults False; CLI flag flipped to --portfolio-adaptive-alloc (enable) from --portfolio-static-alloc; the default portfolio keeps never-freeze + legacy n_ess reweighting (the validated 53-on-S250114ax / replicates-AV behavior). The MECHANISM is still sound when the signal is right: test_portfolio_adaptive_alloc.py now broad-seeds the GMM member (so it reliably models the correlation instead of gambling on cold GMM finding a thin ridge) and, opting in to adaptive, robustly shows across seeds that on a compound-symmetric CORRELATED Gaussian the portfolio concentrates on GMM and BEATS standalone AV (n_eff ~390 vs ~20), unbiased. DESIGN doc updated with the regression and the honest verdict. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…daptive allocation
Replaces the per-member Kish n_ess quality signal with the right objective:
g_m = 2*mean_w_m/S - mean_w2_m/Q (S=sum w, Q=sum w^2 over ALL members' samples)
i.e. each member's MARGINAL GAIN IN POOLED n_eff PER SAMPLE, derived from n_eff = S^2/Q. It
credits a member for the weight MASS it supplies and debits it for the weight VARIANCE it injects.
Selectable via portfolio_quality_signal / --portfolio-quality-signal ('global' default, 'ness'
legacy). Also: first observation is adopted outright (the global signal has an arbitrary scale, so
EMA-ing from the placeholder would bias it), no fair-allocation gating needed for 'global' (it is
self-correcting at low allocation), and default alloc exponent 2.0 -> 1.0.
Two simpler candidates were implemented and measured first; both fail, and the reasons are recorded
in DESIGN_portfolio_freeze_policy.md:
* Kish n_ess is SCALE-INVARIANT, so it cannot see whether a member carries any integral mass.
* mean weight alone is BACKWARDS: a well-matched contracted AV correctly has small uniform
weights while a broad GMM's rare outlier sets the max (S250114ax: AV 1e-40 vs GMM 2e-4).
Synthetic (test_portfolio_adaptive_alloc.py, 2 seeds): on a compound-symmetric CORRELATED Gaussian
the portfolio concentrates on the full-cov GMM and BEATS standalone AV (~375 vs ~6-61 n_eff),
unbiased; uncorrelated case also unbiased.
IMPORTANT NEGATIVE RESULT on the real S250114ax event: the global signal still ranks GMM first, and
the measured values say why -- GMM's g is exactly 1.053e-4 = 1/9500 = 1/n_GMM, the analytic
signature of ONE sample owning the whole estimator (g = 2/n - 1/n for the holder of the dominant
outlier), vs AV ~1e-73. The chunk max is a catastrophic GMM outlier ~1e73x any AV weight (a draw
where q_mix ~ 0 but the target is nonzero). So on that event ALLOCATION IS NOT THE BOTTLENECK --
unbounded member importance weights are; even floored at 5% the GMM member still injects outliers,
which is why the legacy allocation reached 53 and not AV's 100. The next lever is weight bounding /
member exclusion (defensive component, clipping), a member-quality fix rather than an allocation
one. Adaptive allocation therefore stays OPT-IN; the default remains never-freeze + legacy
reweighting.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… mass + q_mix underflow guard Adds portfolio_weight_clip / --portfolio-weight-clip C (default OFF): cap each importance weight at tau = C*sqrt(n)*mean(w) (Ionides truncated IS). The removed and total weight mass are accumulated in log space and the induced ln Z bias is PRINTED every chunk, so the bias is never silent and is recoverable. Also adds a permanent q_mix UNDERFLOW counter. Findings (all measured; see DESIGN_portfolio_freeze_policy.md): 1. The S250114ax outliers are REAL heavy tails, not the 1e-300 q_mix floor firing: an instrumented run counts ZERO underflows. So clipping is a legitimate tool here, not a mask. 2. Synthetic ground truth (bench_weight_clip.py, analytic ln Z, 2 seeds): where weights are well-behaved clipping is a COMPLETE NO-OP at C=1,5,20 -- no n_eff gain, no bias, clip_frac ~0 on both the uncorrelated and correlated targets. The tracker also works as a bias ESTIMATOR (predicted -0.003 vs measured -0.006, within +/-0.003 seed noise). 3. Real S250114ax -- THE TRAP. Clipping C=1 reaches n_eff=100 in 1.87M evals, 2x FASTER than standalone AV (3.64M), and reports a perfectly converged run -- but its ln Z is 1180.25 vs AV's trusted 1191.79, i.e. 11.5 nats LOW. The tracker flagged it live (removed mass frac 0.87-0.98). n_eff STOPS BEING A VALIDITY CHECK the moment clipping engages. 4. Side finding refining Benchmark 2: even WITHOUT clipping the AV+GMM portfolio reads ln Z=1183.12 on this event, 8.7 nats below AV. Heavy-tailed IS is unbiased in expectation but realizes LOW in nearly every run, so in production it behaves like a bias. "Portfolio replicates AV's ln Z" holds on the four TYPICAL events, NOT on S250114ax -- there the right move is to drop that member. Verdict: clipping is a SAFETY VALVE and a DIAGNOSTIC, not a speedup -- it cannot manufacture n_eff without discarding integral mass one-for-one. Where it is safe it does nothing; where it does something it is biasing. The valuable artifact is the tracked clip_frac. Guidance recorded for promoting this to the individual integrators (AV): never ship bare clipping (needs the mass tracking + a refuse-to-clip gate), surface clip_frac as a first-class diagnostic regardless, and require the FULL LVK PP campaign for deep AV weight changes -- a small ln Z bias would pass a single-event n_eff check and only surface as PP miscalibration. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…iased by construction)
Restructures the opt-in weight clip so it can never bias the estimate. RIFT already separates the
two uses of the per-sample weights; clipping now respects that split:
* log_integrand -> the ESTIMATE (init_log/update_log -> ln Z; maxval -> n_eff): left UNCLIPPED.
* log_weights -> ADAPTATION only (per-member n_ess report, allocation signal, and
member.update_sampling_prior training): clipped copy log_weights_adapt.
Adaptation only shapes proposals (like warm-starts/oracles/q_mix), so clipping it cannot bias ln Z.
This is strictly better than the alternative of DROPPING a chunk that clipped: dropping conditional
on "a big weight appeared" is data-dependent selection and biases ln Z low (it preferentially
discards the rare mass-carrying chunks). The tracked withheld mass becomes a TAIL DIAGNOSTIC.
Measured:
* Synthetic (analytic ln Z): estimator bias UNCHANGED at C=0,1,5 -- the falsifiable proof the
estimate is untouched; no-op where weights are well-behaved.
* S250114ax: adaptation-clip C=1 gives ln Z=1184.4 (matches the unclipped 1183.1, NOT the biased
estimator-clip 1180.3), confirming unbiasedness -- but n_eff COLLAPSES to 1.2 (vs 52.6 unclipped)
because 97% of the weight mass was withheld FROM ADAPTATION: on this event the mass-carrying
samples ARE the signal the GMM must learn, so clipping them starves the proposal. Clipping
cannot fix a fundamentally heavy-tailed member; the right move there stays "drop the GMM member".
Prior estimator-side attempt kept in the design doc as the cautionary result (n_eff=100 at 1.87M,
2x faster than AV, but ln Z biased -11.5 nats -- n_eff stops being a validity check once the
estimator is clipped).
Verdict: adaptation-only clipping is the correct/unbiased form -- a safety valve against one
pathological weight wrecking a member's covariance fit / the allocation, and a no-op otherwise. It
does NOT manufacture n_eff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… n_ess-inflation regression) The previous "adaptation-stream" clipping was too broad: it also clipped the weights used for the per-member n_ess REPORT and the allocation signal. Clipping FLATTENS weights, which INFLATES a member's Kish n_ess, so the allocation perversely rewarded exactly the member whose weights had to be clipped. Measured on S250114ax (legacy alloc + clip C=1): the clipped GMM member's reported n_ess jumped to 60-230, the allocation drove the true AV workhorse to the 1% floor, and n_eff stuck at ~1 (vs 53 unclipped) -- a regression, even though ln Z stayed unbiased. Fix: the clipped copy log_weights_adapt now feeds ONLY member.update_sampling_prior (the GMM covariance fit). The estimator (ln Z, n_eff), the n_ess report, and the allocation signal all use the TRUE unclipped weights. A short S250114ax run now climbs n_eff normally again. Net clipping scope, all measured: * estimator: NEVER (clipping it biased ln Z -11.5 nats on S250114ax). * n_ess report / allocation: NEVER (inflates the clipped member's n_ess -> starves AV). * GMM proposal fit: the one legitimate use (stops a single enormous weight making the covariance fit degenerate); a no-op where weights are well-behaved. Also lands run_multi_event_clip.sh (typical-event clip safety harness). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
4 real events (in-container), proposal-fit clip C=1 vs no-clip: unbiased everywhere, n_eff comparable-or-better, and it recovers the two hard events the earlier broad-scope bug had degraded (S240703ad n_eff 1.1 -> 6.9 -- protecting the GMM covariance fit from outliers gave a better proposal). ln Z differences within the large MC error at these low n_eff. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
… by PR #33 PR #33 found two coupled bugs that invalidate the event-specific numbers in this document: * default-wired portfolio GMM members NEVER TRAINED (n_comp=None silently no-op'd), so the real-ILE portfolio runs here carried an untrained corpse member (found via this PR's freeze probe); * AV draw_simplified head-sliced a BIN-ORDERED cloud (~50-60% of live-volume bins) while sampling_density claimed uniform coverage of ALL bins -- a q_mix DENSITY LIE, which inflates the weight of any member drawing where AV never populates. My earlier "the outliers are real heavy tails" conclusion was based on counting q_mix UNDERFLOWS (zero found). That test was correct but incomplete: it rules out a density underflow, not a density LIE. Lesson recorded: when a weight looks impossible, test whether the member actually DRAWS where it claims density, not just for underflow. The clipping study stands as a METHODOLOGICAL result (which quantities may be clipped and why -- those arguments are analytic), but every S250114ax efficiency/ln Z figure needs re-measuring on top of #33. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ession test pytest-collectable Review finding on #33: importing mcsamplerPortfolio eagerly load()s all RIFT.integrator_plugins entry points, so a torch-free installation died with ModuleNotFoundError before the new regression test could even collect. Guard each pipeline.load() (skip + notice, matching the isolation #28 carries) and convert the regression script into two proper pytest test functions. Verified: pytest -q passes (2 passed) with a torch-import blocker on PYTHONPATH, reviewer's invocation. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…o_freeze_tuning # Conflicts: # MonteCarloMarginalizeCode/Code/RIFT/integrators/mcsamplerPortfolio.py
…in gate probe Re-measured S250114ax on top of PR #33 (GMM members now actually train; AV draw_simplified no longer misreports its density). Result: the portfolio got WORSE when the GMM member came alive -- n_eff 2.1 (default) / 1.1 (adaptive) at 4M, vs 52.6 pre-#33 and 100.2 for standalone AV. Pre-#33 the GMM member was a corpse so the portfolio was effectively AV-only; now the allocation hands the live GMM ~0.84 of the budget and the pooled n_eff collapses. This isolates the DRAW-ALLOCATION pathology in clean form: never-freeze is working (AV updates every chunk) but both allocation rules score by per-chunk n_ess, which sits at ~1 through VARAHA's slow CUMULATIVE contraction, so a member that looks instantly good takes the budget. New opt-in lever portfolio_varaha_min_frac / --portfolio-varaha-min-frac reserves a combined draw fraction for VARAHA members, applied after either allocation rule. It works mechanically (AV share 0.97 at floor 0.5, 0.85 at floor 0.7) but does NOT rescue this event: even a 3-15% GMM share still poisons the pooled n_eff (~1-2). Conclusion recorded: the missing capability is member EXCLUSION, not re-weighting -- any nonzero share of a wrong member's draws enters q_mix and a handful of its samples dominate. Honest production guidance for AV-favorable high-SNR events: run standalone AV, not AV+GMM. The portfolio's demonstrated value stays the correlated regime (Benchmark 3). Also adds test/expensive_before_merging/integrators/probe_portfolio_optin_flags.py: the TESTING.md-required flag-ON probe for the opt-in features, reusing the merge-gate suite as a library (identical targets/metrics/thresholds) and running in-process because the gate's spawn workers would not carry a monkey-patch. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Answers "we have other rules (q_mix) to score, not n_eff?". The per-member score was Kish n_ess,
which -- although computed on q_mix-denominated weights -- is SCALE-INVARIANT and therefore blind to
whether a member carries any integral mass at all. The mixture machinery supports a strictly better,
q_mix-native rule: balance-heuristic CREDIT ASSIGNMENT. Each sample's contribution is owed to
members in proportion to their share of the mixture density there, so
credit_m = sum_i [ frac_m q_m(x_i) / q_mix(x_i) ] * w_i (per drawn sample)
This credits a member for COVERING WHERE THE INTEGRAND IS even if it drew few samples there --
precisely the signal a slow-contracting VARAHA member needs, and precisely what n_ess cannot see.
Implementation: the q_mix block already evaluates every member's q_m, so we retain frac_m*q_m per
member (self._chunk_mix_parts) and form the credit in the report block; no extra density evaluations.
Selectable via portfolio_quality_signal / --portfolio-quality-signal {global,credit,ness}; 'credit'
is zero-based like 'global' (only the legacy Kish signal is floored at 1).
Synthetic check (correlated compound Gaussian, adaptive allocation on): all three signals run and
stay unbiased (bias -0.0003 / -0.0011 / +0.0054 for ness/global/credit) at comparable n_eff.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…get (post-#33); de-circularize credit Post-#33 S250114ax, warm, same budget -- clipping the proposal-fit input is the lever that works: run Neff>=5 Neff>=10 final@4M standalone AV (reference) 0.695M 1.374M 100.2 portfolio, default -- -- 2.1 portfolio + defensive 0.05 -- -- 2.5 portfolio + PROPOSAL-FIT CLIP 0.420M 0.590M 14.4 n_eff=5 1.7x faster than standalone AV and n_eff=10 2.3x faster. The real O4 event configs run --n-eff 10, so AT THE TARGET THAT SHIPS the clipped AV+GMM portfolio beats standalone AV on this event. It plateaus at ~14 rather than reaching AV's 100, so AV alone still wins the stress target. This only became visible after #33: pre-#33 the GMM member was a corpse, so there was nothing to poison and clipping did nothing. Lever ordering on this event: clip (2.1->14.4) >> defensive mixture (2.1->2.5) ~ VARAHA draw floor (no rescue) > adaptive allocation (worse, 1.1). Also de-circularizes the 'credit' signal: the raw MIS share frac_m*q_m/q_mix scales with frac_m, so a member accrued credit BECAUSE it was dominant (a 0.95-share GMM scored 6e-4 vs AV 9e-10 and starved AV to the floor -- the same circularity n_ess has). Now normalized by frac_m => "integral explained per unit allocation", which is allocation-invariant and is what an allocation rule must compare. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
Automated review of
Supporting checks are clean: GitHub Please resolve/revalidate the |
…n_eff in the shape gate) The shape-recovery merge gate (PR #31) flagged a portfolio regression on my branch: ~13 of 20 portfolio rows lost n_eff vs base, several by 2-4x. It did not block (portfolio is warn-only, strict=AV,GMM) but PR #28 changes the portfolio DEFAULT path, so it needed attribution. Isolated on the gate's own targets, same checkout, one knob at a time: * never-freeze (my headline default): ratio 1.00 on every row -- NO effect on these targets. (It matters on the S250114ax freeze-out, not here.) My leading hypothesis was wrong. * plateau-aware _climbing revive: SOLE cause. plateau ON -> OFF reproduces gate base EXACTLY: d4_n1_s101 25.9 -> 53.5 (base 53.5) d4_n3_s202 29.1 -> 83.8 (base 83.8) d6_n1_s202 64.0 -> 102.1 (base 102.1) d6_n3_s202 7.2 -> 31.4 (base 31.4) d8_n1_s101 37.3 -> 61.9 (base 61.9) Mechanism: forcing updates of members the freeze schedule would have parked makes their proposals WORSE, not better -- the opposite of the intuition that motivated it. Fix: portfolio_plateau_revive now defaults False (base-equivalent default path); the behaviour stays available as an opt-in knob with the measurements recorded inline so nobody re-enables it blind. Method note: the first isolation attempt was INVALID -- it put the suite's test dir on sys.path but not the branch on PYTHONPATH, so it imported the INSTALLED RIFT (where the knob does not exist) and reported "no effect" with n_eff nowhere near the gate's. A valid isolation must reproduce the gate's absolute numbers; these do, row for row. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ment Gate (PR #31 requirement), base rift_O4d @4bac7444 vs this branch, both incl. #33, run on an idle node: COMPARE_EXIT=0, 0 blocking regressions, base and PR identical in aggregate (strict 8/8, warn-only 5/5, starved 45/45), 22 of 23 portfolio rows bitwise identical to base. The one remaining difference is mix_d2_n3_s303 (n_eff 736->517, bias -0.0060->-0.0064): the row where never-freeze actually engages. Both PASS comfortably, but it quantifies the trade -- never-freeze buys starvation-immunity and costs ~30% n_eff where freezing would have been harmless. Also hardens probe_portfolio_optin_flags.py to export CUDA_VISIBLE_DEVICES/OMP_NUM_THREADS and put the checkout on sys.path itself. Library-mode use of the suite does NOT inherit what run_shape_recovery.sh exports; without it you import the INSTALLED RIFT and get confident, meaningless numbers (this bit me once -- caught only because the absolute n_eff did not match the gate's). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eatures help) TESTING.md requires opt-in changes be probed with the flag ON, since the default-path gate is bitwise-identical for opt-in code and therefore proves nothing about it. Probe uses the gate's own targets/metrics/evaluate(), so PASS here is PASS by gate criteria. target flagsOFF adaptive_alloc weight_clip d2_n1_s303 1502 3021 3037 d2_n3_s303 517 1058 816 d4_n1_s303 163 415 263 d4_n3_s303 7(starved) 15 54 0 opt-in regressions; |bias| <= 0.024 on every PASS row. Independently corroborates the S250114ax result that proposal-fit clipping is the effective lever (worst row 7 -> 54). Recorded caveat: on the starved row the bias grows (-0.147 -> -0.279). At n_eff <~ 50 the shape is untestable by the gate's own policy, so clipping's gains in the STARVED regime are unvalidated for SHAPE even though n_eff improves -- do not read the starved-row n_eff jump as a shape result. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Portfolio freeze-policy tuning, plus the draw-allocation and weight-clipping levers it exposed.
Base retargeted to
rift_O4d(PRs #26/#27 merged); merged with #33.Problem
The balance heuristic gave a VARAHA/AV member weight ~0.01 before it had contracted (<
portfolio_freeze_wt0.05), so it was frozen from chunk 1 and never contracted — the portfolio rode a stalling GMM member. Grace/revive cannot fix it: a VARAHA member only contracts on the chunk it is updated, and only earns weight once contracted, so reviving 1/N of the time never escapes (measured: the revive sweep stays at n_eff 1–3).What this changes
portfolio_varaha_never_freeze) — they update every chunk past their breakpoint, like standalone AV.q_mixmakes this unbiased for any weights, so it can only cost eval cycles, never bias.--portfolio-grace-iters / --portfolio-revive-period / --portfolio-freeze-wt / --portfolio-varaha-never-freeze / --portfolio-varaha-can-freeze, threaded intosampler.setup.--portfolio-adaptive-alloc) with selectable quality signal (--portfolio-quality-signal {global,credit,ness}), proposal-fit weight clipping (--portfolio-weight-clip), VARAHA draw floor (--portfolio-varaha-min-frac), plateau revive (portfolio_plateau_revive).--sampler-portfolio AV,GMMnow comma-splits as documented (it was silently a 1-member portfolio); portfolio plugin discovery tolerates missing optional deps (theNF→torchimport made the whole portfolio unimportable in the production container — independently fixed in integrators: portfolio GMM dead-member fix (n_comp=None) + AV draw-density repair (bin-ordered head slice) #33, resolved to that version).Validation (both TESTING.md requirements)
Shape-recovery merge gate (base
rift_O4d @4bac7444vs branch, both incl. #33, quiet node):COMPARE_EXIT=0, 0 blocking regressions; base and PR identical in aggregate (strict 8/8, warn-only 5/5, starved 45/45), 22 of 23 portfolio rows bitwise identical to base.Flag-ON probe (required because the default gate is bitwise-identical for opt-in code) — gate's own targets/metrics/
evaluate(): 0 opt-in regressions, both features help.A regression the gate caught in this PR (fixed)
The first gate run showed ~13/20 portfolio rows losing n_eff (2–4×). It did not block (portfolio is warn-only) but this PR changes the portfolio default path, so it was attributed rather than waived:
25.9→53.5,29.1→83.8,64.0→102.1,7.2→31.4,37.3→61.9). Forcing updates of members the freeze schedule would have parked makes their proposals worse. Now defaults off.Honest caveats
draw_simplifieddensity lie). Re-measured post-integrators: portfolio GMM dead-member fix (n_comp=None) + AV draw-density repair (bin-ordered head slice) #33: portfolio + proposal-fit clipping reaches n_eff=10 at 0.59M vs standalone AV's 1.374M (2.3× at the production--n-eff 10target), though it plateaus ~14 vs AV's 100 at the stress target.global/creditsignals are provided as better-founded alternatives;creditis normalized per unit allocation to avoid the circularity that a dominant member accrues credit because it is dominant.Follow-up (not blocking)
Post-#33 multi-event validation on real events via the container family + condor — blocked here only on hardware (the
cuda118image won't run on Blackwell; the busy node kills the suite via RLIMIT_NPROC). Defaults are unaffected by that work.Design doc:
RIFT/integrators/DESIGN_portfolio_freeze_policy.md.🤖 Generated with Claude Code