open-standards/.github/workflows/wai-quantum-webgpu.yml
Transaction Science c2e04bcdf9 wai-quantum CI: the WebGPU hardware job on the aarch64 board too (linux-arm64-hw)
member-hw is now a matrix over the two hosts with GPUs, the way the CUDA
and OpenCL hardware jobs are. Each host's adapters are declared in the
workflow: jetson-hub's discrete and integrated GPUs and the software
Vulkan driver (the set its runner declared until now), and the board's
GPU and the software Vulkan driver. Both steps ran on the board first:
the member's 14 tests, the wide sizes to 26 qubits on its GPU, and the
conformance corpus through the member.

Co-Authored-By: Claude Opus 5.5 <[email protected]>
2026-10-31 22:05:01 -04:00

459 lines
22 KiB
YAML

name: wai-quantum WebGPU member
# The fabric's WebGPU member: one WGSL gate kernel, compiled for every adapter a
# runner has, certified against the CPU reference's pinned corpus digests. A
# second implementation, tile runs (the same kernel followed by apply_tile, which
# applies a run of gates tile by tile in one dispatch), is certified on its own
# against the same digests, on every adapter and in the browser.
#
# Each member job declares the adapters its runner must have in
# WAI_QUANTUM_WEBGPU_EXPECT. The device test fails if the adapters found differ,
# and scripts/wai-quantum-webgpu-member.sh fails unless all tests ran, every
# declared adapter earned one bit-exact certificate per pinned digest, and every
# declared adapter killed every shader mutant.
#
# The GPU jobs then run the quantum conformance corpus through each member
# (`wai_quantum_vectors verify --webgpu`). wai-rs is built there with
# --no-default-features: its default feature, `codecs`, links system codec
# libraries that no GPU host has, and the corpus needs none of them. The GPU
# hosts: jetson-hub (a discrete and an integrated GPU) and framework-128gb (an
# integrated GPU).
#
# The browser job runs the same kernel through a browser's WebGPU, compiled by the
# browser's own WGSL compiler: wai-web's `quantum_webgpu` bundle in a headless page
# (wai/quantum-conformance/browser_webgpu.mjs), which must certify a GPU member with
# the four pinned digests, reproduce every vector and refuse every reject file with
# its class. It is not a required status until it has been green three runs in a row.
#
# The meter (wai/tools/quantum-meter) is wai-quantum's RunMeter over a process's own
# CPU and GPU energy counters. On the Metal host, where those counters exist, it meters
# a member's runs and the CPU reference's over windows they resolve, and its test must
# print what it read; on the software Vulkan host, where they do not, every window must
# come out unmetered.
#
# There is no windows job while the windows runner is offline: a job queued on an
# offline runner never settles. Add it back from the comment at the end when the
# runner has survived a reboot.
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
on:
pull_request:
paths:
- 'wai/wai-quantum/**'
- 'wai/quantum-conformance/**'
- 'wai/tools/quantum-wgsl-check/**'
# The meter, and the counters it reads.
- 'wai/tools/quantum-meter/**'
- 'jouleclaw/jouleclaw-rs/Cargo.toml'
- 'jouleclaw/jouleclaw-rs/crates/jouleclaw-core/**'
- 'jouleclaw/jouleclaw-rs/crates/jouleclaw-energy/**'
- 'wai/wai-rs/src/bin/wai_quantum_vectors.rs'
# The GPU jobs build wai-rs with the forwarded feature, --locked.
- 'wai/wai-rs/Cargo.lock'
- 'rust-toolchain.toml'
# The checks and browser jobs build wai-web with its feature, --locked.
- 'wai/wai-web/Cargo.toml'
- 'wai/wai-web/Cargo.lock'
- 'wai/wai-web/src/quantum_webgpu.rs'
- 'wai/wai-web/src/lib.rs'
- 'wai/wai-web/demo/quantum_webgpu*'
- 'scripts/wai-quantum-webgpu-member.sh'
- '.github/workflows/wai-quantum-webgpu.yml'
schedule:
- cron: '6 7 * * *'
jobs:
# Does this pull request's own diff touch the paths below? This forge matches a
# workflow's `paths:` against the push, so a merge of main into a branch fires the
# workflow whatever the pull request changes; every job here waits on this answer.
# A push to main, a schedule, or anything the script cannot determine answers
# `true` (scripts/pr-touches-paths.py).
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-webgpu.yml \
"${{ github.event.pull_request.base.sha }}" \
"${{ github.event.pull_request.head.sha }}")
echo "this change touches wai-quantum-webgpu: $r"
echo "run=$r" >> "$GITHUB_OUTPUT"
checks:
needs: relevant
if: needs.relevant.outputs.run == 'true'
name: shader checks · browser build · wasip2 inertness · MSRV
runs-on: linux
timeout-minutes: 60
steps:
- uses: actions/checkout@v5
- name: Install Rust 1.98.0 (wasm32 targets) and 1.97.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
rustup toolchain install 1.97.0 --profile minimal
rustup default 1.98.0
rustup target add --toolchain 1.98.0 wasm32-unknown-unknown wasm32-wasip2
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: SPIR-V validator
shell: bash
run: |
set -euo pipefail
SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo"
$SUDO apt-get update -qq
$SUDO apt-get install -y -qq --no-install-recommends spirv-tools
- name: The shaders under naga 30.0.1
shell: bash
working-directory: wai/tools/quantum-wgsl-check
run: |
set -euo pipefail
cargo run --locked --release -- "$RUNNER_TEMP" 2>&1 | tee "$RUNNER_TEMP/wgsl-check.log"
grep -qx "quantum-wgsl-check: ok" "$RUNNER_TEMP/wgsl-check.log"
spirv-val --target-env vulkan1.1 "$RUNNER_TEMP/lanes32.spv"
spirv-val --target-env vulkan1.1 "$RUNNER_TEMP/lanes32_tile.spv"
- name: The member builds for a browser
working-directory: wai/wai-quantum
run: cargo build --locked --lib --target wasm32-unknown-unknown --no-default-features --features quantum_fabric_webgpu
- name: The browser bundle builds with the member
working-directory: wai/wai-web
run: cargo build --locked --release --target wasm32-unknown-unknown --features quantum_webgpu
- name: On wasm32-wasip2 the feature is inert
shell: bash
working-directory: wai/wai-quantum
run: |
set -euo pipefail
cargo check --locked --lib --target wasm32-wasip2 --features full,quantum_fabric_webgpu
w="$RUNNER_TEMP/tree-wasip2.txt"; n="$RUNNER_TEMP/tree-native.txt"
cargo tree --locked --target wasm32-wasip2 --features full,quantum_fabric_webgpu -e normal --prefix none > "$w"
cargo tree --locked --target x86_64-unknown-linux-gnu --features quantum_fabric_webgpu -e normal --prefix none > "$n"
grep -q '^wai-quantum ' "$w" || { echo "::error::cargo tree printed no wasip2 tree"; exit 1; }
grep -q '^wgpu ' "$n" || { echo "::error::the native tree has no wgpu, so this check cannot see it"; exit 1; }
if grep -q '^wgpu' "$w"; then echo "::error::wgpu is in the wasm32-wasip2 tree"; exit 1; fi
- name: The package carries the shader
shell: bash
working-directory: wai/wai-quantum
run: |
set -euo pipefail
cargo package --locked --list --allow-dirty > "$RUNNER_TEMP/package.txt"
grep -qx 'src/quantum_fabric/lanes32.wgsl' "$RUNNER_TEMP/package.txt"
grep -qx 'src/quantum_fabric/lanes32_tile.wgsl' "$RUNNER_TEMP/package.txt"
- name: MSRV 1.97.0
working-directory: wai/wai-quantum
run: cargo +1.97.0 check --locked --lib --features quantum_fabric_webgpu
# The member's unit tests need no GPU (a failed native wait is fed to the
# guard as a panicking closure), and only the feature build compiles them.
- name: The member's unit tests, without a device
shell: bash
working-directory: wai/wai-quantum
run: |
set -euo pipefail
cargo test --locked --lib --features quantum_fabric_webgpu -- quantum_fabric_webgpu:: 2>&1 \
| tee "$RUNNER_TEMP/member-unit.log"
grep -q '^test quantum_fabric_webgpu::tests::a_failed_native_wait_is_a_typed_loss \.\.\. ok$' \
"$RUNNER_TEMP/member-unit.log" \
|| { echo "::error::the member's unit test did not run"; exit 1; }
member-software:
needs: relevant
if: needs.relevant.outputs.run == 'true'
name: member · software Vulkan (linux)
runs-on: linux
timeout-minutes: 120
env:
WAI_QUANTUM_WEBGPU_EXPECT: swgpu-vulkan-10005
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
rustup default 1.98.0
echo "$HOME/.cargo/bin" >> "$GITHUB_PATH"
- name: The Vulkan loader and a software Vulkan driver
shell: bash
run: |
set -euo pipefail
SUDO=""; [ "$(id -u)" -ne 0 ] && SUDO="sudo"
$SUDO apt-get update -qq
$SUDO apt-get install -y -qq --no-install-recommends libvulkan1 mesa-vulkan-drivers
- name: The member's tests
shell: bash
run: scripts/wai-quantum-webgpu-member.sh
# No per-process energy counter exists here: the meter must find none, and a
# run of the member through it must be unmetered.
- name: The meter, where its counters do not exist
shell: bash
working-directory: wai/tools/quantum-meter
run: |
set -euo pipefail
cargo test --locked -- --nocapture 2>&1 | tee "$RUNNER_TEMP/meter.log"
grep -q '^test a_members_runs_are_metered_over_a_window_the_counters_resolve \.\.\. ok$' "$RUNNER_TEMP/meter.log" \
|| { echo "::error::the meter's device test did not run"; exit 1; }
grep -q '^quantum-meter: the counters are not live here' "$RUNNER_TEMP/meter.log" \
|| { echo "::error::the meter's device test found no adapter, so it checked nothing"; exit 1; }
member-metal:
needs: relevant
if: needs.relevant.outputs.run == 'true'
name: member · Metal (macos)
runs-on: macos
timeout-minutes: 90
env:
WAI_QUANTUM_WEBGPU_EXPECT: gpu-metal-0
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: The member's tests
shell: bash
run: nice -n 10 scripts/wai-quantum-webgpu-member.sh
- name: The conformance corpus through the member
shell: bash
working-directory: wai/wai-rs
run: nice -n 10 cargo run --locked --no-default-features --features quantum_fabric_webgpu --bin wai_quantum_vectors -- verify ../quantum-conformance --webgpu
# The counters exist here. The device test prints a `quantum-meter hardware:` line
# for each window it read: a figure, or why another process's GPU use left it
# none. The tool then calibrates both members on one circuit and, where both
# windows earn a figure, places it.
- name: The meter on this process's counters
shell: bash
working-directory: wai/tools/quantum-meter
run: |
set -euo pipefail
nice -n 10 cargo test --locked --release -- --nocapture 2>&1 | tee "$RUNNER_TEMP/meter.log"
grep -q '^test a_members_runs_are_metered_over_a_window_the_counters_resolve \.\.\. ok$' "$RUNNER_TEMP/meter.log" \
|| { echo "::error::the meter's device test did not run"; exit 1; }
grep -q '^quantum-meter hardware: ' "$RUNNER_TEMP/meter.log" \
|| { echo "::error::the meter read no window: its counters are not live on this host"; exit 1; }
nice -n 10 cargo run --locked --release -- --counter-ppm 50000 2>&1 | tee "$RUNNER_TEMP/meter-tool.log"
grep -q '^quantum-meter: local/cpu-' "$RUNNER_TEMP/meter-tool.log" \
|| { echo "::error::the tool ran no window on the CPU reference"; exit 1; }
member-hw:
needs: relevant
if: needs.relevant.outputs.run == 'true'
name: member · GPU, Vulkan (${{ matrix.host }})
runs-on: ${{ matrix.runner }}
timeout-minutes: 150
strategy:
fail-fast: false
matrix:
include:
# Each host's adapters, declared here: its GPUs and the software
# Vulkan driver. The wide sizes run on the GPU only.
- host: jetson-hub
runner: linux-hw
adapters: gpu-vulkan-10de,gpu-vulkan-8086,swgpu-vulkan-10005
- host: thor
runner: linux-arm64-hw
adapters: gpu-vulkan-10de,swgpu-vulkan-10005
env:
WAI_QUANTUM_WEBGPU_EXPECT: ${{ matrix.adapters }}
WAI_QUANTUM_WEBGPU_WIDE: '1'
CARGO_PROFILE_DEV_DEBUG: '0'
CARGO_BUILD_JOBS: '8'
steps:
- name: Fetch the source
shell: bash
run: |
set -euo pipefail
want="${{ github.event.pull_request.head.sha || github.sha }}"
rm -rf src && mkdir src && cd src
git init -q
# Fetch the commit itself, not the ref: a later commit on the ref that this
# workflow's paths do not match would otherwise be fetched instead.
git fetch -q --depth 1 http://100.64.224.0:3400/Transaction-Science/open-standards.git "$want"
git checkout -q FETCH_HEAD
got="$(git rev-parse HEAD)"
[ "$got" = "$want" ] || { echo "::error::fetched $got, not $want"; exit 1; }
- name: The member's tests
shell: bash
working-directory: src
run: |
set -euo pipefail
echo "adapters declared: $WAI_QUANTUM_WEBGPU_EXPECT"
CARGO_TARGET_DIR="$HOME/hw-target/wai-quantum" nice -n 10 scripts/wai-quantum-webgpu-member.sh
- name: The conformance corpus through the member
shell: bash
working-directory: src/wai/wai-rs
run: CARGO_TARGET_DIR="$HOME/hw-target/wai-rs" nice -n 10 cargo run --locked --no-default-features --features quantum_fabric_webgpu --bin wai_quantum_vectors -- verify ../quantum-conformance --webgpu
member-amd:
needs: relevant
if: needs.relevant.outputs.run == 'true'
name: member · integrated GPU, Vulkan (framework-128gb)
runs-on: linux-amd
timeout-minutes: 150
env:
# The adapters are declared by the host, in its runner's environment
# (WAI_QUANTUM_AMD_WEBGPU_EXPECT); with no declaration, the integrated GPU
# and the software Vulkan driver. The wide sizes run on the GPU only.
WAI_QUANTUM_WEBGPU_WIDE: '1'
CARGO_PROFILE_DEV_DEBUG: '0'
CARGO_BUILD_JOBS: '12'
steps:
- name: Fetch the source
shell: bash
run: |
set -euo pipefail
want="${{ github.event.pull_request.head.sha || github.sha }}"
rm -rf src && mkdir src && cd src
git init -q
# Fetch the commit itself, not the ref: a later commit on the ref that this
# workflow's paths do not match would otherwise be fetched instead. The
# host names its own route to the forge (WAI_FORGE_URL).
git fetch -q --depth 1 "${WAI_FORGE_URL:-http://100.64.224.0:3400}/Transaction-Science/open-standards.git" "$want"
git checkout -q FETCH_HEAD
got="$(git rev-parse HEAD)"
[ "$got" = "$want" ] || { echo "::error::fetched $got, not $want"; exit 1; }
- name: The member's tests
shell: bash
working-directory: src
run: |
set -euo pipefail
export WAI_QUANTUM_WEBGPU_EXPECT="${WAI_QUANTUM_AMD_WEBGPU_EXPECT:-gpu-vulkan-1002,swgpu-vulkan-10005}"
echo "adapters declared: $WAI_QUANTUM_WEBGPU_EXPECT"
CARGO_TARGET_DIR="$HOME/hw-target/wai-quantum" nice -n 10 scripts/wai-quantum-webgpu-member.sh
- name: The conformance corpus through the member
shell: bash
working-directory: src/wai/wai-rs
run: CARGO_TARGET_DIR="$HOME/hw-target/wai-rs" nice -n 10 cargo run --locked --no-default-features --features quantum_fabric_webgpu --bin wai_quantum_vectors -- verify ../quantum-conformance --webgpu
browser:
needs: relevant
if: needs.relevant.outputs.run == 'true'
name: member · browser WebGPU (macos)
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"
# The page's file is held to the crate by wai-quantum's tests/fabric_expected.rs:
# exactly the four certificates of the pinned corpora (gateset, corpus, seed,
# cases, wide circuits and the digest the CPU computes) and both implementation
# ids. Editing the file alone, say giving fabric-v1 random-v1's digests so that
# no circuit wider than one tile runs, fails here.
- name: The page's pins are the crate's
shell: bash
working-directory: wai/wai-quantum
run: |
set -euo pipefail
nice -n 10 cargo test --locked --features quantum_fabric --test fabric_expected 2>&1 | tee "$RUNNER_TEMP/fabric-expected.log"
grep -q '^test the_browser_legs_pins_are_the_crates \.\.\. ok$' "$RUNNER_TEMP/fabric-expected.log" \
|| { echo "::error::the pins test did not run"; exit 1; }
# Pinned to the wasm-bindgen version in wai-web/Cargo.lock: the CLI and the crate
# must match exactly.
- name: wasm-bindgen CLI 0.2.127
shell: bash
run: |
set -euo pipefail
rustup target add wasm32-unknown-unknown
cargo install --locked wasm-bindgen-cli --version 0.2.127
- name: The member's bundle
shell: bash
working-directory: wai/wai-web
run: |
set -euo pipefail
nice -n 10 cargo build --locked --release --target wasm32-unknown-unknown --features quantum_webgpu
wasm-bindgen --target web --out-dir demo/pkg-webgpu --out-name wai_quantum_webgpu \
target/wasm32-unknown-unknown/release/wai_web.wasm
# Playwright and its headless browser go into the job's temporary directory, not
# the host's caches. `chromium` is Playwright's name for its engine.
- name: Playwright 1.60.0 and its headless browser
shell: bash
run: |
set -euo pipefail
node --version
pw="$RUNNER_TEMP/playwright"
mkdir -p "$pw"
npm install --prefix "$pw" --no-save --no-audit --no-fund [email protected]
PLAYWRIGHT_BROWSERS_PATH="$pw/browsers" "$pw/node_modules/.bin/playwright" install --only-shell chromium
echo "PLAYWRIGHT_HOST=$pw/" >> "$GITHUB_ENV"
echo "PLAYWRIGHT_BROWSERS_PATH=$pw/browsers" >> "$GITHUB_ENV"
- name: The member in a browser
shell: bash
run: |
set -euo pipefail
WAI_QUANTUM_WEBGPU_REQUIRE=1 nice -n 10 node wai/quantum-conformance/browser_webgpu.mjs 2>&1 \
| tee "$RUNNER_TEMP/browser-webgpu.log"
grep -qE '^browser webgpu: member=local/gpu-webgpu-[0-9a-f]{12} class=gpu certified bit-exact 4 of 4; tiles certified bit-exact 4 of 4; ' \
"$RUNNER_TEMP/browser-webgpu.log" \
|| { echo "::error::no GPU member certified bit-exact 4 of 4, per-gate and in tile runs, in the browser"; exit 1; }
# Windows reaches both of this host's GPUs through both backends, so one job
# certifies five adapters rather than one per backend: the member test
# enumerates every adapter in the one process, and the DX12 keys exercise the
# WGSL -> HLSL path (naga) that no other member covers. Not a required status.
member-windows:
name: member · DX12 and Vulkan (cbio-windows)
runs-on: windows
timeout-minutes: 150
env:
# The key list this host's first run printed:
# gpu-dx12-10de discrete NVIDIA, DX12
# gpu-dx12-8086 integrated Intel, DX12
# gpu-vulkan-10de discrete NVIDIA, Vulkan
# gpu-vulkan-8086 integrated Intel, Vulkan
# swgpu-dx12-1414 Microsoft's software adapter (WARP), class Cpu
# Adding or removing a driver changes this set, and the member test fails
# until this line matches it again.
WAI_QUANTUM_WEBGPU_EXPECT: gpu-dx12-10de,gpu-dx12-8086,gpu-vulkan-10de,gpu-vulkan-8086,swgpu-dx12-1414
steps:
- uses: actions/checkout@v5
# The runner writes an unsigned .ps1 per step, which the default policy
# refuses. pwsh, not Windows PowerShell: the 5.1 parser rejects the PS7
# syntax this repo's scripts use.
- name: Allow the runner's generated scripts to run
shell: pwsh
run: Set-ExecutionPolicy -Scope Process -ExecutionPolicy Bypass -Force
# `bash` on this host resolves to the WindowsApps WSL stub, and the machine
# PATH carries Git\cmd (git.exe) but not Git\bin (bash.exe). Without this
# every `shell: bash` step below would run under WSL, which reaches none of
# the Windows GPU drivers.
- name: Put Git Bash on PATH
shell: pwsh
run: |
$gitBash = 'C:\Program Files\Git\bin'
if (-not (Test-Path "$gitBash\bash.exe")) { throw "Git Bash is missing at $gitBash" }
$gitBash | Out-File -FilePath $env:GITHUB_PATH -Encoding utf8 -Append
- name: The shell is Git Bash, not WSL
shell: bash
run: |
set -euo pipefail
command -v bash
# A WSL bash reports Linux; Git Bash reports MINGW or MSYS.
case "$(uname -s)" in
MINGW*|MSYS*) ;;
*) echo "::error::bash is $(uname -s), not Git Bash"; exit 1 ;;
esac
- name: The member's tests
shell: bash
run: scripts/wai-quantum-webgpu-member.sh
# wai-rs is built with --no-default-features here for the same reason as the
# other GPU members: its default `codecs` feature links system codec
# libraries this host does not have, and the corpus needs none of them.
- name: The conformance corpus through the member
shell: bash
working-directory: wai/wai-rs
run: cargo run --locked --no-default-features --features quantum_fabric_webgpu --bin wai_quantum_vectors -- verify ../quantum-conformance --webgpu