fix(lemonade): pass resolved tag to lemonade resolver; add upstream HIP fallback; stub API in tests

- direct_linux_release_plan: pass bundle.upstream_tag (not requested_tag)
  to resolve_lemonade_rocm_choice so a "latest" request doesn't mix a
  newer lemonade binary with an older planned unsloth release (Codex P2)

- direct_upstream_release_plan: same fix on the Windows path (release_tag
  instead of requested_tag); also add the upstream HIP asset
  (llama-<tag>-bin-win-hip-radeon-x64.zip) as a fallback between lemonade
  and CPU so unsupported GPUs or transient lemonade failures don't silently
  downgrade to CPU when an upstream ROCm prebuilt exists (Codex P2)

- test file: stub fetch_json with a synthetic lemonade release payload so
  the suite is hermetic and not subject to GitHub API rate limits (Codex P1);
  add test_simple_policy_windows_hip_falls_back_to_upstream_when_lemonade_unavailable
  to cover the new HIP fallback path
This commit is contained in:
LeoBorcherding 2026-05-16 15:37:45 -05:00
commit 152e950a94
2 changed files with 82 additions and 13 deletions

View file

@ -3,7 +3,9 @@
"""Validates that the installer correctly resolves lemonade ROCm prebuilt assets.
Hits the real lemonade GitHub API with a faked HostInfo so no AMD GPU is needed.
Uses a faked HostInfo so no AMD GPU is needed. Network calls to the lemonade
GitHub API are stubbed out so the suite runs without internet access and is
not subject to rate limits.
"""
from __future__ import annotations
@ -11,6 +13,7 @@ from __future__ import annotations
import importlib
import sys
from pathlib import Path
from unittest.mock import patch
import pytest
@ -27,6 +30,27 @@ if resolve_lemonade_rocm_choice is None or _LEMONADE_GFX_FAMILIES is None:
pytest.skip("PR symbols not present - check branch", allow_module_level = True)
_STUB_TAG = "b1262"
_STUB_OS_PREFIXES = ("ubuntu", "windows")
_STUB_FAMILIES = ("gfx1151", "gfx1150", "gfx120X", "gfx110X", "gfx103X")
def _stub_lemonade_release() -> dict:
"""Minimal lemonade release payload covering all supported GPU/OS combinations."""
assets = [
{
"name": f"llama-{_STUB_TAG}-{prefix}-rocm-{family}-x64.zip",
"browser_download_url": (
f"https://github.com/lemonade-sdk/llamacpp-rocm/releases/download/"
f"{_STUB_TAG}/llama-{_STUB_TAG}-{prefix}-rocm-{family}-x64.zip"
),
}
for prefix in _STUB_OS_PREFIXES
for family in _STUB_FAMILIES
]
return {"tag_name": _STUB_TAG, "assets": assets}
def _make_rocm_host(gfx_target: str, *, windows: bool = False) -> HostInfo:
return HostInfo(
system = "Windows" if windows else "Linux",
@ -97,9 +121,10 @@ def test_unknown_gpu_not_in_families():
)
def test_asset_resolves_for_known_gpu(gfx, os_prefix, windows):
host = _make_rocm_host(gfx, windows = windows)
result = resolve_lemonade_rocm_choice(
host, os_prefix, "default", llama_tag = "latest"
)
with patch.object(_mod, "fetch_json", return_value = _stub_lemonade_release()):
result = resolve_lemonade_rocm_choice(
host, os_prefix, "default", llama_tag = "latest"
)
assert (
result is not None
), f"Installer will NOT fetch lemonade binary for {gfx} ({os_prefix})"
@ -147,12 +172,13 @@ def _stub_unsloth_release(release_tag: str = "b9022") -> dict:
)
def test_simple_policy_plans_lemonade_for_rocm_host():
host = _make_rocm_host("gfx1151")
plan = direct_linux_release_plan(
_stub_unsloth_release(),
host,
"unslothai/llama.cpp",
"latest",
)
with patch.object(_mod, "fetch_json", return_value = _stub_lemonade_release()):
plan = direct_linux_release_plan(
_stub_unsloth_release(),
host,
"unslothai/llama.cpp",
"latest",
)
assert plan is not None, "ROCm host should not be skipped by simple-policy planner"
kinds = [a.install_kind for a in plan.attempts]
assert (
@ -174,9 +200,39 @@ def test_simple_policy_plans_lemonade_for_windows_hip_host():
"name": "b9022",
"assets": [],
}
plan = direct_upstream_release_plan(release, host, "ggml-org/llama.cpp", "latest")
with patch.object(_mod, "fetch_json", return_value = _stub_lemonade_release()):
plan = direct_upstream_release_plan(release, host, "ggml-org/llama.cpp", "latest")
assert plan is not None, "Windows ROCm host should plan a lemonade HIP attempt"
kinds = [a.install_kind for a in plan.attempts]
assert (
"windows-hip" in kinds
), f"simple-policy planner did not include a lemonade HIP attempt; got {kinds}"
@pytest.mark.skipif(
direct_upstream_release_plan is None,
reason = "simple-policy dispatcher not present on this branch",
)
def test_simple_policy_windows_hip_falls_back_to_upstream_when_lemonade_unavailable():
"""If lemonade returns None (e.g. gfx999 or transient API failure), the planner
must still include the upstream HIP asset rather than silently downgrading to CPU."""
host = _make_rocm_host("gfx999", windows = True)
hip_asset = "llama-b9022-bin-win-hip-radeon-x64.zip"
release = {
"tag_name": "b9022",
"name": "b9022",
"assets": [
{
"name": hip_asset,
"browser_download_url": f"https://example.invalid/{hip_asset}",
},
],
}
plan = direct_upstream_release_plan(release, host, "ggml-org/llama.cpp", "latest")
assert plan is not None
kinds = [a.install_kind for a in plan.attempts]
assert (
"windows-hip" in kinds
), f"upstream HIP asset not included as fallback; got {kinds}"
hip_attempt = next(a for a in plan.attempts if a.install_kind == "windows-hip")
assert hip_attempt.source_label == "upstream"

View file

@ -1304,7 +1304,7 @@ def direct_linux_release_plan(
# If the host glibc is too old, validate_prebuilt_attempts will fail
# the lemonade attempt and we fall through to the source build.
lemonade_choice = resolve_lemonade_rocm_choice(
host, "ubuntu", "linux-rocm", llama_tag = requested_tag
host, "ubuntu", "linux-rocm", llama_tag = bundle.upstream_tag
)
if lemonade_choice is not None:
attempts.append(lemonade_choice)
@ -1358,10 +1358,23 @@ def direct_upstream_release_plan(
)
if host.has_rocm and not host.has_usable_nvidia:
lemonade_choice = resolve_lemonade_rocm_choice(
host, "windows", "windows-hip", llama_tag = requested_tag
host, "windows", "windows-hip", llama_tag = release_tag
)
if lemonade_choice is not None:
attempts.append(lemonade_choice)
hip_asset = f"llama-{release_tag}-bin-win-hip-radeon-x64.zip"
hip_url = assets.get(hip_asset)
if hip_url:
attempts.append(
AssetChoice(
repo = repo,
tag = release_tag,
name = hip_asset,
url = hip_url,
source_label = "upstream",
install_kind = "windows-hip",
)
)
cpu_asset = f"llama-{release_tag}-bin-win-cpu-x64.zip"
cpu_url = assets.get(cpu_asset)
if cpu_url: