Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 6 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,10 @@ jobs:
uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version-file: ".python-version"
- name: Detect CPU ISA features for cache key
id: cpu-id
shell: bash
run: echo "cpu-hash=$(python3 scripts/ci/cpu_isa_hash.py)" >> "$GITHUB_OUTPUT"
- name: Restore Warp kernel cache
if: github.event_name != 'merge_group'
uses: actions/cache@668228422ae6a00e4ad889ee87cd7109ec5666a7 # v5.0.4
Expand All @@ -129,9 +133,9 @@ jobs:
~/.cache/warp
~/Library/Caches/warp
~\AppData\Local\NVIDIA\warp\Cache
key: warp-kernels-${{ runner.os }}-${{ runner.arch }}-${{ hashFiles('uv.lock', 'newton/**/*.py') }}
key: warp-kernels-${{ runner.os }}-${{ runner.arch }}-${{ steps.cpu-id.outputs.cpu-hash }}-${{ hashFiles('uv.lock', 'newton/**/*.py') }}
restore-keys: |
warp-kernels-${{ runner.os }}-${{ runner.arch }}-
warp-kernels-${{ runner.os }}-${{ runner.arch }}-${{ steps.cpu-id.outputs.cpu-hash }}-
- name: Run Tests
run: uv run --extra dev -m newton.tests --no-cache-clear --junit-report-xml rspec.xml --coverage --coverage-xml coverage.xml
- name: Test Summary
Expand Down
114 changes: 114 additions & 0 deletions scripts/ci/cpu_isa_hash.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,114 @@
#!/usr/bin/env python3
# SPDX-FileCopyrightText: Copyright (c) 2026 The Newton Developers
# SPDX-License-Identifier: Apache-2.0

"""Print a short hash of the host CPU's ISA feature set.

Warp 1.13+ compiles CPU kernels with ``-march=native``, so cached object
files are only safe to reuse on a CPU with the same instruction set.
This script detects the ISA features and prints a 16-character hex hash
suitable for use in a CI cache key.

Supported platforms:
- x86_64 Linux / macOS / Windows (via system C compiler)
- AArch64 Linux (via /proc/cpuinfo)
- AArch64 macOS (via sysctl)
"""

import hashlib
import platform
import subprocess
import sys

# ISA-related macro keywords emitted by ``cc -march=native -dM -E``.
# These correspond to the instruction set extensions that affect codegen.
_X86_ISA_KEYWORDS = (
"ADX",
"AES",
"AVX",
"BMI",
"CLFLUSH",
"F16C",
"FMA",
"LZCNT",
"MMX",
"MOVBE",
"PCLMUL",
"POPCNT",
"RDRAND",
"RDSEED",
"SHA",
"SSE",
"VAES",
"VPCLMUL",
)


def _x86_features() -> str:
"""Query ISA features by asking the system C compiler what -march=native enables."""
for cc in ("cc", "gcc", "clang"):
try:
out = subprocess.check_output(
[cc, "-march=native", "-dM", "-E", "-x", "c", "-"],
input="",
text=True,
stderr=subprocess.DEVNULL,
)
except (FileNotFoundError, subprocess.CalledProcessError):
continue

macros = sorted(
line.split()[1]
for line in out.splitlines()
if line.startswith("#define __") and any(k in line for k in _X86_ISA_KEYWORDS)
)
if macros:
return " ".join(macros)

return ""


def _aarch64_features() -> str:
"""Query ISA features from /proc/cpuinfo (Linux) or sysctl (macOS)."""
# Linux: kernel exposes HWCAP flags in /proc/cpuinfo.
try:
with open("/proc/cpuinfo") as f:
for line in f:
if line.startswith("Features"):
return " ".join(sorted(line.split(":")[1].split()))
except FileNotFoundError:
pass

# macOS: sysctl exposes CPU features.
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

🟡 machdep.cpu.features only exists on Intel Macs, so on Apple Silicon runners the sysctl -n machdep.cpu.features call below raises CalledProcessError, _aarch64_features() returns "", and main() falls back to platform.processor() (which is "arm" on Apple Silicon). On macos-latest the cache key ends up not being derived from ISA features at all, so that runner effectively keeps the pre-change behavior and one of the four CI targets does not benefit from this change.

The fallback itself is safe (no crash, stable hash), but the module docstring on line 15 advertises "AArch64 macOS (via sysctl)" support that is not actually working on Apple Silicon.

On Apple Silicon, sysctl hw.optional enumerates per-feature flags such as hw.optional.neon and hw.optional.armv8_2_sha3. Parsing and sorting those keys gives a real ISA fingerprint.

Example macOS ARM branch
# macOS Apple Silicon: hw.optional.* enumerates ISA features.
try:
    out = subprocess.check_output(
        ["sysctl", "-a"],
        text=True,
        stderr=subprocess.DEVNULL,
    )
    features = sorted(
        line.split(":", 1)[0].strip()
        for line in out.splitlines()
        if line.startswith("hw.optional.") and line.rstrip().endswith(": 1")
    )
    if features:
        return " ".join(features)
except (FileNotFoundError, subprocess.CalledProcessError):
    pass

Worth updating the docstring once the macOS ARM path actually contributes ISA features to the hash.

try:
out = subprocess.check_output(
["sysctl", "-n", "machdep.cpu.features"],
text=True,
stderr=subprocess.DEVNULL,
)
if out.strip():
return " ".join(sorted(out.strip().split()))
except (FileNotFoundError, subprocess.CalledProcessError):
pass

return ""


def get_features() -> str:
machine = platform.machine().lower()
if machine in ("x86_64", "amd64", "x86", "i686"):
return _x86_features()
if machine in ("aarch64", "arm64"):
return _aarch64_features()
return ""


def main() -> None:
features = get_features() or platform.processor()
h = hashlib.sha256(features.encode()).hexdigest()[:16]
print(f"features: {features}", file=sys.stderr)
print(h)


if __name__ == "__main__":
main()
Loading