diff --git a/studio/backend/core/inference/video.py b/studio/backend/core/inference/video.py index 11605d0d8a..8a34aaa0ff 100644 --- a/studio/backend/core/inference/video.py +++ b/studio/backend/core/inference/video.py @@ -132,10 +132,24 @@ def _picked_gguf_arch(repo_id: str, gguf_filename: str) -> Optional[str]: path = Path(repo_id).expanduser() / gguf_filename if not path.is_file(): # Not a local dir: resolve a cached HUB blob from the HF cache (no network). The - # cached-gguf picker only offers already-downloaded repos, so the blob is on disk. + # cached-gguf picker only offers already-downloaded repos, so the blob is on disk -- + # but that listing scans the active, legacy, AND default cache roots, so probe all + # three here or a GGUF cached in a non-active root would be offered yet 400 on load. from huggingface_hub import try_to_load_from_cache cached = try_to_load_from_cache(repo_id, gguf_filename) + if not isinstance(cached, str): + from hub.utils.paths import hf_default_cache_dir, legacy_hf_cache_dir + + for root_fn in (legacy_hf_cache_dir, hf_default_cache_dir): + try: + cached = try_to_load_from_cache( + repo_id, gguf_filename, cache_dir = str(root_fn()) + ) + except Exception: # noqa: BLE001 -- a bad/absent root just falls through + cached = None + if isinstance(cached, str): + break if not isinstance(cached, str): return None path = Path(cached) diff --git a/studio/backend/tests/test_video_backend.py b/studio/backend/tests/test_video_backend.py index e0426b2cee..82a8242057 100644 --- a/studio/backend/tests/test_video_backend.py +++ b/studio/backend/tests/test_video_backend.py @@ -278,6 +278,26 @@ def test_detect_load_family_cached_hub_arch_fallback(monkeypatch): ) assert _detect_load_family("someorg/opaque-quants", "model.gguf", None) is None + # The blob lives in a NON-active cache root (legacy / default): the active probe (no cache_dir) + # misses, but the per-root probe finds it, so a GGUF the picker offered from any root resolves. + import hub.utils.paths as hub_paths + + monkeypatch.setattr(hub_paths, "legacy_hf_cache_dir", lambda: "/fake/legacy") + monkeypatch.setattr(hub_paths, "hf_default_cache_dir", lambda: "/fake/default") + monkeypatch.setattr( + gguf_meta, "read_gguf_general_metadata", lambda path: {"general.architecture": "ltxv"} + ) + monkeypatch.setattr( + huggingface_hub, + "try_to_load_from_cache", + # Active root (cache_dir absent) misses; only the legacy/default roots have the blob. + lambda repo_id, filename, cache_dir = None: ( + "/fake/legacy/blobs/model.gguf" if cache_dir else None + ), + ) + fam = _detect_load_family("someorg/opaque-quants", "model.gguf", None) + assert fam is not None and fam.name == "ltx-2" + def test_loading_repo_ids_guards_in_flight_delete(): # During a background load status()["loaded"] is still False, but the target repo (+ its diff --git a/studio/frontend/src/features/video/video-page.tsx b/studio/frontend/src/features/video/video-page.tsx index f2092aa9ba..d1523d7002 100644 --- a/studio/frontend/src/features/video/video-page.tsx +++ b/studio/frontend/src/features/video/video-page.tsx @@ -507,6 +507,10 @@ export function VideoPage({ active = true }: { active?: boolean }) { const lastLoad = useRef<{ repoId: string; kind: "gguf" | "single_file" | "pipeline"; filename?: string } | null>( null, ); + // Whether this session holds a reapply descriptor (set only by our own loads). On a mount/refresh + // with a model already resident, status.loaded is true but lastLoad is null, so Reapply would + // silently do nothing -- hide the button in that case rather than offer a dead control. + const [canReapply, setCanReapply] = useState(false); const [busy, setBusy] = useState(null); // Live per-step progress (phase / step / total + ETA) polled during generation. @@ -873,6 +877,7 @@ export function VideoPage({ active = true }: { active?: boolean }) { lastLoadSig.current = null; loadToastId.current = toast(null, loadToastArgs(IDLE_PROGRESS)); lastLoad.current = { repoId, kind: opts.kind, filename: opts.filename }; + setCanReapply(true); try { // Returns immediately -- the load runs in the background; we poll for it. The backend // infers the family + base diffusers repo from the repo id. Advanced options map @@ -992,6 +997,8 @@ export function VideoPage({ active = true }: { active?: boolean }) { pollTimer.current = null; dismissLoadToast(); lastLoadSig.current = null; + lastLoad.current = null; + setCanReapply(false); setBusy("unloading"); try { setStatus(await unloadVideoModel()); @@ -1153,7 +1160,7 @@ export function VideoPage({ active = true }: { active?: boolean }) { ["fbcache", "First-Block-Cache"], ]} /> - {status?.loaded && ( + {status?.loaded && canReapply && (