Skip to content

Commit 50edf16

Browse files
committed
Do not auto-fall back to Vulkan when a HIP device mask filtered the probe
hipinfo is itself a HIP application, and AMD documents HIP_VISIBLE_DEVICES as "only devices whose index is present in the sequence are visible to HIP", with that spelling recommended on Windows. Under a mask the Windows probe therefore enumerates the visible devices, so rocm_gfx_targets is what survived the mask rather than the physical inventory the auto-Vulkan floor check assumes. A masked-out gfx1100 next to a visible gfx803 made the check conclude that no AMD GPU on the box reaches the Windows HIP prebuilt and route the install to Vulkan, which honours none of these masks and would enumerate the reserved card. Decline to guess when a mask is set: the physical inventory is unknowable from a masked probe, so keep the HIP / fork / source path. This only ever turns the automatic fallback off, never on. The driver-only single-GPU host the fallback exists for sets no mask, an all-hiding "" / -1 mask is still handled as no active target rather than a partial view, and an explicit --llama-backend vulkan or UNSLOTH_LLAMA_BACKEND=vulkan is unaffected. Reading the physical inventory through an unmasked re-probe would also correct _pick_rocm_gfx_target, which indexes the token list by the mask value and so already assumes an unmasked probe. That is pre-existing behaviour on main and is left alone here.
1 parent 792b69a commit 50edf16

2 files changed

Lines changed: 95 additions & 0 deletions

File tree

studio/backend/tests/test_install_resolve_prebuilt.py

Lines changed: 68 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,19 @@
3232
UPSTREAM = ilp.UPSTREAM_REPO # ggml-org/llama.cpp
3333

3434

