ClusterBuilder
ClusterBuilder is the primary API for composing clusters from multiple providers. It follows the builder pattern: chain .add() calls, finish with .build().
Basic Usage
from network.node_providers import ClusterBuilder
from network.distributed_qvm import DistributedQVM, QVMMode
registry = (
ClusterBuilder()
.add("local_gpu_aer", n_nodes=8, max_qubits=28)
.add("local_gpu_mps", n_nodes=4, max_qubits=1000)
.build()
)
qvm = DistributedQVM(registry, mode=QVMMode.EMULATED)
.add(provider_name, **kwargs)
Adds a provider to the cluster plan. Returns self for chaining.
| Parameter | Type | Description |
|---|---|---|
provider_name | str | Key from NodeProviderRegistry.list() or a custom registered name |
**kwargs | any | Passed directly to NodeProvider.build_nodes() — provider-specific |
builder = ClusterBuilder()
# Each .add() returns the same builder
builder = (
builder
.add("local_gpu_aer", n_nodes=4, max_qubits=28) # 4 SV nodes
.add("local_gpu_mps", n_nodes=2, max_qubits=500) # 2 MPS nodes
.add("lumi_amd_gpu", n_nodes=2, gpus_per_node=8)# LUMI nodes
)
Providers are not instantiated until .build() is called — .add() only records the intent.
.build() → QPURegistry
Executes the plan:
- For each slot: instantiate the provider class
- Call
provider.health_check()— warns if it fails (but continues) - Call
provider.build_nodes(**kwargs)→ getList[QPUNodeSpec] - Register all specs in a
QPURegistry
Returns a QPURegistry ready to pass to DistributedQVM.
registry = builder.build()
# Inspect the result
report = registry.status_report()
print(f"Nodes: {report['total_nodes']}")
print(f"Qubits: {report['total_physical_qubits']}")
If all providers fail, build() falls back to QPURegistry.default_simulation_cluster() and logs a warning.
.describe() → str
Prints the planned cluster without building it — useful for debugging:
plan = (
ClusterBuilder()
.add("lumi_amd_gpu", n_nodes=4, gpus_per_node=8)
.add("local_gpu_mps", n_nodes=8, max_qubits=2000)
.add("azure_quantum", targets=["rigetti.qpu.ankaa-3"])
.describe()
)
print(plan)
ClusterBuilder plan:
[1] lumi_amd_gpu (LUMI AMD MI250X (ROCm))
kwargs: n_nodes=4, gpus_per_node=8
[2] local_gpu_mps (Local GPU — Aer MPS (tensor-network))
kwargs: n_nodes=8, max_qubits=2000
[3] azure_quantum (Azure Quantum)
kwargs: targets=['rigetti.qpu.ankaa-3']
Provider kwargs reference
local_cpu
.add("local_cpu",
n_nodes=4, # number of virtual CPU nodes
max_qubits=25, # max qubits per node (statevector, RAM-limited)
)
local_gpu_aer
.add("local_gpu_aer",
n_nodes=8, # number of CUDA statevector nodes
max_qubits=28, # safe default: 28q = 4GB, 32q = 64GB RAM
)
Statevector RAM usage = $2^n \times 16$ bytes. At 32q this is 64 GB — ensure your machine has enough RAM or use MPS.
local_gpu_mps
.add("local_gpu_mps",
n_nodes=4, # number of MPS simulation nodes
max_qubits=2000, # can handle thousands of qubits (low entanglement)
bond_dim=512, # MPS bond dimension (higher = more accurate, slower)
)
local_gpu_cuquantum
.add("local_gpu_cuquantum",
n_nodes=1, # typically 1 per physical GPU
max_qubits=34, # A100 80GB: safe up to 34q exact
)
Requires: pip install cupy-cuda12x
lumi_amd_gpu
.add("lumi_amd_gpu",
n_nodes=4, # LUMI compute nodes
gpus_per_node=8, # MI250X GCDs per node
max_qubits_sv=34, # statevector per GCD
max_qubits_mps=2000, # MPS per node
lumi_host="lumi", # SSH alias in ~/.ssh/config
lumi_project="project_465002463", # LUMI project ID
)
See LUMI GPU Guide.
azure_quantum
.add("azure_quantum",
targets=[
"rigetti.qpu.ankaa-3", # 84q, $0.0009/shot — cheapest
"quantinuum.qpu.h2-1", # 56q, $0.000095/HQC — best fidelity
"ionq.qpu.forte-1", # 35q, $0.00975/shot
"microsoft.estimator", # free, resource estimation
],
resource_id="...", # Azure Quantum workspace resource ID
location="eastus",
subscription_id="...",
)
See Azure Quantum Guide.
ibm_quantum
.add("ibm_quantum",
backends=["ibm_torino", "ibm_kyoto"], # IBM backend names
token="your-ibm-token",
channel="ibm_quantum", # or "ibm_cloud"
)
aws_braket
.add("aws_braket",
devices=["rigetti", "iqm_garnet"], # Braket device names
aws_region="us-east-1",
aws_access_key="...",
aws_secret_key="...",
s3_bucket="your-braket-bucket",
)
custom_rest
.add("custom_rest",
endpoints=[
"http://gpu-node-01.internal:8080",
"http://gpu-node-02.internal:8080",
],
max_qubits=40, # qubits each endpoint can handle
api_key="secret", # sent as Bearer token
timeout_s=120.0, # request timeout
)
See Custom Providers for the endpoint contract.
Convenience Factories
Instead of ClusterBuilder, use these one-liners:
from network.node_providers import (
make_local_cluster,
make_lumi_cluster,
make_azure_cheap_cluster,
make_hybrid_cluster,
)
# Zero-cost local simulation
registry = make_local_cluster(
n_cpu_nodes=0,
n_gpu_sv_nodes=8,
n_gpu_mps_nodes=4,
sv_max_qubits=28,
mps_max_qubits=500,
)
# LUMI-first
registry = make_lumi_cluster(
n_nodes=8,
gpus_per_node=8,
lumi_host="lumi",
include_local_fallback=True,
)
# Azure cheapest QPU
registry = make_azure_cheap_cluster(
azure_resource_id="...",
include_local_fallback=True,
)
# Full hybrid
registry = make_hybrid_cluster(
lumi_nodes=4,
azure_resource_id="...",
ibm_token="...",
n_local_sv=8,
n_local_mps=4,
)
Shard Strategies
Once you have a registry, control how the ShardPlanner assigns shards:
from network.distributed_qvm import DistributedQVM, ShardStrategy, QVMMode
qvm = DistributedQVM(
registry,
mode=QVMMode.HYBRID,
strategy=ShardStrategy.GPU_FIRST, # or BALANCED, QPU_FIRST, NOISE_AWARE
max_wire_cuts=16,
shots=2048,
)
| Strategy | Behavior |
|---|---|
BALANCED | Assign largest-capacity nodes first (default) |
GPU_FIRST | Prefer GPU simulators over QPUs (lower latency) |
QPU_FIRST | Prefer real QPUs over simulators |
NOISE_AWARE | Assign deepest circuits to lowest-noise nodes |
GREEDY | Fill nodes in registration order |
Example: Scaling from laptop to supercomputer
import os
from network.node_providers import ClusterBuilder
from network.distributed_qvm import DistributedQVM, QVMMode, ShardStrategy
LUMI_NODES = int(os.environ.get("LUMI_NODES", "0"))
AZURE_RID = os.environ.get("AZURE_QUANTUM_RESOURCE_ID", "")
registry = (
ClusterBuilder()
# LUMI: only added if LUMI_NODES > 0
*([] if LUMI_NODES == 0 else [
("lumi_amd_gpu", {"n_nodes": LUMI_NODES, "gpus_per_node": 8}),
])
)
# Simpler conditional approach:
b = ClusterBuilder()
if LUMI_NODES > 0:
b = b.add("lumi_amd_gpu", n_nodes=LUMI_NODES, gpus_per_node=8)
if AZURE_RID:
b = b.add("azure_quantum",
targets=["rigetti.qpu.ankaa-3"],
resource_id=AZURE_RID)
# Always add local fallback
b = (
b
.add("local_gpu_mps", n_nodes=8, max_qubits=1000)
.add("local_gpu_aer", n_nodes=4, max_qubits=28)
)
registry = b.build()
qvm = DistributedQVM(registry, mode=QVMMode.HYBRID, strategy=ShardStrategy.GPU_FIRST)
print(b.describe())
print(registry.status_report())
This single codebase works on a laptop (local only), a server with LUMI access, or a full hybrid cloud — driven purely by environment variables.