diff --git a/.github/actions/setup-intel-sde/action.yml b/.github/actions/setup-intel-sde/action.yml new file mode 100644 index 00000000..bb5d2791 --- /dev/null +++ b/.github/actions/setup-intel-sde/action.yml @@ -0,0 +1,65 @@ +name: setup-intel-sde +description: Download and verify the pinned Intel SDE build used by AVX-512 CI. + +inputs: + version: + description: Intel SDE archive basename without the .tar.xz suffix. + default: sde-external-10.7.0-2026-02-18-lin + url-base: + description: Base URL containing the Intel SDE archive. + default: https://downloadmirror.intel.com/913594 + sha256: + description: SHA256 of the Intel SDE .tar.xz archive. + default: ca3d4086de4acb3faedf9f57b541c6936b7d5e19ae2bf763b6ea933573a0a217 + install-dir: + description: Absolute path, or a path relative to GITHUB_WORKSPACE, for extraction. + default: intel-sde + +outputs: + install-dir: + description: Directory containing the extracted Intel SDE files. + value: ${{ steps.install.outputs.install-dir }} + sde-path: + description: Absolute path to the extracted sde64 executable. + value: ${{ steps.install.outputs.sde-path }} + +runs: + using: composite + steps: + - id: install + shell: bash + run: | + set -euo pipefail + + arch="$(uname -m)" + if [ "${arch}" != "x86_64" ]; then + echo "::error::Intel SDE sde64 requires an x86_64 runner; got ${arch}" + exit 1 + fi + + version="${{ inputs.version }}" + url_base="${{ inputs.url-base }}" + sha256="${{ inputs.sha256 }}" + install_dir="${{ inputs.install-dir }}" + case "${install_dir}" in + /*) ;; + *) install_dir="${GITHUB_WORKSPACE}/${install_dir}" ;; + esac + + archive="${RUNNER_TEMP}/${version}.tar.xz" + curl -fsSL "${url_base}/${version}.tar.xz" -o "${archive}" + echo "${sha256} ${archive}" | sha256sum -c - + + mkdir -p "${install_dir}" + tar xf "${archive}" --strip-components=1 -C "${install_dir}" + rm "${archive}" + + sde_path="${install_dir}/sde64" + if [ ! -x "${sde_path}" ]; then + echo "::error::Intel SDE install did not produce an executable sde64 at ${sde_path}" + exit 1 + fi + "${sde_path}" --version + + echo "install-dir=${install_dir}" >> "${GITHUB_OUTPUT}" + echo "sde-path=${sde_path}" >> "${GITHUB_OUTPUT}" diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 022c4c27..62a96f5f 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -226,16 +226,13 @@ jobs: # kernels instead of the scalar/AVX2 fallback. # # Pattern adapted from microsoft/DiskANN's CI (also a vector-search crate). - # SDE pin is a fixed Intel downloadmirror build, SHA256-verified before it is - # unpacked or run; bump the version + checksum together when upgrading. If - # Intel ever moves the URL or the bytes change, this job fails loudly (the - # gate stays honest) rather than silently skipping AVX-512 coverage. + # The local setup-intel-sde action owns the fixed Intel downloadmirror build, + # SHA256 verification, and x86_64 runner guard. If Intel ever moves the URL + # or the bytes change, this job fails loudly (the gate stays honest) rather + # than silently skipping AVX-512 coverage. avx512: name: avx512 (Intel SDE / Sapphire Rapids) - runs-on: ubuntu-latest - env: - SDE_VERSION: sde-external-10.7.0-2026-02-18-lin - SDE_URL_BASE: https://downloadmirror.intel.com/913594 + runs-on: ubuntu-24.04 steps: - uses: step-security/harden-runner@9af89fc71515a100421586dfdb3dc9c984fbf411 # v2.19.4 with: @@ -248,19 +245,11 @@ jobs: toolchain: stable - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: install Intel SDE - env: - # SHA256 of ${SDE_VERSION}.tar.xz — verified before the archive is - # unpacked and executed. Bump together with SDE_VERSION. - SDE_SHA256: ca3d4086de4acb3faedf9f57b541c6936b7d5e19ae2bf763b6ea933573a0a217 - run: | - set -euo pipefail - wget -qO intel-sde.tar.xz "${SDE_URL_BASE}/${SDE_VERSION}.tar.xz" - echo "${SDE_SHA256} intel-sde.tar.xz" | sha256sum -c - - mkdir -p "${GITHUB_WORKSPACE}/intel-sde" - tar xf intel-sde.tar.xz --strip-components=1 -C "${GITHUB_WORKSPACE}/intel-sde" - rm intel-sde.tar.xz - "${GITHUB_WORKSPACE}/intel-sde/sde64" --version + id: sde + uses: ./.github/actions/setup-intel-sde - name: sanity-check AVX-512 detection under SDE + env: + SDE_PATH: ${{ steps.sde.outputs.sde-path }} run: | set -euo pipefail # Prove SDE genuinely exposes AVX-512 to the guest via the SAME @@ -289,11 +278,11 @@ jobs: } EOF cargo build --release --manifest-path "${RUNNER_TEMP}/sde-probe/Cargo.toml" - "${GITHUB_WORKSPACE}/intel-sde/sde64" -spr -- \ + "${SDE_PATH}" -spr -- \ "${RUNNER_TEMP}/sde-probe/target/release/sde-probe" - name: cargo test under SDE (AVX-512 kernels) env: - CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER: ${{ github.workspace }}/intel-sde/sde64 -spr -- + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER: ${{ steps.sde.outputs.sde-path }} -spr -- run: | set -euo pipefail cargo test diff --git a/.github/workflows/coverage.yml b/.github/workflows/coverage.yml index c3781008..e93aabd2 100644 --- a/.github/workflows/coverage.yml +++ b/.github/workflows/coverage.yml @@ -20,8 +20,8 @@ permissions: jobs: coverage: - name: coverage (cargo-llvm-cov) - runs-on: ubuntu-latest + name: coverage (cargo-llvm-cov + Intel SDE) + runs-on: ubuntu-24.04 permissions: contents: read steps: @@ -38,20 +38,49 @@ jobs: - uses: Swatinem/rust-cache@e18b497796c12c097a38f9edb9d0641fb99eee32 # v2 - name: Install cargo-llvm-cov (pinned) run: cargo install cargo-llvm-cov --version 0.8.7 --locked + - name: Install Intel SDE + id: sde + uses: ./.github/actions/setup-intel-sde + - name: Sanity-check AVX-512 detection under SDE + env: + SDE_PATH: ${{ steps.sde.outputs.sde-path }} + run: | + set -euo pipefail + mkdir -p "${RUNNER_TEMP}/sde-probe/src" + cat > "${RUNNER_TEMP}/sde-probe/Cargo.toml" <<'EOF' + [package] + name = "sde-probe" + version = "0.0.0" + edition = "2021" + [[bin]] + name = "sde-probe" + path = "src/main.rs" + EOF + cat > "${RUNNER_TEMP}/sde-probe/src/main.rs" <<'EOF' + fn main() { + let f = is_x86_feature_detected!("avx512f"); + let p = is_x86_feature_detected!("avx512vpopcntdq"); + println!("avx512f={f} avx512vpopcntdq={p}"); + assert!(f, "SDE did not expose avx512f to the guest"); + assert!(p, "SDE did not expose avx512vpopcntdq to the guest"); + } + EOF + cargo build --release --manifest-path "${RUNNER_TEMP}/sde-probe/Cargo.toml" + "${SDE_PATH}" -spr -- \ + "${RUNNER_TEMP}/sde-probe/target/release/sde-probe" # Generate the lcov report AND enforce a regression floor in a single # --all-features invocation, so the floor is computed on exactly the data # that gets uploaded (no separate `report` step that could differ in # selection). On a regression this command exits non-zero, so the upload # below is skipped. # - # The floor is 78%, set under the AVX-512-free figure: this runner has no - # AVX-512, so the runtime SIMD dispatch never reaches the AVX-512 kernels - # (bitmap / quant_kernels / sign_bitmap) and line coverage here is ~82% - # (vs ~90% on an AVX-512 host). Those kernels are exercised by the separate - # `avx512` job under Intel SDE. Follow-up: run THIS job under SDE too, for - # the full number and a tighter floor. + # Run the instrumented test binaries through Intel SDE (-spr) so runtime + # feature detection reaches the AVX-512 kernels. That makes the coverage + # floor reflect the same exercised code as the dedicated ci.yml avx512 job. - name: Generate coverage (lcov) + enforce floor - run: cargo llvm-cov --all-features --fail-under-lines 78 --lcov --output-path lcov.info + env: + CARGO_TARGET_X86_64_UNKNOWN_LINUX_GNU_RUNNER: ${{ steps.sde.outputs.sde-path }} -spr -- + run: cargo llvm-cov --all-features --target x86_64-unknown-linux-gnu --fail-under-lines 85 --lcov --output-path lcov.info - name: Upload coverage to Codecov uses: codecov/codecov-action@e79a6962e0d4c0c17b229090214935d2e33f8354 # v6.0.1 with: diff --git a/codecov.yml b/codecov.yml index 05f4ee71..ac9f14f7 100644 --- a/codecov.yml +++ b/codecov.yml @@ -1,22 +1,19 @@ # Codecov is a dashboard + README badge for this repo. The *enforced* coverage -# gate is the cargo-llvm-cov `--fail-under-lines 78` floor in -# .github/workflows/coverage.yml — set under the AVX-512-free runner figure: -# the hosted coverage runner has no AVX-512, so the runtime SIMD dispatch never -# reaches the AVX-512 kernels (they are exercised by the separate `avx512` job -# under Intel SDE). See issue #68. +# gate is the cargo-llvm-cov `--fail-under-lines 85` floor in +# .github/workflows/coverage.yml. That workflow runs instrumented test binaries +# through Intel SDE's Sapphire Rapids emulation, so runtime SIMD dispatch reaches +# the AVX-512 kernels while the same lcov report is uploaded here. See issue #68. coverage: status: project: default: - target: 78% # mirror the enforced cargo-llvm-cov floor + target: 85% # mirror the enforced cargo-llvm-cov floor threshold: 1% patch: default: - # The AVX-512 kernels cannot be covered on the no-AVX-512 coverage - # runner, so patch coverage on any SIMD-kernel change is a false signal - # (touching a kernel re-indents lines the runner never executes — see - # #68). Keep patch advisory rather than blocking PRs on it; real - # coverage enforcement lives in the workflow floor above. + # Keep patch coverage advisory: the SDE-backed project floor above is + # the blocking gate, while per-PR patch percentages are noisy for + # branch-heavy SIMD and workflow-only changes. informational: true # The cargo-fuzz workspace is excluded from the crate build and is not part of