GPU Acceleration Guide
Overview
The GPU backend provides hardware-accelerated quantum circuit simulation using CuPy for NVIDIA GPUs. It automatically falls back to CPU (NumPy) when GPU is unavailable, ensuring compatibility across different systems.
Key Features
Automatic GPU Detection: Uses GPU when available, CPU otherwise
Transparent API: Same interface for both backends
Performance Benchmarking: Built-in tools to measure speedup
Memory Management: Efficient handling of large quantum states
Device Selection: Support for multi-GPU systems
Quick Start
Basic Usage
from quantum_debugger.backends import GPUBackend
# Create GPU backend (auto-detect)
backend = GPUBackend()
# Check what's being used
info = backend.get_info()
print(f"Backend: {info['backend']}") # 'GPU' or 'CPU'
print(f"Device: {info['device']}")
# Allocate quantum state
state = backend.allocate_state(n_qubits=10)
# Operations automatically use GPU/CPU
state_new = backend.apply_gate(state, gate_matrix, [0, 1])
Installation
Requirements
For GPU Acceleration:
NVIDIA GPU with CUDA support
CUDA Toolkit 11.x or 12.x
CuPy library
For CPU Mode:
NumPy (always available)
Installing CuPy
# For CUDA 12.x
pip install cupy-cuda12x
# For CUDA 11.x
pip install cupy-cuda11x
# Check installation
python -c "import cupy; print(cupy.__version__)"
Verifying GPU
from quantum_debugger.backends import GPUBackend
backend = GPUBackend()
print(backend) # Shows GPU or CPU mode
if backend.use_gpu:
print(f"GPU: {backend.device_name}")
print(f"Memory: {backend.device_memory:.1f} GB")
else:
print("Running in CPU mode")
API Reference
GPUBackend
class GPUBackend(device=None)
Parameters:
device(str, optional): GPU device (‘cuda:0’, ‘cuda:1’, etc.)
Methods:
# Allocate quantum state
state = backend.allocate_state(n_qubits)
# Move arrays between CPU/GPU
gpu_array = backend.to_gpu(numpy_array)
cpu_array = backend.to_cpu(gpu_array)
# Matrix operations
result = backend.kron(a, b) # Kronecker product
result = backend.dot(a, b) # Matrix multiplication
result = backend.matmul(a, b) # Matrix multiplication (alt)
# Apply quantum gates
new_state = backend.apply_gate(state, gate_matrix, qubits)
# Synchronize GPU operations
backend.synchronize()
# Get backend information
info = backend.get_info()
Properties:
use_gpu(bool): Whether GPU is being usedxp: NumPy or CuPy moduledevice_name(str): Device descriptiondevice_memory(float): GPU memory in GB (if available)
Usage Examples
Example 1: Basic Circuit Simulation
import numpy as np
from quantum_debugger.backends import GPUBackend
# Initialize backend
backend = GPUBackend()
# Create quantum state (|00...0⟩)
state = backend.allocate_state(n_qubits=12)
# Define Hadamard gate
H = np.array([[1, 1], [1, -1]]) / np.sqrt(2)
H_gpu = backend.to_gpu(H)
# Apply to first qubit
state = backend._apply_single_qubit_gate(state, H_gpu, qubit=0)
# Get result on CPU
state_cpu = backend.to_cpu(state)
print(f"State norm: {np.linalg.norm(state_cpu):.6f}")
Example 2: Multi-GPU Selection
# Use specific GPU
backend_gpu0 = GPUBackend(device='cuda:0')
backend_gpu1 = GPUBackend(device='cuda:1')
# Distribute workload
state_0 = backend_gpu0.allocate_state(n_qubits=14)
state_1 = backend_gpu1.allocate_state(n_qubits=14)
Example 3: Performance Benchmarking
from quantum_debugger.backends import benchmark_backends
# Benchmark CPU vs GPU
results = benchmark_backends(n_qubits_list=[10, 12, 14, 16])
for n_qubits, metrics in results.items():
print(f"\n{n_qubits} qubits:")
print(f" CPU time: {metrics['cpu_time']:.4f}s")
if metrics['gpu_time']:
print(f" GPU time: {metrics['gpu_time']:.4f}s")
print(f" Speedup: {metrics['speedup']:.2f}x")
Optimal Backend Selection
from quantum_debugger.backends import get_optimal_backend
# Auto-select based on problem size
small_backend = get_optimal_backend(n_qubits=5, prefer_gpu=False)
# Returns CPU for small circuits (overhead not worth it)
large_backend = get_optimal_backend(n_qubits=15, prefer_gpu=True)
# Returns GPU for large circuits (significant speedup)
Integration with Quantum Circuits
With QuantumCircuit
from quantum_debugger import QuantumCircuit
from quantum_debugger.backends import GPUBackend
# Create backend
backend = GPUBackend()
# Create circuit (future integration point)
# qc = QuantumCircuit(15, backend=backend)
# Currently backend is used via state operations
With QNN
from quantum_debugger.qml.qnn import QuantumNeuralNetwork
from quantum_debugger.backends import GPUBackend
# Train QNN with GPU acceleration
backend = GPUBackend()
# Use backend in forward pass computations
# (Future: direct integration)
Performance Guidelines
When to Use GPU
Benefits (2-5x speedup):
Circuit size: > 12 qubits
State vector operations
Many gate applications
Batch processing
Overhead Not Worth It:
Small circuits: < 10 qubits
Single-shot measurements
Simple circuits
Memory Considerations
# Memory usage for n qubits
# State vector: 2^n × 16 bytes (complex128)
# Examples:
# 10 qubits: 16 KB
# 15 qubits: 512 KB
# 20 qubits: 16 MB
# 25 qubits: 512 MB
# 30 qubits: 16 GB
# Check available GPU memory
backend = GPUBackend()
if backend.use_gpu:
print(f"GPU memory: {backend.device_memory:.1f} GB")
# Calculate max qubits
max_qubits = int(np.log2(backend.device_memory * 1024**3 / 16))
print(f"Max qubits: ~{max_qubits}")
Troubleshooting
GPU Not Detected
# Check CUDA installation
nvidia-smi
# Check CuPy
python -c "import cupy; print(cupy.cuda.is_available())"
# Common fixes:
# 1. Install/reinstall CuPy for your CUDA version
# 2. Update NVIDIA drivers
# 3. Set CUDA_PATH environment variable
CUDA DLL Errors
DLLLoadError: Could not find 'nvrtc64_120_0.dll'
Solution:
The backend automatically falls back to CPU
To fix: Install/repair CUDA Toolkit
Or: Use CPU mode (works fine)
Out of Memory
# Reduce problem size
state = backend.allocate_state(n_qubits=15) # Instead of 20
# Or use CPU for large problems
import numpy as np
backend.use_gpu = False
backend.xp = np
Benchmarking Results
Typical speedups on NVIDIA RTX 3080 (10GB):
Qubits |
CPU Time |
GPU Time |
Speedup |
|---|---|---|---|
10 |
0.05s |
0.08s |
0.6x |
12 |
0.15s |
0.12s |
1.3x |
14 |
0.60s |
0.25s |
2.4x |
16 |
2.40s |
0.65s |
3.7x |
18 |
9.80s |
1.95s |
5.0x |
20 |
39.5s |
6.20s |
6.4x |
Note: Results vary by GPU hardware
Advanced Usage
Custom Gate Application
# Define custom two-qubit gate
CNOT = np.array([
[1, 0, 0, 0],
[0, 1, 0, 0],
[0, 0, 0, 1],
[0, 0, 1, 0]
])
# Move to GPU
CNOT_gpu = backend.to_gpu(CNOT)
# Apply to qubits 0 and 1
state = backend._apply_two_qubit_gate(state, CNOT_gpu, [0, 1])
Batch Operations
# Process multiple states in parallel
states = [backend.allocate_state(10) for _ in range(batch_size)]
# Apply same operation to all
for i, state in enumerate(states):
states[i] = backend._apply_single_qubit_gate(state, gate, 0)
# Synchronize once at the end
backend.synchronize()
Best Practices
Reuse Backend Instance
# Good: Create once backend = GPUBackend() for _ in range(100): state = backend.allocate_state(15) # Bad: Create many times for _ in range(100): backend = GPUBackend() # Overhead!
Minimize CPU-GPU Transfers
# Good: Keep data on GPU state_gpu = backend.allocate_state(15) for gate in gates: state_gpu = backend.apply_gate(state_gpu, gate, [0]) result = backend.to_cpu(state_gpu) # One transfer # Bad: Transfer each iteration for gate in gates: state_cpu = backend.to_cpu(state_gpu) # Slow! # process on CPU state_gpu = backend.to_gpu(result) # Slow!
Use Appropriate Backend
# Choose based on problem size if n_qubits < 10: backend.use_gpu = False # CPU faster else: # Use GPU default
References
CuPy Documentation: https://docs.cupy.dev/
CUDA Toolkit: https://developer.nvidia.com/cuda-downloads
GPU Computing Primer: https://developer.nvidia.com/how-to-cuda
See Also
QNN Guide - Quantum Neural Networks
Circuit Optimization Guide - Optimization tips