prism-q 0.28.0

PRISM-Q: Performance Rust Interoperable Simulator for Quantum
# GPU Backend

```admonish info
The GPU backend is optional and gated behind the `gpu` feature. It requires the CUDA
toolkit (12.x or newer) and a CUDA-capable device.
```

```bash
cargo build --release --features "parallel gpu"
cargo test --features "parallel gpu" --test golden_gpu
```

CUDA acceleration covers statevector execution, stabilizer execution, and compiled
BTS sampling. Six entry points are available:

- **`BackendKind::AutoGpu { context }`** (`simulate(circuit).gpu_auto(ctx)`).
  Automatic backend selection with the device opted in. The shape-based decision
  tree runs unchanged; a selected statevector or stabilizer workload that clears the
  family's qubit crossover and fits in VRAM runs on the device. Everything else,
  including a device allocation that fails at `init`, takes the identical CPU path
  (the soft VRAM fallback).
- **`BackendKind::StatevectorGpu { context }`**. Public dispatch path for statevector
  GPU execution. It routes through `simulate(circuit).backend(kind).seed(seed).run()`,
  keeps fusion and subsystem
  decomposition, and uses `crate::gpu::min_qubits()` (default 14,
  `PRISM_GPU_MIN_QUBITS` override) to keep small sub-circuits on CPU.
- **`BackendKind::StabilizerGpu { context }`**. Public dispatch path for stabilizer
  GPU execution. Gate application uses a device tableau and one word-grouped batched
  Clifford kernel (`stab_apply_word_grouped`). Measurement and reset keep pivot
  search, row cascade,
  phase fixup, and deterministic outcomes on the device. The default crossover stays
  conservative (`STABILIZER_MIN_QUBITS_DEFAULT = 100_000`,
  `PRISM_STABILIZER_GPU_MIN_QUBITS` override) until benchmarks justify lowering it.
  Direct backend benchmarks should use `StabilizerBackend::with_gpu(ctx)` to exclude
  diagnostic readbacks from `probabilities()`, `export_tableau()`, and
  `export_statevector()`. Golden tests cover every kernel path, including 500q GHZ
  measure-all.
- **`StatevectorBackend::new(seed).with_gpu(ctx)`**. Direct statevector GPU opt-in.
  Every instruction routes to CUDA after the context is attached. No crossover or
  subsystem decomposition applies.
- **`StabilizerBackend::new(seed).with_gpu(ctx)`**. Direct stabilizer GPU opt-in for
  kernel benchmarks and targeted correctness tests.
- **`run_shots_compiled_with_gpu`** (or `CompiledSampler::with_gpu(ctx)`). GPU BTS
  sampling for flat sparse parity. The path launches one kernel per `65_536`-shot
  chunk, uses random bits generated on the host, and preserves the CPU
  `sample_bts_meas_major` layout. The sampler caches sparse parity CSR arrays, packed
  reference bits, and reusable scratch on the device. It is active only when
  `num_shots >= BTS_MIN_SHOTS_DEFAULT` (`131_072` by default,
  `PRISM_GPU_BTS_MIN_SHOTS` override). `sample_bulk_packed_device` returns a
  `DevicePackedShots` handle. Marginals reduce to one counter per measurement row on
  the device. Exact counts use a bounded device hash reduction for up to 8 packed
  measurement words when the compact result is cheaper to transfer than the full shot
  matrix. Otherwise the API uses a host copy for correctness.

When a GPU context is attached, `Backend::init` allocates state on the device instead
of a host `Vec<Complex64>` and every instruction routes to a CUDA kernel.

## Module layout (`src/gpu/`)

| File | Role |
| ---- | ---- |
| `mod.rs` | `GpuContext`, `GpuState` public entry points |
| `device.rs` | `GpuDevice`: cudarc wrapper, compiles PTX at device construction |
| `memory.rs` | `GpuBuffer`: device `Complex64` storage |
| `kernels/mod.rs` | `KERNEL_NAMES`, `LauncherScratch`, composed `kernel_source()` concatenating dense + stabilizer + BTS |
| `kernels/dense.rs` | PTX source and Rust launcher for every `Gate` variant |
| `kernels/stabilizer.rs` | PTX source and launchers for tableau init, 11 Clifford gates, `rowmul_words` |
| `kernels/bts.rs` | PTX source and launchers for compiled BTS shot sampling |

## Kernel coverage

Every variant in the `Gate` enum has a dedicated kernel. Batched
variants (`BatchPhase`, `BatchRzz`, `DiagonalBatch`, `MultiFused { all_diagonal: true }`)
use LUT kernels that consume the same host table builders as the CPU path.
Non-diagonal `MultiFused` uses a shared memory tiled kernel (`apply_multi_fused_tiled`,
`TILE_Q = 10`, `TILE_SIZE = 1024`). Sub-gates whose target bit is inside the tile apply in
shared memory. Sub-gates whose target bit is outside the tile fall back to per gate
launches. `Multi2q` still launches once per sub-gate; rare in practice.

**PTX template substitution:** the CUDA C source is held as a template string
(`KERNEL_SOURCE_TEMPLATE`) with placeholders such as `{{BP_TABLE_SIZE}}` and
`{{TILE_Q}}`. The `kernel_source()` function substitutes them at device construction from
the Rust constants in `src/backend/statevector/kernels.rs`, keeping CPU and GPU in sync.

## Correctness

`tests/golden_gpu.rs` drives 20 cross-checks comparing GPU amplitudes
against the CPU statevector within 1e-10. Covers every gate variant, fusion paths, and
the `BackendKind::StatevectorGpu` public dispatch path at the crossover boundary.

## Current limits

- Device placement is silent. Circuits below the crossover run on the host, and the
  `AutoGpu` soft VRAM fallback degrades to host execution without a report; nothing
  user-visible says whether a run executed on the device.
- Stabilizer `probabilities()`, `export_tableau()`, and `export_statevector()` read
  back to the CPU.
- A `Custom` Kraus noise event reads the full state back to the host, and every
  trajectory shot rebuilds the backend, reallocating the device buffer.
- Kernel design and crossover analysis live in the module docstrings on
  `src/gpu/kernels/dense.rs`.