177 lines
5.4 KiB
Python
177 lines
5.4 KiB
Python
# SPDX-License-Identifier: Apache-2.0
|
|
"""Create a FastVideo-native LingBot World 2 causal-fast bundle."""
|
|
|
|
from __future__ import annotations
|
|
|
|
import argparse
|
|
import json
|
|
import os
|
|
from pathlib import Path
|
|
|
|
LATENTS_MEAN = [
|
|
-0.7571,
|
|
-0.7089,
|
|
-0.9113,
|
|
0.1075,
|
|
-0.1745,
|
|
0.9653,
|
|
-0.1517,
|
|
1.5508,
|
|
0.4134,
|
|
-0.0715,
|
|
0.5517,
|
|
-0.3632,
|
|
-0.1922,
|
|
-0.9497,
|
|
0.2503,
|
|
-0.2921,
|
|
]
|
|
LATENTS_STD = [
|
|
2.8184,
|
|
1.4541,
|
|
2.3275,
|
|
2.6558,
|
|
1.2196,
|
|
1.7708,
|
|
2.6052,
|
|
2.0743,
|
|
3.2687,
|
|
2.1526,
|
|
2.8652,
|
|
1.5579,
|
|
1.6382,
|
|
1.1253,
|
|
2.8251,
|
|
1.9160,
|
|
]
|
|
|
|
|
|
def _write_json(path: Path, payload: dict) -> None:
|
|
"""Write deterministic JSON config files for a local bundle."""
|
|
path.parent.mkdir(parents=True, exist_ok=True)
|
|
path.write_text(json.dumps(payload, indent=2, sort_keys=True) + "\n")
|
|
|
|
|
|
def _symlink_or_replace(source: Path, target: Path) -> None:
|
|
"""Create a relative symlink, replacing stale symlinks only."""
|
|
target.parent.mkdir(parents=True, exist_ok=True)
|
|
if target.is_symlink() or target.exists():
|
|
if target.is_symlink() and target.resolve() == source.resolve():
|
|
return
|
|
raise FileExistsError(f"Refusing to replace existing path: {target}")
|
|
target.symlink_to(os.path.relpath(source, target.parent))
|
|
|
|
|
|
def convert_vae(source_dir: Path, output_dir: Path) -> None:
|
|
"""Link the exact LingBot World 2 Wan VAE checkpoint consumed by LingBotWorld2WanVAE."""
|
|
_symlink_or_replace(source_dir / "Wan2.1_VAE.pth", output_dir / "vae" / "Wan2.1_VAE.pth")
|
|
|
|
|
|
def build_bundle(source_dir: Path, output_dir: Path) -> None:
|
|
"""Create configs and symlinks for FastVideo-native LingBot World 2 loading."""
|
|
output_dir.mkdir(parents=True, exist_ok=True)
|
|
_write_json(
|
|
output_dir / "model_index.json",
|
|
{
|
|
"_class_name": "LingBotWorld2CausalFastPipeline",
|
|
"_diffusers_version": "0.30.0",
|
|
"scheduler": ["diffusers", "FlowUniPCMultistepScheduler"],
|
|
"text_encoder": ["transformers", "LingBotWorld2T5EncoderModel"],
|
|
"tokenizer": ["transformers", "AutoTokenizer"],
|
|
"transformer": ["diffusers", "LingBotWorld2CausalFastTransformer3DModel"],
|
|
"vae": ["diffusers", "LingBotWorld2WanVAE"],
|
|
},
|
|
)
|
|
_write_json(
|
|
output_dir / "transformer" / "config.json",
|
|
{
|
|
"_class_name": "LingBotWorld2CausalFastTransformer3DModel",
|
|
"_diffusers_version": "0.30.0",
|
|
"model_type": "i2v",
|
|
"patch_size": [1, 2, 2],
|
|
"text_len": 512,
|
|
"in_dim": 36,
|
|
"dim": 5120,
|
|
"ffn_dim": 13824,
|
|
"freq_dim": 256,
|
|
"text_dim": 4096,
|
|
"out_dim": 16,
|
|
"num_heads": 40,
|
|
"num_layers": 40,
|
|
"local_attn_size": 18,
|
|
"sink_size": 6,
|
|
"qk_norm": True,
|
|
"cross_attn_norm": True,
|
|
"eps": 1e-6,
|
|
},
|
|
)
|
|
_write_json(
|
|
output_dir / "text_encoder" / "config.json",
|
|
{
|
|
"architectures": ["LingBotWorld2T5EncoderModel"],
|
|
"vocab_size": 256384,
|
|
"dim": 4096,
|
|
"dim_attn": 4096,
|
|
"dim_ffn": 10240,
|
|
"num_heads": 64,
|
|
"num_layers": 24,
|
|
"num_buckets": 32,
|
|
"text_len": 512,
|
|
"hidden_size": 4096,
|
|
"dropout": 0.1,
|
|
},
|
|
)
|
|
_write_json(
|
|
output_dir / "vae" / "config.json",
|
|
{
|
|
"_class_name": "LingBotWorld2WanVAE",
|
|
"base_dim": 96,
|
|
"z_dim": 16,
|
|
"dim_mult": [1, 2, 4, 4],
|
|
"num_res_blocks": 2,
|
|
"attn_scales": [],
|
|
"temperal_downsample": [False, True, True],
|
|
"dropout": 0.0,
|
|
"latents_mean": LATENTS_MEAN,
|
|
"latents_std": LATENTS_STD,
|
|
},
|
|
)
|
|
_write_json(
|
|
output_dir / "scheduler" / "scheduler_config.json",
|
|
{
|
|
"_class_name": "FlowUniPCMultistepScheduler",
|
|
"num_train_timesteps": 1000,
|
|
"shift": 1.0,
|
|
"use_dynamic_shifting": False,
|
|
"prediction_type": "flow_prediction",
|
|
"solver_order": 2,
|
|
"solver_type": "bh2",
|
|
"final_sigmas_type": "zero",
|
|
},
|
|
)
|
|
_write_json(output_dir / "scheduler" / "config.json",
|
|
json.loads((output_dir / "scheduler" / "scheduler_config.json").read_text()))
|
|
|
|
_symlink_or_replace(source_dir / "models_t5_umt5-xxl-enc-bf16.pth",
|
|
output_dir / "text_encoder" / "pytorch_model.pt")
|
|
_symlink_or_replace(source_dir / "google" / "umt5-xxl", output_dir / "tokenizer")
|
|
for shard in sorted((source_dir / "transformers").glob("*.safetensors")):
|
|
_symlink_or_replace(shard, output_dir / "transformer" / shard.name)
|
|
index_path = source_dir / "transformers" / "diffusion_pytorch_model.safetensors.index.json"
|
|
if index_path.exists():
|
|
_symlink_or_replace(index_path, output_dir / "transformer" / "diffusion_pytorch_model.safetensors.index.json")
|
|
convert_vae(source_dir, output_dir)
|
|
|
|
|
|
def main() -> None:
|
|
"""Parse CLI arguments and build the local converted bundle."""
|
|
parser = argparse.ArgumentParser()
|
|
parser.add_argument("--source-dir", required=True, type=Path)
|
|
parser.add_argument("--output-dir", required=True, type=Path)
|
|
args = parser.parse_args()
|
|
build_bundle(args.source_dir, args.output_dir)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
main()
|