Load cuQuantum libraries with RTLD_LOCAL and explain fork-after-CUDA-init failures - #450
Merged
Conversation
… guard, AST spawn scan, reset checks
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Problem
Two process-coexistence defects surfaced when PECOS shares a Python process with another CUDA-using program:
RTLD_NOW | RTLD_GLOBAL, injecting unmangled C CUDA symbols into the process-global scope. CUDA symbols are identical across versions, so this pollutes symbol resolution for any other CUDA-using library in the same process (and exposes PECOS to the same interference in reverse).cudaErrorInitializationError(cupy wrapper) orCUSTATEVEC_STATUS_NOT_INITIALIZED(Rust wrapper) — CUDA contexts do not survive fork. The resulting errors named neither the cause nor the remedy.Changes
pecos-cuquantum-sysloader:RTLD_LOCALinstead ofRTLD_GLOBAL. Dependencies are preloaded by full path in dependency order and ELF resolves laterNEEDEDentries against already-loaded objects by SONAME, so global visibility was never required. Loader comments corrected: shipped cuQuantum libraries statically link cudart (noNEEDEDon libcudart), so the cudart preload is best-effort; the cuTENSOR preload is the load-bearing one.pecos-cuquantumerror rendering: all fourNotInitializedvariants (state vector, stabilizer, tensor network, density matrix) now explain the fork-after-CUDA-init cause and advise the multiprocessing "spawn" start method.cudaErrorInitializationError) or CUDA driver status 3 (CUDA_ERROR_NOT_INITIALIZED) re-raise with the same guidance, chained to the original exception. Matching is on numeric status codes (cupy exposes no named constants).Verification
RTLD_LOCALbuild; results are identical underRTLD_LOCALandRTLD_GLOBAL.RTLD_LOCAL;dlsym(RTLD_DEFAULT, ...)confirms CUDA symbols no longer enter the global scope; the SONAME-reuse path (libcutensornet resolving a path-preloaded libcutensor absent from every search path) works without global visibility.NotInitializedvariants and absence elsewhere.just lintclean.Known residual: real cuTensorNet contractions / cuDensityMat evolutions (cuTENSOR's runtime-loaded JIT kernels) are not exercised by the repo's tests under either linking mode.
Addendum: fork-poison guard and spawn discipline (second commit)
pecos.simulators._cuda_fork_guard: the Python CUDA simulator wrappers mark their first real CUDA call and registeros.register_at_forkhooks. A child forked after PECOS initialized CUDA now fails deterministically (measured 0.04 ms, before any CUDA call) with the same spawn guidance, and the forking parent gets a one-timeRuntimeWarning. A neighbor's CUDA initialization cannot be detected; those cases still surface the guided error from the CUDA layer. Direct users of the rawpecos_rslib_cudaextension bypass the guard deliberately; the extendedNot initializedmessages are their backstop.is_available()/try_load()only dlopen — never create handles or contexts — so probing can never poison later forks.pecossource tree for fork-hazard patterns (get_context("fork"),set_start_method("fork"), baremultiprocessing.Pool() with an explicitly empty allowlist.just lintclean.