Skip to content

fix: respect requested thread counts and available CPUs - #1419

Closed
M-Colley wants to merge 2 commits into
astroautomata:masterfrom
M-Colley:fix/thread-defaults
Closed

M-Colley wants to merge 2 commits into
astroautomata:masterfrom
M-Colley:fix/thread-defaults

Conversation

@M-Colley

@M-Colley M-Colley commented Oct 8, 2026

Copy link
Copy Markdown
Contributor

Summary

JULIA_NUM_THREADS was ignored

On import, PySR set PYTHON_JULIACALL_THREADS=auto whenever that variable was unset. juliacall 0.9.36 only falls back to JULIA_NUM_THREADS when PYTHON_JULIACALL_THREADS is unset (option('threads', default=os.getenv('JULIA_NUM_THREADS', '1'))). It passes --threads=auto to Julia, which takes precedence over JULIA_NUM_THREADS. Measured with juliacall 0.9.36 and Julia 1.13.1 on a 22-CPU machine:

environment Julia threads
JULIA_NUM_THREADS=2 2
JULIA_NUM_THREADS=2 + PYTHON_JULIACALL_THREADS=auto (what import pysr set) 22

So JULIA_NUM_THREADS=2 python -c "import pysr" started Julia with 22 threads. This oversubscribes nodes shared by several jobs that each limit their threads this way. PySR now defaults to auto only when no thread count was requested through -X juliacall-threads, PYTHON_JULIACALL_THREADS or JULIA_NUM_THREADS, following juliacall's precedence.

GC threads

The JULIA_NUM_GC_THREADS default is now derived from that same requested count, rather than from PYTHON_JULIACALL_THREADS alone. Previously, -X juliacall-threads=4 on a 128-core node gave 64 GC threads. It is also no longer computed at all when JULIA_NUM_GC_THREADS is already set. A thread count it cannot parse is now left for juliacall and Julia to report, instead of raising a bare int() error inside PySR.

PYTHON_JULIACALL_OPTLEVEL removed

juliacall has no optlevel option; the setting is optimize / PYTHON_JULIACALL_OPTIMIZE. So the variable never had any effect. Enabling -O3 as intended would also make Julia reject package images precompiled at the default -O2, so the dead setting is removed rather than renamed.

Worker processes

parallelism="multiprocessing" defaulted procs to multiprocessing.cpu_count(). That counts every CPU on the node, even inside a Slurm job or container restricted to a few cores, and each worker is a full Julia process. It now uses the CPUs this process may run on: os.process_cpu_count() on Python 3.13+, otherwise the affinity mask. A positive procs passed without parallelism, where it was silently dropped, now produces a warning. procs=0 stays silent, as the test suite uses it.

Tests

  • test_julia_num_threads_is_respected (startup): JULIA_NUM_THREADS=2 gives exactly 2 Julia threads. On master it gives threads=22; on this machine.
  • test_parallelism_defaults: the procs warning, and the default worker count coming from the available CPUs.

The existing startup tests (test_bad_startup_options, test_autoload_extension_env_precedence) pass.

🤖 Generated with Claude Code

M-Colley and others added 2 commits October 8, 2026 20:12
Julia threads: on import, PySR set `PYTHON_JULIACALL_THREADS=auto`
whenever that variable was unset. juliacall only falls back to
`JULIA_NUM_THREADS` when `PYTHON_JULIACALL_THREADS` is unset, and passes
`--threads=auto` to Julia, which overrides `JULIA_NUM_THREADS`. So the
standard Julia setting was silently ignored: with `JULIA_NUM_THREADS=2`
on a 22-CPU machine, `import pysr` started Julia with 22 threads. This
oversubscribes nodes shared by several jobs, each of which limits its
threads this way. Only default to `auto` when no thread count was
requested through `-X juliacall-threads`, `PYTHON_JULIACALL_THREADS` or
`JULIA_NUM_THREADS`.

GC threads: the `JULIA_NUM_GC_THREADS` default is now derived from that
same requested count, rather than from `PYTHON_JULIACALL_THREADS` alone
(e.g. `-X juliacall-threads=4` on a 128-core node gave 64 GC threads). It
is no longer computed at all when the variable is already set, and a
thread count it cannot parse is left for juliacall and Julia to report,
instead of raising a bare `int()` error inside PySR.

`PYTHON_JULIACALL_OPTLEVEL` is removed: juliacall has no such option
(the setting is `PYTHON_JULIACALL_OPTIMIZE`), so it never had an effect.
Enabling `-O3` as intended would also make Julia reject package images
precompiled at the default `-O2`.

Worker processes: `parallelism="multiprocessing"` defaulted `procs` to
`multiprocessing.cpu_count()`, which counts every CPU on the node, even
inside a Slurm or container allocation restricted to a few of them. Use
the CPUs this process may run on instead (`os.process_cpu_count()` or
the affinity mask). Also warn when `procs` is passed without
`parallelism`, where it was dropped silently.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
PySR no longer overrides JULIA_NUM_THREADS with PYTHON_JULIACALL_THREADS=auto; an empty JULIA_NUM_THREADS still falls back to auto. Default GC threads are sized from the requested count, including -X juliacall-threads. The warning for a non-auto PYTHON_JULIACALL_THREADS is removed. With parallelism='multiprocessing', the default procs is the CPU affinity count.

Co-authored-by: Miles Cranmer <miles.cranmer@gmail.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants