studio: pick a macOS llama.cpp prebuilt that loads on the host OS (#5883)

Make macOS llama.cpp prebuilt selection host-OS-version aware: skip a prebuilt whose minimum-OS exceeds the host and walk back to the newest release that loads (macOS 26 keeps latest; 14/15 land on a compatible older release). Source-build fallback pins CMAKE_OSX_DEPLOYMENT_TARGET=13.3. CI: binary-load assertion plus a macos-14/15/26 install matrix. No change to Linux/Windows or CUDA selection.
This commit is contained in:
Daniel Han 2026-05-31 00:59:25 -07:00 committed by GitHub
commit 366937de44
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
9 changed files with 705 additions and 53 deletions

57
.github/scripts/assert-llama-loads.sh vendored Executable file
View file

@ -0,0 +1,57 @@
#!/usr/bin/env bash
# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved.
#
# Assert Studio installed a llama.cpp that loads and runs on THIS macOS. Tests
# the contract that matters (binaries load and their minimum-OS is <= this host)
# instead of the old "did install.sh fall back to a source build?" grep, since a
# source build with a correct deployment target is a valid outcome.
set -uo pipefail
UNSLOTH_HOME="${STUDIO_HOME:-$HOME/.unsloth}"
LLAMA_DIR="${LLAMA_CPP_DIR:-$UNSLOTH_HOME/llama.cpp}"
BIN_DIR="$LLAMA_DIR/build/bin"
fail() {
echo "::error::$*"
if [ -f logs/install.log ]; then
echo "---- install.log (llama.cpp lines) ----"
grep -E "llama-prebuilt|llama\.cpp|macos prebuilt|falling back" logs/install.log | tail -80 || true
fi
exit 1
}
SERVER="$(find "$LLAMA_DIR" -type f -name 'llama-server' 2>/dev/null | head -1)"
QUANT="$(find "$LLAMA_DIR" -type f -name 'llama-quantize' 2>/dev/null | head -1)"
[ -n "$SERVER" ] || fail "llama-server not found under $LLAMA_DIR after install"
[ -n "$QUANT" ] || fail "llama-quantize not found under $LLAMA_DIR after install"
HOST_VER="$(sw_vers -productVersion 2>/dev/null || echo '0')"
HOST_MAJOR="${HOST_VER%%.*}"
# Static minimum-OS check on every Mach-O we ship. vtool ships with the Xcode
# command line tools, which GitHub macOS runners always have; if it is somehow
# missing we skip the static check and rely on the runtime launch below.
if command -v vtool >/dev/null 2>&1; then
while IFS= read -r macho; do
[ -n "$macho" ] || continue
minos="$(vtool -show-build "$macho" 2>/dev/null | awk '/minos/{print $2; exit}')"
[ -n "$minos" ] || continue
min_major="${minos%%.*}"
if [ "$min_major" -gt "$HOST_MAJOR" ] 2>/dev/null; then
fail "$(basename "$macho") is built for macOS $minos but this runner is macOS $HOST_VER (prebuilt is newer than the host)"
fi
done < <(find "$BIN_DIR" -type f \( -name '*.dylib' -o -name 'llama-server' -o -name 'llama-quantize' \) 2>/dev/null)
fi
# Runtime launch: --version forces dyld to load every linked dylib (including
# libggml-metal.dylib). A missing Metal symbol or too-new binary fails here.
if ! "$SERVER" --version >/tmp/llama-server-version.txt 2>&1; then
echo "---- llama-server --version output ----"
cat /tmp/llama-server-version.txt || true
fail "llama-server failed to launch on macOS $HOST_VER (dyld load / symbol error)"
fi
echo "llama.cpp load validation passed on macOS $HOST_VER"
echo " server: $SERVER"
sed -n '1,4p' /tmp/llama-server-version.txt 2>/dev/null || true

View file

@ -89,13 +89,8 @@ jobs:
set -o pipefail
bash install.sh --local --no-torch 2>&1 | tee logs/install.log
- name: Assert install.sh used the Mac llama.cpp prebuilt
run: |
if grep -q "falling back to source build" logs/install.log; then
echo "::error::install.sh fell back to source-build llama.cpp on Mac. Studio must install the prebuilt llama-bNNNN-bin-macos-arm64 on Apple Silicon."
grep -E "llama-prebuilt|llama.cpp" logs/install.log | tail -60
exit 1
fi
- name: Assert llama.cpp loads on this macOS
run: bash .github/scripts/assert-llama-loads.sh
- name: Install pyjwt for the JWT-expiry forge test
run: pip install 'pyjwt>=2.6'

View file

@ -114,13 +114,8 @@ jobs:
set -o pipefail
bash install.sh --local --no-torch 2>&1 | tee logs/install.log
- name: Assert install.sh used the Mac llama.cpp prebuilt
run: |
if grep -q "falling back to source build" logs/install.log; then
echo "::error::install.sh fell back to source-build llama.cpp on Mac. Studio must install the prebuilt llama-bNNNN-bin-macos-arm64 on Apple Silicon."
grep -E "llama-prebuilt|llama.cpp" logs/install.log | tail -60
exit 1
fi
- name: Assert llama.cpp loads on this macOS
run: bash .github/scripts/assert-llama-loads.sh
- name: Install OpenAI + Anthropic Python SDKs
run: pip install 'openai>=1.50' 'anthropic>=0.40'
@ -369,13 +364,8 @@ jobs:
set -o pipefail
bash install.sh --local --no-torch 2>&1 | tee logs/install.log
- name: Assert install.sh used the Mac llama.cpp prebuilt
run: |
if grep -q "falling back to source build" logs/install.log; then
echo "::error::install.sh fell back to source-build llama.cpp on Mac. Studio must install the prebuilt llama-bNNNN-bin-macos-arm64 on Apple Silicon."
grep -E "llama-prebuilt|llama.cpp" logs/install.log | tail -60
exit 1
fi
- name: Assert llama.cpp loads on this macOS
run: bash .github/scripts/assert-llama-loads.sh
- name: Reset auth + boot Studio (API-only, default tool policy)
# We deliberately use the API-only mode rather than
@ -760,13 +750,8 @@ jobs:
set -o pipefail
bash install.sh --local --no-torch 2>&1 | tee logs/install.log
- name: Assert install.sh used the Mac llama.cpp prebuilt
run: |
if grep -q "falling back to source build" logs/install.log; then
echo "::error::install.sh fell back to source-build llama.cpp on Mac. Studio must install the prebuilt llama-bNNNN-bin-macos-arm64 on Apple Silicon."
grep -E "llama-prebuilt|llama.cpp" logs/install.log | tail -60
exit 1
fi
- name: Assert llama.cpp loads on this macOS
run: bash .github/scripts/assert-llama-loads.sh
- name: Install OpenAI + Anthropic Python SDKs
run: pip install 'openai>=1.50' 'anthropic>=0.40'

View file

@ -0,0 +1,80 @@
# SPDX-License-Identifier: AGPL-3.0-only
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved.
# Proves Studio's llama.cpp install loads on every supported macOS. The heavy
# app smokes stay single-OS; this matrix covers the OS-version dimension cheaply
# (install.sh + binary-load assert). Regression guard for the macOS-version
# selection in studio/install_llama_prebuilt.py.
name: Mac Studio Install Matrix CI
on:
pull_request:
paths:
- 'studio/install_llama_prebuilt.py'
- 'studio/setup.sh'
- 'install.sh'
- '.github/scripts/assert-llama-loads.sh'
- '.github/workflows/studio-mac-install-matrix.yml'
push:
branches: [main, pip]
workflow_dispatch:
concurrency:
group: ${{ github.workflow }}-${{ github.ref }}
cancel-in-progress: true
permissions:
contents: read
jobs:
install-load:
name: Install + load (${{ matrix.os }})
runs-on: ${{ matrix.os }}
timeout-minutes: 25
continue-on-error: ${{ matrix.experimental }}
strategy:
fail-fast: false
matrix:
include:
- os: macos-14 # Apple Silicon, macOS 14 Sonoma
experimental: false
- os: macos-15 # Apple Silicon, macOS 15 Sequoia
experimental: false
- os: macos-26 # Apple Silicon, macOS 26 Tahoe
experimental: false
- os: macos-15-intel # Intel x86_64, macOS 15 (informational)
experimental: true
- os: macos-26-intel # Intel x86_64, macOS 26 (last Intel macOS)
experimental: true
steps:
- uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
with:
persist-credentials: false
- uses: actions/setup-node@48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e # v6.4.0
with:
node-version: '22'
- uses: actions/setup-python@a309ff8b426b58ec0e2a45f0f869d46889d02405 # v6.2.0
with:
python-version: '3.12'
- name: Install Studio (--local, --no-torch)
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
run: |
mkdir -p logs
set -o pipefail
bash install.sh --local --no-torch 2>&1 | tee logs/install.log
- name: Assert llama.cpp loads on this macOS
run: bash .github/scripts/assert-llama-loads.sh
- name: Upload install log
if: always()
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
with:
name: mac-install-matrix-${{ matrix.os }}-log
path: logs/install.log
retention-days: 7

View file

@ -89,13 +89,8 @@ jobs:
set -o pipefail
bash install.sh --local --no-torch 2>&1 | tee logs/install.log
- name: Assert install.sh used the Mac llama.cpp prebuilt
run: |
if grep -q "falling back to source build" logs/install.log; then
echo "::error::install.sh fell back to source-build llama.cpp on Mac. Studio must install the prebuilt llama-bNNNN-bin-macos-arm64 on Apple Silicon."
grep -E "llama-prebuilt|llama.cpp" logs/install.log | tail -60
exit 1
fi
- name: Assert llama.cpp loads on this macOS
run: bash .github/scripts/assert-llama-loads.sh
- name: Install Playwright + Chromium
# No --with-deps on Mac: that flag installs Linux apt packages.

View file

@ -67,21 +67,8 @@ jobs:
set -o pipefail
bash install.sh --local --no-torch 2>&1 | tee logs/install.log
- name: Assert install.sh used the Mac llama.cpp prebuilt
run: |
# Mac install must take the prebuilt path. Source-build
# fallback here is an Unsloth bug.
if grep -q "falling back to source build" logs/install.log; then
echo "::error::install.sh fell back to source-build llama.cpp on Mac. Studio must install the prebuilt llama-bNNNN-bin-macos-arm64 on Apple Silicon."
grep -E "llama-prebuilt|llama.cpp" logs/install.log | tail -60
exit 1
fi
if ! grep -qE "prebuilt installed and validated|prebuilt up to date and validated|bin-macos-arm64" logs/install.log; then
echo "::error::no Mac prebuilt llama.cpp marker in install.log."
grep -E "llama-prebuilt|llama.cpp" logs/install.log | tail -60
exit 1
fi
echo "install.sh installed the Mac prebuilt llama.cpp"
- name: Assert llama.cpp loads on this macOS
run: bash .github/scripts/assert-llama-loads.sh
- name: First update should be a no-op (prebuilt already validated)
env:

View file

@ -19,6 +19,7 @@ import re
import shutil
import site
import socket
import struct
import subprocess
import sys
import tarfile
@ -160,6 +161,14 @@ DEFAULT_MAX_PREBUILT_RELEASE_FALLBACKS = env_int(
2,
minimum = 1,
)
# Deeper macOS-only walk-back: upstream can ship a run of prebuilts built for a
# newer macOS than the host, only caught at validate time, so an older host must
# skip the whole run. Free on new hosts (first plan validates, extras unused).
DEFAULT_MAX_MACOS_RELEASE_FALLBACKS = env_int(
"UNSLOTH_LLAMA_MAX_MACOS_RELEASE_FALLBACKS",
16,
minimum = 1,
)
FORCE_COMPILE_DEFAULT_REF = os.environ.get("UNSLOTH_LLAMA_FORCE_COMPILE_REF", "master")
DIRECT_LINUX_BUNDLE_PROFILES: dict[str, dict[str, Any]] = {
@ -231,6 +240,9 @@ class HostInfo:
has_usable_nvidia: bool
has_rocm: bool = False
rocm_gfx_target: str | None = None
# (major, minor) from platform.mac_ver(); None off macOS or if unparseable.
# Skips a macos prebuilt whose minimum-OS exceeds this host.
macos_version: tuple[int, int] | None = None
@dataclass
@ -1528,6 +1540,14 @@ def resolve_simple_install_release_plans(
requested_tag == "latest" and not published_release_tag
)
release_limit = max(1, max_release_fallbacks)
# macOS may need to walk past a run of too-new prebuilts. Only when the host
# version is known; otherwise keep the default (cannot tell up front).
if (
host.is_macos
and allow_older_release_fallback
and host.macos_version is not None
):
release_limit = max(release_limit, DEFAULT_MAX_MACOS_RELEASE_FALLBACKS)
plans: list[InstallReleasePlan] = []
last_error: PrebuiltFallback | None = None
@ -2750,6 +2770,8 @@ def detect_host() -> HostInfo:
is_x86_64 = machine in {"x86_64", "amd64"}
is_arm64 = machine in {"arm64", "aarch64"}
macos_version = parse_macos_version(platform.mac_ver()[0]) if is_macos else None
nvidia_smi = shutil.which("nvidia-smi")
driver_cuda_version = None
compute_caps: list[str] = []
@ -2925,6 +2947,7 @@ def detect_host() -> HostInfo:
has_usable_nvidia = has_usable_nvidia,
has_rocm = has_rocm,
rocm_gfx_target = rocm_gfx_target,
macos_version = macos_version,
)
@ -4872,6 +4895,189 @@ def linux_runtime_dirs(binary_path: Path) -> list[str]:
return linux_runtime_dirs_for_required_libraries(missing)
# macOS prebuilt compatibility. Upstream macos prebuilts built on a newer macOS
# (e.g. minos=26, referencing Metal-4 symbols) fail dyld load on macOS 14/15. We
# read the host macOS version and each binary's minimum-OS so selection can skip
# a too-new prebuilt and walk back to the newest release that runs on this host.
# Mach-O constants (Apple mach-o/fat.h, mach-o/loader.h, mach/machine.h).
_MACHO_FAT_MAGICS = {0xCAFEBABE, 0xCAFEBABF} # universal binary (32/64-bit fat)
_LC_VERSION_MIN_MACOSX = 0x24 # legacy min-macOS load command
_LC_BUILD_VERSION = 0x32 # modern platform+minos+sdk load command
_MACHO_PLATFORM_MACOS = 1 # LC_BUILD_VERSION platform id for macOS (iOS=2, ...)
# CPU types (base | ABI64); used to pick the host slice in a fat binary.
_CPU_TYPE_X86_64 = 0x01000007
_CPU_TYPE_ARM64 = 0x0100000C
def parse_macos_version(value: str | None) -> tuple[int, int] | None:
"""Parse a macOS product version string into (major, minor).
Handles "14.7.1", "15.5", "26.0" and bare "26". Returns None when the
value is empty or cannot be parsed (callers then defer to runtime
validation rather than rejecting every prebuilt)."""
if not value:
return None
match = re.match(r"\s*(\d+)(?:\.(\d+))?", str(value))
if not match:
return None
return int(match.group(1)), int(match.group(2) or 0)
def host_supports_macos_minos(host: HostInfo, minos: tuple[int, int] | None) -> bool:
"""True if a prebuilt requiring `minos` can load on this host. Unknown host
version or unknown minos -> True: let runtime validation decide instead of
rejecting a binary we cannot reason about."""
if minos is None or host.macos_version is None:
return True
return host.macos_version >= minos
def _macho_slice_minos(data: bytes, offset: int) -> tuple[int, int] | None:
"""Minimum macOS for a single thin Mach-O at `offset`, via LC_BUILD_VERSION
(platform macOS) or the legacy LC_VERSION_MIN_MACOSX. None if absent."""
if offset + 4 > len(data):
return None
magic = struct.unpack_from(">I", data, offset)[0]
if magic in (0xFEEDFACE, 0xFEEDFACF):
endian, is64 = ">", magic == 0xFEEDFACF
elif magic in (0xCEFAEDFE, 0xCFFAEDFE):
endian, is64 = "<", magic == 0xCFFAEDFE
else:
return None
header_size = 32 if is64 else 28
if offset + header_size > len(data):
return None
ncmds = struct.unpack_from(endian + "I", data, offset + 16)[0]
cursor = offset + header_size
for _ in range(ncmds):
if cursor + 8 > len(data):
break
cmd, cmdsize = struct.unpack_from(endian + "II", data, cursor)
if cmdsize < 8:
break
if cmd == _LC_BUILD_VERSION and cursor + 16 <= len(data):
platform_id, minos = struct.unpack_from(endian + "II", data, cursor + 8)
if platform_id == _MACHO_PLATFORM_MACOS:
return (minos >> 16) & 0xFFFF, (minos >> 8) & 0xFF
elif cmd == _LC_VERSION_MIN_MACOSX and cursor + 12 <= len(data):
version = struct.unpack_from(endian + "I", data, cursor + 8)[0]
return (version >> 16) & 0xFFFF, (version >> 8) & 0xFF
cursor += cmdsize
return None
def macho_minimum_macos(
path: Path, host: HostInfo | None = None
) -> tuple[int, int] | None:
"""Minimum macOS (major, minor) a Mach-O binary or dylib requires.
Pure-Python so it works on consumer Macs without the Xcode command line
tools (otool/vtool). For universal binaries it prefers the host-arch slice,
else the highest minos found. Returns None for non-Mach-O files or when no
version load command is present."""
try:
data = path.read_bytes()
except Exception:
return None
if len(data) < 8:
return None
magic = struct.unpack_from(">I", data, 0)[0]
if magic in _MACHO_FAT_MAGICS:
is64 = magic == 0xCAFEBABF
nfat = struct.unpack_from(">I", data, 4)[0]
entry = 8
slices: list[tuple[int, tuple[int, int]]] = []
for _ in range(nfat):
if is64:
if entry + 32 > len(data):
break
cputype = struct.unpack_from(">I", data, entry)[0]
slice_offset = struct.unpack_from(">Q", data, entry + 8)[0]
entry += 32
else:
if entry + 20 > len(data):
break
cputype = struct.unpack_from(">I", data, entry)[0]
slice_offset = struct.unpack_from(">I", data, entry + 8)[0]
entry += 20
minos = _macho_slice_minos(data, slice_offset)
if minos is not None:
slices.append((cputype, minos))
if not slices:
return None
if host is not None:
want = (
_CPU_TYPE_ARM64
if host.is_arm64
else (_CPU_TYPE_X86_64 if host.is_x86_64 else None)
)
for cputype, minos in slices:
if cputype == want:
return minos
return max(minos for _cputype, minos in slices)
return _macho_slice_minos(data, 0)
def looks_like_macos_incompatibility(text: str) -> bool:
"""True when dyld output means a prebuilt needs a newer macOS than the host
(the runtime backstop for cases the static minos scan cannot read)."""
if not text:
return False
if "built for macOS" in text and "newer than running OS" in text:
return True
return "Symbol not found" in text and "MTLResidency" in text
def macos_binary_minos_issues(
binaries: Iterable[Path],
install_dir: Path,
host: HostInfo,
) -> list[str]:
"""Issue strings for every installed Mach-O whose minimum macOS exceeds the
host. Scans the given executables plus every bundled .dylib next to them --
the dyld failure originates in libggml-metal.dylib, not the executable."""
candidates: list[Path] = list(binaries)
bin_dir = install_dir / "build" / "bin"
if bin_dir.is_dir():
candidates.extend(sorted(bin_dir.rglob("*.dylib")))
issues: list[str] = []
seen: set[Path] = set()
for path in candidates:
try:
resolved = path.resolve()
except Exception:
resolved = path
if resolved in seen or not path.exists():
continue
seen.add(resolved)
minos = macho_minimum_macos(path, host)
if minos is not None and not host_supports_macos_minos(host, minos):
issues.append(
f"{path.name}: built for macOS {minos[0]}.{minos[1]} > "
f"host macOS {host.macos_version[0]}.{host.macos_version[1]}"
)
return issues
def preflight_macos_installed_binaries(
binaries: Iterable[Path],
install_dir: Path,
host: HostInfo,
) -> None:
"""Reject a macos prebuilt whose minimum-OS is newer than the host so the
release walk-back advances to the newest compatible release. No-op when the
host macOS version is unknown (runtime validation remains the backstop)."""
if not host.is_macos or host.macos_version is None:
return
issues = macos_binary_minos_issues(binaries, install_dir, host)
if issues:
raise PrebuiltFallback(
"macos prebuilt requires a newer macOS than this host:\n"
+ "\n".join(issues)
)
def preflight_linux_installed_binaries(
binaries: Iterable[Path],
install_dir: Path,
@ -5030,10 +5236,17 @@ def validate_quantize(
or not quantized_path.exists()
or quantized_path.stat().st_size == 0
):
combined = result.stdout + ("\n" + result.stderr if result.stderr else "")
# Backstop for prebuilts the static minos scan could not read: a dyld
# "built for macOS N" / missing Metal symbol failure means this binary
# needs a newer macOS than the host, so fall back to an older release.
prefix = (
"macos prebuilt requires a newer macOS than this host: "
if looks_like_macos_incompatibility(combined)
else ""
)
raise PrebuiltFallback(
"llama-quantize validation failed:\n"
+ result.stdout
+ ("\n" + result.stderr if result.stderr else "")
prefix + "llama-quantize validation failed:\n" + combined
)
@ -5451,6 +5664,14 @@ def resolve_install_release_plans(
requested_tag == "latest" and not published_release_tag
)
release_limit = max(1, max_release_fallbacks)
# macOS may need to walk past a run of too-new prebuilts. Only when the host
# version is known; otherwise keep the default (cannot tell up front).
if (
host.is_macos
and allow_older_release_fallback
and host.macos_version is not None
):
release_limit = max(release_limit, DEFAULT_MAX_MACOS_RELEASE_FALLBACKS)
plans: list[InstallReleasePlan] = []
last_error: PrebuiltFallback | None = None
@ -5835,6 +6056,7 @@ def validate_prebuilt_choice(
choice, host, install_dir, work_dir
)
preflight_linux_installed_binaries((server_path, quantize_path), install_dir, host)
preflight_macos_installed_binaries((server_path, quantize_path), install_dir, host)
ensure_repo_shape(install_dir)
write_prebuilt_metadata(
install_dir,

View file

@ -1084,6 +1084,15 @@ else
_IS_MACOS_ARM64=true
fi
# macOS: pin a low deployment target so the source build loads on
# older macOS too (else a macOS 26 host stamps minos=26). Set before
# CPU_FALLBACK_CMAKE_ARGS copies CMAKE_ARGS so both paths inherit it.
if [ "$_HOST_SYSTEM" = "Darwin" ]; then
_MACOS_DEPLOYMENT_TARGET="${UNSLOTH_MACOS_DEPLOYMENT_TARGET:-13.3}"
CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_OSX_DEPLOYMENT_TARGET=${_MACOS_DEPLOYMENT_TARGET}"
export MACOSX_DEPLOYMENT_TARGET="${_MACOS_DEPLOYMENT_TARGET}"
fi
if command -v ccache &>/dev/null; then
CMAKE_ARGS="$CMAKE_ARGS -DCMAKE_C_COMPILER_LAUNCHER=ccache -DCMAKE_CXX_COMPILER_LAUNCHER=ccache -DCMAKE_CUDA_COMPILER_LAUNCHER=ccache"
fi

View file

@ -0,0 +1,322 @@
"""Tests for the host-macOS-version-aware llama.cpp prebuilt selection added
for the Mac "Failing CI" fix.
Covers: parse_macos_version, host_supports_macos_minos, the pure-Python Mach-O
minimum-OS parser (macho_minimum_macos), the dyld-incompatibility classifier,
the install preflight that rejects a too-new prebuilt, and the deeper macOS
release walk-back in resolve_simple_install_release_plans.
No GPU, no network, no torch, no real Mach-O toolchain required -- the Mach-O
samples are synthesized in-process and all I/O is monkeypatched.
"""
import importlib.util
import struct
import sys
from pathlib import Path
import pytest
PACKAGE_ROOT = Path(__file__).resolve().parents[3]
MODULE_PATH = PACKAGE_ROOT / "studio" / "install_llama_prebuilt.py"
SPEC = importlib.util.spec_from_file_location(
"studio_install_llama_prebuilt_macos", MODULE_PATH
)
assert SPEC is not None and SPEC.loader is not None
ILP = importlib.util.module_from_spec(SPEC)
sys.modules[SPEC.name] = ILP
SPEC.loader.exec_module(ILP)
HostInfo = ILP.HostInfo
PrebuiltFallback = ILP.PrebuiltFallback
_CPU_TYPE_ARM64 = 0x0100000C
_CPU_TYPE_X86_64 = 0x01000007
def make_macos_host(macos_version, *, arm64 = True):
return HostInfo(
system = "Darwin",
machine = "arm64" if arm64 else "x86_64",
is_windows = False,
is_linux = False,
is_macos = True,
is_x86_64 = not arm64,
is_arm64 = arm64,
nvidia_smi = None,
driver_cuda_version = None,
compute_caps = [],
visible_cuda_devices = None,
has_physical_nvidia = False,
has_usable_nvidia = False,
macos_version = macos_version,
)
def thin_macho(minos = (14, 0), *, cputype = _CPU_TYPE_ARM64, build_version = True):
"""Synthesize a minimal little-endian 64-bit Mach-O carrying a macOS
minimum-version load command."""
encoded = (minos[0] << 16) | (minos[1] << 8)
if build_version:
# LC_BUILD_VERSION: cmd, cmdsize, platform(=1 macOS), minos, sdk, ntools
load_command = struct.pack("<6I", 0x32, 24, 1, encoded, encoded, 0)
else:
# LC_VERSION_MIN_MACOSX: cmd, cmdsize, version, sdk
load_command = struct.pack("<4I", 0x24, 16, encoded, encoded)
header = struct.pack("<8I", 0xFEEDFACF, cputype, 0, 0x2, 1, len(load_command), 0, 0)
return header + load_command
def fat_macho(slices):
"""Synthesize a big-endian universal binary from (cputype, thin_bytes)."""
header = struct.pack(">2I", 0xCAFEBABE, len(slices))
data_offset = 8 + 20 * len(slices)
arch_entries = b""
body = b""
for cputype, thin in slices:
offset = data_offset + len(body)
arch_entries += struct.pack(">5I", cputype, 0, offset, len(thin), 0)
body += thin
return header + arch_entries + body
class TestParseMacosVersion:
@pytest.mark.parametrize(
"value, expected",
[
("14.7.1", (14, 7)),
("15.5", (15, 5)),
("26.0", (26, 0)),
("26", (26, 0)),
("13", (13, 0)),
("", None),
(None, None),
("not-a-version", None),
],
)
def test_parse(self, value, expected):
assert ILP.parse_macos_version(value) == expected
class TestHostSupportsMacosMinos:
def test_older_host_rejects_newer_prebuilt(self):
assert not ILP.host_supports_macos_minos(make_macos_host((14, 0)), (26, 0))
def test_same_version_supported(self):
assert ILP.host_supports_macos_minos(make_macos_host((26, 0)), (26, 0))
def test_newer_host_supports_older_prebuilt(self):
assert ILP.host_supports_macos_minos(make_macos_host((15, 5)), (14, 0))
def test_unknown_host_defers_to_runtime(self):
assert ILP.host_supports_macos_minos(make_macos_host(None), (26, 0))
def test_unknown_minos_defers_to_runtime(self):
assert ILP.host_supports_macos_minos(make_macos_host((14, 0)), None)
class TestMachoMinimumMacos:
def test_build_version_thin(self, tmp_path):
path = tmp_path / "lib.dylib"
path.write_bytes(thin_macho((26, 0)))
assert ILP.macho_minimum_macos(path) == (26, 0)
def test_legacy_version_min_thin(self, tmp_path):
path = tmp_path / "lib.dylib"
path.write_bytes(thin_macho((14, 0), build_version = False))
assert ILP.macho_minimum_macos(path) == (14, 0)
def test_universal_prefers_host_arch_slice(self, tmp_path):
# arm64 slice needs macOS 14, x86_64 slice needs macOS 26.
path = tmp_path / "fat"
path.write_bytes(
fat_macho(
[
(_CPU_TYPE_ARM64, thin_macho((14, 0), cputype = _CPU_TYPE_ARM64)),
(_CPU_TYPE_X86_64, thin_macho((26, 0), cputype = _CPU_TYPE_X86_64)),
]
)
)
assert ILP.macho_minimum_macos(path, make_macos_host((14, 0))) == (14, 0)
assert ILP.macho_minimum_macos(path, make_macos_host((26, 0), arm64 = False)) == (
26,
0,
)
def test_non_macho_returns_none(self, tmp_path):
path = tmp_path / "script.sh"
path.write_bytes(b'#!/bin/sh\nexec real "$@"\n')
assert ILP.macho_minimum_macos(path) is None
def test_missing_file_returns_none(self, tmp_path):
assert ILP.macho_minimum_macos(tmp_path / "nope") is None
class TestLooksLikeMacosIncompatibility:
def test_built_for_newer_os(self):
assert ILP.looks_like_macos_incompatibility(
"dyld: ... (built for macOS 26.0 which is newer than running OS)"
)
def test_metal_residency_symbol(self):
assert ILP.looks_like_macos_incompatibility(
"Symbol not found: _OBJC_CLASS_$_MTLResidencySetDescriptor"
)
def test_benign_error(self):
assert not ILP.looks_like_macos_incompatibility("some unrelated failure")
def test_empty(self):
assert not ILP.looks_like_macos_incompatibility("")
class TestPreflightMacosInstalledBinaries:
def _install_dir(self, tmp_path, dylib_minos):
bin_dir = tmp_path / "build" / "bin"
bin_dir.mkdir(parents = True)
(bin_dir / "libggml-metal.dylib").write_bytes(thin_macho(dylib_minos))
server = tmp_path / "llama-server"
server.write_bytes(thin_macho(dylib_minos))
quantize = tmp_path / "llama-quantize"
quantize.write_bytes(thin_macho(dylib_minos))
return tmp_path, (server, quantize)
def test_rejects_too_new_dylib(self, tmp_path):
install_dir, binaries = self._install_dir(tmp_path, (26, 0))
with pytest.raises(PrebuiltFallback, match = "newer macOS"):
ILP.preflight_macos_installed_binaries(
binaries, install_dir, make_macos_host((14, 0))
)
def test_accepts_compatible_prebuilt(self, tmp_path):
install_dir, binaries = self._install_dir(tmp_path, (14, 0))
# Must not raise on a macOS 15 host.
ILP.preflight_macos_installed_binaries(
binaries, install_dir, make_macos_host((15, 5))
)
def test_skips_when_host_version_unknown(self, tmp_path):
install_dir, binaries = self._install_dir(tmp_path, (26, 0))
# Unknown host version -> defer to runtime validation, do not raise.
ILP.preflight_macos_installed_binaries(
binaries, install_dir, make_macos_host(None)
)
def test_noop_on_non_macos_host(self, tmp_path):
install_dir, binaries = self._install_dir(tmp_path, (26, 0))
linux_host = HostInfo(
system = "Linux",
machine = "x86_64",
is_windows = False,
is_linux = True,
is_macos = False,
is_x86_64 = True,
is_arm64 = False,
nvidia_smi = None,
driver_cuda_version = None,
compute_caps = [],
visible_cuda_devices = None,
has_physical_nvidia = False,
has_usable_nvidia = False,
)
ILP.preflight_macos_installed_binaries(binaries, install_dir, linux_host)
def _fake_macos_releases(tags):
return [
{
"tag_name": tag,
"assets": [
{
"name": f"llama-{tag}-bin-macos-arm64.tar.gz",
"browser_download_url": f"https://example.com/{tag}.tar.gz",
}
],
}
for tag in tags
]
class TestMacosReleaseWalkback:
"""A known-version macOS host must generate enough older-release plans to
walk back past a run of too-new prebuilts; unknown-version and non-macOS
hosts keep the conservative 2-release default."""
TAGS = [f"b{n}" for n in range(9437, 9400, -1)] # 37 newest-first releases
def _patch_releases(self, monkeypatch):
monkeypatch.setattr(
ILP,
"iter_release_payloads_by_time",
lambda repo, published_release_tag, requested_tag: _fake_macos_releases(
self.TAGS
),
)
def test_known_macos_host_walks_back_deeper(self, monkeypatch):
self._patch_releases(monkeypatch)
_tag, plans = ILP.resolve_simple_install_release_plans(
"latest",
make_macos_host((14, 0)),
"ggml-org/llama.cpp",
"",
)
assert len(plans) == ILP.DEFAULT_MAX_MACOS_RELEASE_FALLBACKS
assert len(plans) > ILP.DEFAULT_MAX_PREBUILT_RELEASE_FALLBACKS
def test_unknown_macos_host_uses_default(self, monkeypatch):
self._patch_releases(monkeypatch)
_tag, plans = ILP.resolve_simple_install_release_plans(
"latest",
make_macos_host(None),
"ggml-org/llama.cpp",
"",
)
assert len(plans) == ILP.DEFAULT_MAX_PREBUILT_RELEASE_FALLBACKS
class TestForwardsBackwardsCompat:
"""The gate is host >= prebuilt minos with no hardcoded version, so it holds
for older and future macOS alike. Emulate the walk-back over a release set
spanning several minos tiers and assert each host takes the newest release
it can load."""
# Newest first: future 27 builds, current 26 builds, an old 14 tier, a 13.
RELEASES = [
("b9600", (27, 0)),
("b9450", (26, 0)),
("b9415", (14, 0)),
("b8300", (13, 0)),
]
def _select(self, tmp_path, host_version):
for tag, minos in self.RELEASES:
bin_dir = tmp_path / tag / "build" / "bin"
bin_dir.mkdir(parents = True)
(bin_dir / "libggml-metal.dylib").write_bytes(thin_macho(minos))
try:
ILP.preflight_macos_installed_binaries(
(), tmp_path / tag, make_macos_host(host_version)
)
return tag
except PrebuiltFallback:
continue
return None
@pytest.mark.parametrize(
"host_version, expected",
[
((13, 0), "b8300"), # older host takes the older prebuilt
((14, 7), "b9415"), # backwards: skip 26/27, take newest that loads
((15, 5), "b9415"),
((26, 0), "b9450"), # unchanged: newest <= host
((27, 1), "b9600"), # forwards: future host takes the future build
],
)
def test_selects_newest_loadable(self, tmp_path, host_version, expected):
assert self._select(tmp_path, host_version) == expected
def test_host_below_prebuilt_floor_falls_through(self, tmp_path):
# macOS 12 is below every prebuilt -> nothing matches -> source build.
assert self._select(tmp_path, (12, 0)) is None