Skip to content

fix: restore custatevec import compatibility#334

Merged
ciaranra merged 15 commits into
devfrom
codex/fix-custatevec-cuquantum-import-compat
Jun 27, 2026
Merged

fix: restore custatevec import compatibility#334
ciaranra merged 15 commits into
devfrom
codex/fix-custatevec-cuquantum-import-compat

Conversation

@qartik

@qartik qartik commented Jun 25, 2026

Copy link
Copy Markdown
Collaborator

Summary

Make the Python CuStateVec backend fail loud on unsupported cuQuantum, and support both CUDA 12 and CUDA 13.

  • CuStateVec now requires the bindings-era cuQuantum API (cuquantum.bindings, cuQuantum >= 25.03) and never uses the legacy top-level cuquantum.custatevec.
  • Missing/too-old/broken CuPy or cuQuantum no longer silently sets CuStateVec = None. import pecos stays non-fatal without CUDA; the failure surfaces as a clear, actionable error at construction (which package to install/upgrade) — mirroring the existing Rust CudaStateVec pattern.
  • Packaging: the cu13-only [cuda] extra is replaced by explicit [cuda12] / [cuda13] extras (Python cuStateVec/MPS path), declared mutually exclusive via [tool.uv] conflicts so each resolves to its latest independently.

Background

cuquantum.bindings.custatevec was introduced in cuQuantum 25.03 (top-level cuquantum.custatevec deprecated then, removed at 25.9.0). The previous code imported the new path unconditionally, so deployed environments with an older pre-pin cuQuantum saw gpu-state-vector reported as unavailable. Rather than fall back to the legacy API, this PR requires the bindings era and fails loud below it — a one-line pip upgrade for users, independent of CUDA/hardware.

Changes

  • _cuquantum_compat.py (new): the single place CuPy + cuquantum.bindings are imported. Never raises at import; records a classified reason (not-installed / pre-25.03 / present-but-broken / cupy-missing). Exposes cusv, ComputeType, cudaDataType, custatevec_available(), custatevec_unavailable_reason(), require_custatevec().
  • state.py / gate modules: import CuPy + cuQuantum via the compat; CuStateVec.__init__ calls require_custatevec() first. simulators/__init__.py drops the import-time CuPy/cuQuantum preflight (the class is always importable now).
  • Packaging: [cuda12] (cuquantum-python-cu12>=25.3.0) / [cuda13] (cuquantum-python-cu13>=25.9.0), uv conflicts, root extras+groups mirrored, uv.lock regenerated. The Rust GPU backend (pecos-rslib-cuda) is no longer bundled in these extras — install it separately.
  • Consumers: pecos cuda setup-python / pecos setup and the Justfile auto-detect the CUDA major; setup_cuda.sh, README, DEVELOPMENT.md, simulators.md, and cuda-setup.md updated (CC 7.5+ default; V100/Volta via the capped cuquantum-python-cu12>=25.3,<25.9).
  • Tests: test_cuquantum_compat.py rewritten (available / too-old / missing / cupy-missing / broken-import cases). test_statevec.py and test_backend_seed_determinism.py gate on custatevec_available() instead of CuStateVec is None.

Notes for reviewers

  • state.py is not rewritten. Its large diff is a one-time CRLF → LF normalization (the file was committed with CRLF, violating .gitattributes eol=lf) plus the small import/require_custatevec() edits. Review with whitespace hidden to see the real change.
  • V100 / Volta (CC 7.0) is supported only on CUDA 12 with cuQuantum 25.3–25.6 (cuStateVec dropped Volta at 25.09); the default [cuda12] resolves to current cuQuantum, so V100 users pin cuquantum-python-cu12>=25.3,<25.9 (documented).
  • Recommend squash-merge (the branch has iterative review/fix commits).

Validation

  • Full CI green (78/78).
  • test_cuquantum_compat.py unit tests; test_statevec.py / seed-determinism on an RTX 4090 (CUDA 13) — correct Bell state.
  • Simulated no-CUDA env: import pecos non-fatal; CuStateVec(2) and QuantumSimulator("CuStateVec") raise the actionable error.
  • uv lock --check, cargo clippy -p pecos-cli -D warnings, and full pre-commit all clean.

Developed and reviewed with Claude Code (Claude Opus 4.8) and cross-reviewed with Codex (GPT-5.5).

@ciaranra

ciaranra commented Jun 26, 2026

Copy link
Copy Markdown
Member

Correction (updated): this comment originally said the legacy cuquantum.custatevec module was removed in the 26.x line. That is wrong — it was removed at 25.9.0 (cu12 25.3/25.6 still have it; all cu13 and cu12 ≥ 25.9 do not). The corrected facts and a P0 regression found in the latest PR commit are in the consolidated re-review: #334 (comment)


