Skip to content

fix: bundle the Open MPI runtime so the packaged Palace actually runs - #40

Open
benvial wants to merge 2 commits into
EpsilonForge:mainfrom
benvial:fix/bundle-mpi-runtime
Open

fix: bundle the Open MPI runtime so the packaged Palace actually runs#40
benvial wants to merge 2 commits into
EpsilonForge:mainfrom
benvial:fix/bundle-mpi-runtime

Conversation

@benvial

@benvial benvial commented Aug 27, 2026

Copy link
Copy Markdown

The problem

The packaged Palace binary can't start — it can't even print its version:

$ palace --version
opal_shmem_base_select failed
*** An error occurred in MPI_Init_thread

Same with --serial, so it isn't about mpirun or the host's MPI setup.

The cause

stage_palace_binary.sh collects dependencies from ldd, which finds the Open MPI
libraries but not the two pieces it can't see: the MCA components (lib/openmpi/,
lib/pmix/), which are dlopened, and orted, which Open MPI execs to start a rank —
even a single serial one. The wheel ships part of an Open MPI install, so it only runs
where a matching one already exists to borrow from.

Changes

Packaging — stage the MCA components, orted/orterun and the help text, walk their
dependencies, set their RPATHs. The upstream launcher becomes palace-launcher behind a
wrapper that points Open MPI at the bundled copy and puts the bundled bin/ first on
PATH; otherwise the launcher grabs whatever mpirun is installed, which on my machine
was MPICH. The build now fails if components and libraries come from different Open MPI
builds.

CI — after building the wheel and before uploading it, install into a clean venv, hide
the runner's Open MPI, and require palace --version to work.

Runtime_binary_is_runnable() only checked that the file existed and was
executable, which is how an unusable runtime got reported as fine; it now runs --version
(cached, one launch per process). install_palace_runtime() verifies too, so install and
check no longer disagree. Rejections say why, instead of "No Palace executable found".

Tests — 12 in tests/test_palace_runtime_resolution.py, using small executable
stand-ins rather than mocking subprocess.

Verification

Built a wheel through the fixed pipeline, installed it into a clean venv, and ran it with
MPICH's mpirun first on PATH--version, -np 2 and --serial all print
Palace version: v0.17.0-272-gb22f654ab. 31 unit tests pass, and reverting
_binary_is_runnable confirms the new tests fail against the old version.

Questions

  1. palace-cpu-wheel.yml only runs on workflow_dispatch or a palace-cpu-v* tag, so the
    new smoke test doesn't run on this PR. Could you kick off a manual run? It's the check
    this PR rests on, and so far it's only been proven on my laptop.

  2. docs.yml installs with --skip-verify for now, since the released wheel is the broken
    one and verification would take the docs build down. Both lines have a comment. Happy to
    drop it and let docs fail loudly instead.

  3. Unrelated aside, take it or leave it: building the binary package with cibuildwheel in
    a manylinux image would make it PyPI-installable, which would let the empty palace-cpu
    extra become a real dependency and retire the download-from-Releases path in
    palace_runtime.py. It wouldn't have caught this bug — auditwheel follows ldd too —
    so it's a separate change, and only worth it if PyPI distribution is a goal.

Once a new palace-cpu-v* wheel is released, anyone with a cached runtime should clear
~/.cache/palacetoolkit/runtime/.

The staging script collected dependencies from ldd output alone, which sees
only the Open MPI shared libraries. It misses the two other halves of an
Open MPI installation: the MCA components (lib/openmpi, lib/pmix), which are
dlopened, and orted, which Open MPI execs to bootstrap even a single rank.

The resulting wheel shipped a Palace binary that could not start on any
machine without a matching host Open MPI, aborting in
opal_shmem_base_select before MPI_Init -- for `palace --version` as much as
for a real simulation, and for `--serial` as much as under mpirun.

Stage the components, the runtime executables and the help text, walk their
dependency trees, and give them their own RPATHs. Install the upstream
launcher as palace-launcher behind a wrapper that points Open MPI at the
bundled runtime and puts the bundled bin dir first on PATH, so the launcher
no longer resolves `mpirun` to whatever MPI the host happens to have -- which
may be a different implementation entirely.

Guard the build against staging one Open MPI's components beside another's
libraries, and gate the release on a smoke test that runs `palace --version`
from the fresh wheel with the runner's Open MPI hidden, before upload.
_binary_is_runnable checked only is_file() and os.access(X_OK), ignoring the
lib_dir and timeout arguments it accepted. A runtime whose bundled MPI is
incomplete passes both checks and then fails at the user's first simulation,
and install_palace_runtime reported "OK" for it while check_palace_runtime
reported FAIL -- the two disagreed about the same runtime.

Run `--version` and require a clean exit, cached per binary so repeated
resolution costs one launch. Verify on install too, on the fresh-unpack path
and on the cached early return, with --skip-verify for installing on a
machine that will not run Palace.

Rejecting a broken runtime during resolution would have swallowed the cause,
leaving a bare "No Palace executable found", so record the failed launch
output and surface it in that error.

Also copy share/ and restore the executable bit across all of bin/ when
unpacking a cached runtime; zip extraction drops it, and orted must be
executable for the runtime to start.

The docs workflow installs with --skip-verify until a wheel is rebuilt
through the fixed staging pipeline.
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.

1 participant