Keep curated models listed, guard the video companion repo, pin diffusers
Three review findings: - The picker filtered every catalog member out of Recommended and Hub search on the way to canonical group rows, but nothing renders those rows yet (catalogGroupFitsDevice and groupMatchesQuery are imported and unused). A task-scoped picker's models list is catalogToModelOptions(), i.e. group members exclusively, so both lists came back empty and no curated model could be discovered or downloaded. Keep the artifacts listed until the grouped UI exists. - The video delete guard compared only repo_id, so deleting the companion base of a loaded GGUF video model was allowed even though it supplies the VAE and text encoders. Compare base_repo too, matching what the images guard already does for its companions. - diffusers was declared unversioned while the diffusion stack requires 0.39 (Krea2Pipeline, the cache_context child registries, the Flux2 and Z-Image pipelines), so an upgrade could keep an older release and selecting an advertised model failed until the user upgraded by hand.
This commit is contained in:
parent
a515fbfed3
commit
950da4cba8
4 changed files with 43 additions and 8 deletions
|
|
@ -88,7 +88,11 @@ huggingfacenotorch = [
|
|||
"peft>=0.18.0,!=0.11.0",
|
||||
"huggingface_hub>=0.34.0",
|
||||
"hf_transfer",
|
||||
"diffusers",
|
||||
# 0.39 is the floor the diffusion stack already assumes: Krea2Pipeline (the Krea 2
|
||||
# workflow raises without it), the cache_context child-registry behavior, and the
|
||||
# Flux2 / Z-Image pipelines. An unversioned entry let an upgrade keep an older
|
||||
# diffusers, so advertised models failed to load until the user upgraded by hand.
|
||||
"diffusers>=0.39.0",
|
||||
"transformers>=4.51.3,!=4.52.0,!=4.52.1,!=4.52.2,!=4.52.3,!=4.53.0,!=4.54.0,!=4.55.0,!=4.55.1,!=4.57.0,!=4.57.4,!=4.57.5,!=5.0.0,!=5.1.0,<=5.5.0",
|
||||
"trl>=0.18.2,!=0.19.0,<=0.24.0",
|
||||
"sentence-transformers",
|
||||
|
|
|
|||
|
|
@ -629,9 +629,14 @@ def _video_blocks_delete(repo_id: str) -> Optional[str]:
|
|||
logger.debug(f"Video backend unavailable during delete guard for {repo_id}: {e}")
|
||||
return None
|
||||
status = backend.status()
|
||||
if status.get("loaded") and status.get("repo_id"):
|
||||
if _loaded_id_matches_repo(str(status["repo_id"]), repo_id):
|
||||
return "Unload the model before deleting"
|
||||
if status.get("loaded"):
|
||||
# repo_id names the checkpoint; for a GGUF / single-file load the companion base
|
||||
# supplies the VAE and text encoders and is just as much part of the live model,
|
||||
# so refuse it too (the Images guard above does the same via loaded_repo_ids).
|
||||
for key in ("repo_id", "base_repo"):
|
||||
held = status.get(key)
|
||||
if held and _loaded_id_matches_repo(str(held), repo_id):
|
||||
return "Unload the model before deleting"
|
||||
for lid in getattr(backend, "loading_repo_ids", tuple)():
|
||||
if _loaded_id_matches_repo(str(lid), repo_id):
|
||||
return "A Video model load is using this repo; wait for it to finish"
|
||||
|
|
|
|||
|
|
@ -734,3 +734,26 @@ def test_export_endpoint_validation(client, monkeypatch):
|
|||
resp = client.get(f"/api/inference/video/gallery/{video['id']}/export?format=webm")
|
||||
assert resp.status_code == 501
|
||||
assert "PyAV" in resp.json()["detail"]
|
||||
|
||||
|
||||
def test_delete_guard_protects_the_loaded_video_companion_base(monkeypatch):
|
||||
# For a GGUF / single-file video load the companion base supplies the VAE and text
|
||||
# encoders, so it is as much part of the live model as the checkpoint. Deleting it used
|
||||
# to sail past the guard, which only compared repo_id.
|
||||
from hub.services.models import deletion
|
||||
|
||||
class _Backend:
|
||||
def status(self):
|
||||
return {
|
||||
"loaded": True,
|
||||
"repo_id": "unsloth/LTX-2.3-GGUF",
|
||||
"base_repo": "unsloth/LTX-2.3",
|
||||
}
|
||||
|
||||
def loading_repo_ids(self):
|
||||
return ()
|
||||
|
||||
monkeypatch.setattr(video_module, "get_video_backend", lambda: _Backend())
|
||||
assert deletion._video_blocks_delete("unsloth/LTX-2.3-GGUF") is not None
|
||||
assert deletion._video_blocks_delete("unsloth/LTX-2.3") is not None
|
||||
assert deletion._video_blocks_delete("unsloth/something-else") is None
|
||||
|
|
|
|||
|
|
@ -1921,8 +1921,11 @@ export function HubModelPicker({
|
|||
? isKnownGgufRepo(id)
|
||||
: !chatOnly || isRecommendableFormat(id, isKnownGgufRepo(id), isMac),
|
||||
)
|
||||
// Member repos of a catalog group collapse into the canonical group row.
|
||||
.filter((id) => !catalog || !groupForRepoId(id, catalog))
|
||||
// Member repos of a catalog group would collapse into the canonical group row --
|
||||
// but nothing renders those rows yet (catalogGroupFitsDevice / groupMatchesQuery are
|
||||
// imported and unused), and a task-scoped picker's `models` is catalogToModelOptions(),
|
||||
// i.e. group members exclusively. Suppressing them here emptied Recommended on the
|
||||
// Images and Video pages, so keep the artifacts listed until the grouped UI lands.
|
||||
.filter((id) => !/-FP8[-.]|FP8-Dynamic/i.test(id));
|
||||
// Sort: GGUFs first, then hub models
|
||||
const gguf: string[] = [];
|
||||
|
|
@ -1970,8 +1973,8 @@ export function HubModelPicker({
|
|||
: rows.filter((r) => matchesFormatFilter(r.id, r.isGguf, formatFilter));
|
||||
// Task-scoped pages load single-file GGUF only.
|
||||
if (task) rows = rows.filter((r) => r.isGguf);
|
||||
// Members already render under their canonical group row.
|
||||
if (catalog) rows = rows.filter((r) => !groupForRepoId(r.id, catalog));
|
||||
// Members would render under their canonical group row, but that row does not exist
|
||||
// yet (see recommendedIds): filtering here removed curated models from Hub search too.
|
||||
// The "recommended" sort always applies the device-fit filter; the shared
|
||||
// "Fits on device" tick extends it to the other sorts too.
|
||||
if (recommendedSort !== "recommended" && !fitOnDeviceOnly) return rows;
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue