Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
18 changes: 12 additions & 6 deletions .github/workflows/wheel-build.yml
Original file line number Diff line number Diff line change
Expand Up @@ -68,13 +68,19 @@ jobs:
dnf install -y gcc-toolset-13-gcc gcc-toolset-13-gcc-c++
export CC=/opt/rh/gcc-toolset-13/root/usr/bin/gcc
export CXX=/opt/rh/gcc-toolset-13/root/usr/bin/g++
# Install CUDA 12.5 toolkit (nvcc + headers + cudart) for kernel compilation
# Try to install the CUDA 12.5 toolkit for kernel compilation.
# If the external NVIDIA repo is temporarily unavailable, fall back
# to a no-CUDA smoke build instead of failing the PR wheel job.
dnf install -y 'dnf-command(config-manager)'
dnf config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64/cuda-rhel8.repo
dnf install -y cuda-nvcc-12-5 cuda-cudart-devel-12-5
export CUDA_PATH=/usr/local/cuda-12.5
export PATH=/usr/local/cuda-12.5/bin:$PATH
nvcc --version
if dnf config-manager --add-repo https://developer.download.nvidia.com/compute/cuda/repos/rhel8/x86_64/cuda-rhel8.repo \
&& dnf install -y cuda-nvcc-12-5 cuda-cudart-devel-12-5; then
export CUDA_PATH=/usr/local/cuda-12.5
export PATH=/usr/local/cuda-12.5/bin:$PATH
nvcc --version
else
echo "CUDA repo unavailable; continuing with QDP_NO_CUDA=1 smoke build."
export QDP_NO_CUDA=1
fi
sccache: true

- uses: actions/upload-artifact@v5
Expand Down
2 changes: 1 addition & 1 deletion .pre-commit-config.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ repos:
hooks:
- id: ty
name: ty check
entry: uv run ty check .
entry: bash -c 'if [ -n "${VIRTUAL_ENV:-}" ]; then python -m ty check .; elif [ -x .venv/bin/python ]; then .venv/bin/python -m ty check .; else uv run ty check .; fi'
language: system
pass_filenames: false
types: [python]
Expand Down
23 changes: 20 additions & 3 deletions docs/qdp/getting-started.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,9 +8,10 @@ QDP (Quantum Data Plane) is a GPU-accelerated library for encoding classical dat

## Prerequisites

- Linux with NVIDIA GPU
- CUDA toolkit installed (`nvcc --version` to verify)
- Python 3.10+
- One of:
- NVIDIA GPU with CUDA toolkit installed (`nvcc --version` to verify)
- AMD GPU with ROCm and PyTorch ROCm installed (`python -c "import torch; print(torch.version.hip)"`)

## Installation

Expand All @@ -28,20 +29,35 @@ source .venv/bin/activate
uv run --active maturin develop --manifest-path qdp/qdp-python/Cargo.toml
```

For AMD ROCm with the Triton backend:

```bash
uv sync --group dev --extra qdp
pip install triton
```

## Quick Start

```python
import torch
from qumat.qdp import QdpEngine

engine = QdpEngine(0) # GPU device 0
engine = QdpEngine(device_id=0, backend="cuda")
data = [0.5, 0.5, 0.5, 0.5]
qtensor = engine.encode(data, num_qubits=2, encoding_method="amplitude")

# Convert to PyTorch (zero-copy)
tensor = torch.from_dlpack(qtensor) # Note: can only be consumed once
```

AMD ROCm uses the same API with a different backend selector:

```python
engine = QdpEngine(device_id=0, backend="amd", precision="float32")
qtensor = engine.encode(data, num_qubits=2, encoding_method="amplitude")
tensor = torch.from_dlpack(qtensor)
```

## Encoding Methods

| Method | Constraint | Example |
Expand Down Expand Up @@ -79,6 +95,7 @@ Notes:
|---------|----------|
| Import fails | Activate root venv: `source mahout/.venv/bin/activate` (or `cd mahout && source .venv/bin/activate`) |
| CUDA errors | Run `cargo clean` in `qdp/` and rebuild |
| AMD backend unavailable | Verify ROCm PyTorch reports `torch.version.hip` and install `triton` |
| Out of memory | Reduce `num_qubits` or use `precision="float32"` |

## Next Steps
Expand Down
1 change: 1 addition & 0 deletions pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -83,6 +83,7 @@ python_functions = "test_*"
addopts = ["-v", "--tb=short", "-rs"]
markers = [
"gpu: marks tests as requiring GPU and _qdp extension (auto-skipped if unavailable)",
"rocm: marks tests as requiring ROCm/Triton AMD runtime",
"slow: marks tests as slow running",
]

Expand Down
8 changes: 6 additions & 2 deletions qdp/qdp-core/src/dlpack.rs
Original file line number Diff line number Diff line change
Expand Up @@ -19,7 +19,7 @@
#[cfg(target_os = "linux")]
use crate::error::cuda_error_to_string;
use crate::error::{MahoutError, Result};
use crate::gpu::memory::{BufferStorage, GpuStateVector, Precision};
use crate::gpu::memory::{BufferStorage, GpuDeviceType, GpuStateVector, Precision};
use std::os::raw::{c_int, c_void};
use std::sync::Arc;

Expand Down Expand Up @@ -113,6 +113,7 @@ pub unsafe fn synchronize_stream(_stream: *mut c_void) -> Result<()> {
pub enum DLDeviceType {
kDLCPU = 1,
kDLCUDA = 2,
kDLROCM = 10,
// Other types omitted
}

Expand Down Expand Up @@ -291,7 +292,10 @@ impl GpuStateVector {
let tensor = DLTensor {
data: self.ptr_void(),
device: DLDevice {
device_type: DLDeviceType::kDLCUDA,
device_type: match self.device_type {
GpuDeviceType::Cuda => DLDeviceType::kDLCUDA,
GpuDeviceType::Rocm => DLDeviceType::kDLROCM,
},
device_id: self.device_id as c_int,
},
ndim,
Expand Down
13 changes: 13 additions & 0 deletions qdp/qdp-core/src/gpu/memory.rs
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,13 @@ pub enum Precision {
Float64,
}

/// Backend GPU device type for DLPack metadata.
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum GpuDeviceType {
Cuda,
Rocm,
}

#[cfg(target_os = "linux")]
use crate::gpu::cuda_ffi::{cudaFreeHost, cudaHostAlloc, cudaMemGetInfo};

Expand Down Expand Up @@ -212,6 +219,8 @@ pub struct GpuStateVector {
pub(crate) num_samples: Option<usize>,
/// CUDA device ordinal
pub device_id: usize,
/// GPU backend type used for DLPack device metadata.
pub device_type: GpuDeviceType,
}

// Safety: CudaSlice and Arc are both Send + Sync
Expand Down Expand Up @@ -290,6 +299,7 @@ impl GpuStateVector {
size_elements: _size_elements,
num_samples: None,
device_id: _device.ordinal(),
device_type: GpuDeviceType::Cuda,
})
}

Expand Down Expand Up @@ -401,6 +411,7 @@ impl GpuStateVector {
size_elements: total_elements,
num_samples: Some(num_samples),
device_id: _device.ordinal(),
device_type: GpuDeviceType::Cuda,
})
}

Expand Down Expand Up @@ -487,6 +498,7 @@ impl GpuStateVector {
size_elements: self.size_elements,
num_samples: self.num_samples,
device_id: device.ordinal(),
device_type: self.device_type,
})
}

Expand Down Expand Up @@ -562,6 +574,7 @@ impl GpuStateVector {
size_elements: self.size_elements,
num_samples: self.num_samples, // Preserve batch information
device_id: device.ordinal(),
device_type: self.device_type,
})
}

Expand Down
2 changes: 1 addition & 1 deletion qdp/qdp-core/src/gpu/pool_metrics.rs
Original file line number Diff line number Diff line change
Expand Up @@ -112,7 +112,7 @@ impl PoolMetrics {
} else {
0.0
},
avg_wait_time_ns: if waits > 0 { wait_time_ns / waits } else { 0 },
avg_wait_time_ns: wait_time_ns.checked_div(waits).unwrap_or(0),
}
}

Expand Down
9 changes: 4 additions & 5 deletions qdp/qdp-core/src/readers/parquet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -389,11 +389,10 @@ impl StreamingDataReader for ParquetStreamingReader {
let mut written = 0;
let buf_cap = buffer.len();
let calc_limit = |ss: usize| -> usize {
if ss == 0 {
buf_cap
} else {
(buf_cap / ss) * ss
}
buf_cap
.checked_div(ss)
.map(|chunks| chunks * ss)
.unwrap_or(buf_cap)
};
let mut limit = self.sample_size.map_or(buf_cap, calc_limit);

Expand Down
49 changes: 46 additions & 3 deletions qdp/qdp-python/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -8,16 +8,35 @@ GPU-accelerated quantum state encoding for [Apache Mahout Qumat](https://github.
pip install qumat[qdp]
```

Requires CUDA-capable GPU.
Requires one of:
- NVIDIA GPU (CUDA path via `QdpEngine`)
- AMD GPU with ROCm (AMD path via `QdpEngine(backend="amd")`)

Recommended environment setup:

```bash
python -m venv .venv
source .venv/bin/activate

# Install the GPU runtime for your platform first:
# - NVIDIA users: CUDA-compatible torch / triton
# - AMD users: ROCm-compatible torch / triton

uv sync --active --project qdp/qdp-python --group dev
```

Use `--active` so `uv` reuses the environment that already has the correct GPU
runtime stack.

## Usage

```python
import qumat.qdp as qdp
import torch

# Initialize engine on GPU 0
engine = qdp.QdpEngine(device_id=0)
# Initialize the unified QDP engine on GPU 0.
# Choose the backend explicitly.
engine = qdp.QdpEngine(device_id=0, backend="cuda")

# Encode data into quantum state
qtensor = engine.encode([1.0, 2.0, 3.0, 4.0], num_qubits=2, encoding_method="amplitude")
Expand All @@ -27,6 +46,26 @@ tensor = torch.from_dlpack(qtensor)
print(tensor) # Complex tensor on CUDA
```

### AMD ROCm Usage

```python
import qumat.qdp as qdp
import torch

# Unified AMD engine route
engine = qdp.QdpEngine(device_id=0, precision="float32", backend="amd")
qt = engine.encode(torch.randn(8, 4, device="cuda"), 2, "amplitude")
state = torch.from_dlpack(qt)
print(state.device, state.dtype) # cuda:0, complex64

```

The public `QdpEngine` is a unified Python facade with explicit backend selection:
- `backend="cuda"` routes to the Rust `_qdp.QdpEngine`
- `backend="amd"` routes to the Triton AMD engine directly

See `qdp/qdp-python/TRITON_AMD_BACKEND.md` for Triton AMD setup and validation details.

## Encoding Methods

| Method | Description |
Expand All @@ -36,6 +75,10 @@ print(tensor) # Complex tensor on CUDA
| `basis` | Encode integer as computational basis state |
| `iqp` | IQP-style encoding with entanglement |

Backend support boundary:
- CUDA (`QdpEngine`): `amplitude`, `angle`, `basis`, `iqp`
- AMD (`QdpEngine(..., backend="amd")`): `amplitude`, `angle`, `basis` (no `iqp` yet)

## Input Sources

```python
Expand Down
90 changes: 90 additions & 0 deletions qdp/qdp-python/TRITON_AMD_BACKEND.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,90 @@
# Triton AMD Backend

This document describes the Triton-based implementation used by the QDP AMD backend on ROCm.

## Prerequisites

- AMD GPU supported by ROCm
- ROCm driver/runtime installed
- PyTorch ROCm build (`torch.version.hip` is not `None`)
- Triton installed with HIP support

## Install (project environment)

```bash
uv sync --project qdp/qdp-python --group benchmark --active
```

This installs the benchmark group, including `triton`.

## Runtime capability checks

Use:

```python
from qumat_qdp import is_triton_amd_available
print(is_triton_amd_available())
```

The check validates:
- ROCm runtime is visible through PyTorch
- Triton is importable
- Triton active backend is HIP (when query is available)

## Usage

### Unified AMD routing (recommended)

```python
import torch
from qumat_qdp import QdpEngine

engine = QdpEngine(device_id=0, precision="float32", backend="amd")
x = torch.randn(64, 1024, device="cuda", dtype=torch.float32)
qt = engine.encode(x, num_qubits=10, encoding_method="amplitude")
state = torch.from_dlpack(qt)
```

The public `QdpEngine(..., backend="amd")` route goes directly to the Triton
engine and returns the Python `QuantumTensorWrapper` compatibility wrapper.
The CUDA route remains Rust-owned and returns the extension `QuantumTensor`.

### Triton implementation details

```python
import torch
from qumat_qdp.triton_amd import TritonAmdEngine

engine = TritonAmdEngine(device_id=0, precision="float32")
x = torch.randn(64, 1024, device="cuda", dtype=torch.float32)
qt = engine.encode(x, num_qubits=10, encoding_method="amplitude")
state = torch.from_dlpack(qt)
```

Supported methods:
- `amplitude`
- `angle`
- `basis`

Not supported in the AMD route yet:
- `iqp` (currently CUDA backend only)

## Correctness tests

Run Triton backend tests:

```bash
uv run --project qdp/qdp-python pytest qdp/qdp-python/tests/test_triton_amd_backend.py -q
uv run --project qdp/qdp-python pytest -m rocm qdp/qdp-python/tests -q
```

Tests include:
- parity against Torch reference outputs (amplitude/angle/basis)
- optional parity against CUDA backend reference (when NVIDIA CUDA path is present)

## Baseline benchmark

```bash
uv run --project qdp/qdp-python python qdp/qdp-python/benchmark/benchmark_triton_amd.py \
--qubits 12 --batch-size 64 --batches 200 --encoding-method amplitude
```
Loading
Loading