Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Local Solvers

Three of the five backends run entirely on the machine calling them, using only local hardware and no account. cpu runs on the CPU; cuda and metal run custom simulated-annealing kernels on an NVIDIA or Apple Silicon GPU. The metal examples below ran for real, on an Apple Silicon Mac; no NVIDIA GPU was available while writing this chapter, so the cuda material comes from reading xqsa/inprocess/cuda.py rather than from execution.

SolverCPU: The Baseline

SolverCPU wraps dwave-samplers’ SimulatedAnnealingSampler. It needs only a CPU and no credentials, and that is exactly what makes cpu the xqsa.DEFAULT_SOLVER: every other backend in this chapter is worth comparing against it before you trust a faster or more exotic answer. It ships in the base package – pip install xqsa is enough.

from xqsa import SolverCPU
from xquad.types import XqmxModel

model = XqmxModel.binary(size=4)
model.set_linear(0, -1)
model.set_quad(0, 1, 2)

solver = SolverCPU(seed=42)
result = solver.solve(model)
print(result.energy, result.metadata)

Constructor parameters, all overridable per call via solve(**kwargs):

ParameterDefaultMeaning
num_reads100Independent annealing runs; the best is kept
num_sweeps1000Sweeps per run
num_sweeps_per_beta1Sweeps held at each temperature level before cooling; num_sweeps must divide evenly
beta_rangeNoneInverse-temperature (start, end); None lets dwave-samplers pick
seedNoneRNG seed; None means non-deterministic

SolverCPU also takes algorithm, which is "sa" and has no other value yet. A misspelled parameter raises TypeError, whether you pass it to the constructor or to solve().

GPU Backends: SolverGPU

SolverGPU bypasses the D-Wave SDK entirely. It converts the model to a dense array and runs parallel-replica simulated annealing directly on the GPU, in custom CUDA or Metal kernels. engine picks which, and it is required: SolverGPU(engine="cuda") or SolverGPU(engine="metal"), which are what the cuda and metal names build. Picking between the CPU backend and either GPU engine is a performance question, not a correctness one – see Backends; all three run the identical annealing algorithm family, just on different processors.

Install the extra that matches your hardware:

pip install xqsa[cuda]     # NVIDIA GPU, CUDA 12.x driver
pip install xqsa[metal]    # Apple Silicon Mac with a Metal device
from xqsa import SolverGPU
from xquad.types import XqmxModel

model = XqmxModel.binary(size=4)
model.set_linear(0, -1)
model.set_quad(0, 1, 2)

solver = SolverGPU(engine="metal", algorithm="sa", num_reads=200, num_sweeps=2000, seed=42)
result = solver.solve(model)
print(result.energy)
# -1

Wall-clock result.timing is not shown here: it varies by roughly 2x between runs on identical hardware, so a single pasted number would read as a performance claim it cannot support. The Metal engine also supports algorithm="gibbs" (block Gibbs sampling over a greedy graph coloring of the coupling graph). Both algorithms produce correct output on real Metal hardware: xqsa/tests/test_gpu_validation.py checks each against models with a known exact ground state, plus a spin-glass model matched within 2% of the CPU reference energy, all six of which pass.

The CUDA engine supports only algorithm="sa" – CUDA has no Gibbs kernel. Any other algorithm raises ValueError saying it is not available in-process; further algorithms arrive with the solver binaries. See xqsa/inprocess/cuda.py for the implementation the rest of this section describes.

Parameterengine="cuda" defaultengine="metal" defaultMeaning
algorithm"sa" (only option)"sa"; also "gibbs"Kernel to dispatch; constructor-only
num_reads100100Parallel replicas, one per GPU threadgroup/block
num_sweeps10001000Sweeps per replica
num_sweeps_per_beta11Sweeps held per temperature level before cooling
beta_schedule_type"linear""geometric"Shape of the cooling schedule – note the two engines default to different shapes
beta_rangeNoneNoneInverse-temperature (start, end); None auto-computes it from the model’s coefficients
seedNoneNoneBase seed for the on-device RNG

Every parameter except algorithm is overridable per call via solve(**kwargs). These are the constructor’s own defaults. xqsa.build_solver, the backend-agnostic constructor covered in Solving Overview, raises num_reads to 200 and num_sweeps to 2000 for both GPU engines instead of using these defaults.

The Metal engine computes coefficients in float32; the CUDA engine computes them in float64, end to end in its kernels. result.energy is always recomputed afterward in exact integer arithmetic on both, so either GPU run and a CPU run report the same authoritative energy regardless of which precision the search itself used. See Energy and Precision for what the float32 downcast costs the Metal engine and when it matters.

Driver Prerequisites

ExtraHardwarePrerequisiteVerify
[cuda]NVIDIA GPUCUDA 12.x drivernvidia-smi
[metal]Apple SiliconA Metal devicebuilt in on every Apple Silicon Mac

After installing, confirm the solver class constructs before building a model around it:

python -c "from xqsa import SolverGPU; SolverGPU(engine='cuda'); print('ok')"
python -c "from xqsa import SolverGPU; SolverGPU(engine='metal'); print('ok')"

Both raise ImportError with a pip install xqsa[...] hint if the extra is missing, and RuntimeError if the extra is installed but no matching GPU is detected – import xqsa itself never fails on a missing extra, only constructing the solver that needs it does.