Skip to content

Commit 38eecd2

Browse files
oshaughnclaude
andcommitted
distance slices: cupy-safe export (GPU runs were silently skipping every binary)
On a GPU run (--gpu/--force-xpy) the sampler stores CUPY arrays in _rvs and the cached likelihood returns cupy. The .dslice export did plain numpy on them (np.array(sampler._rvs["distance"]) etc.), so cupy raised "Implicit conversion to a NumPy array is not allowed". That throw happens inside analyze_event, so ILE caught it, printed the MISLEADING generic hint "Probable reasons: SEOB nyquist ..." + "Skipping the following binary!", and skipped EVERY binary at the extrinsic stage -> all_dslice.dat empty (and consolidate --allow-empty masked it). Intrinsic iterations never run the export, so they were clean -- the symptom was "only the extrinsic ILE barfed" on the same binaries. Fix: convert cupy -> numpy at the boundary. - ILE dslice block: read _rvs / pdf outputs through the module-level identity_convert (= cupy.asnumpy, or no-op without cupy): distance, log_weights, log_integrand, joint_prior/joint_s_prior (+ log_ variants), integrand, the sampler pdf/prior_pdf evaluations, and prior_pdf_d(d_all). - distance_slices.py: add _to_cpu() helper; apply to every sampler._rvs read in _ln_omega_iw_factor and importance_reweight_slices, to the cached-likelihood return in importance_reweight_slices and fresh_sample_slices. Pure-numpy callers (the CPU path, unit tests) are unaffected. Affects both core+wing and all-fresh. A CPU smoke test passes even while broken, so this MUST be validated on GPU (done separately). Re-verified the CPU all-fresh smoke test still writes a 6-row .dslice. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
1 parent 91e78c5 commit 38eecd2

2 files changed

Lines changed: 38 additions & 15 deletions

File tree

MonteCarloMarginalizeCode/Code/RIFT/misc/distance_slices.py

Lines changed: 24 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,21 @@
3232
import numpy as np
3333

3434

