Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

1 Commit
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

spinq-cudaq-backend

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.

Verified Versions

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.

Installation

From this directory:

python -m pip install -e .

For test dependencies:

python -m pip install -e ".[test]"

Basic Usage

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)

Parameter-Shift Gradient

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]))

Backend Mode Integration

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".

Runnable Examples

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.py

The 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.

Configuration Examples

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.py

Prefer the mpiexec installed in the CUDA-Q conda environment. Mixing a system MPI launcher with conda MPI libraries can fail before CUDA-Q starts.

Useful Settings

  • configure_shots(shots) sets sampling shots.
  • configure_measure_qubits(mqubits) measures only selected qubits.
  • configure_target(name, option=...) accepts CUDA-Q target names such as qpp-cpu, nvidia, tensornet, tensornet-mps, density-matrix-cpu, and stim.
  • configure_device("cpu" | "gpu") selects qpp-cpu or nvidia.
  • 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) and configure_chunk_size(n) control multiprocessing used while turning large count dictionaries into probabilities.

Noisy Settings

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-Q NoiseModel.
  • configure_num_trajectories(n) controls trajectory averaging for noisy observe on 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. sample and observe are Monte Carlo estimates. states is blocked by default because it would be one pure-state trajectory, not the true mixed state; set allow_trajectory_state=True only when that is intentional.
  • For get_state, the noisy result temporarily calls cudaq.set_noise() and clears it with cudaq.unset_noise().

Large-Scale GPU Experiments

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 -s

Monitor GPU usage in another shell:

watch -n 1 nvidia-smi

To 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 --mpi

The 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=2

The 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.

CUDA-Q Environment Notes

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.sh

For MPI runs you may also need:

export OMPI_MCA_opal_cuda_support=true OMPI_MCA_btl='^openib'

About

This extension package provides CUDA-Q backends for executing SpinQit Circuit and IR objects.

Resources

Stars

0 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages