LongLive-Plug-Wan2.1-T2V-14B-few-step: Our Generated Video, File List

October 2, 2026

LongLive-Plug-Wan2.1-T2V-14B-few-step: Our Generated Video, File List

At a Glance

Item Value
Repository Efficient-Large-Model/LongLive-Plug-Wan2.1-T2V-14B-few-step
Publisher guide Efficient Large Model (NVIDIA and MIT): models and licenses
Published 2026-09-30
License apache-2.0
Formats safetensors
Source type Primary source (the publisher itself)

Values determined by this site’s code when the information was collected. Dates are JST.

What we checked ourselves

  • We generated video on a rented cloud GPU from our own fixed prompts and show it unedited.

Details and conditions are in “Our Own Measurements” below.

Overview

Efficient-Large-Model has released a LoRA adapter named LongLive-Plug-Wan2.1-T2V-14B-few-step designed to accelerate video generation, using Wan-AI/Wan2.1-T2V-14B as the base model. This model is designed to enable generation in fewer steps during the text-to-video process. Because this model functions as an adapter, it requires the corresponding base model to operate. Additionally, it is recommended to use it in combination with the corresponding CFG LoRA (LongLive-Plug-Wan2.1-T2V-14B-cfg).

Specifications

This model targets a base model with the following specifications:

  • Base Model: Wan-AI/Wan2.1-T2V-14B
  • Architecture: Video Diffusion DiT (Flow Matching framework)
  • Base Model (14B) Architecture Details:
    • Dimension: 5120 – Input Dimension: 16 – Output Dimension: 16 – Feedforward Dimension: 13824 – Frequency Dimension: 256 – Number of Heads: 40 – Number of Layers: 40
  • Recommended Settings:
    • It is recommended to set the weight ratio of few-step to CFG at 1 : 0.5. Note that this refers to the adapter weight ratio, not the CFG scale during inference.
  • License: apache-2.0

Performance and Quality

The base model Wan2.1-T2V-14B, which serves as the foundation for this adapter, is reported to establish new State-of-the-Art (SOTA) performance benchmarks across both open-source and commercial models.

The detailed configuration of the base model’s architecture is as follows:

→ Scroll horizontally to see all columns

Model Dimension Input Dimension Output Dimension Feedforward Dimension Frequency Dimension Number of Heads Number of Layers
14B 5120 16 16 13824 256 40 40

In evaluations conducted by the publishers, multidimensional verification spanning 14 primary dimensions and 26 sub-dimensions was performed using 1,035 internal prompts. These results were calculated using human preference-based weighting, and it has been confirmed that the model exhibits superior performance compared to existing models.

Additionally, the following features are cited as technologies supporting the quality of the base model:

  • High-Quality Visuals and Motion Dynamics: Capable of generating videos with high-quality visuals and significant motion dynamics.
  • Advanced Text Generation: It is reported to be the first video model capable of generating both Chinese and English text within videos.
  • High-Performance Video VAE: Adopts a proprietary Wan-VAE, making it possible to efficiently encode and decode 1080P videos of arbitrary length while preserving temporal information.
  • Resolution Flexibility: Supports video generation at both 480P and 720P resolutions.

This adapter, LongLive-Plug-Wan2.1-T2V-14B-few-step, achieves efficiency through a reduced number of generation steps by applying distillation techniques to these powerful base models.

Strengths and Use Cases

This model excels at performing fast video generation with a reduced number of steps while maintaining the performance of the base model Wan-AI/Wan2.1-T2V-14B in text-to-video tasks. It is designed as an adapter leveraging PEFT, LoRA, and distillation techniques, and is intended to be used in combination with the corresponding CFG LoRA. Furthermore, it contributes to efficient video generation by taking advantage of the base model’s capabilities, such as generating both Chinese and English text, visual expressions with high motion dynamics, and 480P or 720P resolutions.

How It Differs from Similar Models

While LongLive-Plug-Wan2.1-T2V-14B-cfg, which has already been introduced on this site, aims to perform distillation for conditional-only video generation using Classifier-Free Guidance (CFG) to reduce the execution of unconditional inference branches, this new LongLive-Plug-Wan2.1-T2V-14B-few-step differs in that it functions as a LoRA adapter designed to achieve video generation in fewer steps. Both target the same base model, Wan-AI/Wan2.1-T2V-14B, and it is recommended to use the few-step and CFG adapter weights together at a ratio of 1 : 0.5.

Distributed Files

Weight files published in Efficient-Large-Model/LongLive-Plug-Wan2.1-T2V-14B-few-step, listed by this site from the Hugging Face API. Sizes are the actual file sizes.

File Size
generator_lora_lightx2v.safetensors 1.23GB
training_checkpoint/model.pt 4.91GB

How to Get It

It is available from the official Hugging Face repository. It is distributed as a LoRA adapter weight compatible with the PEFT library, to be used in combination with the corresponding base model and CFG LoRA.

Can You Run It Locally?

The publisher distributes this model as safetensors.

License — apache-2.0 (Commercial use allowed): Permits commercial use, modification and redistribution. Redistribution requires including the license and stating changes; includes a patent grant.

Compiled by this site’s code from the published formats and the license field. License summaries are not legal advice — check the publisher’s original terms before relying on them.

Our Own Measurements

Generated by us — not samples copied from the model card. We cannot run video models on our own server (no GPU), so we generated on a rented cloud GPU (conditions below). How we measure, and the results for every model: Our Measurements.

Video We Generated (Cloud GPU)

Efficient-Large-Model/LongLive-Plug-Wan2.1-T2V-14B-few-step (apache-2.0 license) is a LoRA adapter and does not run on its own, so we merged it into the base model Wan-AI/Wan2.1-T2V-14B-Diffusers (apache-2.0 license) in the combination the publisher recommends (LongLive-Plug-Wan2.1-T2V-14B-few-step at weight 1.0, LongLive-Plug-Wan2.1-T2V-14B-cfg at weight 0.5) and generated with 4 steps and guidance scale 1.0. We set the sampler to match the publisher’s inference code (LongLive-Plug inference.py): UniPC with shift 5.0 (diffusers’ UniPCMultistepScheduler). The publisher says to use this adapter together with LongLive-Plug-Wan2.1-T2V-14B-cfg, so we merged both, and the same videos also appear in that adapter’s article. We ran diffusers’ WanPipeline (diffusers 0.40.0) and did not run any code from the publisher. The LoRA was merged by our own code: we map the original Wan layer names to diffusers’ names and add weight × (alpha / r) × BA to each weight. This mapping is our interpretation, not something the publisher documents; we only confirmed that every layer name and shape matched.

Conditions: NVIDIA L40S (VRAM 44GB) on Modal, weights in BF16 (VAE in FP32), 832×480 pixels, 81 frames at 16 fps (about 5.06 s), seed 0, two fixed prompts without people, logos or text, generated 2026-10-02 (JST). Not selected — each clip is the first output. To fit our server’s upload limit we re-encoded each clip as H.264 (under 12.0 MB), so fine detail may be softer than the raw output.

Prompt: “a small red paper boat drifting down a clear forest stream, sunlight flickering through the leaves, gentle ripples, realistic”

Without the LoRA (base model, same 4 steps), 50 s to generate

With the LoRA, 51 s to generate

Prompt: “time-lapse of white clouds rolling over snowy mountain peaks at sunrise, golden light, wide shot”

Without the LoRA (base model, same 4 steps), 51 s to generate

With the LoRA, 52 s to generate

Item Result
VRAM after loading the model (BF16, text encoder already released) 27.3GB
Peak VRAM while generating 35.5GB

Generation times are for a rented cloud NVIDIA L40S and are not a guide to your own GPU; the text encoder runs first and is released before the video model loads, so the VRAM above does not include it. The “without the LoRA” clips use the same 4 steps for comparison only; the base model’s usual settings use many more steps with guidance, so they do not show what the base model can do.

Setup, Download and Run Steps (What We Actually Ran)

We split the work into two containers on Modal, both built from the Debian (slim) image with Python 3.12. The downloading container fetches only the listed files (configs, safetensors weights and the tokenizer) from Hugging Face at pinned revisions; the generating container has no network access. These are the packages we installed:

# 1. Environment for downloading (no GPU)
python -m pip install huggingface_hub

# 2. Environment for generating (GPU, network blocked)
python -m pip install torch diffusers transformers accelerate safetensors sentencepiece ftfy imageio-ffmpeg numpy

We then called download(job) in the first container and run(job) in the second one from the script below (video_remote.py, written by us; full text). Files go under /models/<dir>:

"""
Modal のコンテナの中で動く、動画生成モデルの生成(2026-10-01、ユーザーの判断)。lmw/lab/gpu_modal.py の
video_download / video_run から呼ばれる。**標準ライブラリだけを先頭で読み**、huggingface_hub・torch・diffusers は
各関数の中で読む(コンテナごとに入っているものが違う)。

【配布元のコードは動かさない】diffusers・transformers の公開パッケージだけで読み込む。重みは safetensors だけで、
Hugging Face のリポジトリのコードを読み込む設定は使わない。LoRA は当サイトのコード(merge_lora)が、名前の対応表と
形を照合しながら元の重みに足し込む(配布元の読み込み処理は使わない)。

【2つのコンテナに分ける】(lmw/lab/remote_code_remote.py と同じ)
- download: GPU の無いコンテナ・ネットワークあり。Hugging Face から、版を固定して決まったファイルだけを落とす。
- run: GPU のコンテナ・**ネットワーク遮断**・Modal の機能も使えない・Volume は読み取り専用。

【VRAM を節約する順番】テキストエンコーダ(UMT5-XXL は 11GB)でプロンプトを先に全部埋め込みにしてから捨て、
その後で動画の本体(transformer)と VAE を読む。2つを同時に GPU に載せないので、安い GPU に収まる。

【結果の返し方】run は生成器で、動画のバイト列を CHUNK_BYTES ずつ(kind: "chunk")、最後に記録(kind: "record")を返す。
Modal は 2MiB を超える戻り値を自分の API で預けるが、生成のコンテナは Modal の機能を遮断している
(restrict_modal_access)ので断られる(2026-10-02、14B の4本を作り終えた後に 401 で全て失った)。1回の戻り値を小さく保つ。

【費用を抑える】同じ読み込みのまま、LoRA を足す前(元のモデル)と足した後を続けて生成する(読み込みは1回)。
動画は WordPress のアップロードの上限に収まるよう H.264 で圧縮する(MAX_BYTES を超えたら画質を下げて作り直す)。

記事執筆用の LLM は呼ばない。
"""
import hashlib
import json
import os
import platform
import subprocess
import time
from datetime import datetime, timezone

MOUNT = "/models"
PYTHON_VERSION = "3.12"
DOWNLOAD_PACKAGES = ("huggingface_hub",)
# diffusers の Wan のパイプラインが使うもの(ftfy はプロンプトの整形、imageio-ffmpeg は H.264 の書き出しに使う)
RUN_PACKAGES = ("torch", "diffusers", "transformers", "accelerate", "safetensors", "sentencepiece", "ftfy",
                "imageio-ffmpeg", "numpy")
# 落としてよいファイル(pickle の .bin / .pt / .ckpt は落とさない)
ALLOWED_EXTS = (".json", ".safetensors", ".model", ".txt")

# 元の Wan の層の名前(LoRA が使う)→ diffusers の WanTransformer3DModel の層の名前。
# diffusers の変換スクリプト(scripts/convert_wan_to_diffusers.py)と同じ対応
WAN_LORA_MODULES = {
    "self_attn.q": "attn1.to_q", "self_attn.k": "attn1.to_k", "self_attn.v": "attn1.to_v",
    "self_attn.o": "attn1.to_out.0",
    "cross_attn.q": "attn2.to_q", "cross_attn.k": "attn2.to_k", "cross_attn.v": "attn2.to_v",
    "cross_attn.o": "attn2.to_out.0",
    "ffn.0": "ffn.net.0.proj", "ffn.2": "ffn.net.2",
}
# 1回に返すバイト数(Modal が自分の API に預けずにそのまま返せる 2MiB より十分小さく)
CHUNK_BYTES = 1024 * 1024
LORA_PREFIXES = ("base_model.model.", "diffusion_model.", "model.")
# 生成に使うデバイス。Modal では常に cuda(当サイトの評価で、小さなモデルを CPU で通すときだけ cpu にする)
DEVICE = "cuda"

def installed_packages() -> list[str]:
    from importlib import metadata
    seen = {}
    for dist in metadata.distributions():
        name = dist.metadata["Name"]
        if name and name.lower() not in seen:
            seen[name.lower()] = f"{name}=={dist.version}"
    return sorted(seen.values(), key=str.lower)

def _log(t0: float, message: str) -> None:
    print(f" {message}", flush=True)

def _sha256(path: str) -> str:
    h = hashlib.sha256()
    with open(path, "rb") as f:
        for chunk in iter(lambda: f.read(1 << 20), b""):
            h.update(chunk)
    return h.hexdigest()

def download(job: dict) -> dict:
    """
    job: {"dir", "base": {"repo", "revision", "files"}, "loras": [{"repo", "revision", "file"}]}。
    元のモデルは <dir>/base、LoRA は <dir>/lora<i>/ に置く。戻り値: {"bytes", "seconds", "packages", "lora_sha256"}。
    """
    from huggingface_hub import hf_hub_download
    t0 = time.time()
    base_dir = os.path.join(MOUNT, job["dir"])
    total = 0
    targets = [(job["base"]["repo"], job["base"]["revision"], name, os.path.join(base_dir, "base"))
               for name in job["base"]["files"]]
    targets += [(lora["repo"], lora["revision"], lora["file"], os.path.join(base_dir, f"lora{i}"))
                for i, lora in enumerate(job.get("loras") or [])]
    for repo, revision, name, dest in targets:
        if not name.endswith(ALLOWED_EXTS) or ".." in name.split("/"):
            raise ValueError(f"落とさない種類のファイルです: {name}")
        path = hf_hub_download(repo, name, revision=revision, local_dir=dest)
        total += os.path.getsize(path)
    lora_sha = [_sha256(os.path.join(base_dir, f"lora{i}", lora["file"]))
                for i, lora in enumerate(job.get("loras") or [])]
    seconds = round(time.time() - t0)
    _log(t0, f"ダウンロード {total / 1024 ** 3:.1f}GB・{seconds}秒")
    return {"bytes": int(total), "seconds": int(seconds), "packages": installed_packages(), "lora_sha256": lora_sha}

def _lora_pairs(state: dict) -> dict:
    """{元の層の名前: {"A", "B", "alpha"?}}。名前の頭(base_model.model. 等)を外し、A と B の揃わない層は断る。"""
    pairs: dict = {}
    for key, tensor in state.items():
        name = key
        for prefix in LORA_PREFIXES:
            if name.startswith(prefix):
                name = name[len(prefix):]
                break
        if name.endswith(".lora_A.weight"):
            pairs.setdefault(name[:-len(".lora_A.weight")], {})["A"] = tensor
        elif name.endswith(".lora_B.weight"):
            pairs.setdefault(name[:-len(".lora_B.weight")], {})["B"] = tensor
        elif name.endswith(".alpha"):
            pairs.setdefault(name[:-len(".alpha")], {})["alpha"] = float(tensor.item())
        else:
            raise ValueError(f"LoRA の重みではない名前です: {key}")
    broken = [n for n, p in pairs.items() if "A" not in p or "B" not in p]
    if broken:
        raise ValueError(f"A と B が揃わない層があります: {broken[:5]}")
    return pairs

def diffusers_module_name(name: str) -> str:
    """blocks.12.self_attn.q → blocks.12.attn1.to_q。対応表に無い層は断る(推測で当てない)。"""
    parts = name.split(".")
    if len(parts) < 4 or parts[0] != "blocks" or not parts[1].isdigit():
        raise ValueError(f"対応表に無い層です: {name}")
    tail = ".".join(parts[2:])
    if tail not in WAN_LORA_MODULES:
        raise ValueError(f"対応表に無い層です: {name}")
    return f"blocks.{parts[1]}.{WAN_LORA_MODULES[tail]}"

def merge_lora(transformer, path: str, weight: float, alpha: float | None, rank: int | None) -> dict:
    """
    LoRA を元の重みに足し込む: W ← W + weight × (alpha / r) × (B @ A)。alpha と r は配布元の adapter_config.json の値を
    ホスト側が渡す(重みのファイルに alpha があればそちらを使う)。足し込みは float32 で計算してから元の型に戻す。
    全ての層が対応表で見つかり、形が一致することを確かめる(1つでも外れたら断る)。
    """
    import torch
    from safetensors.torch import load_file
    pairs = _lora_pairs(load_file(path))
    merged = 0
    for name, p in sorted(pairs.items()):
        module = transformer.get_submodule(diffusers_module_name(name))
        a, b = p["A"].float(), p["B"].float()
        r = a.shape[0]
        if rank and r != rank:
            raise ValueError(f"{name}: ランク {r} が adapter_config.json の {rank} と違います")
        scale = weight * ((p.get("alpha") or alpha or r) / r)
        w = module.weight
        delta = (b.to(w.device) @ a.to(w.device)) * scale
        if tuple(delta.shape) != tuple(w.shape):
            raise ValueError(f"{name}: 形 {tuple(delta.shape)} が元の重み {tuple(w.shape)} と違います")
        with torch.no_grad():
            w.copy_((w.float() + delta).to(w.dtype))
        merged += 1
    return {"modules": merged, "weight": weight, "scale": round(weight * ((alpha or rank or 1) / (rank or 1)), 6)}

def encode_mp4(frames, fps: int, path: str, max_bytes: int) -> dict:
    """
    0〜1 の RGB のコマ(F, H, W, 3)を H.264(yuv420p)の mp4 にする。max_bytes を超えたら、ビットレートの上限を
    下げて作り直す(3回まで)。戻り値: 実際に使った ffmpeg の引数と大きさ。
    """
    import imageio_ffmpeg
    import numpy as np
    pixels = (np.clip(frames, 0.0, 1.0) * 255).round().astype(np.uint8)
    count, height, width = pixels.shape[0], pixels.shape[1], pixels.shape[2]
    duration = count / fps
    # 上限の大きさの 85% に収まるビットレート(音声なし)
    kbps = int(max_bytes * 8 * 0.85 / duration / 1000)
    for attempt, crf in enumerate((18, 22, 26)):
        rate = int(kbps * (0.8 ** attempt))
        args = ["-y", "-f", "rawvideo", "-pix_fmt", "rgb24", "-s", f"{width}x{height}", "-r", str(fps), "-i", "-",
                "-an", "-c:v", "libx264", "-preset", "slow", "-crf", str(crf), "-maxrate", f"{rate}k",
                "-bufsize", f"{rate * 2}k", "-pix_fmt", "yuv420p", "-movflags", "+faststart", path]
        proc = subprocess.run([imageio_ffmpeg.get_ffmpeg_exe(), *args], input=pixels.tobytes(),
                              capture_output=True, timeout=600)
        if proc.returncode != 0:
            raise RuntimeError(f"ffmpeg が失敗しました: {proc.stderr.decode(errors='replace')[-400:]}")
        size = os.path.getsize(path)
        if size <= max_bytes:
            return {"ffmpeg_args": ["ffmpeg", *args[:-1], os.path.basename(path)], "bytes": size,
                    "frames": int(count), "width": int(width), "height": int(height), "fps": int(fps),
                    "duration": round(duration, 2)}
    raise ValueError(f"動画を {max_bytes} バイト以下に圧縮できませんでした")

def run(job: dict):
    """
    job: {"dir", "pipeline", "settings": {height, width, num_frames, fps, steps, guidance, flow_shift?},
          "seed", "prompts": [{"id", "en"}], "loras": [{"file", "weight", "alpha", "rank"}], "compare_base",
          "max_bytes"}。
    生成器: {"kind": "chunk", "id", "index", "data": bytes} を動画ごとに順に返し、最後に
    {"kind": "record", ...記録の一部(秒数・VRAM・版。動画の中身は含まない)} を返す。
    """
    import diffusers
    import numpy as np
    import torch
    import transformers
    os.environ["HF_HUB_OFFLINE"] = "1"
    os.environ["TRANSFORMERS_OFFLINE"] = "1"
    os.environ["HF_HUB_DISABLE_TELEMETRY"] = "1"
    t0 = time.time()
    base_dir = os.path.join(MOUNT, job["dir"])
    model_dir = os.path.join(base_dir, "base")
    settings = job["settings"]
    guidance = float(settings["guidance"])
    cuda = DEVICE == "cuda"
    gpu = torch.cuda.get_device_name(0) if cuda else "cpu"
    _log(t0, f"GPU: {gpu}・diffusers {diffusers.__version__}・torch {torch.__version__}")
    record = {"gpu": gpu, "vram_total": int(torch.cuda.get_device_properties(0).total_memory) if cuda else 0,
              "diffusers": str(diffusers.__version__), "transformers": str(transformers.__version__),
              "torch": str(torch.__version__), "dtype": "bfloat16", "vae_dtype": "float32",
              "packages": installed_packages(), "python": platform.python_version(),
              "measured_at": datetime.now(timezone.utc).isoformat()}
    pipeline_cls = getattr(diffusers, job["pipeline"])

    # 1. プロンプトを埋め込みにする(テキストエンコーダだけを GPU に載せ、終わったら捨てる)
    with open(os.path.join(model_dir, "model_index.json"), encoding="utf-8") as f:
        index = json.load(f)
    skip_models = {name: None for name, spec in index.items()
                   if isinstance(spec, list) and name not in ("text_encoder", "tokenizer", "scheduler")}
    text_pipe = pipeline_cls.from_pretrained(model_dir, torch_dtype=torch.bfloat16, use_safetensors=True,
                                             local_files_only=True, **skip_models)
    text_pipe.text_encoder.to(DEVICE)
    embeds = {}
    with torch.no_grad():
        for p in job["prompts"]:
            # max_sequence_length はパイプラインの __call__ の既定(512。元の Wan も 512)に合わせる。
            # encode_prompt の既定は 226 で、そのままだと標準の呼び出しと違う埋め込みになる
            pos, neg = text_pipe.encode_prompt(prompt=p["en"], negative_prompt="", device=DEVICE,
                                               do_classifier_free_guidance=guidance > 1.0,
                                               max_sequence_length=int(settings.get("max_sequence_length", 512)))
            embeds[p["id"]] = (pos.cpu(), None if neg is None else neg.cpu())
    del text_pipe
    if cuda:
        torch.cuda.empty_cache()
    record["encode_sec"] = round(time.time() - t0)

    # 2. 本体と VAE を読む(VAE は float32。diffusers の Wan の例と同じ)
    vae_cls = getattr(diffusers, index["vae"][1])
    vae = vae_cls.from_pretrained(model_dir, subfolder="vae", torch_dtype=torch.float32, use_safetensors=True,
                                  local_files_only=True)
    pipe = pipeline_cls.from_pretrained(model_dir, vae=vae, text_encoder=None, tokenizer=None,
                                        torch_dtype=torch.bfloat16, use_safetensors=True, local_files_only=True)
    if settings.get("flow_shift") is not None:
        pipe.scheduler = type(pipe.scheduler).from_config(pipe.scheduler.config, flow_shift=float(settings["flow_shift"]))
    record["scheduler"] = {"class": type(pipe.scheduler).__name__,
                           **{k: v for k, v in dict(pipe.scheduler.config).items()
                              if k in ("flow_shift", "use_flow_sigmas", "prediction_type", "solver_order")}}
    pipe.to(DEVICE)
    if cuda:
        torch.cuda.synchronize()
    record["vram_loaded"] = int(torch.cuda.memory_allocated()) if cuda else 0
    record["load_sec"] = round(time.time() - t0)
    _log(t0, f"読み込み: VRAM {record['vram_loaded'] / 1024 ** 3:.1f}GB・{record['load_sec']}秒")
    if cuda:
        torch.cuda.reset_peak_memory_stats()

    def generate(group: str) -> list[dict]:
        out = []
        for p in job["prompts"]:
            pos, neg = embeds[p["id"]]
            started = time.time()
            generator = torch.Generator("cpu").manual_seed(int(job["seed"]))
            frames = pipe(prompt_embeds=pos.to(DEVICE),
                          negative_prompt_embeds=None if neg is None else neg.to(DEVICE),
                          height=int(settings["height"]), width=int(settings["width"]),
                          num_frames=int(settings["num_frames"]), num_inference_steps=int(settings["steps"]),
                          guidance_scale=guidance, generator=generator, output_type="np").frames[0]
            if cuda:
                torch.cuda.synchronize()
            seconds = time.time() - started
            frames = np.asarray(frames, dtype=np.float32)
            if not np.isfinite(frames).all():
                raise FloatingPointError("生成したコマに数でない値があります")
            # 処理の誤り(真っ黒・一色の動画)を取り込み役の機械の検査(lmw/lab/sanity.py)が見つけるための値
            stats = {"pixel_mean": round(float(frames.mean()) * 255, 2), "pixel_std": round(float(frames.std()) * 255, 2)}
            path = os.path.join("/tmp", f"{group}-{p['id']}.mp4")
            encoded = encode_mp4(frames, int(settings["fps"]), path, int(job["max_bytes"]))
            with open(path, "rb") as f:
                raw = f.read()
            blobs[f"{group}-{p['id']}"] = raw
            out.append({"id": f"{group}-{p['id']}", "prompt_id": p["id"], "group": group,
                        "seconds": round(seconds, 1), **encoded, **stats,
                        "sha256": hashlib.sha256(raw).hexdigest()})
            _log(t0, f"{group}/{p['id']}: {encoded['frames']}コマ・生成 {seconds:.0f}秒・{encoded['bytes'] / 1e6:.2f}MB")
        return out

    videos = []
    blobs: dict[str, bytes] = {}
    loras = job.get("loras") or []
    # 3. LoRA を足す前の元のモデル(比べるため。同じプロンプト・シード・ステップ数)
    if loras and job.get("compare_base"):
        videos += generate("base")
    # 4. LoRA を足し込んでから生成する
    merges = []
    for i, lora in enumerate(loras):
        path = os.path.join(base_dir, f"lora{i}", lora["file"])
        merges.append(merge_lora(pipe.transformer, path, float(lora["weight"]), lora.get("alpha"), lora.get("rank")))
        _log(t0, f"LoRA {i}: {merges[-1]['modules']}層を足し込みました(重み {lora['weight']})")
    if merges:
        record["merges"] = merges
    videos += generate("lora" if loras else "model")
    record["vram_peak"] = int(torch.cuda.max_memory_allocated()) if cuda else 0
    record["videos"] = videos
    for vid, raw in blobs.items():
        for index in range(0, len(raw), CHUNK_BYTES):
            yield {"kind": "chunk", "id": vid, "index": index // CHUNK_BYTES, "data": raw[index:index + CHUNK_BYTES]}
    yield {"kind": "record", **plain(record)}

def plain(value):
    """JSON に通して、標準の型だけにする(ホストには torch も diffusers も無い)。"""
    return json.loads(json.dumps(value, default=str))

The job passed to download (as used):

{
  "dir": "video-1790914468",
  "base": {
    "repo": "Wan-AI/Wan2.1-T2V-14B-Diffusers",
    "revision": "38ec498cb3208fb688890f8cc7e94ede2cbd7f68",
    "files": [
      "model_index.json",
      "scheduler/scheduler_config.json",
      "text_encoder/config.json",
      "text_encoder/model-00001-of-00005.safetensors",
      "text_encoder/model-00002-of-00005.safetensors",
      "text_encoder/model-00003-of-00005.safetensors",
      "text_encoder/model-00004-of-00005.safetensors",
      "text_encoder/model-00005-of-00005.safetensors",
      "text_encoder/model.safetensors.index.json",
      "tokenizer/special_tokens_map.json",
      "tokenizer/spiece.model",
      "tokenizer/tokenizer.json",
      "tokenizer/tokenizer_config.json",
      "transformer/config.json",
      "transformer/diffusion_pytorch_model-00001-of-00012.safetensors",
      "transformer/diffusion_pytorch_model-00002-of-00012.safetensors",
      "transformer/diffusion_pytorch_model-00003-of-00012.safetensors",
      "transformer/diffusion_pytorch_model-00004-of-00012.safetensors",
      "transformer/diffusion_pytorch_model-00005-of-00012.safetensors",
      "transformer/diffusion_pytorch_model-00006-of-00012.safetensors",
      "transformer/diffusion_pytorch_model-00007-of-00012.safetensors",
      "transformer/diffusion_pytorch_model-00008-of-00012.safetensors",
      "transformer/diffusion_pytorch_model-00009-of-00012.safetensors",
      "transformer/diffusion_pytorch_model-00010-of-00012.safetensors",
      "transformer/diffusion_pytorch_model-00011-of-00012.safetensors",
      "transformer/diffusion_pytorch_model-00012-of-00012.safetensors",
      "transformer/diffusion_pytorch_model.safetensors.index.json",
      "vae/config.json",
      "vae/diffusion_pytorch_model.safetensors"
    ]
  },
  "loras": [
    {
      "repo": "Efficient-Large-Model/LongLive-Plug-Wan2.1-T2V-14B-few-step",
      "revision": "f125af0533b94e2cee0163a7a42bb736d59a1d37",
      "file": "generator_lora_lightx2v.safetensors"
    },
    {
      "repo": "Efficient-Large-Model/LongLive-Plug-Wan2.1-T2V-14B-cfg",
      "revision": "32b8aa3d1db3d178a9c82f3731cc917a34fb7693",
      "file": "adapter_model.safetensors"
    }
  ]
}

The job passed to run (as used):

{
  "dir": "video-1790914468",
  "pipeline": "WanPipeline",
  "settings": {
    "height": 480,
    "width": 832,
    "num_frames": 81,
    "fps": 16,
    "steps": 4,
    "guidance": 1.0,
    "flow_shift": 5.0
  },
  "seed": 0,
  "prompts": [
    {
      "id": "boat",
      "en": "a small red paper boat drifting down a clear forest stream, sunlight flickering through the leaves, gentle ripples, realistic"
    },
    {
      "id": "clouds",
      "en": "time-lapse of white clouds rolling over snowy mountain peaks at sunrise, golden light, wide shot"
    }
  ],
  "loras": [
    {
      "file": "generator_lora_lightx2v.safetensors",
      "weight": 1.0,
      "alpha": 128,
      "rank": 128
    },
    {
      "file": "adapter_model.safetensors",
      "weight": 0.5,
      "alpha": 128,
      "rank": 128
    }
  ],
  "compare_base": true,
  "max_bytes": 12000000
}
Package versions in the generating container (full list)
accelerate==1.15.0 aiohappyeyeballs==2.6.1 aiohttp==3.12.7 aiosignal==1.3.2 annotated-doc==0.0.5 anyio==4.15.1 attrs==25.3.0 cbor2==5.7.0 certifi==2026.7.22 charset-normalizer==3.5.2 click==8.5.0 cuda-bindings==13.4.3 cuda-pathfinder==1.8.2 cuda-toolkit==13.0.3.0 diffusers==0.40.0 filelock==4.0.9 frozenlist==1.6.0 fsspec==2026.9.0 ftfy==6.3.1 grpclib==0.4.8 h11==0.16.0 h2==4.2.0 hf-xet==1.6.0 hpack==4.1.0 httpcore==1.0.9 httpx==0.28.1 huggingface_hub==1.33.0 hyperframe==6.1.0 idna==3.20 imageio-ffmpeg==0.6.0 importlib_metadata==9.0.1 Jinja2==3.1.6 markdown-it-py==4.2.0 MarkupSafe==3.0.3 mdurl==0.1.2 mpmath==1.3.0 multidict==6.4.4 networkx==3.7 numpy==2.5.3 nvidia-cublas==13.1.1.3 nvidia-cuda-cupti==13.0.85 nvidia-cuda-nvrtc==13.0.88 nvidia-cuda-runtime==13.0.96 nvidia-cudnn-cu13==9.24.0.43 nvidia-cufft==12.0.0.61 nvidia-cufile==1.15.1.6 nvidia-curand==10.4.0.35 nvidia-cusolver==12.0.4.66 nvidia-cusparse==12.6.3.3 nvidia-cusparselt-cu13==0.8.1 nvidia-nccl-cu13==2.30.7 nvidia-nvjitlink==13.4.92 nvidia-nvshmem-cu13==3.4.5 nvidia-nvtx==13.0.85 packaging==26.3 pillow==12.3.0 pip==25.1.1 propcache==0.3.1 protobuf==6.31.1 psutil==7.2.2 Pygments==2.21.0 PyYAML==6.0.3 regex==2026.9.29 requests==2.34.2 rich==15.0.0 safetensors==0.8.0 sentencepiece==0.2.2 setuptools==84.0.0 shellingham==1.5.4 sympy==1.14.0 tokenizers==0.23.2 torch==2.14.1 tqdm==4.70.1 transformers==5.18.0 triton==3.8.0 typer==0.27.2 typing_extensions==4.16.0 urllib3==2.8.0 uv==0.7.19 wcwidth==0.9.1 wheel==0.45.1 yarl==1.20.0 zipp==4.1.0
Package versions in the downloading container (full list)
aiohappyeyeballs==2.6.1 aiohttp==3.12.7 aiosignal==1.3.2 anyio==4.15.1 attrs==25.3.0 cbor2==5.7.0 certifi==2025.4.26 click==8.5.0 filelock==4.0.7 frozenlist==1.6.0 fsspec==2026.9.0 grpclib==0.4.8 h11==0.16.0 h2==4.2.0 hf-xet==1.6.0 hpack==4.1.0 httpcore2==2.13.1 httpx2==2.13.1 huggingface_hub==2.0.0 hyperframe==6.1.0 idna==3.20 multidict==6.4.4 packaging==26.3 pip==25.1.1 propcache==0.3.1 protobuf==6.31.1 PyYAML==6.0.3 tqdm==4.70.1 truststore==0.10.4 typing_extensions==4.16.0 uv==0.7.19 wheel==0.45.1 yarl==1.20.0

Related Articles

What to Read Next

Sources

Update History

  • 2026-09-30: Changed the title to show what the article covers (VRAM requirements, file list, etc.).
  • 2026-10-02: Added our own measurements: video we generated.
  • 2026-10-02: Changed the title to show what the article covers (VRAM requirements, file list, etc.).