name: wai-ops-npu integer operators on neural processors # wai-ops/1-npu on neural processors (wai/wai-ops-npu): the integer operator set's # accelerator subset (extensions/integer-operators.md §9.2, §9.5) with its arithmetic # on one compute core of a tile array. The crate builds the core's program with the # array's open toolchain (Apache-2.0 with the LLVM exception, pinned below) and its # host side with the system's C++ compiler against the device runtime, both at run # time, into a cache keyed by their sources and the toolchain. # # - npu-hw (`linux-amd`, framework-128gb, its neural processor): the probes (the # unit's rounding modes at every shift, its saturation, its accumulator lanes, # and the kernel's requantisation on 786,432 cases); every graph vector of # wai/ops-conformance whose graph is in the subset, against the committed results # and the interpreter; the twelve kernel edits, each its own program, every one of # which the corpus must catch; and the graphs the certificate keeps out of the # subset by acc32 alone, which must not all agree. WAI_OPS_NPU_EXPECT declares the # one device the host must have, and WAI_OPS_NPU_REQUIRE makes an absent device a # failure, never a skip; the step then requires the report lines with their # counts, so a filtered or empty run fails too. # - checks (`linux-arm64`, no such device): the crate and its tests build, its unit # tests pass, the device tests print SKIPPED and pass, and the same tests under # WAI_OPS_NPU_REQUIRE=1 fail: an absent device is not a pass. # # Time and energy (`ops-npu-bench`) are measured by hand and recorded in # integer-operators §9.5; this workflow does not run the bench. # One run per branch at a time; a push supersedes the run before it. concurrency: group: ${{ github.workflow }}-${{ github.ref }} cancel-in-progress: true on: pull_request: paths: - 'wai/wai-ops-npu/**' - 'wai/wai-ops/**' - 'wai/ops-conformance/**' - 'rust-toolchain.toml' - '.github/workflows/wai-ops-npu.yml' schedule: - cron: '41 7 * * *' env: # The tile array's toolchain: the compiler and its core back end, the build the # compiler's release pins (its utils/peano-requirements.txt). The same environment # as the wai-quantum npu-cores member's, at the same path. TOOLCHAIN_VERSION: 1.4.3 CORE_COMPILER: llvm-aie==22.0.0.2026090701+3e93bf7b 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-ops-npu.yml \ "${{ github.event.pull_request.base.sha }}" \ "${{ github.event.pull_request.head.sha }}") echo "this change touches wai-ops-npu: $r" echo "run=$r" >> "$GITHUB_OUTPUT" npu-hw: name: neural processor (framework-128gb silicon) needs: relevant if: needs.relevant.outputs.run == 'true' runs-on: linux-amd timeout-minutes: 90 env: 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. 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 toolchain (cached) shell: bash run: | set -euo pipefail venv="$HOME/aie-$TOOLCHAIN_VERSION" if ! "$venv/bin/python" -I -c 'import aie.iron' 2>/dev/null; then rm -rf "$venv" python3 -m venv "$venv" tag="$(python3 -c 'import sys; print(f"cp{sys.version_info.major}{sys.version_info.minor}")')" "$venv/bin/pip" install -q "https://github.com/Xilinx/mlir-aie/releases/download/v$TOOLCHAIN_VERSION/mlir_aie-$TOOLCHAIN_VERSION-$tag-$tag-manylinux_2_35_x86_64.whl" # allow-name: the toolchain's release URL "$venv/bin/pip" install -q -f https://github.com/Xilinx/llvm-aie/releases/expanded_assets/nightly "$CORE_COMPILER" # allow-name: the core compiler's release index "$venv/bin/pip" install -q "numpy>=2.5.2,<3.0" rich ml_dtypes cloudpickle aiofiles "$venv/bin/pip" install -q -f https://llvm.github.io/eudsl "eudsl-python-extras==0.1.0.20260801.905+68a0d7a" --config-settings=EUDSL_PYTHON_EXTRAS_HOST_PACKAGE_PREFIX=aie fi echo "WAI_OPS_NPU_PYTHON=$venv/bin/python" >> "$GITHUB_ENV" - name: The probes, the corpus, the kernel edits and the width, on the device shell: bash working-directory: src/wai/wai-ops-npu run: | set -euo pipefail export PATH="$HOME/.cargo/bin:$PATH" export WAI_OPS_NPU_CACHE="$HOME/ops-npu-cache" # The device this host has, declared here (or by the runner). k="${WAI_OPS_NPU_HOST_EXPECT:-npu-1022-17f0}" log="$RUNNER_TEMP/ops-npu.log" export WAI_OPS_NPU_REPORT="$RUNNER_TEMP/ops-npu.report" : > "$WAI_OPS_NPU_REPORT" WAI_OPS_NPU_EXPECT="$k" WAI_OPS_NPU_REQUIRE=1 CARGO_TARGET_DIR="$HOME/hw-target/wai-ops-npu" \ nice -n 10 cargo test --locked --release -- --nocapture --test-threads=1 2>&1 | tee "$log" # Fourteen tests in five binaries: the library's eight, corpus's one, probe's # three, mutants' one and width's one, none filtered, ignored or failed. ran=$(awk '/^test result: ok\. [0-9]+ passed; 0 failed; 0 ignored; 0 measured; 0 filtered out/ { n += $4 } END { print n + 0 }' "$log") [ "$ran" = 14 ] || { echo "::error::$ran tests ran and passed, not 14"; exit 1; } ! grep -q '^test result: FAILED' "$log" || { echo "::error::a test failed"; exit 1; } rep="$WAI_OPS_NPU_REPORT" grep -qxF "ops-npu devices: as declared ($k)" "$rep" || { echo "::error::the device found is not $k"; exit 1; } grep -qE "^ops-npu requantisation: key=$k [0-9]+ cases over shifts 0\.\.63 .*, 0 differ$" "$rep" || { echo "::error::the requantisation probe did not agree"; exit 1; } grep -qE "^ops-npu corpus: key=$k .* vectors=[1-9][0-9]* decodes=[1-9][0-9]* .* declined=0 mismatches=0 errors=0 " "$rep" \ || { echo "::error::no equal corpus run on $k"; exit 1; } grep -qxF "ops-npu mutants: key=$k killed 12 of 12" "$rep" || { echo "::error::a kernel edit survived on $k, or the mutant test did not run"; exit 1; } grep -qE "^ops-npu width: key=$k .* on the device [1-9][0-9]* differ " "$rep" || { echo "::error::the width test did not run"; exit 1; } cat "$rep" checks: name: builds · no device is not a pass (linux-arm64) needs: relevant if: needs.relevant.outputs.run == 'true' runs-on: linux-arm64 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 rustup default 1.98.0 echo "$HOME/.cargo/bin" >> "$GITHUB_PATH" # This host has no such device. The device tests must say so and pass; under # WAI_OPS_NPU_REQUIRE=1 the same tests must fail. - name: The crate's tests, and an absent device under WAI_OPS_NPU_REQUIRE shell: bash working-directory: wai/wai-ops-npu env: WAI_OPS_NPU_EXPECT: none run: | set -euo pipefail log="$RUNNER_TEMP/ops-npu.log" rep="$RUNNER_TEMP/ops-npu.report" : > "$rep" WAI_OPS_NPU_REPORT="$rep" cargo test --locked --release -- --nocapture --test-threads=1 2>&1 | tee "$log" ran=$(awk '/^test result: ok\. [0-9]+ passed; 0 failed; 0 ignored; 0 measured; 0 filtered out/ { n += $4 } END { print n + 0 }' "$log") [ "$ran" = 14 ] || { echo "::error::$ran tests ran and passed, not 14"; exit 1; } grep -qxF 'ops-npu devices: as declared (none)' "$rep" || { echo "::error::the absent case was not established"; exit 1; } for t in corpus mutants width 'requantisation probe' 'rounding probe' 'lane probe'; do grep -qF "ops-npu $t: SKIPPED" "$rep" || { echo "::error::the $t did not say SKIPPED"; exit 1; } done grep -qE '^ops-npu subset: vectors=[1-9][0-9]* .* in_subset=[1-9][0-9]* ' "$rep" || { echo "::error::the corpus was not read"; exit 1; } if WAI_OPS_NPU_REQUIRE=1 cargo test --locked --release --test corpus --test probe --test mutants --test width > "$RUNNER_TEMP/require.log" 2>&1; then echo "::error::with no device, the device tests passed under WAI_OPS_NPU_REQUIRE=1"; exit 1 fi grep -q 'WAI_OPS_NPU_REQUIRE is set' "$RUNNER_TEMP/require.log" \ || { echo "::error::the tests failed under WAI_OPS_NPU_REQUIRE=1, but not for the absent device"; cat "$RUNNER_TEMP/require.log"; exit 1; } echo "no device: SKIPPED without WAI_OPS_NPU_REQUIRE, a failure with it"