Comment-only pass over the Python this PR touches: drop what the code already says, collapse multi-line explanations that still read on one line, and keep the reasoning that is not recoverable from the code. No code, docstring semantics or behaviour changes; verified with an AST comparison against the previous revision, and the backend suite is unchanged (same 37 environment failures as before: the API integration tests that need a live keyed server, the flash-attn install hooks, and the GPU memory fields).
125 lines
4.8 KiB
Python
125 lines
4.8 KiB
Python
# SPDX-License-Identifier: AGPL-3.0-only
|
|
# Copyright 2026-present the Unsloth AI Inc. team. All rights reserved. See /studio/LICENSE.AGPL-3.0
|
|
|
|
"""Build a pre-cast text-encoder checkpoint for the Studio TE prequant path.
|
|
|
|
Apply the runtime layerwise-fp8 STORAGE cast (``diffusion_precision._cast_fp8``) to a
|
|
model's dense text encoder ONCE and save the cast state dict, so the backend can load the
|
|
~half-size artifact (meta-init + ``load_state_dict(assign=True)``, see
|
|
``core/inference/diffusion_te_prequant.py``) instead of downloading the full bf16 encoder
|
|
and casting on every load. The cast is a deterministic storage transform, so the loaded
|
|
encoder is bit-identical to dense-load-then-cast by construction. CPU-runnable: the cast
|
|
touches storage dtypes only, no kernels.
|
|
|
|
python scripts/build_te_prequant_checkpoint.py \
|
|
--base Lightricks/LTX-2 --family ltx-2 --component text_encoder \
|
|
--out outputs/te_prequant/ltx2/text_encoder_fp8.pt
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import sys
|
|
import time
|
|
from pathlib import Path
|
|
|
|
BACKEND = Path(__file__).resolve().parent.parent / "studio" / "backend"
|
|
|
|
|
|
def main(argv = None) -> int:
|
|
p = argparse.ArgumentParser()
|
|
p.add_argument(
|
|
"--base", required = True, help = "diffusers base repo (carries the component subfolder)"
|
|
)
|
|
p.add_argument("--family", required = True, help = "diffusion/video family name or alias")
|
|
p.add_argument(
|
|
"--component",
|
|
default = "text_encoder",
|
|
help = "pipeline component attribute (also the repo subfolder)",
|
|
)
|
|
p.add_argument(
|
|
"--config-subfolder",
|
|
default = None,
|
|
help = "where the encoder lives inside --base (default: the component name; "
|
|
"pass '' for a standalone encoder repo whose config sits at the root, "
|
|
"e.g. HiDream's Llama text_encoder_4)",
|
|
)
|
|
p.add_argument("--scheme", default = "fp8", choices = ["fp8"])
|
|
p.add_argument("--out", required = True, help = "output .pt path for the checkpoint")
|
|
p.add_argument("--dtype", default = "bfloat16", choices = ["bfloat16"])
|
|
p.add_argument("--hf-token", default = None)
|
|
args = p.parse_args(argv)
|
|
|
|
sys.path.insert(0, str(BACKEND))
|
|
import torch
|
|
import transformers
|
|
|
|
from core.inference.diffusion_precision import _cast_fp8
|
|
from core.inference.diffusion_te_prequant import TE_PREQUANT_FORMAT
|
|
|
|
# Family is forensic metadata; detection differs per branch, so resolve best-effort by name.
|
|
family = args.family.strip().lower()
|
|
|
|
subfolder = args.component if args.config_subfolder is None else args.config_subfolder
|
|
from_pretrained_kwargs = {"token": args.hf_token}
|
|
if subfolder:
|
|
from_pretrained_kwargs["subfolder"] = subfolder
|
|
|
|
print(f"== build TE prequant ({family}/{args.component}/{args.scheme}) ==", flush = True)
|
|
print(f" loading dense encoder from {args.base} (subfolder={subfolder!r}) ...", flush = True)
|
|
t0 = time.time()
|
|
config = transformers.AutoConfig.from_pretrained(args.base, **from_pretrained_kwargs)
|
|
# Prefer the checkpoint's own architecture; AutoModel.from_config gives the bare base class,
|
|
# whose state dict the pipeline cannot use.
|
|
arch = (getattr(config, "architectures", None) or [None])[0]
|
|
if arch and hasattr(transformers, arch):
|
|
encoder_cls_name = arch
|
|
else:
|
|
encoder = transformers.AutoModel.from_config(config)
|
|
encoder_cls_name = type(encoder).__name__
|
|
del encoder
|
|
encoder = getattr(transformers, encoder_cls_name).from_pretrained(
|
|
args.base,
|
|
torch_dtype = torch.bfloat16,
|
|
**from_pretrained_kwargs,
|
|
)
|
|
print(f" casting in place (layerwise {args.scheme}) ...", flush = True)
|
|
|
|
class _Target:
|
|
dtype = torch.bfloat16
|
|
|
|
_cast_fp8(encoder, _Target())
|
|
|
|
state_dict = {
|
|
k: (v.detach().to("cpu") if hasattr(v, "detach") else v)
|
|
for k, v in encoder.state_dict().items()
|
|
}
|
|
metadata = {
|
|
"base_model_id": args.base,
|
|
"family": family,
|
|
"scheme": args.scheme,
|
|
"component": args.component,
|
|
"te_class": encoder_cls_name,
|
|
"torch_dtype": args.dtype,
|
|
"cast_backend": "diffusers_layerwise",
|
|
# str(): a pickled TorchVersion makes torch.load(weights_only=True) reject the artifact.
|
|
"torch_version": str(torch.__version__),
|
|
"transformers_version": str(transformers.__version__),
|
|
}
|
|
ckpt = {
|
|
"format": TE_PREQUANT_FORMAT,
|
|
"metadata": metadata,
|
|
"state_dict": state_dict,
|
|
}
|
|
out = Path(args.out)
|
|
out.parent.mkdir(parents = True, exist_ok = True)
|
|
torch.save(ckpt, out)
|
|
size_gb = out.stat().st_size / 1e9
|
|
print(f" saved {out} ({size_gb:.2f} GB) in {time.time() - t0:.0f}s", flush = True)
|
|
print(f" metadata: {metadata}", flush = True)
|
|
print("BUILD-TE-PREQUANT-DONE", flush = True)
|
|
return 0
|
|
|
|
|
|
if __name__ == "__main__":
|
|
raise SystemExit(main())
|