Repository navigation
Conversation
The base cudaq-qec-cuXX wheel now depends only on numpy. `import cudaq_qec` works without CUDA-Q and gives the decoders (including nv-qldpc-decoder), decoding configuration, DEM construction from matrices, extended DEMs and the decoding task graph. Codes, circuit experiments, device calls, GPU DEM sampling and the surface-code layout need CUDA-Q, which is installed with `pip install cudaq-qec-cuXX[cudaq]`; touching one of those names without it raises an ImportError that names that command. `cudaq_qec.has_cudaq()` reports which mode is active, and CUDAQ_QEC_DISABLE_CUDAQ=1 forces the CUDA-Q-free mode for testing. Python bindings: a new CUDA-Q-free extension, `_qec_core`, binds the decoder API, decoding config, DEM construction, extended DEMs and the task graph and links only libcudaq-qec-decoders and libcudaq-qec-realtime-decoding. The existing extension keeps its name, binds the CUDA-Q-dependent parts into the core module's `qecrt` submodule, and adds the code-object overloads of `css_matrices_from_code` / `dem_from_css_matrices` (now behind CUDAQX_QEC_BIND_CODE_BRIDGE) as nanobind overloads on the core module. The redundant wheel-root `_qec_decoders_standalone` copy is dropped; the decoders-only build's module of that name is unchanged. cudart is linked statically into libcudaq-qec-decoders, libcudaq-qec-realtime-decoding and _qec_core, with --exclude-libs so the symbols never interpose on a shared libcudart loaded by CUDA-Q or TensorRT. libcudaq-qec.so and the TensorRT plugin stay on the shared runtime that their own dependencies bring. Wheel tooling: build_wheels.sh also excludes TensorRT 11 and libibverbs from auditwheel grafting, and check_grafted_libs.py now fails on any `.libs/` content. validate_wheels.sh installs the `cudaq` extra. Docs and the README describe the extra; test_no_cudaq.py exercises the package in a subprocess with CUDA-Q disabled. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Ben Howe <bhowe@nvidia.com>
Docs: the shared `qecrt` submodule is now named after the CUDA-Q-free extension, so nanobind spells the `operation` type as `cudaq_qec._qec_core.qecrt.operation` in generated signatures. Teach conf.py.in the new prefix (doc_replacements, the __module__ fix-up and nitpick_ignore_regex) and extend build_docs.sh's leaked-name check to it. Tests: add tests/conftest.py with a `requires_cudaq` marker. Without CUDA-Q (or with CUDAQ_QEC_DISABLE_CUDAQ=1) the eight modules that need CUDA-Q throughout are not collected and marked tests are skipped; everything else must pass with no `cudaq` in the process. Mark the eleven individual tests in otherwise CUDA-Q-free modules that build fixtures from a code object. lib_qec.yaml and test_wheels.sh run the suite a second time with CUDAQ_QEC_DISABLE_CUDAQ=1. dem_sampling(): raise the same install-hint ImportError as the package attributes when called without CUDA-Q, instead of a bare undefined-symbol error from loading the CUDA-Q extension without cudaq. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Ben Howe <bhowe@nvidia.com>
…moved module The wheel no longer ships the root-level `_qec_decoders_standalone` module, so the smoke test that imported it failed. Replace it with the equivalent check on the package itself: with CUDAQ_QEC_DISABLE_CUDAQ=1, `import cudaq_qec` must decode with no `cudaq` in the process and `get_code` must raise the install hint. cudart is static in the decoder stack now, so the CUDA runtime wheel install and LD_LIBRARY_PATH juggling are gone too. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Signed-off-by: Ben Howe <bhowe@nvidia.com>
bmhowe23
marked this pull request as draft
November 2, 2026 15:44
This branch has not been deployed
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.
Make CUDA-Q an optional
[cudaq]extra of the cudaq-qec wheelSummary
pip install cudaq-qecno longer installs CUDA-Q. The base wheel depends onlyon numpy and provides the decoders (including
nv-qldpc-decoder), the decodingconfiguration and task-graph tooling, DEM construction from parity-check
matrices and the extended-DEM helpers. Everything that needs CUDA-Q is
installed with the new extra:
Without the extra, touching a CUDA-Q-only name raises an
ImportErrorthatsays exactly what to install:
cudaq_qec.has_cudaq()reports which mode is active. The metapackage picks theextra up automatically from
pyproject.toml.cuXX, sopip install cudaq-qec[cudaq]works unchanged.Why
The nv-qldpc decoder and the rest of the decoder stack never needed CUDA-Q.
Today a user who only wants to decode pays for cuda-quantum, cuquantum-python
and their transitive dependencies (cupy, custatevec, cutensornet, cudensitymat,
nvmath-python; well over a gigabyte on disk) for nothing. Installing the base
wheel in a bare venv now pulls in exactly one dependency, numpy.
What is CUDA-Q-free and what is not
[cudaq]get_decoder,Decoder, all result types, every built-in and plugin decoderget_code,Code,get_available_codes,operation,TwoQubit*noise opsDetectorErrorModel,dem_from_stim_text, allpcm_*helperssample_memory_circuit,dem_from_memory_circuitand their x/z variantsdecoder_config,multi_decoder_config,configure_decoders*, schemassample_code_capacity,generate_random_bit_flipsexperimental,relay_solutionsdecoder_context_from_memory_circuit,DecoderContext,d_sparsecss_matrices_from_code/dem_from_css_matricesmatrix overloads, extended DEMsplugins.decoders,plugins.nv_qldpcstabilizer_grid,role_to_str,sc_orientation,patch,dem_sampling, device calls, playback emulator,plugins.codesHow
Python bindings. A second nanobind extension,
_qec_core, binds theCUDA-Q-free pieces and links only
libcudaq-qec-decodersandlibcudaq-qec-realtime-decoding. The existing extension keeps its name andbinds the CUDA-Q-dependent pieces into the core module's
qecrtsubmodule, sothere is still one
cudaq_qec.qecrtnamespace and the device-kernel registrynames are unchanged. The only binding source that had to be split is
py_dem_construction.cpp: its two code-object overloads moved behindCUDAQX_QEC_BIND_CODE_BRIDGEand are added to the core module as nanobindoverloads by the CUDA-Q extension. Both modules share
NB_DOMAIN cudaq, sotypes bound in one are usable from the other.
Package init.
cudaq_qec/__init__.pyimports the CUDA-Q half only if thecudaqpackage is importable; otherwise a module-level__getattr__raises theImportErrorabove for the CUDA-Q-only names.CUDAQ_QEC_DISABLE_CUDAQ=1forces the CUDA-Q-free mode on a machine that has CUDA-Q, which is how the new
test runs.
Static cudart.
libcudaq-qec-decoders,libcudaq-qec-realtime-decodingand
_qec_corelinkcudart_staticwith--exclude-libs, so the base wheelneeds no CUDA runtime wheel and the static symbols never interpose on a shared
libcudartloaded by CUDA-Q or TensorRT.libcudaq-qec.soand the TensorRTplugin stay on the shared runtime that their own dependencies already bring.
Device memory, streams and events are driver objects and remain interoperable
across runtime copies. The one visible change is that
cudaSetDevicefrom usercode no longer reaches the decoders; the existing
cuda_device_idconstructionparameter is the way to pick a GPU.
Wheel tooling. The redundant wheel-root
_qec_decoders_standalonemoduleis dropped (the decoders-only build's module of that name is unchanged).
build_wheels.shalso excludes TensorRT 11 andlibibverbsfrom auditwheelgrafting, and
check_grafted_libs.pynow fails on any.libs/content, sinceevery runtime dependency comes from a sibling wheel or is linked statically.
validate_wheels.shinstalls thecudaqextra, andtest_wheels.shreplacesthe smoke test of the removed module with the same check on
cudaq_qecitself.
Two-mode test suite.
tests/conftest.pyadds arequires_cudaqmarker.When
cudaq_qec.has_cudaq()is false, the eight test modules that needCUDA-Q throughout (they import cudaq at module level or build every fixture
from a code object) are not collected and marked tests are skipped; the
eleven individual tests in otherwise CUDA-Q-free modules that build fixtures
from a code object carry the marker. The GPU-test job in
lib_qec.yamlandtest_wheels.shrun the suite twice, once normally and once withCUDAQ_QEC_DISABLE_CUDAQ=1, so the pymatching, chromobius, LUT, nv-qldpc,decoding-config, task-graph and DEM-construction tests are exercised with no
cudaqin the process on every PR.Docs. The shared
qecrtsubmodule is now named after the CUDA-Q-freeextension, so nanobind spells type names as
cudaq_qec._qec_core.qecrt.*ingenerated signatures.
conf.py.inlearned the new prefix alongside the oldone, and
build_docs.sh's leaked-name check covers it.Verification
Local, against the in-tree build and a locally built cu13 wheel:
CUDAQ_QEC_DISABLE_CUDAQ=1: 839 passed, 17 skipped, 10 xfailed, andcudaqnever enterssys.modules.check_grafted_libs.pypasses, no.libs/directory.get_coderaises the hint.readelf: the decoders library, realtime-decoding library and_qec_corehave nolibcudartor CUDA-QDT_NEEDEDentries and export zerocuda*symbols.CI on this PR is green across all lanes (cu12 and cu13, amd64 and arm64,
Python 3.11 to 3.13, CPU and A100 wheel tests, docs). Cost of the added
CUDA-Q-disabled pass, from this PR's run:
The TensorRT decoder tests dominate the disabled pass wherever TensorRT is
installed, since that plugin is CUDA-Q-free and runs in both modes.
Breaking change
Users who relied on
pip install cudaq-qecto install CUDA-Q must add the[cudaq]extra. Follow-ups, not in this PR: a release note and an update tothe PyPI-facing README for the dependency change, and documentation that
cuda_device_idis the GPU selector for decoders.🤖 Generated with Claude Code