CUDA-Q Backend

The CUDA-Q backend leverages NVIDIA GPUs via the cuda-quantum framework. When no compatible GPU is detected it transparently falls back to a CPU target, so the same code prototyped on a laptop will also run on an accelerated machine.

Installation

CUDA-Q is not shipped with QiliSDK, install the build that matches your CUDA toolkit yourself and the backend becomes available:

pip install "cuda-quantum-cu12>=0.14.0"   # or cuda-quantum-cu13

Note

CudaqBackend requires CUDA-Q 0.14.0 or newer. CUDA-Q publishes no wheels for Python 3.14, so the backend is unavailable there until it does.

Quick start

import numpy as np
from qilisdk.digital import Circuit, H, RX, CNOT
from qilisdk.backends import CudaqBackend, CudaqSamplingMethod
from qilisdk.functionals import DigitalPropagation
from qilisdk.readout import Readout

circuit = Circuit(2)
circuit.add(RX(0, theta=np.pi / 4))
circuit.add(H(0))
circuit.add(CNOT(0, 1))

backend = CudaqBackend(sampling_method=CudaqSamplingMethod.STATE_VECTOR)
result = backend.execute(DigitalPropagation(circuit), Readout().with_sampling(nshots=500))
print(result.get_samples())

Functional support

Functional

Support

Notes

DigitalPropagation

Native CUDA-Q kernel. Sampling method selected via CudaqSamplingMethod. Intermediate measurements raise NotImplementedError.

AnalogEvolution

Driven by cudaq.evolve on the dynamics target (always GPU-accelerated when available, independent of the digital sampling method).

QuantumReservoir

🟡

The CudaqBackend does not natively implement Backend._execute_quantum_reservoir. Circuit steps inside the reservoir layer fall back to dense QTensor unitary multiplication on CPU; Schedule steps still use CUDA-Q’s evolve. Any attached noise model is ignored.

VariationalProgram

Reuses the digital/analog handlers above for each optimization step.

Configuration

The CUDA backend exposes a single configuration parameter — CudaqSamplingMethod — that selects the underlying CUDA-Q target used for digital circuits. If no method is specified, STATE_VECTOR is used. Analog evolution always runs on the dynamics target and ignores this setting.

Method

CUDA-Q target

Supports Sampling

Supports Expectation Values

Supports State Tomography

STATE_VECTOR

nvidia when a GPU is available, otherwise qpp-cpu. Precision matches Precision.

STATE_VECTOR_MGPU

nvidia-mgpu when multiple GPUs are available, otherwise falls back to nvidia.

TENSOR_NETWORK

tensornet. Good for shallow, wide circuits.

MATRIX_PRODUCT_STATE

tensornet-mps. Good for low-entanglement, long circuits.

CPU

cpu. Force running on CPU. Mostly useful for benchmarking.

Set the method at construction time:

from qilisdk.backends import CudaqBackend, CudaqSamplingMethod

backend = CudaqBackend(sampling_method=CudaqSamplingMethod.MATRIX_PRODUCT_STATE)

Some CUDA simulation methods support parameters being set via environment variables, notably the MATRIX_PRODUCT_STATE and TENSOR_NETWORK methods. See the CUDA-Q documentation for details.

To set the precision of the simulation, use the Settings object:

from qilisdk.settings import get_settings, Precision

settings = get_settings()
settings.complex_precision = Precision.COMPLEX_64  # or COMPLEX_32

Noise model support

A NoiseModel can be passed to CudaqBackend(noise_model=…):

  • For DigitalPropagation, qilisdk noise channels are translated into a cudaq.NoiseModel (Kraus channels for static / time-derived noise, parameter perturbations applied to the circuit). With noise enabled, only a single SamplingReadout is supported.

  • For AnalogEvolution, Lindblad-compatible noise channels become CUDA-Q jump operators and Hamiltonian deltas fed to cudaq.evolve.

  • For QuantumReservoir, the fallback implementation drops the noise model (a warning is logged).

Example: a depolarising channel applied to every gate of a digital circuit:

from qilisdk.backends import CudaqBackend, CudaqSamplingMethod
from qilisdk.noise import NoiseModel, Depolarizing

nm = NoiseModel()
nm.add(Depolarizing(probability=1e-3))

backend = CudaqBackend(
    sampling_method=CudaqSamplingMethod.STATE_VECTOR,
    noise_model=nm,
)