Reproduced the import behavior across cuQuantum versions to pin down exactly which environments hit this. The failure is real and reproducible, but it's narrower than "deployed GPU containers" in general — it's specifically cuQuantum < 25.03.

Root cause confirmed

cuquantum.bindings.custatevec was introduced in cuQuantum Python 25.03, which simultaneously deprecated the top-level cuquantum.custatevec. The legacy module was then removed at 25.9.0 (corrected — originally said 26.x). (25.03 release notes: "The original cuquantum.custatevec and cuquantum.cutensornet modules are now deprecated... All low-level APIs ... are now migrated to cuquantum.bindings.")

Three regimes (each verified by running the import probe)

cuquantum-python cuquantum.bindings.custatevec (new) cuquantum.custatevec (legacy)
≤ 24.11.0 (pre-25.03, cu12 only) ModuleNotFoundError ✅ works
25.3.0 – 25.x ✅ works ✅ works (deprecated)
26.x ✅ works ❌ removed

The PR's failing container is the top row; a fresh modern install is the bottom row.

Reproduce the failing env (isolated, no GPU needed for the import probe)

uv run --no-project --python 3.12 --with "cuquantum-python-cu12==24.11.0" python - <<'PY'
import importlib
for m in ["cuquantum.custatevec", "cuquantum.bindings.custatevec"]:
    try:
        importlib.import_module(m); print("OK  ", m)
    except Exception as e:
        print("FAIL", m, "->", type(e).__name__, e)
PY
# OK   cuquantum.custatevec
# FAIL cuquantum.bindings.custatevec -> ModuleNotFoundError: No module named 'cuquantum.bindings.custatevec'

Other pre-25.03 versions that reproduce it: 24.8.0, 24.3.0.post1, 23.10.0. All are -cu12 — there is no -cu13 wheel before 25.9.0.

Confirmed on an RTX 4090 box at the other end of the range

With cuquantum-python-cu13==26.3.2 the situation is reversed: the new path works and the legacy path is gone:

OK    from cuquantum.bindings import custatevec
FAIL  from cuquantum import custatevec -> ModuleNotFoundError: No module named 'cuquantum.custatevec'

So pecos.simulators.CuStateVec already loads fine here without the shim, and the PR's legacy fallback is a dead path on 26.x.

Implication for the shim

The shim is fixing a genuine, reproducible bug — but only for environments stuck on cuquantum < 25.03. Worth noting: PECOS's own pin is cuquantum-python-cu13>=25.3.0, and cu13 wheels don't exist before 25.9.0, so anyone honoring the pin already has bindings and never hits this. The real audience is environments with a stale pre-pin cuquantum pre-installed in a base image. Recommend calling that out in the PR description so the scope is clear, and noting the legacy fallback is inert on 26.x (it only helps the pre-25.03 case).

Doc follow-up (separate from this PR)

docs/user-guide/cuda-setup.md:236 still has a troubleshooting check using the legacy import:

python -c "from cuquantum import custatevec; print('OK')"

On any 26.x install that prints a scary ModuleNotFoundError even when everything is fine. Should be from cuquantum.bindings import custatevec to match line 168.


Generated with Claude Code (Opus 4.8). Reproduced on an RTX 4090 / CUDA 13 workstation.

@qartik
qartik marked this pull request as ready for review June 26, 2026 18:11
@qartik
qartik requested a review from ciaranra as a code owner June 26, 2026 18:11
@qartik
qartik force-pushed the codex/fix-custatevec-cuquantum-import-compat branch from 65d2f13 to be57215 Compare June 26, 2026 18:11

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Adds a cuQuantum import-compatibility shim so PECOS’s CuStateVec implementation can work with both newer cuquantum.bindings.custatevec installs and older legacy cuquantum.custatevec installs, plus a unit test and a docs update to reflect the modern import surface.

Changes:

  • Introduce a custatevec import shim that prefers cuquantum.bindings.custatevec and falls back to legacy cuquantum.custatevec.
  • Route the cuStateVec state + gate modules through the shared shim rather than importing cuquantum.bindings.custatevec directly.
  • Add a GPU-free unit test to lock in import preference/fallback behavior; update CUDA setup docs to use the modern import.