35+
@pytest.fixture(autouse = True)
36+
def _no_ambient_hip_device_mask(monkeypatch):
37+
"""These tests describe hosts through HostInfo, not through the environment.
38+
39+
A HIP visible-device mask inherited from the shell (an ML box commonly exports
40+
CUDA_VISIBLE_DEVICES) means the arch probe saw only part of the GPUs, which the
41+
Windows auto-Vulkan guard treats as an unknown physical inventory. Clear all
42+
three so a host is described by its fields alone; the tests that are about the
43+
mask set it explicitly."""
44+
for _env in ("HIP_VISIBLE_DEVICES", "ROCR_VISIBLE_DEVICES", "CUDA_VISIBLE_DEVICES"):
45+
monkeypatch.delenv(_env, raising = False)
46+
47+
3548
def _host(**kw):
3649
base = dict(
3750
system = "Linux",
@@ -875,6 +888,61 @@ def test_route_to_vulkan_prebuilt_auto_fallback_when_no_amd_gpu_reaches_floor():
875888
assert routed.has_rocm is False
876889

877890

891+
@pytest.mark.parametrize(
892+
"mask_env", ["HIP_VISIBLE_DEVICES", "ROCR_VISIBLE_DEVICES", "CUDA_VISIBLE_DEVICES"]
893+
)
894+
def test_auto_vulkan_declines_when_a_hip_device_mask_filtered_the_probe(mask_env, monkeypatch):
895+
# hipinfo is a HIP application, so under a visible-device mask it enumerates only
896+
# the devices that survived it: rocm_gfx_targets is then the VISIBLE set and a
897+
# HIP-capable card can be masked out of view entirely. "No AMD GPU on this box
898+
# reaches the floor" is unprovable there, and Vulkan honours none of these masks,
899+
# so the automatic fallback must decline rather than hand it the reserved card.
900+
monkeypatch.setenv(mask_env, "1")
901+
host = _windows_amd_host(rocm_gfx_target = "gfx803", rocm_gfx_targets = ["gfx803"])
902+
assert ilp._should_auto_vulkan_for_amd_windows(host, FORK) is False
903+
routed, repo, _tag, persist = ilp._route_to_vulkan_prebuilt(host, FORK, "pin", force_cpu = False)
904+
assert routed is host
905+
assert repo == FORK
906+
assert persist is None
907+
908+
909+
@pytest.mark.parametrize("mask_value", ["", " ", "-1"])
910+
def test_auto_vulkan_still_fires_when_the_mask_hides_every_amd_gpu(mask_value, monkeypatch):
911+
# Negative control: an empty / "-1" mask is not a partial view, it hides every AMD
912+
# GPU, and _pick_rocm_gfx_target already reports that as no active target. Such a
913+
# value must not be read as "the probe list may be incomplete" and suppress the
914+
# fallback on a host whose arch came from --rocm-gfx.
915+
monkeypatch.setenv("HIP_VISIBLE_DEVICES", mask_value)
916+
host = _windows_amd_host(rocm_gfx_target = "gfx803", rocm_gfx_targets = ["gfx803"])
917+
assert ilp._should_auto_vulkan_for_amd_windows(host, FORK) is True
918+
_routed, repo, _tag, persist = ilp._route_to_vulkan_prebuilt(host, FORK, "pin", force_cpu = False)
919+
assert repo == UPSTREAM
920+
assert persist == "vulkan"
921+
922+
923+
def test_hip_device_mask_check_takes_the_first_variable_that_is_set(monkeypatch):
924+
# Same first-var-wins order as _pick_rocm_gfx_target, which reads the identical
925+
# three: HIP_VISIBLE_DEVICES="" means no AMD GPU visible even with a later
926+
# CUDA_VISIBLE_DEVICES set, so the two must not disagree about the host.
927+
monkeypatch.setenv("HIP_VISIBLE_DEVICES", "")
928+
monkeypatch.setenv("CUDA_VISIBLE_DEVICES", "1")
929+
assert ilp._hip_visible_device_mask_set() is False
930+
monkeypatch.setenv("HIP_VISIBLE_DEVICES", "1")
931+
assert ilp._hip_visible_device_mask_set() is True
932+
933+
934+
def test_masked_probe_suppression_does_not_touch_non_amd_auto_paths(monkeypatch):
935+
# The mask says nothing about an Intel iGPU, whose Vulkan auto path is unrelated
936+
# and predates this feature.
937+
monkeypatch.setenv("HIP_VISIBLE_DEVICES", "1")
938+
host = _host(
939+
system = "Windows", is_windows = True, has_intel_gpu = True,
940+
has_rocm = False, has_physical_nvidia = False, has_usable_nvidia = False,
941+
)
942+
_routed, repo, _tag, _persist = ilp._route_to_vulkan_prebuilt(host, FORK, "pin", force_cpu = False)
943+
assert repo == UPSTREAM
944+
945+
878946
def test_route_to_vulkan_prebuilt_hip_masked_host_still_honours_explicit_optin(monkeypatch):
879947
# The mask guard only suppresses the AUTOMATIC fallback; an explicit opt-in is
880948
# the user taking responsibility for the Vulkan device mask themselves.

studio/install_llama_prebuilt.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6165,6 +6165,24 @@ def _active_rocm_gfx_target(host: HostInfo) -> str | None:
61656165
return None
61666166

61676167

6168+
def _hip_visible_device_mask_set() -> bool:
6169+
"""Whether a HIP visible-device mask is filtering this process's view of the GPUs.
6170+
6171+
The Windows arch probe is hipinfo, itself a HIP application, and AMD documents these
6172+
as "only devices whose index is present in the sequence are visible to HIP" (with
6173+
HIP_VISIBLE_DEVICES the Windows spelling), so under a mask it enumerates the VISIBLE
6174+
devices, not the physical ones. Same first-var-wins order as _pick_rocm_gfx_target,
6175+
which reads the identical three. Empty / "-1" hides every AMD GPU, which that
6176+
function already reports as no active target, so it is not a partial mask here."""
6177+
for _env in ("HIP_VISIBLE_DEVICES", "ROCR_VISIBLE_DEVICES", "CUDA_VISIBLE_DEVICES"):
6178+
_val = os.environ.get(_env)
6179+
if _val is None:
6180+
continue
6181+
_val = _val.strip()
6182+
return bool(_val) and _val != "-1"
6183+
return False
6184+
6185+
61686186
def _windows_hip_gfx_targets(published_repo: str | None) -> frozenset[str]:
61696187
"""gfx targets the Windows HIP bundle of ``published_repo`` is actually built for.
61706188
@@ -6217,6 +6235,15 @@ def _should_auto_vulkan_for_amd_windows(host: HostInfo, published_repo: str | No
62176235
# would happily enumerate the HIP-capable card the user deliberately hid, possibly
62186236
# one reserved for another workload. Auto-fall back only when no AMD device on the
62196237
# box can be exposed to HIP; an explicit vulkan opt-in is unaffected.
6238+
#
6239+
# Under a mask the probe cannot supply that inventory at all: hipinfo is a HIP
6240+
# application, so it enumerates only the visible devices and rocm_gfx_targets lists
6241+
# what survived the mask rather than what is installed. "No AMD GPU here reaches the
6242+
# floor" is then unprovable, and guessing wrong is the same reserved-card handover,
6243+
# so decline to guess. The mask is only ever set deliberately, and the driver-only
6244+
# single-GPU host this fallback exists for does not set one.
6245+
if _hip_visible_device_mask_set():
6246+
return False
62206247
targets = list(dict.fromkeys([*_host_rocm_gfx_targets(host), active]))
62216248
return not any(_gfx_is_windows_hip_supported(target, published_repo) for target in targets)
62226249

0 commit comments

Comments
 (0)