Shrink the LTX-2.3 base pull to the components the assembly reads

A 2.3 checkpoint (GGUF or single file) carries the DiT and, with its extras
files, the connectors, both VAEs and the vocoder; only the 2.0 base repo's
scheduler, text encoder and tokenizer are read. Detect 2.3 from the
checkpoint header after the pull, re-estimate, and scope the base
pre-download accordingly (about 6 GB less per fresh install).
This commit is contained in:
Daniel Han 2026-07-05 04:38:28 +00:00
commit e2fd90783e
2 changed files with 82 additions and 11 deletions

View file

@ -310,15 +310,49 @@ class VideoBackend:
# unload/eviction can preempt the multi-GB pull; the pipeline
# companions pre-download the same way (scoped file list, cancellable,
# resumes from the cache so a cancelled pull costs nothing).
checkpoint_local: Optional[Path] = None
if kwargs.get("gguf_filename") and not Path(kwargs["repo_id"]).expanduser().exists():
from utils.hf_xet_fallback import hf_hub_download_with_xet_fallback
hf_hub_download_with_xet_fallback(
kwargs["repo_id"],
kwargs["gguf_filename"],
kwargs.get("hf_token"),
cancel_event = self._cancel_event,
checkpoint_local = Path(
hf_hub_download_with_xet_fallback(
kwargs["repo_id"],
kwargs["gguf_filename"],
kwargs.get("hf_token"),
cancel_event = self._cancel_event,
)
)
kwargs["_base_local_dir"] = self._predownload_base(base, kwargs.get("hf_token"), kind)
# An LTX-2.3 checkpoint replaces the base VAEs/vocoder/connectors too, so
# its base pull shrinks to scheduler + text encoder + tokenizer; the
# estimate is recomputed to match (detectable only once the checkpoint
# header is on disk, hence after the pull above).
ltx23 = False
if fam is not None and fam.name == "ltx-2" and kind != "pipeline":
from .video_ltx2 import is_ltx23_checkpoint
probe = checkpoint_local
if probe is None:
root = Path(kwargs["repo_id"]).expanduser()
probe = root if root.is_file() else None
ltx23 = probe is not None and is_ltx23_checkpoint(probe)
if ltx23:
expected = self._estimate_download_bytes(
kwargs["repo_id"],
kwargs.get("gguf_filename"),
base,
kwargs.get("hf_token"),
kind,
ltx23 = True,
)
with self._lock:
if self._load_token == token and self._loading is not None:
self._loading.expected_bytes = expected
base_local = self._predownload_base(
base, kwargs.get("hf_token"), kind, ltx23 = ltx23
)
# The 2.3 assembly pulls per component from the hub id (its snapshot here
# deliberately lacks the base VAEs), so it only gets the warmed cache; the
# generic from_pretrained paths get the complete local snapshot.
kwargs["_base_local_dir"] = None if ltx23 else base_local
self.load_pipeline(**kwargs)
with self._lock:
if self._load_token == token:
@ -333,8 +367,13 @@ class VideoBackend:
if self._load_token == token and self._loading is not None:
self._loading.error = redact_native_paths(str(exc))
# Base-repo subfolders an LTX-2.3 assembly reads: the checkpoint (plus the GGUF
# repo's extras files) supplies the DiT, connectors, both VAEs and the vocoder,
# so only the 2.0 base's scheduler / text encoder / tokenizer are pulled.
_LTX23_BASE_PREFIXES = ("scheduler/", "text_encoder/", "tokenizer/")
@staticmethod
def _base_download_files(info: Any, kind: str) -> list[tuple[str, int]]:
def _base_download_files(info: Any, kind: str, *, ltx23: bool = False) -> list[tuple[str, int]]:
"""The (rfilename, size) list a load actually needs from the base repo.
Single source of truth for the progress estimate AND the scoped pre-download,
@ -344,7 +383,10 @@ class VideoBackend:
- the duplicate ``text_encoder/diffusion_pytorch_model*`` shard set (the LTX-2
base repo ships its text encoder twice; transformers loads the ``model-*``
naming via the shard index);
- ``transformer/`` when a GGUF/single-file checkpoint replaces the DiT."""
- ``transformer/`` when a GGUF/single-file checkpoint replaces the DiT;
- everything but scheduler / text encoder / tokenizer for an LTX-2.3
checkpoint (``ltx23``), whose VAEs/vocoder/connectors come from the
checkpoint and its extras, not the 2.0 base."""
files: list[tuple[str, int]] = []
for sibling in info.siblings or []:
name, size = sibling.rfilename, sibling.size or 0
@ -356,6 +398,12 @@ class VideoBackend:
continue
if name.startswith("text_encoder/diffusion_pytorch_model"):
continue
if (
ltx23
and "/" in name
and not name.startswith(VideoBackend._LTX23_BASE_PREFIXES)
):
continue
files.append((name, int(size)))
return files
@ -366,6 +414,7 @@ class VideoBackend:
base: str,
hf_token: Optional[str],
kind: str,
ltx23: bool = False,
) -> Optional[int]:
"""Total bytes this load will pull (checkpoint + companions), or None."""
try:
@ -380,12 +429,14 @@ class VideoBackend:
total += int(sibling.size)
if base and not Path(base).expanduser().exists():
info = api.model_info(base, files_metadata = True)
total += sum(size for _, size in self._base_download_files(info, kind))
total += sum(size for _, size in self._base_download_files(info, kind, ltx23 = ltx23))
return total or None
except Exception: # noqa: BLE001 -- progress totals are best-effort only
return None
def _predownload_base(self, base: str, hf_token: Optional[str], kind: str) -> Optional[str]:
def _predownload_base(
self, base: str, hf_token: Optional[str], kind: str, *, ltx23: bool = False
) -> Optional[str]:
"""Pull exactly the base-repo files the load needs; return the local snapshot dir.
A bare ``from_pretrained(repo_id)`` snapshot of Lightricks/LTX-2 downloads the
@ -401,7 +452,7 @@ class VideoBackend:
from huggingface_hub import HfApi
info = HfApi(token = hf_token or None).model_info(base, files_metadata = True)
files = self._base_download_files(info, kind)
files = self._base_download_files(info, kind, ltx23 = ltx23)
if not any(name == "model_index.json" for name, _ in files):
return None
from utils.hf_xet_fallback import hf_hub_download_with_xet_fallback

View file

@ -509,3 +509,23 @@ def test_pipeline_load_uses_predownloaded_dir(fake_runtime, tmp_path):
)
assert _FakePipeline.last["base"] == str(tmp_path)
backend.unload()
def test_base_download_files_ltx23_keeps_only_shared_components():
# A 2.3 checkpoint supplies the DiT, connectors, both VAEs and the vocoder, so
# the base pull shrinks to scheduler + text encoder + tokenizer (+ root manifest).
siblings = _LTX2_SIBLINGS + [
_sibling("scheduler/scheduler_config.json", 1),
_sibling("connectors/diffusion_pytorch_model.safetensors", 3),
_sibling("latent_upsampler/diffusion_pytorch_model.safetensors", 1),
]
info = types.SimpleNamespace(siblings = siblings)
names = [n for n, _ in VideoBackend._base_download_files(info, "gguf", ltx23 = True)]
assert "model_index.json" in names
assert "scheduler/scheduler_config.json" in names
assert "text_encoder/model-00001-of-00002.safetensors" in names
assert "tokenizer/tokenizer.model" in names
assert not any(
n.startswith(("vae/", "connectors/", "latent_upsampler/", "transformer/"))
for n in names
)