35+
def _to_cpu(x):
36+
"""Host (numpy) view of x, converting cupy arrays if needed.
37+
38+
On GPU ILE runs the sampler stores CUPY arrays in ``_rvs`` and the cached
39+
likelihood returns cupy; numpy refuses implicit conversion of those
40+
("Implicit conversion to a NumPy array is not allowed. Please use .get()").
41+
This collapses cupy -> numpy at the boundary. Plain numpy arrays / python
42+
scalars pass straight through (cupy is not even imported here).
43+
"""
44+
get = getattr(x, "get", None)
45+
if get is not None and type(x).__module__.split(".")[0] == "cupy":
46+
return get()
47+
return x
48+
49+
3550
DISTANCE_SLICE_FIELDS = (
3651
"lnL", # extrinsic-marginalized lnL at d=dist (pure likelihood,
3752
# i.e. distance sampling prior divided out)
@@ -105,14 +120,14 @@ def _ln_omega_iw_factor(rvs, ln_prior_d_at_samples, ln_proposal_d_at_samples):
105120
"""
106121
# Pull joint prior / proposal ratio
107122
if "joint_prior" in rvs and "joint_s_prior" in rvs:
108-
jp = np.asarray(rvs["joint_prior"], float)
109-
jsp = np.asarray(rvs["joint_s_prior"], float)
123+
jp = np.asarray(_to_cpu(rvs["joint_prior"]), float)
124+
jsp = np.asarray(_to_cpu(rvs["joint_s_prior"]), float)
110125
with np.errstate(divide="ignore"):
111126
ln_pi_over_q_joint = np.log(np.maximum(jp, np.finfo(float).tiny)) \
112127
- np.log(np.maximum(jsp, np.finfo(float).tiny))
113128
elif "log_joint_prior" in rvs and "log_joint_s_prior" in rvs:
114-
ln_pi_over_q_joint = np.asarray(rvs["log_joint_prior"], float) \
115-
- np.asarray(rvs["log_joint_s_prior"], float)
129+
ln_pi_over_q_joint = np.asarray(_to_cpu(rvs["log_joint_prior"]), float) \
130+
- np.asarray(_to_cpu(rvs["log_joint_s_prior"]), float)
116131
else:
117132
raise KeyError("sampler._rvs missing joint prior/proposal columns")
118133
return ln_pi_over_q_joint - (np.asarray(ln_prior_d_at_samples, float)
@@ -159,7 +174,7 @@ def importance_reweight_slices(
159174
if a not in rvs:
160175
raise KeyError("sampler._rvs missing required column {!r} for "
161176
"slice reweighting".format(a))
162-
fixed_inputs[a] = np.asarray(rvs[a])
177+
fixed_inputs[a] = np.asarray(_to_cpu(rvs[a]))
163178

164179
K = len(d_slices)
165180
lnL_out = np.empty(K)
@@ -173,7 +188,7 @@ def importance_reweight_slices(
173188
else:
174189
like_inputs.append(fixed_inputs[a])
175190
lnL_at = like_to_integrate(*like_inputs)
176-
lnL_at = np.asarray(lnL_at, dtype=np.float64)
191+
lnL_at = np.asarray(_to_cpu(lnL_at), dtype=np.float64)
177192
if not return_lnL:
178193
# function returned exp(lnL - overflow); take log
179194
with np.errstate(divide="ignore"):
@@ -435,7 +450,9 @@ def like_at_pinned_d(**kw):
435450
eps = 1e-12 * max(abs(hi - lo), 1.0)
436451
full[p] = np.clip(np.asarray(arr, float), lo + eps, hi - eps)
437452
full["distance"] = d_arr
438-
return like_to_integrate(*(full[a] for a in arg_names))
453+
# like_to_integrate is the cached ILE likelihood -> returns CUPY on a
454+
# GPU run; the fresh AV integrator here is host-side, so bring it back.
455+
return _to_cpu(like_to_integrate(*(full[a] for a in arg_names)))
439456

440457
try:
441458
res = sampler.integrate_log(

MonteCarloMarginalizeCode/Code/bin/integrate_likelihood_extrinsic_batchmode

Lines changed: 14 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -2632,11 +2632,16 @@ def analyze_event(P_list, indx_event, data_dict, psd_dict, fmax, opts,inv_spec_t
26322632
if opts.sampler_method == "GMM" and neff < 50:
26332633
print(" WARNING: --export-distance-slices with --sampler-method GMM at main n_eff={:.1f} (<50). ".format(neff)
26342634
+ "B2-reweight may be biased; prefer --sampler-method AV or raise --n-max.")
2635-
dL_samp = np.array(sampler._rvs["distance"])
2635+
# On GPU the sampler stores CUPY arrays in _rvs / pdf outputs; convert
2636+
# to numpy at the boundary (identity_convert = cupy.asnumpy, or a no-op
2637+
# without cupy) before ANY numpy math. Otherwise cupy raises "Implicit
2638+
# conversion to a NumPy array is not allowed", analyze_event throws, and
2639+
# EVERY binary is skipped -> empty .dslice (silent failure).
2640+
dL_samp = np.asarray(identity_convert(sampler._rvs["distance"]), float)
26362641
# ln(pi_d) at samples uses the actual sampler prior (volumetric or
26372642
# pseudo-cosmo, whichever was registered).
26382643
prior_pdf_d = sampler.prior_pdf["distance"]
2639-
pi_d_samp = np.asarray(prior_pdf_d(dL_samp), float)
2644+
pi_d_samp = np.asarray(identity_convert(prior_pdf_d(dL_samp)), float)
26402645
pi_d_samp = np.where(pi_d_samp > 0, pi_d_samp, np.finfo(float).tiny)
26412646
ln_pi_d_samp = np.log(pi_d_samp)
26422647
# ln(q_d) at samples; for the standard ILE path the proposal is the
@@ -2647,7 +2652,7 @@ def analyze_event(P_list, indx_event, data_dict, psd_dict, fmax, opts,inv_spec_t
26472652
# and q_d at the SAMPLES to isolate the Omega-only factor. Compute
26482653
# q_d as a normalized 1-D density on the supported range.
26492654
try:
2650-
q_d_raw = np.asarray(sampler.pdf["distance"](dL_samp), float)
2655+
q_d_raw = np.asarray(identity_convert(sampler.pdf["distance"](dL_samp)), float)
26512656
except Exception:
26522657
q_d_raw = np.ones_like(dL_samp)
26532658
q_d_norm = float(getattr(sampler, "_pdf_norm", {}).get("distance", 1.0)) or 1.0
@@ -2658,13 +2663,14 @@ def analyze_event(P_list, indx_event, data_dict, psd_dict, fmax, opts,inv_spec_t
26582663
# log-importance weights with the same fallback chain we use for
26592664
# .dgrid.
26602665
rvs = sampler._rvs
2666+
_rvs = lambda k: np.asarray(identity_convert(rvs[k]), float) # cupy-safe column read
26612667
if 'log_weights' in rvs:
2662-
ln_w_full = np.array(rvs['log_weights'])
2668+
ln_w_full = _rvs('log_weights')
26632669
elif 'log_integrand' in rvs:
2664-
ln_w_full = np.array(rvs['log_integrand'] + rvs['log_joint_prior'] - rvs['log_joint_s_prior'])
2670+
ln_w_full = _rvs('log_integrand') + _rvs('log_joint_prior') - _rvs('log_joint_s_prior')
26652671
else:
2666-
integrand = np.asarray(rvs['integrand'])
2667-
jp = np.asarray(rvs['joint_prior']); jsp = np.asarray(rvs['joint_s_prior'])
2672+
integrand = _rvs('integrand')
2673+
jp = _rvs('joint_prior'); jsp = _rvs('joint_s_prior')
26682674
keep = (integrand > 0) & (jp > 0) & (jsp > 0)
26692675
ln_w_full = np.full(len(integrand), -np.inf)
26702676
ln_w_full[keep] = np.log(integrand[keep]) + np.log(jp[keep]) - np.log(jsp[keep])
@@ -2760,7 +2766,7 @@ def analyze_event(P_list, indx_event, data_dict, psd_dict, fmax, opts,inv_spec_t
27602766
np.full(len(d_core), distance_slices.METHOD_REWEIGHT, dtype=int),
27612767
np.full(len(d_wings), distance_slices.METHOD_FRESH, dtype=int),
27622768
])
2763-
ln_pi_d_all = np.log(np.maximum(prior_pdf_d(d_all), np.finfo(float).tiny))
2769+
ln_pi_d_all = np.log(np.maximum(np.asarray(identity_convert(prior_pdf_d(d_all)), float), np.finfo(float).tiny))
27642770
order = np.argsort(d_all)
27652771

27662772
try:

0 commit comments

Comments
 (0)