quantum_cutrank_metal (feature quantum_cutrank_metal, macOS) runs the cut-rank walk's passes on the platform's GPU: - each shot's table is 2^cut single-precision entries in one shared buffer, at most 2^31 a table, so every index fits in 32 bits; - each pass is one dispatch over (entry, shot), in the walk's order and with its arithmetic, done in f32; the passes are the same for every shot, and only each shot's weights (set by the outcomes it drew) differ, read from a small per-shot array; an amplitude is every pass encoded into one command buffer; - a sample's sums are reduced per threadgroup on the device (a SIMD-group sum, then the groups in order) and added on the host in f64, in order, for each shot; the outcome is drawn as the host's walk draws it (outcome_norm and draw, now shared by both); - a batch of shots is sampled side by side, so a qubit is one command buffer for the batch: the previous qubit's passes for each shot's outcome, then this qubit's sums; - each shot draws from a stream of its own, so a seed's samples are the same at any batch size. The kernels are compiled in the platform's safe math mode, so a run is the same bits on one device. Tests hold it to the double-precision walk on states of 10 to 34 qubits: amplitudes within 1e-5 of each amplitude's size and the same bits run to run; samples whose probabilities are their own and whose counts follow them; and the same samples one at a time and seven side by side. Without a device the tests skip; WAI_QUANTUM_CUTRANK_METAL_EXPECT makes that a failure, and the service workflow's macOS job sets it and checks both GPU tests by name. The engine's cutrank_single method runs a cutrank job on that table where the build has the feature and the host a GPU; auto never picks it. It samples up to 32 shots a batch, as many as 2 GiB beside its first table holds within the job's memory, and the plan counts them. Its amplitudes carry the circuit's global phase, found on the same table (catalog::cutrank_phase_with takes the amplitude function). The job service, the command line's metal feature and the desktop app's metal feature build it in. The service's schemas list it, and it marks the method's energy figure partial, since the counter does not read the GPU. examples/cutrank_gpu times one state's amplitudes and samples on the host's tables and on the GPU, and each kernel's pass alone. On a shared desktop machine, each device at its quietest (either side ran up to five times slower under other work), seconds for one amplitude of a random graph state: widest cut host, double host, single GPU 23 bits 0.027 0.024 0.005 25 bits 0.057 0.045 0.019 27 bits 0.18 0.17 0.046 29 bits 1.03 0.54 0.34 and for 64 samples, 16 a batch, the host at about half its quietest speed: widest cut host, single GPU, one at a time GPU, 16 a batch 16 bits 0.52 0.62 0.036 20 bits 1.97 0.73 0.067 23 bits 3.87 0.91 0.34 25 bits 7.48 1.82 1.20 A pass runs at about 550 GB/s on an idle device. Co-Authored-By: Claude Opus 5.5 <[email protected]> (cherry picked from commit f7c7160a9ab5a6255c28d34af57ee2fbc017b055)
142 lines
5.9 KiB
YAML
142 lines
5.9 KiB
YAML
name: wai-quantum service
|
|
|
|
# The WAI quantum stack as a public job service (wai/wai-quantum-service): its
|
|
# tests, clippy with warnings as errors, and its smoke checks over HTTP against
|
|
# an instance started here, on Linux and on the macOS runner (the host the
|
|
# service is deployed to).
|
|
|
|
concurrency:
|
|
group: ${{ github.workflow }}-${{ github.ref }}
|
|
cancel-in-progress: true
|
|
|
|
on:
|
|
push:
|
|
branches: [main]
|
|
paths:
|
|
- 'wai/wai-quantum/**'
|
|
- 'wai/wai-quantum-service/**'
|
|
- 'rust-toolchain.toml'
|
|
- '.github/workflows/wai-quantum-service.yml'
|
|
pull_request:
|
|
paths:
|
|
- 'wai/wai-quantum/**'
|
|
- 'wai/wai-quantum-service/**'
|
|
- 'rust-toolchain.toml'
|
|
- '.github/workflows/wai-quantum-service.yml'
|
|
|
|
jobs:
|
|
relevant:
|
|
name: does this change touch it
|
|
runs-on: linux-arm64
|
|
outputs:
|
|
run: ${{ steps.gate.outputs.run }}
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
with:
|
|
fetch-depth: 0
|
|
- id: gate
|
|
run: |
|
|
set -euo pipefail
|
|
r=$(python3 scripts/pr-touches-paths.py .github/workflows/wai-quantum-service.yml \
|
|
"${{ github.event.pull_request.base.sha }}" \
|
|
"${{ github.event.pull_request.head.sha }}")
|
|
echo "this change touches wai-quantum-service: $r"
|
|
echo "run=$r" >> "$GITHUB_OUTPUT"
|
|
|
|
service-linux:
|
|
name: service · linux
|
|
needs: relevant
|
|
if: needs.relevant.outputs.run == 'true'
|
|
runs-on: linux
|
|
timeout-minutes: 60
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
- name: Install Rust 1.98.0
|
|
shell: bash
|
|
run: |
|
|
export PATH="$HOME/.cargo/bin:$PATH"
|
|
command -v rustup >/dev/null 2>&1 || \
|
|
curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --profile minimal --default-toolchain 1.98.0
|
|
rustup toolchain install 1.98.0 --profile minimal --component clippy
|
|
rustup default 1.98.0
|
|
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
|
- name: Tests and clippy
|
|
shell: bash
|
|
working-directory: wai/wai-quantum-service
|
|
run: |
|
|
set -euo pipefail
|
|
cargo test --locked --release 2>&1 | tee "$RUNNER_TEMP/test.log"
|
|
grep -qE '^test result: ok\. [1-9][0-9]* passed; 0 failed' "$RUNNER_TEMP/test.log" || { echo "::error::no test ran"; exit 1; }
|
|
cargo clippy --locked --release --all-targets -- -D warnings
|
|
- name: The smoke checks over HTTP
|
|
shell: bash
|
|
working-directory: wai/wai-quantum-service
|
|
run: |
|
|
set -euo pipefail
|
|
key="$RUNNER_TEMP/service.key"; rm -f "$key"
|
|
cargo build --locked --release
|
|
WAI_QSVC_KEY="$key" WAI_QSVC_ADDR=127.0.0.1:18241 WAI_QSVC_BUDGET_GB=8 WAI_QSVC_JOB_GB=4 WAI_QSVC_WORKERS=2 \
|
|
./target/release/wai-quantum-service serve 2> "$RUNNER_TEMP/service.log" &
|
|
pid=$!
|
|
trap 'kill $pid 2>/dev/null || true; cat "$RUNNER_TEMP/service.log"' EXIT
|
|
for _ in $(seq 1 50); do curl -s -o /dev/null http://127.0.0.1:18241/v1/health && break; sleep 0.2; done
|
|
bash smoke.sh http://127.0.0.1:18241
|
|
|
|
service-macos:
|
|
name: service · macos
|
|
needs: relevant
|
|
if: needs.relevant.outputs.run == 'true'
|
|
runs-on: macos
|
|
timeout-minutes: 60
|
|
steps:
|
|
- uses: actions/checkout@v5
|
|
# launchd's PATH omits cargo on the macOS host runner.
|
|
- name: Put cargo on PATH
|
|
shell: bash
|
|
run: echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
|
|
- name: Tests
|
|
shell: bash
|
|
working-directory: wai/wai-quantum-service
|
|
run: |
|
|
set -euo pipefail
|
|
nice -n 10 cargo test --locked --release 2>&1 | tee "$RUNNER_TEMP/test.log"
|
|
grep -qE '^test result: ok\. [1-9][0-9]* passed; 0 failed' "$RUNNER_TEMP/test.log" || { echo "::error::no test ran"; exit 1; }
|
|
# The single-precision state vector on this host's GPU, against the
|
|
# double-precision one; the GPU is declared, so its absence fails.
|
|
- name: The GPU state vector
|
|
shell: bash
|
|
working-directory: wai/wai-quantum
|
|
env:
|
|
WAI_QUANTUM_SV_METAL_EXPECT: "1"
|
|
run: |
|
|
set -euo pipefail
|
|
nice -n 10 cargo test --locked --release --features quantum_sv_metal --lib quantum_sv_metal 2>&1 | tee "$RUNNER_TEMP/gpu.log"
|
|
grep -qE '^test result: ok\. 4 passed; 0 failed' "$RUNNER_TEMP/gpu.log" || { echo "::error::not every GPU test ran and passed"; exit 1; }
|
|
# The cut-rank table on this host's GPU, against the double-precision
|
|
# walk; the GPU is declared, so its absence fails, and each GPU test is
|
|
# checked by name.
|
|
- name: The GPU cut-rank table
|
|
shell: bash
|
|
working-directory: wai/wai-quantum
|
|
env:
|
|
WAI_QUANTUM_CUTRANK_METAL_EXPECT: "1"
|
|
run: |
|
|
set -euo pipefail
|
|
nice -n 10 cargo test --locked --release --features quantum_cutrank_metal --lib quantum_cutrank 2>&1 | tee "$RUNNER_TEMP/cutrank-gpu.log"
|
|
for t in the_gpu_table_agrees gpu_samples_follow_the_probabilities; do
|
|
grep -q "^test quantum_cutrank::tests::$t ... ok$" "$RUNNER_TEMP/cutrank-gpu.log" \
|
|
|| { echo "::error::quantum_cutrank::tests::$t did not run and pass"; exit 1; }
|
|
done
|
|
- name: The smoke checks over HTTP
|
|
shell: bash
|
|
working-directory: wai/wai-quantum-service
|
|
run: |
|
|
set -euo pipefail
|
|
key="$RUNNER_TEMP/service.key"; rm -f "$key"
|
|
cargo build --locked --release
|
|
WAI_QSVC_KEY="$key" WAI_QSVC_ADDR=127.0.0.1:18241 WAI_QSVC_BUDGET_GB=8 WAI_QSVC_JOB_GB=4 WAI_QSVC_WORKERS=2 \
|
|
./target/release/wai-quantum-service serve 2> "$RUNNER_TEMP/service.log" &
|
|
pid=$!
|
|
trap 'kill $pid 2>/dev/null || true; cat "$RUNNER_TEMP/service.log"' EXIT
|
|
for _ in $(seq 1 50); do curl -s -o /dev/null http://127.0.0.1:18241/v1/health && break; sleep 0.2; done
|
|
bash smoke.sh http://127.0.0.1:18241
|