diff --git a/.github/scripts/assert-llama-loads.sh b/.github/scripts/assert-llama-loads.sh
new file mode 100755
index 0000000000..c2ffe27469
--- /dev/null
+++ b/.github/scripts/assert-llama-loads.sh
@@ -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
diff --git a/.github/workflows/studio-mac-api-smoke.yml b/.github/workflows/studio-mac-api-smoke.yml
index b4e274155e..8839b559fd 100644
--- a/.github/workflows/studio-mac-api-smoke.yml
+++ b/.github/workflows/studio-mac-api-smoke.yml
@@ -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'
diff --git a/.github/workflows/studio-mac-inference-smoke.yml b/.github/workflows/studio-mac-inference-smoke.yml
index fab0a36bd1..1096b1abb4 100644
--- a/.github/workflows/studio-mac-inference-smoke.yml
+++ b/.github/workflows/studio-mac-inference-smoke.yml
@@ -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'
diff --git a/.github/workflows/studio-mac-install-matrix.yml b/.github/workflows/studio-mac-install-matrix.yml
new file mode 100644
index 0000000000..4e2722d1cd
--- /dev/null
+++ b/.github/workflows/studio-mac-install-matrix.yml
@@ -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
diff --git a/.github/workflows/studio-mac-ui-smoke.yml b/.github/workflows/studio-mac-ui-smoke.yml
index b353f0ec83..0176b0a168 100644
--- a/.github/workflows/studio-mac-ui-smoke.yml
+++ b/.github/workflows/studio-mac-ui-smoke.yml
@@ -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.
diff --git a/.github/workflows/studio-mac-update-smoke.yml b/.github/workflows/studio-mac-update-smoke.yml
index b65439f174..1eca227096 100644
--- a/.github/workflows/studio-mac-update-smoke.yml
+++ b/.github/workflows/studio-mac-update-smoke.yml
@@ -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:
diff --git a/studio/backend/tests/test_gguf_routing.py b/studio/backend/tests/test_gguf_routing.py
new file mode 100644
index 0000000000..1b299cba19
--- /dev/null
+++ b/studio/backend/tests/test_gguf_routing.py
@@ -0,0 +1,102 @@
+# SPDX-License-Identifier: AGPL-3.0-only
+# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
+
+"""
+Tests for GGUF routing in detect_gguf_model.
+
+Regression test for the bug where a .gguf file temporarily appears
+inaccessible on Windows during llama-server process teardown, causing
+is_file() to return False and the model to be routed to the transformers
+backend instead of llama-server.
+"""
+
+import sys
+import os
+import types
+from pathlib import Path
+from unittest.mock import patch
+
+# Stub structlog before importing backend modules (mirrors other tests in this suite)
+if "structlog" not in sys.modules:
+
+ class _DummyLogger:
+ def __getattr__(self, _):
+ return lambda *a, **k: None
+
+ sys.modules["structlog"] = types.SimpleNamespace(
+ get_logger = lambda *a, **k: _DummyLogger(),
+ BoundLogger = _DummyLogger,
+ )
+
+sys.path.insert(0, os.path.join(os.path.dirname(__file__), ".."))
+
+from utils.models.model_config import detect_gguf_model
+
+
+def test_detects_gguf_file_normally(tmp_path):
+ """Normal case: .gguf file exists and is accessible."""
+ gguf = tmp_path / "gpt-oss-20b-MXFP4.gguf"
+ gguf.write_bytes(b"")
+ result = detect_gguf_model(str(gguf))
+ assert result is not None
+ assert result.endswith("gpt-oss-20b-MXFP4.gguf")
+
+
+def test_detects_gguf_when_stat_raises_oserror(tmp_path):
+ """
+ Regression: on Windows, both is_file() and exists() call stat() internally.
+ During the brief lock window after llama-server is killed, stat() raises
+ OSError, causing both to return False. detect_gguf_model must still route
+ to llama-server based on the file extension alone.
+ """
+ gguf = tmp_path / "gpt-oss-20b-MXFP4.gguf"
+ gguf.write_bytes(b"")
+
+ original_stat = Path.stat
+
+ def flaky_stat(self, **kwargs):
+ if self.suffix.lower() == ".gguf":
+ raise OSError("file temporarily inaccessible (Windows lock window)")
+ return original_stat(self, **kwargs)
+
+ with patch.object(Path, "stat", flaky_stat):
+ result = detect_gguf_model(str(gguf))
+
+ assert result is not None, (
+ "detect_gguf_model returned None when stat() raised OSError. "
+ "This causes the model to fall through to the transformers backend."
+ )
+
+
+def test_does_not_detect_mmproj_as_main_model(tmp_path):
+ """mmproj files must never be returned as the primary model."""
+ mmproj = tmp_path / "mmproj-model-f16.gguf"
+ mmproj.write_bytes(b"")
+ result = detect_gguf_model(str(mmproj))
+ assert result is None
+
+
+def test_detects_gguf_in_directory(tmp_path):
+ """Directory containing a .gguf file is resolved to that file."""
+ gguf = tmp_path / "model-Q4_K_M.gguf"
+ gguf.write_bytes(b"")
+ result = detect_gguf_model(str(tmp_path))
+ assert result is not None
+ assert result.endswith("model-Q4_K_M.gguf")
+
+
+def test_directory_named_like_gguf_scans_inside(tmp_path):
+ """A directory named *.gguf resolves the real .gguf inside, not itself."""
+ gguf_dir = tmp_path / "mymodel.gguf"
+ gguf_dir.mkdir()
+ inner = gguf_dir / "model-Q4_K_M.gguf"
+ inner.write_bytes(b"")
+ result = detect_gguf_model(str(gguf_dir))
+ assert result is not None
+ assert result.endswith("model-Q4_K_M.gguf")
+
+
+def test_returns_none_for_non_gguf_path(tmp_path):
+ """Non-.gguf paths with no .gguf files inside return None."""
+ result = detect_gguf_model(str(tmp_path))
+ assert result is None
diff --git a/studio/backend/utils/models/model_config.py b/studio/backend/utils/models/model_config.py
index 993995ee57..dc34444ccb 100644
--- a/studio/backend/utils/models/model_config.py
+++ b/studio/backend/utils/models/model_config.py
@@ -1156,13 +1156,18 @@ def detect_gguf_model(path: str) -> Optional[str]:
p = Path(path)
# Case 1: direct .gguf file
- if p.suffix.lower() == ".gguf" and p.is_file():
+ if p.suffix.lower() == ".gguf":
if _is_mmproj(p.name):
return None
- # Use absolute (not resolve) to preserve symlink names -- e.g.
- # Ollama .studio_links/model.gguf -> blobs/sha256-... should
- # keep the readable symlink name, not the opaque blob hash.
- return str(p.absolute())
+ # Extension is authoritative: don't gate on is_file()/exists(), which
+ # can fail in the Windows lock window after llama-server is killed.
+ try:
+ is_dir = p.is_dir()
+ except OSError:
+ is_dir = False # stat() unavailable in the lock window
+ if not is_dir:
+ return str(p.absolute()) # absolute() keeps symlink names readable
+ # Directory named "*.gguf": fall through to the dir scan below.
# Case 2: directory containing .gguf files (skip mmproj)
if p.is_dir():
diff --git a/studio/frontend/src/components/app-sidebar.tsx b/studio/frontend/src/components/app-sidebar.tsx
index 849e017ea8..4e4a6130cd 100644
--- a/studio/frontend/src/components/app-sidebar.tsx
+++ b/studio/frontend/src/components/app-sidebar.tsx
@@ -49,7 +49,7 @@ import {
Edit03Icon,
Globe02Icon,
HelpCircleIcon,
- Logout01Icon,
+ Logout05Icon,
Search01Icon,
PowerIcon,
PencilEdit02Icon,
@@ -796,7 +796,7 @@ export function AppSidebar() {
void navigate({ to: "/login" });
}}
>
-
+ {t("shell.navigation.logOut")} setShutdownOpen(true)}>
diff --git a/studio/install_llama_prebuilt.py b/studio/install_llama_prebuilt.py
index 8b979b092d..af42b5d0af 100644
--- a/studio/install_llama_prebuilt.py
+++ b/studio/install_llama_prebuilt.py
@@ -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]] = {
@@ -213,6 +222,71 @@ DIRECT_LINUX_BUNDLE_PROFILES: dict[str, dict[str, Any]] = {
},
}
+# Lowest CUDA major we ship prebuilts for, and the highest major we probe for
+# installed runtime libraries. Detection and runtime-line derivation are
+# generated per major so a new toolkit (cuda14, ...) needs no code change while
+# llama.cpp keeps the cudart64_.dll / libcudart.so. naming.
+_MIN_CUDA_MAJOR = 12
+_MAX_PROBE_CUDA_MAJOR = 19
+
+# Last ggml-org release whose Windows win-cuda-13 build is still sub-13.3
+# (cuda-13.1, b9360, 2026-05-27). Upstream bumped win-cuda-13 to 13.3 at b9365
+# and now ships only cuda-12.4 + cuda-13.3. cuda-12.4 predates Blackwell (ggml
+# compiles sm_120 only at toolkit >= 12.8), so a Blackwell host on a 13.1/13.2
+# driver is gated off 13.3 and would drop to a CPU-only 12.4 build. b9360 is
+# immutable, so we pin its cuda-13.1 build (plus paired cudart) as a GPU
+# fallback for exactly those hosts. See unslothai/unsloth#5887.
+_PINNED_BLACKWELL_FALLBACK_TAG = "b9360"
+_PINNED_BLACKWELL_FALLBACK_RUNTIME = "13.1"
+_PINNED_BLACKWELL_DRIVER_FLOOR = (13, 1)
+_BLACKWELL_MIN_SM = 120
+# ggml compiles Blackwell sm_120 only at toolkit >= 12.8, so an in-release
+# windows-cuda build at or above this already covers Blackwell and makes the
+# older pinned 13.1 fallback unnecessary (cuda-12.4 is below it).
+_BLACKWELL_MIN_TOOLKIT = (12, 8)
+_PINNED_BLACKWELL_LLAMA_SHA256 = (
+ "31ddb8b42d7ab4a47cab8c48c397519f580ca502df7e73f3ab396eacc16c8e8d"
+)
+_PINNED_BLACKWELL_CUDART_SHA256 = (
+ "f96935e7e385e3b2d0189239077c10fe8fd7e95690fea4afec455b1b6c7e3f18"
+)
+
+
+def _cuda_runtime_lines_for_major(major: int) -> list[str]:
+ """Runtime lines a driver of this CUDA major can use, newest major first
+ down to the minimum we ship. A driver runs its own major and any older one
+ (backward compatibility)."""
+ return [f"cuda{m}" for m in range(major, _MIN_CUDA_MAJOR - 1, -1)]
+
+
+def _resolve_linux_bundle_profile(bundle_profile: str) -> "dict[str, Any] | None":
+ """Profile (runtime line + sm coverage) for a linux-x64-cuda-
+ bundle. Known majors use their published coverage; an unknown future major
+ reuses the newest known major's coverage for the same class as a forward
+ default, with the post-build GPU smoke test as the backstop."""
+ known = DIRECT_LINUX_BUNDLE_PROFILES.get(bundle_profile)
+ if known is not None:
+ return known
+ m = re.fullmatch(
+ r"cuda(?P\d+)-(?Polder|newer|portable)", bundle_profile
+ )
+ if not m:
+ return None
+ base_key = max(
+ (
+ k
+ for k, v in DIRECT_LINUX_BUNDLE_PROFILES.items()
+ if v["coverage_class"] == m.group("klass")
+ ),
+ key = lambda k: int(re.match(r"cuda(\d+)-", k).group(1)),
+ default = None,
+ )
+ if base_key is None:
+ return None
+ profile = dict(DIRECT_LINUX_BUNDLE_PROFILES[base_key])
+ profile["runtime_line"] = f"cuda{m.group('major')}"
+ return profile
+
@dataclass
class HostInfo:
@@ -231,6 +305,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
@@ -757,6 +834,26 @@ def windows_cuda_asset_aliases(
return aliases
+def _published_windows_cuda_runtime(
+ upstream_assets: dict[str, str], major: int, driver: tuple[int, int] | None
+) -> str | None:
+ """Highest cuda-. published upstream that `driver` can run by
+ default CUDA compatibility, i.e. (major, minor) <= driver. None if nothing
+ qualifies. Gating on the driver (not just the major) keeps a 13.3 build off
+ a driver that only advertises 13.1, where it would otherwise rely on the
+ unguaranteed minor-version-compatibility path."""
+ if driver is None:
+ return None
+ best: int | None = None
+ for name in upstream_assets:
+ m = re.search(r"-bin-win-cuda-(\d+)\.(\d+)-x64\.zip$", name)
+ if m and int(m.group(1)) == major:
+ minor = int(m.group(2))
+ if (major, minor) <= driver and (best is None or minor > best):
+ best = minor
+ return f"{major}.{best}" if best is not None else None
+
+
def format_byte_count(num_bytes: float) -> str:
units = ["B", "KiB", "MiB", "GiB", "TiB"]
value = float(num_bytes)
@@ -1341,7 +1438,7 @@ def parse_direct_linux_release_bundle(
inferred_labels: list[str] = []
linux_asset_re = re.compile(
- r"^app-(?P