Reviewed changes

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Show a summary per file
File Description
python/quantum-pecos/src/pecos/simulators/custatevec/_cuquantum_compat.py Adds the shared import shim for custatevec.
python/quantum-pecos/src/pecos/simulators/custatevec/state.py Switches cuStateVec state implementation to use the shim.
python/quantum-pecos/src/pecos/simulators/custatevec/gates_one_qubit.py Switches one-qubit gate implementation to use the shim.
python/quantum-pecos/src/pecos/simulators/custatevec/gates_two_qubit.py Switches two-qubit gate implementation to use the shim.
python/quantum-pecos/src/pecos/simulators/custatevec/gates_meas.py Switches measurement gate implementation to use the shim.
python/quantum-pecos/tests/pecos/unit/test_cuquantum_compat.py Adds unit tests validating preferred vs fallback import behavior without GPU runtime.
docs/user-guide/cuda-setup.md Updates troubleshooting command to use the modern cuquantum.bindings.custatevec import.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

Comment thread python/quantum-pecos/src/pecos/simulators/custatevec/_cuquantum_compat.py Outdated
Comment thread python/quantum-pecos/tests/pecos/unit/test_cuquantum_compat.py Outdated
@ciaranra

ciaranra commented Jun 26, 2026

Copy link
Copy Markdown
Member

Re-review at a981a8d1 — the narrowing reintroduces the original bug on 24.11

Thanks for iterating — the docs troubleshooting fix, narrowing the except, and the propagation test are all good moves. One blocker though: the narrowing breaks the fallback for the PR's prime target.

Blocker: the narrowed fallback no longer fires on cuQuantum 24.11

a981a8d1 changed the catch from except ImportError to except ModuleNotFoundError. But from cuquantum.bindings import custatevec does not raise ModuleNotFoundError on every pre-25.03 install. On 24.11, cuquantum.bindings exists as a package but lacks the custatevec member, so Python raises plain ImportError ("cannot import name 'custatevec' from 'cuquantum.bindings'"). Since ModuleNotFoundError is a subclass of ImportError, the original broad catch handled both shapes; narrowing to the subclass drops the 24.11 case.

Reproduced on real wheels (pure import, no GPU):

cu12 version from cuquantum.bindings import custatevec raises narrowed shim
24.03.0.post1 ModuleNotFoundError(name='cuquantum.bindings') catches → falls back ✓
24.08.0 ModuleNotFoundError(name='cuquantum.bindings') catches → falls back ✓
24.11.0 ImportError (bindings pkg present, custatevec absent) misses → propagates ✗

On 24.11 the ImportError propagates, pecos.simulators.__init__ turns it into CuStateVec = None, and you're back to the original "gpu-state-vector unavailable" symptom — for the most recent pre-25.03 release, i.e. exactly the stale-base-image case this PR targets. (Loading the actual _cuquantum_compat.py from a981a8d1 against the 24.11 wheel reproduces this.)

The new test passes but doesn't cover this

test_falls_back_to_legacy_custatevec mocks cuquantum as a bare module with cuquantum.bindings deleted, which raises ModuleNotFoundError — the 24.03/24.08 shape. It never exercises the real 24.11 shape (cuquantum.bindings present, custatevec absent → ImportError), so it's green while 24.11 is broken. A faithful mock:

cuquantum = ModuleType("cuquantum"); cuquantum.__path__ = []; cuquantum.custatevec = legacy
bindings = ModuleType("cuquantum.bindings"); bindings.__path__ = []
sys.modules["cuquantum"] = cuquantum; sys.modules["cuquantum.bindings"] = bindings

Loading the shim under that raises the same ImportError. (test_propagates_non_missing_module_errors is good — keep it.)

Suggested direction

Cleaner long-term fix: drop the legacy fallback and fail loud when cuQuantum is below the bindings-era floor — an actionable "upgrade cuquantum-python to >= …" message, surfaced on every CuStateVec construction path (not just QuantumSimulator("CuStateVec") — a direct CuStateVec(2) currently becomes None(2)). This regression is itself a sign that threading the right exception across cuQuantum layouts is fragile.

If you'd rather keep the shim, catch ImportError (the parent) and fall back only for the known absence shapes, re-raising everything else:

  • ModuleNotFoundError with name in {cuquantum.bindings, cuquantum.bindings.custatevec} (24.03/24.08), or
  • plain ImportError matching cannot import name 'custatevec' from 'cuquantum.bindings' (24.11);
  • re-raise any other ImportError so missing .so / partial installs surface their real cause.

Don't blanket-catch (ModuleNotFoundError, ImportError) and always fall back — that re-opens the masking problem on modern installs. Add a 24.11-shaped test for the new branch.

