Skip to content

ci: resolve target-cpu=native per runner and key the cache to it - #1526

Merged
AdaWorldAPI merged 4 commits into
mainfrom
ccr-9f3b8075-74u47l-cpukey
Oct 13, 2026
Merged

AdaWorldAPI merged 4 commits into
mainfrom
ccr-9f3b8075-74u47l-cpukey

Conversation

@AdaWorldAPI

@AdaWorldAPI AdaWorldAPI commented Oct 13, 2026 •

Copy link
Copy Markdown
Owner

dir-sim log in Lance has been red on main since 10325b3, and on any PR whose job lands on the wrong runner. The lance-graph-contract build script dies with SIGILL before anything compiles.

Cause

  • .cargo/config.toml builds with -Ctarget-cpu=native.
  • dir-sim-persist.yml and identity.yml set no RUSTFLAGS and restore target/ with Swatinem/rust-cache.
  • Cargo fingerprints the flag text, which is identical on every runner. So an artifact built on a runner with AVX-512 is restored and reused on a runner without it.

The same thing happened locally when this container moved from a host with avx512fp16/bf16 to one that rustc reports as cascadelake (32 instead of 44 target features).

Change

  • .github/scripts/resolve-native-rustflags.sh writes native out as the exact feature list from rustc --print cfg -C target-cpu=native. The output is only -Ctarget-feature=+…, with no -Ctarget-cpu=<name>.
    • Why no CPU name (codex P1, fixed in 8342c9f): a named CPU switches on its model's default features, and a +feature list cannot turn them off. rustc --print cfg -Ctarget-cpu=skylake-avx512 -Ctarget-feature=+sse2 still reports avx512f. On a VM that hides AVX-512 from a CPU that keeps its model name, the earlier version compiled AVX-512 back in. The baseline CPU plus the exact list has no such hidden defaults.
    • Cost: codegen is no longer tuned for the specific CPU; correctness is unaffected. Features that stable rustc hides (amx, avx10) are not in the list, so they are never enabled.
    • Self-check: the script fails unless rustc --print cfg under the produced flags equals the native set exactly.
    • --cpu prints the name native resolves to, for logs.
  • Workflows: before each of the three cache steps in dir-sim-persist.yml and identity.yml, a step runs the script, sets RUSTFLAGS, and writes the CPU name and the flags hash to the job summary. A runner with a different CPU produces different flags, and Cargo recompiles on its own.
  • Cache key: the rust-cache key is derived from the same flags, so the key and the build cannot disagree.
  • Unchanged:
    • .cargo/config.toml sets only target-cpu, so RUSTFLAGS replacing it loses nothing.
    • build.yml, rust-test.yml and style.yml already set -C target-cpu=x86-64-v3; their artifacts do not depend on the CPU.
  • Comment fix: corrects the comment in .cargo/config.toml that claimed every workflow overrides native.

