# SPDX-License-Identifier: AGPL-3.0-only # Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0 """Builds the int8 ConvRot Gemma 4 encoder for LTX-2.5's sidecar; keys use the loaded model's names.""" from __future__ import annotations import argparse import hashlib import json import os import sys from pathlib import Path from typing import Optional ROOT = Path(__file__).resolve().parent.parent BACKEND = ROOT / "studio" / "backend" def main(argv = None) -> int: p = argparse.ArgumentParser() p.add_argument("--base", default = "Lightricks/LTX-2.5-Diffusers") p.add_argument( "--repo", default = None, help = "local pack folder or Hub id to read (default: --base)" ) p.add_argument("--out", required = True) p.add_argument( "--device", default = "cuda:0", help = "the at-load path quantizes on the card when it has one" ) p.add_argument( "--venv-dir", default = None, help = "the transformers 5.10 sidecar (default: Unsloth's)" ) p.add_argument("--hf-token", default = None) args = p.parse_args(argv) sys.path.insert(0, str(BACKEND)) venv_dir = args.venv_dir if venv_dir is None: from utils.transformers_version import _VENV_T5_510_DIR venv_dir = _VENV_T5_510_DIR result = build( args.repo or args.base, Path(args.out), base = args.base, device = args.device, venv_dir = venv_dir, hf_token = args.hf_token, ) print(json.dumps(result)) return 0 def build( repo: str, out: Path, *, base: str, device: str, venv_dir: Optional[str] = None, hf_token: Optional[str] = None, ) -> dict: """Load ``repo``'s encoder the worker's way, quantized, and write it to ``out``; its size, sha256 and counts.""" sys.path.insert(0, str(BACKEND)) sys.path.insert(0, str(ROOT / "scripts")) from core.inference import video_ltx25_te_worker as worker if venv_dir: worker._activate_sidecar(venv_dir) import safetensors import torch from build_te_int8_convrot_checkpoint import _write_safetensors from core.inference.prequant_safetensors import UNSLOTH_FORMAT_KEY, UNSLOTH_METADATA_KEY from core.inference.video_ltx25 import LTX25_TE_HOSTED_FORMAT from core.inference.video_minimax_h3_te import _int8_convrot_linear_class device = _available_device(torch, device) on_card = device.startswith(("cuda", "xpu")) config = { "repo": repo, "hf_token": hf_token, "device": device, "dtype": "bfloat16", "resident": on_card, "te_quant": worker.TE_QUANT_INT8_CONVROT, } _tokenizer, model = worker._load(config) if config.get("_te_source") != "quantized_at_load": raise SystemExit(f"expected the at-load build, got {config.get('_te_source')!r}") quantized = sum(1 for m in model.modules() if isinstance(m, _int8_convrot_linear_class())) state = model.state_dict() # Tied lm_head is the embedding again: the loader re-ties it, so drop it before shipping. if _head_is_tied(state): state.pop("lm_head.weight") tensors = {k: v.detach().to("cpu").contiguous() for k, v in state.items()} del model names = sorted(tensors) try: import transformers cfg = transformers.AutoConfig.from_pretrained( repo, subfolder = "text_encoder", token = hf_token ) te_class = (getattr(cfg, "architectures", None) or [None])[0] transformers_version = transformers.__version__ except Exception: # noqa: BLE001 -- forensic metadata only te_class, transformers_version = None, None metadata = { "base_model_id": base, "family": "ltx-2.5", "scheme": "int8", "component": "text_encoder", "te_class": te_class or "Gemma4UnifiedForConditionalGeneration", "torch_dtype": "bfloat16", "quant": { "format": "int8_convrot", "group_size": worker.CONVROT_GROUP, "weight_only": True, "scale": "per_output_channel_absmax_over_127", "linears": quantized, "quantized_on": torch.device(device).type if on_card else "cpu", }, "lm_head": "tied to the input embedding, no key", "torch_version": torch.__version__, "transformers_version": transformers_version, "safetensors_version": safetensors.__version__, } header = { UNSLOTH_FORMAT_KEY: LTX25_TE_HOSTED_FORMAT, UNSLOTH_METADATA_KEY: json.dumps(metadata, sort_keys = True), "tensor_names": json.dumps(names), } # Plain tensors only, so the shared reader (prequant_safetensors.load_plain_prequant_safetensors) accepts it. header.update({name: json.dumps({"_type": "Tensor"}) for name in names}) out.parent.mkdir(parents = True, exist_ok = True) tmp = out.with_name(out.name + ".tmp") _write_safetensors(tmp, tensors, header) os.replace(tmp, out) digest = hashlib.sha256() with open(out, "rb") as fh: for chunk in iter(lambda: fh.read(1 << 24), b""): digest.update(chunk) return { "out": str(out), "bytes": out.stat().st_size, "sha256": digest.hexdigest(), "quantized_linears": quantized, "tensors": len(names), } def _available_device(torch, device: str) -> str: """``device`` when its backend is usable here, else ``cpu``: the worker moves a resident build onto the device.""" kind = torch.device(device).type backend = getattr(torch, kind, None) if kind in ("cuda", "xpu") else None if backend is None: return device is_available = getattr(backend, "is_available", None) if callable(is_available) and is_available(): return device print(f"{device} is not available on this host; building on the CPU", file = sys.stderr) return "cpu" def _head_is_tied(state: dict) -> bool: """Whether ``lm_head.weight`` is the same storage as another tensor in ``state`` (the input embedding).""" head = state.get("lm_head.weight") if head is None: return False return any( name != "lm_head.weight" and t.shape == head.shape and t.data_ptr() == head.data_ptr() for name, t in state.items() ) if __name__ == "__main__": raise SystemExit(main())