You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@mahout.apache.org by hc...@apache.org on 2026/04/25 18:33:42 UTC
(mahout) branch main updated: [QDP] Add AMD backend selection to QDP encoding benchmarks (#1289)
This is an automated email from the ASF dual-hosted git repository.
ryankert01 pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/mahout.git
The following commit(s) were added to refs/heads/main by this push:
new 5e5d20ff2 [QDP] Add AMD backend selection to QDP encoding benchmarks (#1289)
5e5d20ff2 is described below
commit 5e5d20ff2d0a13b9ec349e2631a03f7f03f52b4b
Author: Jie-Kai Chang <ji...@apache.org>
AuthorDate: Sun Apr 26 02:33:37 2026 +0800
[QDP] Add AMD backend selection to QDP encoding benchmarks (#1289)
* Add AMD backend selection to QDP encoding benchmarks
Signed-off-by: 400Ping <ji...@apache.org>
* Fix AMD parity gaps surfaced by --qdp-backend amd
* TritonAmdEngine.encode_amplitude now zero-pads when sample_size <
2**num_qubits, matching the CUDA Rust path's contract; oversize input
remains a hard error. Without this, the SVHN-kernel benchmark
(3072-D vectors -> 12-qubit / 4096-D state) crashed on the AMD route.
* qdp_pipeline/mnist_amplitude.py falls back from lightning.gpu to
default.qubit when lightning.gpu is unavailable (CUDA-only), mirroring
the iris pattern. CUDA hosts are unaffected; AMD hosts now run
end-to-end with PennyLane training on CPU.
* Adds two pad-parity tests for the AMD amplitude encoder.
* README note that MNIST AMD training currently runs on CPU because
lightning.gpu is CUDA-only.
Verified on AMD Instinct MI300X (ROCm 7.2 / torch 2.9.0+rocm6.4):
iris --qdp-backend amd best test acc 0.40 (smoke)
mnist --qdp-backend amd (n=200, 3v6) best test acc 0.98
svhn-kernel --qdp-backend amd (n=500) CV acc 0.7981 +/- 0.0294
Tests: 13 passed, 2 skipped (NVIDIA-CUDA-only refs).
---------
Signed-off-by: 400Ping <ji...@apache.org>
Co-authored-by: Ryan Huang <hc...@apache.org>
---
.../benchmark/encoding_benchmarks/README.md | 57 ++++++++++++++++++++--
.../qdp_pipeline/iris_amplitude.py | 16 ++++--
.../qdp_pipeline/mnist_amplitude.py | 32 +++++++++---
.../qdp_pipeline/svhn_kernel_amplitude.py | 25 +++++++---
qdp/qdp-python/qumat_qdp/triton_amd.py | 11 +++--
qdp/qdp-python/tests/test_triton_amd_backend.py | 29 +++++++++++
6 files changed, 145 insertions(+), 25 deletions(-)
diff --git a/qdp/qdp-python/benchmark/encoding_benchmarks/README.md b/qdp/qdp-python/benchmark/encoding_benchmarks/README.md
index 2726ce78d..6ce913096 100644
--- a/qdp/qdp-python/benchmark/encoding_benchmarks/README.md
+++ b/qdp/qdp-python/benchmark/encoding_benchmarks/README.md
@@ -4,7 +4,7 @@ This directory is used to **compare a pure PennyLane baseline with a QDP pipelin
Both scripts use the same dataset and the same variational model; **only the encoding step is different**.
- **`pennylane_baseline/`**: pure PennyLane (sklearn / official Iris file → encoding → variational classifier).
-- **`qdp_pipeline/`**: same data and model, but encoding is done via the QDP `QuantumDataLoader` (amplitude).
+- **`qdp_pipeline/`**: same data and model, but encoding is done via QDP direct state preparation.
Run all commands from the `qdp-python` directory:
@@ -15,11 +15,14 @@ cd qdp/qdp-python
## Environment (one-time setup)
```bash
-uv sync --group benchmark # install PennyLane, torch(+CUDA), scikit-learn, etc.
+uv sync --group benchmark # install PennyLane, torch, scikit-learn, etc.
uv run maturin develop # build QDP Python extension (qumat_qdp)
```
-If your CUDA driver is not 12.6, adjust the PyTorch index URL in `pyproject.toml` according to the comments there.
+Install the GPU runtime for your platform first. CUDA users need a CUDA-compatible
+PyTorch stack; AMD users need a ROCm-compatible PyTorch/Triton stack. If your
+CUDA driver is not 12.6, adjust the PyTorch index URL in `pyproject.toml`
+according to the comments there.
## Iris amplitude baseline (pure PennyLane)
@@ -49,7 +52,7 @@ uv run python benchmark/encoding_benchmarks/pennylane_baseline/iris_amplitude.py
## Iris amplitude (QDP pipeline)
Pipeline is identical to the baseline except for encoding:
-4-D vectors → QDP `QuantumDataLoader` (amplitude) → `StatePrep(state_vector)` → same variational classifier.
+4-D vectors → QDP `QdpEngine.encode` (amplitude) → `StatePrep(state_vector)` → same variational classifier.
```bash
uv run python benchmark/encoding_benchmarks/qdp_pipeline/iris_amplitude.py
@@ -59,6 +62,7 @@ The CLI mirrors the baseline, plus:
- **QDP-specific flags**
- `--device-id`: QDP device id (default: 0)
+ - `--qdp-backend`: QDP backend, `cuda` or `amd` (default: `cuda`)
- `--data-dir`: directory for temporary `.npy` files (default: system temp directory)
Example (same settings as the baseline example, but with QDP encoding):
@@ -69,6 +73,15 @@ uv run python benchmark/encoding_benchmarks/qdp_pipeline/iris_amplitude.py \
--optimizer nesterov --lr 0.01 --layers 6 --trials 3 --iters 80 --early-stop 0
```
+Example using the AMD backend:
+
+```bash
+uv run python benchmark/encoding_benchmarks/qdp_pipeline/iris_amplitude.py \
+ --data-file benchmark/encoding_benchmarks/pennylane_baseline/data/iris_classes1and2_scaled.txt \
+ --optimizer nesterov --lr 0.01 --layers 6 --trials 3 --iters 80 --early-stop 0 \
+ --qdp-backend amd
+```
+
## MNIST amplitude baseline (pure PennyLane)
Pipeline: 2-class MNIST (default: digits 3 vs 6) → PCA (784 → 2^qubits) → L2 normalize → `AmplitudeEmbedding` → variational classifier.
@@ -108,6 +121,7 @@ The CLI mirrors the baseline, plus:
- **QDP-specific flags**
- `--device-id`: QDP device id (default: 0)
+ - `--qdp-backend`: QDP backend, `cuda` or `amd` (default: `cuda`)
- `--data-dir`: directory for temporary `.npy` files (default: system temp directory)
Example (same settings as the baseline example, but with QDP encoding):
@@ -117,6 +131,39 @@ uv run python benchmark/encoding_benchmarks/qdp_pipeline/mnist_amplitude.py \
--digits "3,6" --n-samples 100 --trials 3 --iters 500 --early-stop 0
```
+Example using the AMD backend:
+
+```bash
+uv run python benchmark/encoding_benchmarks/qdp_pipeline/mnist_amplitude.py \
+ --digits "3,6" --n-samples 100 --trials 3 --iters 500 --early-stop 0 \
+ --qdp-backend amd
+```
+
+> **Note:** With `--qdp-backend amd`, encoding runs on the AMD GPU but the
+> variational training falls back to PennyLane's `default.qubit` simulator on
+> CPU because `lightning.gpu` is CUDA-only. Expect training to be much slower
+> than the CUDA path.
+
+## SVHN kernel amplitude (QDP pipeline)
+
+Pipeline: SVHN digit 1 vs 7 → standardize → QDP `QdpEngine.encode` (amplitude, 12 qubits) → precomputed squared inner-product quantum kernel → sklearn SVM.
+
+```bash
+uv run python benchmark/encoding_benchmarks/qdp_pipeline/svhn_kernel_amplitude.py
+```
+
+QDP-specific flags:
+
+- `--device-id`: QDP device id (default: 0)
+- `--qdp-backend`: QDP backend, `cuda` or `amd` (default: `cuda`)
+
+Example using the AMD backend:
+
+```bash
+uv run python benchmark/encoding_benchmarks/qdp_pipeline/svhn_kernel_amplitude.py \
+ --n-samples 500 --folds 3 --qdp-backend amd
+```
+
## SVHN IQP baseline (pure PennyLane)
Pipeline: 2-class SVHN (digit 1 vs 7) → PCA (3072 → n_qubits) → custom IQP circuit (H^n · Diag · H^n) → variational classifier.
@@ -157,6 +204,7 @@ uv run python benchmark/encoding_benchmarks/qdp_pipeline/svhn_iqp.py
The CLI mirrors the baseline, plus:
> **Note:** The QDP pipeline always performs the encoding step on a CUDA GPU via QDP. A CUDA-capable device is required even when you select `--backend cpu` for the training backend.
+> AMD is not supported for this benchmark yet because the AMD backend currently supports `amplitude`, `angle`, and `basis`, but not `iqp`.
- **QDP-specific flags**
- `--device-id`: CUDA device id (default: 0)
@@ -182,5 +230,6 @@ uv run python benchmark/encoding_benchmarks/pennylane_baseline/mnist_amplitude.p
uv run python benchmark/encoding_benchmarks/pennylane_baseline/svhn_iqp.py --help
uv run python benchmark/encoding_benchmarks/qdp_pipeline/iris_amplitude.py --help
uv run python benchmark/encoding_benchmarks/qdp_pipeline/mnist_amplitude.py --help
+uv run python benchmark/encoding_benchmarks/qdp_pipeline/svhn_kernel_amplitude.py --help
uv run python benchmark/encoding_benchmarks/qdp_pipeline/svhn_iqp.py --help
```
diff --git a/qdp/qdp-python/benchmark/encoding_benchmarks/qdp_pipeline/iris_amplitude.py b/qdp/qdp-python/benchmark/encoding_benchmarks/qdp_pipeline/iris_amplitude.py
index 049bf6c52..7b0132343 100644
--- a/qdp/qdp-python/benchmark/encoding_benchmarks/qdp_pipeline/iris_amplitude.py
+++ b/qdp/qdp-python/benchmark/encoding_benchmarks/qdp_pipeline/iris_amplitude.py
@@ -114,20 +114,21 @@ def encode_via_qdp(
X_norm: np.ndarray,
batch_size: int, # kept for CLI symmetry; not used here
device_id: int = 0,
+ qdp_backend: str = "cuda",
data_dir: str | None = None,
filename: str = "iris_4d.npy",
) -> torch.Tensor:
"""QDP: use QdpEngine.encode on 4-D vectors (amplitude), return encoded (n, 4) on GPU.
Uses in-memory encoding via QdpEngine instead of writing/reading .npy files. The returned
- tensor stays on the selected CUDA device and can be fed directly to qml.StatePrep.
+ tensor stays on the selected GPU device and can be fed directly to qml.StatePrep.
"""
n, dim = X_norm.shape
if dim != STATE_DIM:
raise ValueError(
f"X_norm must have {STATE_DIM} features for 2 qubits, got {dim}"
)
- engine = QdpEngine(device_id=device_id, precision="float32")
+ engine = QdpEngine(device_id=device_id, precision="float32", backend=qdp_backend)
qt = engine.encode(
X_norm.astype(np.float64),
num_qubits=NUM_QUBITS,
@@ -447,6 +448,12 @@ def main() -> None:
parser.add_argument(
"--device-id", type=int, default=0, help="QDP device (default: 0)"
)
+ parser.add_argument(
+ "--qdp-backend",
+ choices=("cuda", "amd"),
+ default="cuda",
+ help="QDP backend for direct state preparation (default: cuda)",
+ )
parser.add_argument(
"--data-dir", type=str, default=None, help="Dir for .npy files (default: temp)"
)
@@ -476,6 +483,7 @@ def main() -> None:
X_train_4d,
batch_size=args.batch_size,
device_id=args.device_id,
+ qdp_backend=args.qdp_backend,
data_dir=args.data_dir,
filename="iris_4d_train.npy",
)
@@ -483,6 +491,7 @@ def main() -> None:
X_test_4d,
batch_size=args.batch_size,
device_id=args.device_id,
+ qdp_backend=args.qdp_backend,
data_dir=args.data_dir,
filename="iris_4d_test.npy",
)
@@ -490,7 +499,8 @@ def main() -> None:
print("Iris amplitude (QDP encoding) — 2-class variational classifier")
print(f" Data: {data_src} → QDP amplitude (n={n}; 2-class Iris = 100 samples)")
print(
- f" Iters: {args.iters}, batch_size: {args.batch_size}, layers: {args.layers}, lr: {args.lr}, optimizer: {args.optimizer}"
+ f" Iters: {args.iters}, batch_size: {args.batch_size}, layers: {args.layers}, "
+ f"lr: {args.lr}, optimizer: {args.optimizer}, qdp_backend: {args.qdp_backend}"
)
results: list[dict[str, Any]] = []
diff --git a/qdp/qdp-python/benchmark/encoding_benchmarks/qdp_pipeline/mnist_amplitude.py b/qdp/qdp-python/benchmark/encoding_benchmarks/qdp_pipeline/mnist_amplitude.py
index 21266e702..6d206f2fd 100644
--- a/qdp/qdp-python/benchmark/encoding_benchmarks/qdp_pipeline/mnist_amplitude.py
+++ b/qdp/qdp-python/benchmark/encoding_benchmarks/qdp_pipeline/mnist_amplitude.py
@@ -131,13 +131,14 @@ def encode_via_qdp(
num_qubits: int,
batch_size: int = 10, # kept for CLI symmetry; not used here
device_id: int = 0,
+ qdp_backend: str = "cuda",
data_dir: str | None = None,
filename: str = "mnist_nd.npy",
) -> torch.Tensor:
"""QDP: use QdpEngine.encode on PCA-reduced vectors (amplitude), return encoded tensor on GPU.
Uses in-memory encoding via QdpEngine instead of writing/reading .npy files. The returned
- tensor stays on the selected CUDA device and can be fed directly to qml.StatePrep.
+ tensor stays on the selected GPU device and can be fed directly to qml.StatePrep.
"""
n, dim = X_norm.shape
state_dim = 2**num_qubits
@@ -145,7 +146,7 @@ def encode_via_qdp(
raise ValueError(
f"X_norm must have {state_dim} features for {num_qubits} qubits, got {dim}"
)
- engine = QdpEngine(device_id=device_id, precision="float32")
+ engine = QdpEngine(device_id=device_id, precision="float32", backend=qdp_backend)
qt = engine.encode(
X_norm.astype(np.float64),
num_qubits=num_qubits,
@@ -171,12 +172,21 @@ def run_training(
early_stop_target: float | None = None,
) -> dict[str, Any]:
"""Train variational classifier: StatePrep(encoded) + Rot layers + bias, square loss, batched.
- Uses lightning.gpu + torch interface + adjoint diff. Data stays on GPU.
- Optional early stop when test acc >= target."""
+ Prefers lightning.gpu (CUDA-only); falls back to default.qubit on CPU when unavailable
+ (e.g. AMD/ROCm host). Optional early stop when test acc >= target."""
n_train = len(Y_train)
rng = np.random.default_rng(seed)
wires = tuple(range(num_qubits))
+ qml_device_name = "cuda"
+ try:
+ dev_qml = qml.device("lightning.gpu", wires=num_qubits)
+ except Exception:
+ dev_qml = qml.device("default.qubit", wires=num_qubits)
+ qml_device_name = "cpu"
+ encoded_train = encoded_train.cpu()
+ encoded_test = encoded_test.cpu()
+
device = encoded_train.device
# Encoded data may be complex (from QDP); use real dtype for weights and labels.
real_dtype = (
@@ -185,8 +195,6 @@ def run_training(
Y_train_t = torch.tensor(Y_train, dtype=real_dtype, device=device)
Y_test_t = torch.tensor(Y_test, dtype=real_dtype, device=device)
- dev_qml = qml.device("lightning.gpu", wires=num_qubits)
-
@qml.qnode(dev_qml, interface="torch", diff_method="adjoint")
def circuit(weights, state_vector):
qml.StatePrep(state_vector, wires=wires)
@@ -262,7 +270,7 @@ def run_training(
"samples_per_sec": (steps_done * batch_size) / train_sec
if train_sec > 0
else 0.0,
- "qml_device": "cuda",
+ "qml_device": qml_device_name,
}
@@ -325,6 +333,12 @@ def main() -> None:
parser.add_argument(
"--device-id", type=int, default=0, help="QDP device (default: 0)"
)
+ parser.add_argument(
+ "--qdp-backend",
+ choices=("cuda", "amd"),
+ default="cuda",
+ help="QDP backend for direct state preparation (default: cuda)",
+ )
parser.add_argument(
"--data-dir", type=str, default=None, help="Dir for .npy files (default: temp)"
)
@@ -356,6 +370,7 @@ def main() -> None:
num_qubits=args.qubits,
batch_size=args.batch_size,
device_id=args.device_id,
+ qdp_backend=args.qdp_backend,
data_dir=args.data_dir,
filename="mnist_nd_train.npy",
)
@@ -364,6 +379,7 @@ def main() -> None:
num_qubits=args.qubits,
batch_size=args.batch_size,
device_id=args.device_id,
+ qdp_backend=args.qdp_backend,
data_dir=args.data_dir,
filename="mnist_nd_test.npy",
)
@@ -376,7 +392,7 @@ def main() -> None:
)
print(
f" Qubits: {args.qubits}, iters: {args.iters}, batch_size: {args.batch_size}, "
- f"layers: {args.layers}, lr: {args.lr}"
+ f"layers: {args.layers}, lr: {args.lr}, qdp_backend: {args.qdp_backend}"
)
print(
f" QDP encode: {encode_sec:.4f} s (train + test, {n_train} + {n - n_train} samples)"
diff --git a/qdp/qdp-python/benchmark/encoding_benchmarks/qdp_pipeline/svhn_kernel_amplitude.py b/qdp/qdp-python/benchmark/encoding_benchmarks/qdp_pipeline/svhn_kernel_amplitude.py
index dfd80feae..9deb1bb65 100644
--- a/qdp/qdp-python/benchmark/encoding_benchmarks/qdp_pipeline/svhn_kernel_amplitude.py
+++ b/qdp/qdp-python/benchmark/encoding_benchmarks/qdp_pipeline/svhn_kernel_amplitude.py
@@ -22,7 +22,7 @@ Pipeline:
SVHN (32×32×3) → Flatten (3072) → QdpEngine.encode(amplitude) on GPU (4096, 12 qubits)
→ Quantum Kernel K[i,j] = (encoded[i] · encoded[j])² → sklearn SVM
-Encoding: QdpEngine (GPU) — data stays on CUDA for kernel matmul, then moves to CPU for SVM.
+Encoding: QdpEngine (GPU) — data stays on GPU for kernel matmul, then moves to CPU for SVM.
Kernel: Precomputed squared inner product (GPU torch.mm).
Classifier: sklearn.svm.SVC(kernel='precomputed').
@@ -124,9 +124,11 @@ def _filter_binary(X, Y):
return X[mask], np.where(Y[mask] == CLASS_POS, 1, -1)
-def encode_qdp(X: np.ndarray, device_id: int = 0) -> torch.Tensor:
- """QdpEngine amplitude encode → CUDA float64 tensor (n, 4096)."""
- engine = QdpEngine(device_id=device_id, precision="float64")
+def encode_qdp(
+ X: np.ndarray, device_id: int = 0, qdp_backend: str = "cuda"
+) -> torch.Tensor:
+ """QdpEngine amplitude encode → GPU float64 tensor (n, 4096)."""
+ engine = QdpEngine(device_id=device_id, precision="float64", backend=qdp_backend)
qt = engine.encode(
X.astype(np.float64),
num_qubits=NUM_QUBITS,
@@ -171,7 +173,13 @@ def main() -> None:
help="SVM regularisation C (default: 100.0)",
)
parser.add_argument(
- "--device-id", type=int, default=0, help="CUDA device (default: 0)"
+ "--device-id", type=int, default=0, help="QDP device (default: 0)"
+ )
+ parser.add_argument(
+ "--qdp-backend",
+ choices=("cuda", "amd"),
+ default="cuda",
+ help="QDP backend for direct state preparation (default: cuda)",
)
parser.add_argument("--data-home", type=str, default=None, help="Data cache dir")
args = parser.parse_args()
@@ -181,7 +189,10 @@ def main() -> None:
f" {NUM_QUBITS} qubits, {STATE_DIM}-dim state, binary: digit {CLASS_POS} vs {CLASS_NEG}"
)
print(f" n_samples={args.n_samples}, {args.folds}-fold CV, C={args.svm_c}")
- print(f" CUDA: {torch.cuda.is_available()}, device_id: {args.device_id}")
+ print(
+ f" GPU available: {torch.cuda.is_available()}, device_id: {args.device_id}, "
+ f"qdp_backend: {args.qdp_backend}"
+ )
print()
# Load & filter
@@ -205,7 +216,7 @@ def main() -> None:
t0 = time.perf_counter()
scaler = StandardScaler().fit(X_bin)
X_scaled = scaler.transform(X_bin)
- X_encoded = encode_qdp(X_scaled, args.device_id)
+ X_encoded = encode_qdp(X_scaled, args.device_id, args.qdp_backend)
torch.cuda.synchronize()
encode_sec = time.perf_counter() - t0
print(
diff --git a/qdp/qdp-python/qumat_qdp/triton_amd.py b/qdp/qdp-python/qumat_qdp/triton_amd.py
index a4453b427..1a531c4e7 100644
--- a/qdp/qdp-python/qumat_qdp/triton_amd.py
+++ b/qdp/qdp-python/qumat_qdp/triton_amd.py
@@ -115,15 +115,20 @@ class TritonAmdEngine:
def encode_amplitude(self, data: Any, num_qubits: int) -> Any:
torch_mod = self._require_torch()
x = self._to_2d(data, dtype=self._real_dtype())
- _, sample_size = x.shape
+ batch, sample_size = x.shape
state_len = 1 << num_qubits
- if sample_size != state_len:
+ if sample_size > state_len:
raise ValueError(
- f"Amplitude encoding expects sample size {state_len} (=2^num_qubits), got {sample_size}."
+ f"Amplitude encoding expects sample size <= {state_len} (=2^num_qubits), got {sample_size}."
)
norms = torch_mod.linalg.vector_norm(x, dim=1, keepdim=True).clamp_min(1e-12)
amp = x / norms
+ if sample_size < state_len:
+ pad = torch_mod.zeros(
+ (batch, state_len - sample_size), device=amp.device, dtype=amp.dtype
+ )
+ amp = torch_mod.cat([amp, pad], dim=1)
return torch_mod.complex(amp, torch_mod.zeros_like(amp)).to(
self._complex_dtype()
)
diff --git a/qdp/qdp-python/tests/test_triton_amd_backend.py b/qdp/qdp-python/tests/test_triton_amd_backend.py
index 74730999b..1263f65e9 100644
--- a/qdp/qdp-python/tests/test_triton_amd_backend.py
+++ b/qdp/qdp-python/tests/test_triton_amd_backend.py
@@ -77,6 +77,35 @@ def test_triton_amd_amplitude_parity() -> None:
assert torch.allclose(got, ref, atol=1e-5, rtol=1e-5)
+@pytest.mark.skipif(
+ not is_triton_amd_available(), reason="Triton AMD backend unavailable"
+)
+@pytest.mark.rocm
+def test_triton_amd_amplitude_pad_parity() -> None:
+ """Sample sizes < 2**num_qubits zero-pad to the state length, matching the CUDA path."""
+ engine = TritonAmdEngine(device_id=0, precision="float32")
+ x = torch.randn(4, 6, device="cuda", dtype=torch.float32)
+ got = _as_torch(engine.encode(x, 3, "amplitude"))
+ assert got.shape == (4, 8)
+ assert got.dtype == torch.complex64
+ norms = torch.linalg.vector_norm(x, dim=1, keepdim=True).clamp_min(1e-12)
+ expected_real = torch.cat([x / norms, torch.zeros((4, 2), device=x.device)], dim=1)
+ assert torch.allclose(got.real, expected_real, atol=1e-5, rtol=1e-5)
+ assert torch.allclose(got.imag, torch.zeros_like(got.imag), atol=0.0)
+
+
+@pytest.mark.skipif(
+ not is_triton_amd_available(), reason="Triton AMD backend unavailable"
+)
+@pytest.mark.rocm
+def test_triton_amd_amplitude_oversize_rejected() -> None:
+ """Sample sizes > 2**num_qubits remain a hard error (matches CUDA contract)."""
+ engine = TritonAmdEngine(device_id=0, precision="float32")
+ x = torch.randn(2, 9, device="cuda", dtype=torch.float32)
+ with pytest.raises(ValueError, match="<= 8"):
+ engine.encode(x, 3, "amplitude")
+
+
@pytest.mark.skipif(
not is_triton_amd_available(), reason="Triton AMD backend unavailable"
)