Companion: build-time drift warning in ndarray (AdaWorldAPI/ndarray#360)

This PR stops a cached artifact from reaching a CPU it was not built for. The SIGILL guard has a second gap, and it sits in ndarray.

  • The gap: ndarray::cpu_guard compares the running CPU only against the features that have a row in its table (cfg!(target_feature = …) per row). A feature the build enables but the table lacks is never checked. The guard passes and the binary can still die with SIGILL. This happens when a toolchain bump lets LLVM enable a feature the table predates.
  • The workaround: ndarray#360 adds a build.rs that reads CARGO_CFG_TARGET_FEATURE, which only build scripts see, and emits cargo:warning for every enabled feature without a guard row. The sse/sse2/x87 baseline is excluded. The parser and filter live in src/cpu_guard_drift.rs, which both build.rs and three unit tests include, so the tests exercise the code the build runs.
  • Measured (both runs at target-cpu=sapphirerapids):
    • stable rustc 1.99: silent;
    • nightly: cpu_guard has no row for enabled target features: amx-bf16, amx-int8, amx-tile, clflushopt, ermsb, lahfsahf, movdir64b, movdiri, prfchw.
  • Limits:
    • On stable, the cfg omits unstable features, so the guard and the warning are blind to them together.
    • Cargo hides build-script warnings from git and registry dependencies, so the warning only appears when ndarray itself is built. It does not appear when lance-graph builds ndarray as a git dependency.
    • Rows for the listed features (AMX also needs XCR0 tile-state gating) are a separate change.

Verification (local)

  • The flags reproduce native exactly (rustc --print cfg is identical), and compiling with them gives no rustc warnings.

  • Disable run: putting the CPU name back (-Ctarget-cpu=sapphirerapids on this cascadelake host) makes the self-check fail and list the 16 features the name adds.

  • Rebuild behaviour of lance-graph-contract in a fresh target dir (measured with the first version of the flags):

    build crates recompiled
    same CPU, built twice 0
    another CPU's flags (Sapphire Rapids) 16
  • cargo clippy -p lance-graph-contract --all-targets -- -D warnings passes with the resolved flags.

  • Both workflow files parse as YAML; the resolve step runs after the toolchain setup and before the cache.

Not yet verified on a runner: whether runners with different CPUs really get separate caches only shows once several runs have landed.

This PR is the only place the CI change lives. It was briefly ported into #1525 and taken back out (5b37379), so the NAL PR stays NAL-only. #1525 picks it up from main after this merges.

🤖 Generated with Claude Code

https://claude.ai/code/session_011BMJnDBAfzcuQh5HeT39Rh


Generated by Claude Code

.cargo/config.toml builds with target-cpu=native, and cargo fingerprints the
flag text, not the CPU. dir-sim-persist.yml and identity.yml set no RUSTFLAGS
and restore target/ from Swatinem/rust-cache, so an artifact built on a
runner with AVX-512 was reused on one without: the lance-graph-contract build
script died with SIGILL (dir-sim log in Lance, red on main since 10325b3).
Both workflows now hash /proc/cpuinfo's flags into the cache key.

Also corrects the config comment that claimed every workflow overrides
native with RUSTFLAGS; only build, rust-test and style do.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BMJnDBAfzcuQh5HeT39Rh
@coderabbitai

coderabbitai Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Warning

Review limit reached

Your organization has reached its usage spending cap. Adjust your spending cap in the billing tab.

Next included review available in 49 minutes.

Check out review usage here.

View limit details

Limit details: You’ve used the included review currently available. Your 62 included PR review attempts over the past 7 days set your current allowance at 1 review per hour.

Learn how review limits work.

Review configuration:

⚙️ Run configuration
  • Configuration used: Organization UI
  • Review profile: CHILL
  • Plan: Essentials
  • Run ID: 942f938e-0f0d-4f61-946d-00a4de977eb5
📥 Commits

Reviewing files that changed from the base of the PR and between 8884173 and 8342c9f.

📒 Files selected for processing (4)
  • .cargo/config.toml
  • .github/scripts/resolve-native-rustflags.sh
  • .github/workflows/dir-sim-persist.yml
  • .github/workflows/identity.yml
  • Autofix · Keep fixing CodeRabbit findings and required CI, and resolving merge conflicts

Warning

Billing warning: we have not been able to collect payment for this subscription for more than 72 hours. Please update the payment method or pay any pending invoices in Billing to avoid service interruption.

Comment @coderabbitai help to get the list of available commands.

A step resolves native to the CPU name and exact target-feature list
(.github/scripts/resolve-native-rustflags.sh) and sets RUSTFLAGS. Cargo then
sees different flag text on a different CPU and recompiles by itself instead
of reusing an artifact that dies with SIGILL. The cache key is derived from
the same flags, replacing the /proc/cpuinfo hash.

Checked locally: the resolved flags give the same cfg as native; the same
CPU rebuilds nothing; another CPU's flags rebuild 16 crates of
lance-graph-contract; clippy -D warnings passes with them.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BMJnDBAfzcuQh5HeT39Rh
@AdaWorldAPI AdaWorldAPI changed the title ci: key the native-target caches to the runner's CPU ci: resolve target-cpu=native per runner and key the cache to it Oct 13, 2026
Each resolve step writes the CPU name and feature hash it resolved native
to into $GITHUB_STEP_SUMMARY, so every run shows which runner CPU the job
landed on without a tracked list.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BMJnDBAfzcuQh5HeT39Rh
@AdaWorldAPI
AdaWorldAPI marked this pull request as ready for review October 13, 2026 05:15
@chatgpt-codex-connector

chatgpt-codex-connector Bot commented Oct 13, 2026 •

Copy link
Copy Markdown

Codex Review Summary

This comment shows the latest Codex review activity on this pull request.

Review Status Commit Review trigger
📝 Code Review ✅ Completed 2026-10-13T05:17:49.271159Z d30832a Draft marked ready
ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review" or "@codex security review".

Codex reacts with 👀 while any review is running, comments if it has suggestions, and reacts with 👍 once all reviews finish with no findings.

@chatgpt-codex-connector chatgpt-codex-connector Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: d30832a99c

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

features=$(rustc --print cfg -C target-cpu=native \
| sed -n 's/^target_feature="\(.*\)"$/+\1/p' | sort | paste -sd, -)
[ -n "$features" ] || { echo "resolve-native-rustflags: no target features reported" >&2; exit 1; }
echo "-Ctarget-cpu=$cpu -Ctarget-feature=$features${*:+ $*}"

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Disable CPU-model features absent from the runner

On a VM that masks AVX-512 while retaining a Skylake server CPU model, these flags are not equivalent to target-cpu=native: the named target-cpu enables its model defaults, while this generated target-feature value contains only +feature entries and therefore cannot disable features hidden by the hypervisor. With the pinned rustc 1.99.0, rustc -C help identifies this option as “target specific attributes,” and rustc --print cfg -Ctarget-cpu=skylake-avx512 -Ctarget-feature=+sse2 still reports target_feature="avx512f"; thus the identity and dir-sim workflows can compile AVX-512 instructions and then SIGILL on precisely the masked runner this change intends to support. Compare the named CPU's defaults with the native feature set and emit negative overrides such as -avx512f for missing features.

Useful? React with 👍 / 👎.

Copy link
Copy Markdown
Owner Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Correct, and reproduced here: rustc --print cfg -Ctarget-cpu=skylake-avx512 -Ctarget-feature=+sse2 still reports avx512f. Fixed in 8342c9f. The flags no longer name a CPU. They are now the target's baseline CPU plus the exact native feature list, and the script fails unless rustc --print cfg under those flags matches the native set exactly. Disable run: putting the CPU name back (sapphirerapids, on a cascadelake host) makes that check fail and list the 16 features the name adds.


Generated by Claude Code

A named target-cpu enables its model's default features, and a +feature
list cannot take them back: rustc --print cfg -Ctarget-cpu=skylake-avx512
-Ctarget-feature=+sse2 still reports avx512f. On a VM that hides AVX-512
from a CPU that keeps its model name, the resolved flags compiled AVX-512
back in, the SIGILL this script exists to prevent.

The flags are now the target's baseline CPU plus the exact native feature
list, and the script checks that rustc --print cfg under those flags equals
the native set, failing otherwise. Disable run: with the CPU name put back
(sapphirerapids on this cascadelake host) the check fails and lists the 16
features the name adds. --cpu prints the native CPU name for the job
summary.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BMJnDBAfzcuQh5HeT39Rh
AdaWorldAPI pushed a commit that referenced this pull request Oct 13, 2026
The four ci commits ported here from #1526 are reverted so this PR carries
only the NAL work and the CI change stays visible in its own PR. #1526
merges first; this branch then picks it up from main.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_011BMJnDBAfzcuQh5HeT39Rh
@AdaWorldAPI
AdaWorldAPI merged commit abc0a1f into main Oct 13, 2026
4 checks passed
AdaWorldAPI pushed a commit that referenced this pull request Oct 13, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants