This extension package provides CUDA-Q backends for executing SpinQit Circuit
and IR objects. CudaQSimulatorBackend is ideal-only and supports CUDA-Q CPU,
NVIDIA GPU, TensorNet, async multi-QPU, and MPI/multi-GPU workflows through
CudaQSimulatorConfig. CudaQNoisyBackend owns noisy ansatz simulation through
CudaQNoisyConfig.
The older CudaQBackend and CudaQConfig names are kept as aliases, but new
code should use the simulator names.
This package is currently developed and tested with the following versions:
| Component | Verified version |
|---|---|
| SpinQit | 0.2.4 |
| NVIDIA CUDA-Q | 0.14.0 |
These are verified compatibility versions, not an assertion that every other
version is incompatible. When reporting a problem, include the output of
python -m pip show spinqit cudaq spinq-cudaq-backend.
From this directory:
python -m pip install -e .For test dependencies:
python -m pip install -e ".[test]"from spinqit import Circuit, H, CX
from cudaq_backend import CudaQSimulatorBackend, CudaQSimulatorConfig
circuit = Circuit()
q = circuit.allocateQubits(2)
circuit << (H, q[0])
circuit << (CX, (q[0], q[1]))
config = CudaQSimulatorConfig(shots=1024)
backend = CudaQSimulatorBackend()
result = backend.run(circuit, config)
print(result.counts)
print(result.probabilities)
print(result.states)Gradient inputs must use SpinQit's trainability-aware Parameter type. Plain
NumPy arrays remain valid for forward-only execution and are intentionally
treated as non-trainable.
import numpy as np
from spinqit import Circuit, Ry
from spinqit.algorithm.loss import MeasureOp
from spinqit.grad import qgrad
from spinqit.interface.qlayer import QLayer
from spinqit.model.parameter import Parameter
from cudaq_backend import CudaQSimulatorConfig
circuit = Circuit()
q = circuit.allocateQubits(1)
angle = circuit.add_params(shape=(1,))
circuit << (Ry, q[0], angle[0])
qlayer = QLayer(
circuit,
measure=MeasureOp("expval", mqubits=[0], hamiltonian=[("Z", 1.0)]),
backend_mode="cudaq",
interface="spinq",
grad_method="param_shift",
config=CudaQSimulatorConfig(target="qpp-cpu", shots=None),
)
theta = Parameter([0.2], trainable=True)
gradient = qgrad(qlayer)(theta)
print(np.asarray(gradient[0]))Importing cudaq_backend registers the backend with SpinQit at runtime without
editing the main SpinQit package:
import cudaq_backend
from spinqit import Circuit, H
from spinqit.interface.qlayer import QLayer
from spinqit.algorithm.loss import probs
circuit = Circuit()
q = circuit.allocateQubits(1)
circuit << (H, q[0])
qlayer = QLayer(
circuit,
measure=probs(),
backend_mode="cudaq",
target="qpp-cpu",
)
print(qlayer())backend_mode="cudaq_simulator" is also accepted. For noisy simulation, use
backend_mode="cudaq_noisy".
The example/ directory contains complete programs for the most common paths:
python example/basic_sampling.py
python example/parameter_shift_gradient.py
python example/noisy_density_matrix.py
python example/gpu_parameter_shift.pyThe GPU example uses GPU 0 unless SPINQ_CUDAQ_GPU is set, and prints a skip
message on systems where CUDA-Q cannot see an NVIDIA GPU.
CPU state-vector simulation:
config = CudaQSimulatorConfig(target="qpp-cpu")Single-GPU NVIDIA simulation:
config = CudaQSimulatorConfig(target="nvidia", precision="fp64")Multi-GPU NVIDIA state-vector simulation:
config = CudaQSimulatorConfig(target="nvidia", precision="fp32")
config.configure_gpu_count(2, "mgpu")This configures CUDA-Q as cudaq.set_target("nvidia", option="mgpu,fp32").
It does not create MPI ranks by itself. For distributed multi-GPU state-vector
runs, launch the Python program with an external MPI launcher that belongs to
the same CUDA-Q environment.
TensorNet:
config = CudaQSimulatorConfig(target="tensornet", precision="fp64")Density-matrix noisy simulation:
import cudaq
from spinqit import Circuit, X
from cudaq_backend import CudaQNoisyBackend, CudaQNoisyConfig
circuit = Circuit()
q = circuit.allocateQubits(1)
circuit << (X, q[0])
noise = cudaq.NoiseModel()
noise.add_all_qubit_channel("x", cudaq.DepolarizationChannel(0.1))
config = CudaQNoisyConfig(target="density-matrix-cpu", noise_model=noise)
result = CudaQNoisyBackend().run(circuit, config)
print(result.counts)GPU noisy trajectory simulation:
import cudaq
from spinqit import Circuit, H
from spinqit.algorithm.loss import MeasureOp
from cudaq_backend import CudaQNoisyBackend, CudaQNoisyConfig
circuit = Circuit()
q = circuit.allocateQubits(1)
circuit << (H, q[0])
noise = cudaq.NoiseModel()
noise.add_all_qubit_channel("h", cudaq.DepolarizationChannel(0.001))
config = CudaQNoisyConfig(
target="nvidia",
precision="fp32",
shots=4096,
noise_model=noise,
num_trajectories=100,
)
value = CudaQNoisyBackend().run(
circuit,
config,
measure_op=MeasureOp("expval", mqubits=[0], hamiltonian=[("Z", 1.0)]),
)
print(value)Async sampling across multiple QPUs:
config = CudaQSimulatorConfig(target="nvidia", option="mqpu", shots=4096)
config.configure_async_execution(True, qpu_ids=[0, 1])MPI observe execution:
config = CudaQSimulatorConfig(target="nvidia", option="mgpu,fp64")
config.configure_mpi(True, auto_finalize=True)
config.configure_parallel_execution("mpi")Example two-GPU launch:
OMPI_MCA_opal_cuda_support=true OMPI_MCA_btl='^openib' \
mpiexec -np 2 python your_script.pyPrefer the mpiexec installed in the CUDA-Q conda environment. Mixing a system
MPI launcher with conda MPI libraries can fail before CUDA-Q starts.
configure_shots(shots)sets sampling shots.configure_measure_qubits(mqubits)measures only selected qubits.configure_target(name, option=...)accepts CUDA-Q target names such asqpp-cpu,nvidia,tensornet,tensornet-mps,density-matrix-cpu, andstim.configure_device("cpu" | "gpu")selectsqpp-cpuornvidia.configure_precision("fp32" | "fp64")appends precision to target options.configure_gpu_count(n, "mgpu" | "mqpu")sets CUDA-Q multi-GPU options.configure_result_processes(n)andconfigure_chunk_size(n)control multiprocessing used while turning large count dictionaries into probabilities.
Use CudaQNoisyBackend and CudaQNoisyConfig for noise. The ideal simulator
raises an error if it receives a noisy config.
configure_noise_model(noise_model)stores a CUDA-QNoiseModel.configure_num_trajectories(n)controls trajectory averaging for noisyobserveon trajectory targets.target="density-matrix-cpu"performs density-matrix evolution and can represent the exact mixed state up to numerical precision.target="nvidia"uses CUDA-Q trajectory simulation.sampleandobserveare Monte Carlo estimates.statesis blocked by default because it would be one pure-state trajectory, not the true mixed state; setallow_trajectory_state=Trueonly when that is intentional.- For
get_state, the noisy result temporarily callscudaq.set_noise()and clears it withcudaq.unset_noise().
The file test/test_large_scale.py contains 25-28 qubit experiments for
monitoring GPU usage. GPU-specific tests are skipped automatically on machines
without the required NVIDIA GPU count or CUDA-Q target plugin.
Run the NVIDIA fp32, NVIDIA multi-GPU, TensorNet, CPU, and statevector experiments:
pytest spinqkit/cudaqbackend/test/test_large_scale.py -q -sMonitor GPU usage in another shell:
watch -n 1 nvidia-smiTo verify that mgpu is really distributed across two GPUs instead of running
on only one GPU, use the diagnostic probe:
OMPI_MCA_opal_cuda_support=true OMPI_MCA_btl='^openib' \
mpiexec -np 2 python spinqkit/cudaqbackend/test/probe_mgpu_usage.py \
--target nvidia --option mgpu,fp32 --qubits 28 --layers 8 --mode state --mpiThe SUMMARY line reports each GPU's baseline, peak, and delta_mib. A true
two-rank distributed run should show positive delta_mib on both GPUs. Running
the same probe without mpiexec -np 2 may only allocate memory on one GPU,
depending on the CUDA-Q environment.
Useful knobs:
SPINQ_CUDAQ_LARGE_QUBITS=25,26,27,28
SPINQ_CUDAQ_LARGE_MGPU_QUBITS=28
SPINQ_CUDAQ_LARGE_TENSORNET_QUBITS=25
SPINQ_CUDAQ_LARGE_SHOTS=128
SPINQ_CUDAQ_LARGE_LAYERS=2The large tests skip only for missing prerequisites such as absent cudaq,
missing CUDA-Q target plugins, or insufficient NVIDIA GPUs. After a target is
confirmed available, execution errors are treated as real test failures.
One possible CUDA-Q environment:
cuda_version=12.4.0
conda create -y -n cudaq-env python=3.10 pip
conda install -y -n cudaq-env -c "nvidia/label/cuda-${cuda_version}" cuda
conda install -y -n cudaq-env -c conda-forge mpi4py openmpi">=5.0.3" cxx-compiler
conda env config vars set -n cudaq-env LD_LIBRARY_PATH="$CONDA_PREFIX/envs/cudaq-env/lib:$LD_LIBRARY_PATH"
conda env config vars set -n cudaq-env MPI_PATH=$CONDA_PREFIX/envs/cudaq-env
conda activate cudaq-env
pip install cudaq
source $CONDA_PREFIX/lib/python3.10/site-packages/distributed_interfaces/activate_custom_mpi.shFor MPI runs you may also need:
export OMPI_MCA_opal_cuda_support=true OMPI_MCA_btl='^openib'