Skip to content

Commit 5bf32da

Browse files
authored
Merge pull request #1443 from PyAutoLabs/feature/nautilus-1core-serial-pool
Merged under the human session authorization quoted on #1442 (2026-08-01: release push, blockers pre-authorized). CI 3/3 green; regression test red-on-main/green-here; control-vs-patched release-env proof on #1442.
2 parents 67c4090 + 75dccb9 commit 5bf32da

2 files changed

Lines changed: 47 additions & 1 deletion

File tree

autofit/non_linear/search/nest/nautilus/search.py

Lines changed: 13 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
import logging
33
import os
44
import sys
5+
from contextlib import nullcontext
56
from pathlib import Path
67
from typing import Dict, Optional, Tuple
78

@@ -327,7 +328,18 @@ def fit_multiprocessing(self, fitness, model, analysis):
327328
# A pool object is passed rather than pool=<int> so the pool uses the
328329
# "fork" start method (see autofit.non_linear.parallel.fork_context) —
329330
# nautilus builds its internal pools from the default context.
330-
with fork_context().Pool(self.number_of_cores) as pool:
331+
#
332+
# For a single core no pool may be created at all: nautilus treats
333+
# pool=None (and the int 1) as fully serial, whereas a Pool(1) object
334+
# forces every likelihood call into a forked worker — which deadlocks
335+
# in XLA compilation when the likelihood touches JAX, since a forked
336+
# child of a JAX-initialized parent cannot compile.
337+
if self.number_of_cores <= 1:
338+
pool_context = nullcontext(None)
339+
else:
340+
pool_context = fork_context().Pool(self.number_of_cores)
341+
342+
with pool_context as pool:
331343
search_internal = self.sampler_cls(
332344
prior=PriorVectorized(model=model),
333345
likelihood=fitness.call_wrap,

test_autofit/non_linear/search/nest/test_nautilus.py

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
import numpy as np
12
import pytest
23

34
import autofit as af
@@ -51,3 +52,36 @@ def test__test_mode():
5152
search.apply_test_mode()
5253

5354
assert search.n_like_max == 1
55+
56+
57+
def test__single_core_builds_no_pool(monkeypatch):
58+
"""
59+
number_of_cores=1 must not construct a multiprocessing pool: nautilus
60+
treats pool=None as fully serial, whereas a Pool(1) object forces every
61+
likelihood call into a forked worker — which deadlocks in XLA compilation
62+
when the likelihood touches JAX (#1442).
63+
"""
64+
from autofit.non_linear.search.nest.nautilus import search as nautilus_search
65+
66+
def no_fork_context():
67+
raise AssertionError(
68+
"fork_context must not be used when number_of_cores == 1"
69+
)
70+
71+
monkeypatch.setattr(nautilus_search, "fork_context", no_fork_context)
72+
monkeypatch.setenv("PYAUTO_TEST_MODE", "1")
73+
74+
model = af.Model(af.ex.Gaussian)
75+
analysis = af.ex.Analysis(
76+
data=np.full(100, 5.0),
77+
noise_map=np.full(100, 1.0),
78+
)
79+
80+
search = af.Nautilus(
81+
name="nautilus_single_core",
82+
unique_tag="single_core_no_pool_test",
83+
n_live=10,
84+
number_of_cores=1,
85+
)
86+
87+
search.fit(model=model, analysis=analysis)

0 commit comments

Comments
 (0)