diff --git a/studio/backend/tests/test_lemonade_llamacpp_rocm_bins_mock.py b/studio/backend/tests/test_lemonade_llamacpp_rocm_bins_mock.py index 2db541894d..5ee4e9ae6b 100644 --- a/studio/backend/tests/test_lemonade_llamacpp_rocm_bins_mock.py +++ b/studio/backend/tests/test_lemonade_llamacpp_rocm_bins_mock.py @@ -423,3 +423,62 @@ def test_pick_rocm_gfx_target_same_arch_multi_gpu(monkeypatch): monkeypatch.delenv("CUDA_VISIBLE_DEVICES", raising = False) monkeypatch.setenv("HIP_VISIBLE_DEVICES", "2") assert _pick_rocm_gfx_target(probe_out) == "gfx1151" + + +# --------------------------------------------------------------------------- +# Fork release scan: Windows ROCm resolves lemonade by the requested tag +# --------------------------------------------------------------------------- + +_resolve_release_asset_choice = getattr(_mod, "resolve_release_asset_choice", None) +_ApprovedReleaseChecksums = getattr(_mod, "ApprovedReleaseChecksums", None) + + +@pytest.mark.skipif( + _resolve_release_asset_choice is None or _ApprovedReleaseChecksums is None, + reason = "fork release planner not present on this branch", +) +def test_fork_scan_windows_rocm_resolves_lemonade_by_requested_tag(): + """The fork release scan pins llama_tag to per-release upstream tags + (b9457, ...) that lemonade's own tag series never contains, so the + lemonade lookup must use the requested tag ("latest") instead. Pinning + lemonade to the per-release tag 404s on every scanned release and a + Windows ROCm host ends in a rate-limited fatal instead of the lemonade + prebuilt.""" + host = _make_rocm_host("gfx1151", windows = True) + # No windows-rocm artifact in the bundle, matching current fork releases. + bundle = _rocm_bundle("gfx1151", ["gfx1151"]) + checksums = _ApprovedReleaseChecksums( + repo = "unslothai/llama.cpp", + release_tag = "v1.0", + upstream_tag = "b9457", + artifacts = {}, + ) + seen_urls: list[str] = [] + + def _fake_fetch(api_url, *args, **kwargs): + seen_urls.append(api_url) + if "lemonade-sdk" in api_url: + if api_url.endswith("/releases/latest"): + return _stub_lemonade_release() + raise RuntimeError(f"unexpected pinned lemonade fetch: {api_url}") + # ggml-org asset listing for the upstream HIP/CPU filename fallbacks. + return {"tag_name": "b9457", "assets": []} + + with patch.object(_mod, "fetch_json", side_effect = _fake_fetch): + attempts = _resolve_release_asset_choice( + host, + "b9457", # concrete per-release upstream tag from the scan loop + bundle, + checksums, + requested_tag = "latest", + ) + + lemonade = [a for a in attempts if a.source_label == "lemonade"] + assert lemonade, f"lemonade attempt missing for Windows ROCm host; got {attempts}" + assert "gfx1151" in lemonade[0].name + assert any( + u.endswith("/releases/latest") for u in seen_urls + ), f"lemonade was never resolved via /releases/latest; fetches: {seen_urls}" + assert not any( + "lemonade-sdk" in u and "/releases/tags/" in u for u in seen_urls + ), f"lemonade lookup was pinned to the fork release tag: {seen_urls}" diff --git a/studio/install_llama_prebuilt.py b/studio/install_llama_prebuilt.py index fb94581b9c..9aea485827 100644 --- a/studio/install_llama_prebuilt.py +++ b/studio/install_llama_prebuilt.py @@ -3702,7 +3702,14 @@ def resolve_lemonade_rocm_choice( ) -def resolve_upstream_asset_choice(host: HostInfo, llama_tag: str) -> AssetChoice: +def resolve_upstream_asset_choice( + host: HostInfo, llama_tag: str, lemonade_tag: "str | None" = None +) -> AssetChoice: + # lemonade_tag: tag for the lemonade lookup only. The release scan pins + # llama_tag to per-release upstream tags (b9518, ...) that lemonade's own + # tag series (b1292, ...) never contains, so pinning lemonade to them 404s + # on every scanned release. Scan callers pass the original request + # (normally "latest") here; upstream asset names keep the pinned tag. upstream_assets = github_release_assets(UPSTREAM_REPO, llama_tag) if host.is_linux and host.is_x86_64: # AMD ROCm: try upstream ROCm prebuilt first, then fall back to source build. @@ -3713,7 +3720,7 @@ def resolve_upstream_asset_choice(host: HostInfo, llama_tag: str) -> AssetChoice # Try lemonade-sdk per-GPU prebuilt first: these are built against # specific gfx targets and bundle all required ROCm runtime libs. lemonade_choice = resolve_lemonade_rocm_choice( - host, "ubuntu", "linux-rocm", llama_tag = llama_tag + host, "ubuntu", "linux-rocm", llama_tag = lemonade_tag or llama_tag ) if lemonade_choice is not None: return lemonade_choice @@ -3799,7 +3806,7 @@ def resolve_upstream_asset_choice(host: HostInfo, llama_tag: str) -> AssetChoice # AMD ROCm on Windows: try lemonade per-GPU prebuilt first, then upstream HIP if host.has_rocm: lemonade_choice = resolve_lemonade_rocm_choice( - host, "windows", "windows-hip", llama_tag = llama_tag + host, "windows", "windows-hip", llama_tag = lemonade_tag or llama_tag ) if lemonade_choice is not None: return lemonade_choice @@ -3858,12 +3865,14 @@ def resolve_upstream_asset_choice(host: HostInfo, llama_tag: str) -> AssetChoice raise PrebuiltFallback(f"no prebuilt policy exists for {host.system} {host.machine}") -def resolve_asset_choice(host: HostInfo, llama_tag: str) -> AssetChoice: +def resolve_asset_choice( + host: HostInfo, llama_tag: str, lemonade_tag: "str | None" = None +) -> AssetChoice: if host.is_linux and host.is_x86_64 and host.has_usable_nvidia: raise PrebuiltFallback( "Linux CUDA installs require a compatible published bundle; upstream fallback is not available" ) - return resolve_upstream_asset_choice(host, llama_tag) + return resolve_upstream_asset_choice(host, llama_tag, lemonade_tag = lemonade_tag) def resolve_release_asset_choice( @@ -3871,6 +3880,7 @@ def resolve_release_asset_choice( llama_tag: str, release: PublishedReleaseBundle, checksums: ApprovedReleaseChecksums, + requested_tag: "str | None" = None, ) -> list[AssetChoice]: if host.is_windows and host.is_x86_64 and host.has_usable_nvidia: torch_preference = detect_torch_cuda_runtime_preference(host) @@ -3926,7 +3936,10 @@ def resolve_release_asset_choice( f"{release.repo}@{release.release_tag} {published_choice.name} ({exc})" ) - return apply_approved_hashes([resolve_asset_choice(host, llama_tag)], checksums) + return apply_approved_hashes( + [resolve_asset_choice(host, llama_tag, lemonade_tag = requested_tag)], + checksums, + ) def extract_archive(archive_path: Path, destination: Path) -> None: @@ -5726,6 +5739,7 @@ def _fork_manifest_release_plans( resolved_tag, bundle, checksums, + requested_tag = requested_tag, ) if not attempts: raise PrebuiltFallback("no compatible prebuilt asset was found")