Also (not merge-blockers, but worth folding in)

  • Line endings: state.py is mixed now — dev is 148 CRLF lines, the PR adds 6 LF-only lines. Normalize.
  • The docs fix is good (from cuquantum import custateveccuquantum.bindings), but it's still a raw cuQuantum probe; a PECOS-level check tracks what users actually care about: python -c "from pecos.simulators import CuStateVec; print('OK' if CuStateVec is not None else 'NOT AVAILABLE')".
  • Volta/V100 & versions (for the docs/packaging story): legacy cuquantum.custatevec is removed at 25.9.0 (not 26.x); and cuStateVec drops Volta/V100 (CC 7.0) at 25.09 — Volta is only in cu12 25.03–25.06. So the docs' "CC 7.0+" claim and any cu12 floor need a Volta policy (e.g. pin cuquantum-python-cu12>=25.3,<25.9 for V100, or drop CC 7.0 for Python CuStateVec). Splitting [cuda] into [cuda12]/[cuda13] would make this explicit.

All exception/version facts above were reproduced on isolated wheel probes; the legacy path itself was separately confirmed to run a correct Bell state on an RTX 4090 with cu12 24.11, so the runtime works — the issue is purely the import guard.


Authored with Claude Code (Claude Opus 4.8) and cross-checked with Codex (GPT-5.5). The P0 regression and the version/Volta facts were independently reproduced by both via isolated wheel probes; the legacy path was confirmed end-to-end on an RTX 4090 / CUDA 13 workstation.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 7 out of 7 changed files in this pull request and generated 2 comments.

Comment thread python/quantum-pecos/tests/pecos/unit/test_cuquantum_compat.py Outdated
Comment thread python/quantum-pecos/tests/pecos/unit/test_cuquantum_compat.py Outdated
@ciaranra

Copy link
Copy Markdown
Member

I encouraged the bots to think about dropping support for older versions of cuquantum but keeping support for both CUDA 12 and 13. And then failing loud if older versions of cuquantum are used and having useful error messages. I think the newer cuquantum drops support of "Volta", which seems to be the only downside of not supporting the older cuquantum, which I guess are RTX 2000s series... and others that came out around 2017-2018.

@qartik

qartik commented Jun 26, 2026

Copy link
Copy Markdown
Collaborator Author

I encouraged the bots to think about dropping support for older versions of cuquantum but keeping support for both CUDA 12 and 13. And then failing loud if older versions of cuquantum are used and having useful error messages. I think the newer cuquantum drops support of "Volta", which seems to be the only downside of not supporting the older cuquantum, which I guess are RTX 2000s series... and others that came out around 2017-2018.

Since it's beyond what I understand and have access to (gpus), can I leave the rest of this PR to you to take to the finish line?

@ciaranra

Copy link
Copy Markdown
Member

I encouraged the bots to think about dropping support for older versions of cuquantum but keeping support for both CUDA 12 and 13. And then failing loud if older versions of cuquantum are used and having useful error messages. I think the newer cuquantum drops support of "Volta", which seems to be the only downside of not supporting the older cuquantum, which I guess are RTX 2000s series... and others that came out around 2017-2018.

Since it's beyond what I understand and have access to (gpus), can I leave the rest of this PR to you to take to the finish line?

Yeah, sure. Can do.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

Copilot reviewed 20 out of 21 changed files in this pull request and generated 3 comments.

Comment on lines +3 to +9
This is the single place the cuStateVec simulator imports CuPy and the cuQuantum
bindings. It never raises at import time: when CuPy or a bindings-era cuQuantum
(``cuquantum.bindings``, introduced in cuQuantum 25.03) is missing, the names are
set to ``None`` and a classified, actionable reason is recorded. ``CuStateVec``
stays importable so ``import pecos`` works without CUDA; the failure surfaces
(loudly) only when the user constructs/uses ``CuStateVec``, via
``require_custatevec()``.
Comment on lines +43 to 49
# Python cuQuantum CuStateVec backend. Import always succeeds if the package is
# present; CuPy / cuQuantum availability is checked at construction time (like the
# Rust CudaStateVec below), so users get a clear error only when they use it.
try:
import cupy
import cuquantum

from pecos.simulators.custatevec.state import (
CuStateVec,
)
from pecos.simulators.custatevec.state import CuStateVec
except ImportError:
CuStateVec = None
Comment thread scripts/setup_cuda.sh
Comment on lines 438 to 446
# Test 3: cuQuantum
print_info "Test 3: Testing cuQuantum..."
CUQUANTUM_TEST=$(python3 -c "
import sys
try:
from cuquantum import custatevec
from cuquantum.bindings import custatevec
print('cuStateVec imported successfully')
sys.exit(0)
except Exception as e:
@ciaranra
ciaranra merged commit e34b4c0 into dev Jun 27, 2026
78 checks passed
@ciaranra
ciaranra deleted the codex/fix-custatevec-cuquantum-import-compat branch June 27, 2026 18:30
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