portfolio: build the fair-draw weights on the sampler's own backend (backport from rift_O4d) - #168
Conversation
Plugin discovery calls pipeline.load() unguarded, so `import
RIFT.integrators.mcsamplerPortfolio` raises whatever the plugin raises. On every
environment here that is
ModuleNotFoundError: No module named 'nflows'
from the optional NF pipeline -- which makes --sampler-method portfolio
unselectable, and mcsamplerPortfolio untestable, on any install without torch/nflows.
Prerequisite for the fair-draw fix that follows: without it that code cannot be
reached or tested at all on this branch.
Guard each load and report the skip, as rift_O4d already does.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
The fair-draw block BUILDS the weights with the module-global converter but DRAWS
with self.xpy, and the two are independent. identity_convert_togpu is cupy.asarray
whenever cupy imports, while self.xpy is numpy for any sampler whose driver did not
set it (MCSampler.__init__ defaults it to numpy) and under --sampler-xpy numpy,
which sets sampler.xpy = numpy while set_xpy_to_numpy() leaves the module globals on
cupy. Whenever they disagree, ln_wt went to the device, numpy.exp dispatched
through cupy's __array_ufunc__ and returned a cupy array, and numpy.random.choice
was handed a device array as p=:
TypeError: Implicit conversion to a NumPy array is not allowed.
Please use `.get()` to construct a NumPy array explicitly.
That is an abort, not a degraded result: the traceback runs analyze_event ->
sampler.integrate -> integrate_log, so the ILE process dies and no extrinsic samples
are written.
Backport of the rift_O4d fix. There integrate_log ADDITIONALLY forces self.xpy =
numpy (the portfolio aggregates on the host), so the backends always disagree and
--sampler-method portfolio could not run on a GPU host at all: measured on
ldas-pcdev13 with the extrinsic-collapse demo at rho_net 146.8, 6/6 replicates of
--sampler-portfolio AV,GMM died at this line and 6/6 complete with the fix. This
branch leaves self.xpy alone, so the trigger is narrower -- same defect, conditional
rather than unconditional.
Two parts, as in the O4d fix:
* build the weights on self.xpy, never on the module-global backend; and
* gather on the HOST -- _rvs entries need not share a backend with the index array
(sample_n is written through the INSTANCE identity_convert_togpu, which the ILE
sets to cupy.asarray, while the aggregated keys arrive host-typed), and indexing
a numpy array with a cupy array raises the same TypeError.
Also host-converts the three _rvs operands before the arithmetic one line above, for
the same reason: numpy.array(<cupy>) raises identically one line earlier.
The regression suite uses a _DeviceArray stand-in reproducing the two cupy behaviours
the bug turns on, so it hits the exact production traceback on a CPU-only host. All
five runnable tests fail without this change.
Two tests SKIP on this branch, with the reason recorded in the code: driving the
INSTANCE converter device-typed trips a separate, pre-existing defect in draw(),
which converts member draws with self.identity_convert_togpu and then assigns them
into host-typed buffers. That is untouched here (rift_O4d solves it by aggregating
on the host) and would be wrong to attribute to the fair draw. The gather itself is
pinned regardless by the device-typed INDEX test, which does run.
Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
|
Heads-up on an overlap, and a question about how you'd like it handled. I opened #172, the driver-side half of portfolio selectability (
I had independently written a plugin-discovery guard identical to your One thing the combined tree turned up that neither PR fixes: with both applied the portfolio gets as far as the integration loop and then aborts in the AV member's prior evaluation —
|
|
Correction / follow-up to my note above, since I chased the It is not a single site, and the O4d fix is not a safe backport. Fixing each abort just revealed the next:
all the same More importantly — backporting O4d's
The reason is So I'm not proposing any of it. I've left the reproduction on a local branch and written it up in cd ~/rift_O4c_base/MonteCarloMarginalizeCode/Code/RIFT/integrators
grep -nE '(temp_ret|p_out|joint_p_s|joint_p_prior)\s*\*=' mcsampler*.py |
…ng a sampler Driver-side half of making --sampler-method portfolio usable on rift_O4c. The module-side half -- guarding the unguarded entry-point plugin load, so one missing optional dependency does not make mcsamplerPortfolio unimportable -- is PR #168's first commit (aaa8a6e) and is deliberately NOT duplicated here. Neither half is sufficient alone; #168 does not touch this file. Four defects, all in the sampler-construction chain, all of the same kind: a request the driver cannot honour is answered with something other than an error. 1. `elif opts.sampler_method == "portfolio" and mcsampler_Portfolio_ok:` made the very next statement -- `if not(mcsampler_Portfolio_ok): raise` -- unreachable, and sent an unavailable portfolio down the chain to the terminal `else`, which prints " ILE: **original sampler** " and proceeds with the plain mcsampler.MCSampler constructed before the chain. A run that asked for the portfolio would have integrated with a different sampler. Drop the ok-flag from the test so the existing raise becomes reachable and does its job. 2. That terminal `else` then dereferences `mcsamplerPortfolio.known_pipelines` in its own diagnostic print -- but the name is only bound if the import succeeded, so the diagnostic for a failed import raised NameError itself. This is what '--sampler-method portfolio' actually produced on a torch-free container: NameError: name 'mcsamplerPortfolio' is not defined 3. The plugin-pipeline branch had the same unguarded dereference in its `elif` test. Gate it on mcsampler_Portfolio_ok. 4. --sampler-portfolio is action='append' while its help documents a comma-separated list, and the member loop had no else clause. So the documented invocation '--sampler-portfolio AV,GMM' arrived as the single member name "AV,GMM", matched no branch, and appended whatever `sampler` happened to hold -- the plain MCSampler from before the chain, or on later iterations the PREVIOUS member. The portfolio then ran with a member nobody asked for and died later and elsewhere with a misleading "no attribute 'draw_simplified'". Accept both spellings (and a mix), and make an unrecognized member name an error naming the known members. The committed demo harness already works around this by splitting on commas in shell before invoking the driver (extrinsic_collapse_demo/run_demo.sh:58), which is why the append form is the only one that had been exercised. Ported to rift_O4d (cherry-pick of the rift_O4c commit). O4d already carried HALF of defect 4 -- it splits on commas, but with no None guard, no empty-member check and no else clause -- so that hunk conflicted and was resolved by keeping O4d's comment and expression and adding the two missing guards. Defects 1, 2 and 3 were entirely absent from O4d and applied clean. Co-Authored-By: Claude Opus 5 <noreply@anthropic.com> (cherry picked from commit 3201b00)
Backport of the
rift_O4dfix in oshaughnessy-junior#76. Two commits, separable if you'd rather take them apart.1. Plugin discovery guard (prerequisite)
import RIFT.integrators.mcsamplerPortfoliocurrently raises whatever an optional plugin raises, becausepipeline.load()is unguarded. On every environment available here that is:from the optional NF pipeline — so
--sampler-method portfoliois unselectable andmcsamplerPortfoliountestable on any install without torch/nflows. This is a hard prerequisite: without it the fair-draw code below cannot be reached or tested on this branch at all.rift_O4dalready carries this guard; this is the same four lines.2. The fair-draw backend fix
The fair-draw block builds the weights with the module-global converter but draws with
self.xpy, and the two are independent:identity_convert_togpuiscupy.asarraywhenever cupy imports, whileself.xpyis numpy for any sampler whose driver did not set it (MCSampler.__init__defaults it to numpy) and under--sampler-xpy numpy, which setssampler.xpy = numpywhileset_xpy_to_numpy()only rebinds local variables and leaves the module globals on cupy. When they disagree,ln_wtgoes to the device,numpy.expdispatches through cupy's__array_ufunc__and returns a cupy array, andnumpy.random.choiceis handed a device array asp=:That is an abort, not a degraded result — the traceback runs
analyze_event -> sampler.integrate -> integrate_log, so the ILE process dies and no extrinsic samples are written.Two parts, matching the O4d fix:
self.xpy, never the module-global backend._rvsentries need not share a backend with the index array (sample_nis written through the instanceidentity_convert_togpu, which the ILE sets tocupy.asarray, while the aggregated keys arrive host-typed), and indexing a numpy array with a cupy array raises the sameTypeError.Plus one line beyond that: the three
_rvsoperands are host-converted before the arithmetic one line above, sincenumpy.array(<cupy>)raises identically one line earlier.Severity differs from O4d — stated plainly
On
rift_O4d,integrate_logadditionally forcesself.xpy = numpy(the portfolio aggregates on the host), so the two backends always disagree and--sampler-method portfoliocannot run on a GPU host at all. That is where this was measured: ldas-pcdev13, extrinsic-collapse demo at rho_net 146.8, 6/6 replicates of--sampler-portfolio AV,GMMdied at this line, and 6/6 complete with the fix.This branch leaves
self.xpyalone, so the abort is conditional on the two backends disagreeing rather than unconditional. Same defect, narrower trigger. I did not reproduce it end-to-end on an O4c GPU run — the evidence here is the unit reproduction plus the O4d campaign.Testing
New
test/test_portfolio_fairdraw_backend.py: 5 passed, 3 skipped (numpy 1.24.4 env). All five runnable tests fail without commit 2.A
_DeviceArraystand-in reproduces the two cupy behaviours the bug turns on (ufunc dispatch via__array_ufunc__,__array__raising), so the tests hit the exact production traceback — samemtrand.pyxframe, same message — on a CPU-only host rather than skipping.Two tests skip on this branch, and the reason is recorded in the code. Driving the instance converter device-typed trips a separate, pre-existing defect in
draw(), which converts member draws withself.identity_convert_togpuand then assigns them into host-typed buffers — so it raises insidedraw(), long before the fair draw. That is untouched here (rift_O4dsolves it by aggregating on the host) and it would be wrong to attribute it to the fair draw. The skip is conditional on the traceback actually passing throughdraw(), so these re-enable themselves if that is ever fixed. The gather is pinned regardless by the device-typed index test, which does run.The test file deliberately differs from the O4d copy in its prose and in those two skips, because the branches genuinely differ in how
self.xpyis bound.Note
There is an unpushed local branch (
rift_O4c_portfolio_usable) carrying a similar plugin guard alongside other O4c portfolio work. This PR stands alone againstrift_O4cand does not depend on it; if that branch lands first, commit 1 here will likely conflict trivially or become redundant.🤖 Generated with Claude Code