Skip to content

Commit 0552681

Browse files
committed
remove cpu , only use gpu
1 parent 4e6b534 commit 0552681

File tree

5 files changed

+208
-785
lines changed

5 files changed

+208
-785
lines changed

qdp/qdp-python/benchmark/encoding_benchmarks/pennylane_baseline/creditcardfraud_amplitude.py

Lines changed: 62 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,8 @@
2929
CSV with columns V1..V28, Amount, Class (0=legit, 1=fraud). Example: Kaggle
3030
"Credit Card Fraud Detection" (https://www.kaggle.com/datasets/mlg-ulb/creditcardfraud).
3131
Pass path via --data-file. If no file, a small synthetic imbalanced dataset is used for smoke test.
32+
33+
Training always runs on GPU via lightning.gpu for fair comparison with QDP pipeline.
3234
"""
3335

3436
from __future__ import annotations
@@ -39,11 +41,10 @@
3941
from typing import Any
4042

4143
import numpy as np
44+
import torch
4245

4346
try:
4447
import pennylane as qml
45-
from pennylane import numpy as pnp
46-
from pennylane.optimize import AdamOptimizer
4748
except ImportError as e:
4849
raise SystemExit(
4950
"PennyLane is required. Install with: uv sync --group benchmark"
@@ -69,10 +70,10 @@
6970
FEATURE_DIM = 2**NUM_QUBITS # amplitude embedding dimension (32 for 5 qubits)
7071

7172

72-
def _layer(layer_weights: pnp.ndarray, wires: tuple[int, ...]) -> None:
73+
def _layer(layer_weights: torch.Tensor, wires: tuple[int, ...]) -> None:
7374
"""Single variational layer: Rot on each wire + ring of CNOTs."""
7475
for i, w in enumerate(wires):
75-
qml.Rot(*layer_weights[i], wires=w)
76+
qml.Rot(layer_weights[i, 0], layer_weights[i, 1], layer_weights[i, 2], wires=w)
7677
for i in range(len(wires)):
7778
qml.CNOT(wires=[wires[i], wires[(i + 1) % len(wires)]])
7879

@@ -199,66 +200,86 @@ def run_training(
199200
lr: float,
200201
seed: int,
201202
) -> dict[str, Any]:
202-
"""Train 5-qubit amplitude VQC with class-weighted loss; report AUPRC, F1, compile/train time."""
203-
dev = qml.device("default.qubit", wires=NUM_QUBITS)
203+
"""Train 5-qubit amplitude VQC on GPU with class-weighted loss; report AUPRC, F1, compile/train time."""
204+
if not torch.cuda.is_available():
205+
raise RuntimeError("CUDA GPU is required for training. No CUDA device found.")
206+
try:
207+
dev = qml.device("lightning.gpu", wires=NUM_QUBITS)
208+
except Exception as e:
209+
raise RuntimeError(
210+
"lightning.gpu is required for GPU training. Install with: "
211+
"pip install pennylane-lightning[gpu]"
212+
) from e
213+
214+
device = torch.device("cuda")
215+
dtype = torch.float64
204216
wires = tuple(range(NUM_QUBITS))
205217

206-
@qml.qnode(dev, interface="autograd", diff_method="backprop")
207-
def circuit(weights: pnp.ndarray, features: pnp.ndarray) -> pnp.ndarray:
218+
@qml.qnode(dev, interface="torch", diff_method="adjoint")
219+
def circuit(weights: torch.Tensor, features: torch.Tensor) -> torch.Tensor:
208220
qml.AmplitudeEmbedding(features, wires=wires, normalize=True)
209221
for w in weights:
210222
_layer(w, wires)
211223
return qml.expval(qml.PauliZ(0))
212224

213-
def model(weights: pnp.ndarray, bias: pnp.ndarray, x: pnp.ndarray) -> pnp.ndarray:
225+
def model(
226+
weights: torch.Tensor, bias: torch.Tensor, x: torch.Tensor
227+
) -> torch.Tensor:
214228
return circuit(weights, x) + bias
215229

216230
def cost(
217-
weights: pnp.ndarray,
218-
bias: pnp.ndarray,
219-
X_batch: pnp.ndarray,
220-
Y_batch: pnp.ndarray,
221-
w_batch: pnp.ndarray,
222-
) -> pnp.ndarray:
231+
weights: torch.Tensor,
232+
bias: torch.Tensor,
233+
X_batch: torch.Tensor,
234+
Y_batch: torch.Tensor,
235+
w_batch: torch.Tensor,
236+
) -> torch.Tensor:
223237
# Y in {0,1} -> target in {-1, 1}
224-
target = pnp.array(Y_batch * 2.0 - 1.0)
238+
target = Y_batch * 2.0 - 1.0
225239
pred = model(weights, bias, X_batch)
226-
return pnp.sum(w_batch * (target - pred) ** 2) / (pnp.sum(w_batch) + 1e-12)
240+
return (w_batch * (target - pred) ** 2).sum() / (w_batch.sum() + 1e-12)
227241

228242
n_train = len(y_train)
229-
rng = np.random.default_rng(seed)
230-
weights_init = 0.01 * pnp.random.randn(
231-
num_layers, NUM_QUBITS, 3, requires_grad=True
243+
244+
torch.manual_seed(seed)
245+
weights = torch.nn.Parameter(
246+
0.01 * torch.randn(num_layers, NUM_QUBITS, 3, device=device, dtype=dtype)
247+
)
248+
bias = torch.nn.Parameter(torch.tensor(0.0, device=device, dtype=dtype))
249+
opt = torch.optim.Adam([weights, bias], lr=lr)
250+
251+
X_train_t = torch.tensor(X_train, dtype=dtype, device=device)
252+
# Float so autograd does not try to differentiate ints
253+
Y_train_t = torch.tensor(
254+
np.asarray(y_train, dtype=np.float64), dtype=dtype, device=device
232255
)
233-
bias_init = pnp.array(0.0, requires_grad=True)
234-
opt = AdamOptimizer(lr)
256+
W_train_t = torch.tensor(sample_weights, dtype=dtype, device=device)
235257

236-
X_train_pnp = pnp.array(X_train, requires_grad=False)
237-
# Float so PennyLane autograd does not try to differentiate ints (align with QDP pipeline)
238-
Y_train_pnp = pnp.array(np.asarray(y_train, dtype=np.float64), requires_grad=False)
239-
W_train_pnp = pnp.array(sample_weights, requires_grad=False)
258+
X_test_t = torch.tensor(X_test, dtype=dtype, device=device)
240259

241260
# Compile (first forward + cost)
242261
t0 = time.perf_counter()
243-
_ = circuit(weights_init, X_train_pnp[0])
244-
_ = cost(weights_init, bias_init, X_train_pnp[:1], Y_train_pnp[:1], W_train_pnp[:1])
262+
_ = circuit(weights, X_train_t[0])
263+
_ = cost(weights, bias, X_train_t[:1], Y_train_t[:1], W_train_t[:1])
245264
compile_sec = time.perf_counter() - t0
246265

247266
# Train
267+
_batch_n = min(batch_size, n_train)
248268
t0 = time.perf_counter()
249-
weights, bias = weights_init, bias_init
250-
for step in range(iterations):
251-
idx = rng.integers(0, n_train, size=(min(batch_size, n_train),))
252-
Xb = X_train_pnp[idx]
253-
Yb = Y_train_pnp[idx]
254-
Wb = W_train_pnp[idx]
255-
out = opt.step(cost, weights, bias, Xb, Yb, Wb)
256-
weights, bias = out[0], out[1]
269+
for _ in range(iterations):
270+
opt.zero_grad()
271+
idx = torch.randint(0, n_train, (_batch_n,), device=device)
272+
Xb = X_train_t[idx]
273+
Yb = Y_train_t[idx]
274+
Wb = W_train_t[idx]
275+
loss = cost(weights, bias, Xb, Yb, Wb)
276+
loss.backward()
277+
opt.step()
257278
train_sec = time.perf_counter() - t0
258279

259280
# Test-set predictions and scores (for AUPRC we need continuous scores)
260-
X_test_pnp = pnp.array(X_test, requires_grad=False)
261-
pred_scores = np.array(model(weights, bias, X_test_pnp)).flatten()
281+
with torch.no_grad():
282+
pred_scores = model(weights, bias, X_test_t).cpu().numpy().flatten()
262283
pred_binary = (np.sign(pred_scores) > 0).astype(np.int32)
263284
# Map expval in [-1,1] to positive-class score in [0,1] for AUPRC
264285
scores_positive = (pred_scores + 1.0) / 2.0
@@ -272,7 +293,7 @@ def cost(
272293
return {
273294
"compile_time_sec": compile_sec,
274295
"train_time_sec": train_sec,
275-
"samples_per_sec": (iterations * min(batch_size, n_train)) / train_sec
296+
"samples_per_sec": (iterations * _batch_n) / train_sec
276297
if train_sec > 0
277298
else 0.0,
278299
"auprc": auprc,
@@ -287,7 +308,7 @@ def cost(
287308

288309
def main() -> None:
289310
parser = argparse.ArgumentParser(
290-
description="PennyLane Credit Card Fraud baseline (amplitude, 5 qubits, AUPRC/F1)"
311+
description="PennyLane Credit Card Fraud baseline (amplitude, 5 qubits, AUPRC/F1, GPU training)"
291312
)
292313
parser.add_argument(
293314
"--data-file",
@@ -353,7 +374,7 @@ def main() -> None:
353374
val_size=0.1,
354375
)
355376

356-
print("Credit Card Fraud amplitude baseline (PennyLane)")
377+
print("Credit Card Fraud amplitude baseline (PennyLane, GPU)")
357378
print(
358379
f" Data: {data_src} → StandardScaler → PCA({args.pca_dim}) → pad to {FEATURE_DIM} → L2 norm"
359380
)

0 commit comments

Comments
 (0)