Instructions to use vanzl/DiffusionART-HunyuanVideo with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use vanzl/DiffusionART-HunyuanVideo with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("vanzl/DiffusionART-HunyuanVideo", dtype=torch.bfloat16, device_map="cuda") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Notebooks
- Google Colab
- Kaggle
Add pinned public inference entry and installation guide
Browse files- README.md +93 -54
- infer.py +200 -0
- install.sh +80 -0
- requirements-inference.txt +42 -0
README.md
CHANGED
|
@@ -11,68 +11,107 @@ tags:
|
|
| 11 |
|
| 12 |
# DiffusionART HunyuanVideo 7:2:1 — selected step 30
|
| 13 |
|
| 14 |
-
This private release
|
| 15 |
-
|
| 16 |
-
|
| 17 |
-
|
| 18 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 19 |
|
| 20 |
-
|
| 21 |
-
DiffusionART feedback from fixed-normalized temporal alignment, video quality,
|
| 22 |
-
and motion quality rewards at weights **0.7 / 0.2 / 0.1**, plus a frozen
|
| 23 |
-
pretrained-velocity MSE regularizer with beta **0.1**. This is a full F32
|
| 24 |
-
transformer export. Tensor names, shapes, dtypes, and values were preserved
|
| 25 |
-
when the original safetensors file was split into shards smaller than 5 GB.
|
| 26 |
-
The package includes no optimizer state, RNG state, VAE, text encoders, or
|
| 27 |
-
tokenizers.
|
| 28 |
|
| 29 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 30 |
|
| 31 |
-
|
| 32 |
-
[`hunyuanvideo-community/HunyuanVideo`](https://huggingface.co/hunyuanvideo-community/HunyuanVideo)
|
| 33 |
-
at revision `e8c2aaa66fe3742a32c11a6766aecbf07c56e773`. The training and
|
| 34 |
-
evaluation runtime used the native `hunyuan_hf` loader in
|
| 35 |
-
[`XueZeyue/DanceGRPO`](https://github.com/XueZeyue/DanceGRPO) at revision
|
| 36 |
-
`15cc71d53cc2e6e18a68ee607d5fb6ba9a99e344`. Its transformer loading
|
| 37 |
-
call takes the downloaded `transformer/` directory as the checkpoint and the
|
| 38 |
-
public base snapshot as the model root:
|
| 39 |
|
| 40 |
-
|
| 41 |
-
|
| 42 |
-
|
| 43 |
-
|
| 44 |
|
| 45 |
-
|
| 46 |
-
|
| 47 |
-
|
| 48 |
-
|
| 49 |
-
|
| 50 |
-
|
| 51 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 52 |
)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 53 |
```
|
| 54 |
|
| 55 |
-
|
| 56 |
-
|
| 57 |
-
|
| 58 |
-
|
| 59 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 60 |
|
| 61 |
-
The
|
| 62 |
-
|
| 63 |
-
|
| 64 |
-
|
| 65 |
-
|
|
|
|
|
|
|
|
|
|
| 66 |
|
| 67 |
-
##
|
| 68 |
|
| 69 |
-
|
| 70 |
-
|
| 71 |
-
|
| 72 |
-
|
| 73 |
-
|
| 74 |
-
|
| 75 |
-
revision `6204ad6aea1a77ff5aba337c88278bb9500eb37`. Review those terms,
|
| 76 |
-
including their territory and use restrictions, before distributing or using
|
| 77 |
-
the model. The community Diffusers conversion did not provide a separate
|
| 78 |
-
license file at the pinned base revision.
|
|
|
|
| 11 |
|
| 12 |
# DiffusionART HunyuanVideo 7:2:1 — selected step 30
|
| 13 |
|
| 14 |
+
This private release is a **full HunyuanVideo transformer**, not a complete
|
| 15 |
+
pipeline or LoRA adapter. It is the selected optimizer-step-30 checkpoint from
|
| 16 |
+
[run `tk1w4go1`](https://wandb.ai/vanzl3386-chinese-university-of-hong-kong-shenzhen/flow-grpo-algo/runs/tk1w4go1).
|
| 17 |
+
The original HunyuanVideo-13B transformer was trained with temporal alignment,
|
| 18 |
+
video quality, and motion quality rewards weighted 0.7 / 0.2 / 0.1, plus a
|
| 19 |
+
frozen pretrained-velocity MSE regularizer with beta 0.1. The transformer is
|
| 20 |
+
stored as F32 safetensors shards. The public base model supplies the VAE, text
|
| 21 |
+
encoders, tokenizers, and scheduler. `release.json` records the source and shard
|
| 22 |
+
hashes; this inference update does not alter those files.
|
| 23 |
|
| 24 |
+
## Requirements
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 25 |
|
| 26 |
+
- Access to this private Hugging Face repository through `HF_TOKEN` or an
|
| 27 |
+
interactive Hugging Face login. The commands keep the login and model cache
|
| 28 |
+
in the chosen work directory. Do not put a token in a script or shell history.
|
| 29 |
+
- Linux x86-64, Python **3.12**, a CUDA 12.1 compatible NVIDIA driver and CUDA
|
| 30 |
+
toolkit (`nvcc`) for building the pinned `flash-attn` version, plus `curl`,
|
| 31 |
+
`tar`, and `sha256sum`.
|
| 32 |
+
- One H800 with **at least 100 GiB available GPU memory** for the verified
|
| 33 |
+
default path. Independent inference used one 140 GiB H800 and peaked at
|
| 34 |
+
92.39 GiB allocated. Allow at least **100 GiB writable disk space** for the
|
| 35 |
+
private transformer, pinned public base assets, and installation caches.
|
| 36 |
+
`--cpu-offload` is available for constrained GPUs but was not part of the
|
| 37 |
+
independent acceptance run.
|
| 38 |
|
| 39 |
+
## Install and generate one MP4
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 40 |
|
| 41 |
+
The commands use only this release and the fixed **public**
|
| 42 |
+
[`XueZeyue/DanceGRPO`](https://github.com/XueZeyue/DanceGRPO/tree/15cc71d53cc2e6e18a68ee607d5fb6ba9a99e344)
|
| 43 |
+
runtime. No private training repository, saved prompt embeddings, notebook, or
|
| 44 |
+
MPI launcher is needed. Choose a work directory with the required free space.
|
| 45 |
|
| 46 |
+
```bash
|
| 47 |
+
export RUN_DIR="${RUN_DIR:-$PWD/diffusionart-t2v-demo}"
|
| 48 |
+
export HF_HOME="$RUN_DIR/cache/hf"
|
| 49 |
+
export HF_HUB_CACHE="$HF_HOME/hub"
|
| 50 |
+
export PIP_CACHE_DIR="$RUN_DIR/pip-cache"
|
| 51 |
+
export TMPDIR="$RUN_DIR/tmp"
|
| 52 |
+
export XDG_CACHE_HOME="$RUN_DIR/xdg-cache"
|
| 53 |
+
mkdir -p "$RUN_DIR" "$HF_HOME" "$HF_HUB_CACHE" "$PIP_CACHE_DIR" "$TMPDIR" "$XDG_CACHE_HOME"
|
| 54 |
+
cd "$RUN_DIR"
|
| 55 |
+
python3.12 -m venv bootstrap
|
| 56 |
+
bootstrap/bin/python -m pip install 'huggingface-hub==0.26.1'
|
| 57 |
+
if [ -z "${HF_TOKEN:-}" ] && [ ! -f "$HF_HOME/token" ]; then
|
| 58 |
+
bootstrap/bin/huggingface-cli login
|
| 59 |
+
fi
|
| 60 |
+
|
| 61 |
+
# If MODEL_REVISION is already set to a known 40-character commit SHA, keep it.
|
| 62 |
+
# Otherwise resolve main exactly once, then pin every download to that SHA.
|
| 63 |
+
if [ -z "${MODEL_REVISION:-}" ]; then
|
| 64 |
+
MODEL_REVISION="$(bootstrap/bin/python -c 'from huggingface_hub import HfApi; print(HfApi().model_info("vanzl/DiffusionART-HunyuanVideo-7-2-1", token=True).sha)')"
|
| 65 |
+
fi
|
| 66 |
+
export MODEL_REVISION
|
| 67 |
+
printf 'Pinned model revision: %s\n' "$MODEL_REVISION"
|
| 68 |
+
|
| 69 |
+
bootstrap/bin/python - <<'PY'
|
| 70 |
+
import os
|
| 71 |
+
from huggingface_hub import snapshot_download
|
| 72 |
+
snapshot_download(
|
| 73 |
+
repo_id="vanzl/DiffusionART-HunyuanVideo-7-2-1",
|
| 74 |
+
revision=os.environ["MODEL_REVISION"],
|
| 75 |
+
token=True,
|
| 76 |
+
allow_patterns=["README.md", "infer.py", "install.sh", "requirements-inference.txt"],
|
| 77 |
+
local_dir="release",
|
| 78 |
)
|
| 79 |
+
PY
|
| 80 |
+
|
| 81 |
+
bash release/install.sh "$RUN_DIR/venv" "$RUN_DIR/DanceGRPO-public"
|
| 82 |
+
"$RUN_DIR/venv/bin/python" release/infer.py \
|
| 83 |
+
--revision "$MODEL_REVISION" \
|
| 84 |
+
--prompt 'A white horse gallops through shallow water along a sunlit beach.' \
|
| 85 |
+
--seed 42 \
|
| 86 |
+
--output "$RUN_DIR/horse_seed42.mp4" \
|
| 87 |
+
--cache-dir "$RUN_DIR/cache"
|
| 88 |
```
|
| 89 |
|
| 90 |
+
`install.sh` installs PyTorch **2.5.0+cu121**, torchvision **0.20.0+cu121**,
|
| 91 |
+
the versions in `requirements-inference.txt`, `flash-attn==2.7.0.post2`,
|
| 92 |
+
`liger-kernel==0.4.1`, and the public DanceGRPO commit
|
| 93 |
+
`15cc71d53cc2e6e18a68ee607d5fb6ba9a99e344`. It verifies the public
|
| 94 |
+
source archive SHA256 before installation. `infer.py` downloads this model at
|
| 95 |
+
`--revision` and the base model at fixed revision
|
| 96 |
+
`e8c2aaa66fe3742a32c11a6766aecbf07c56e773` into `--cache-dir`.
|
| 97 |
+
Pass a different `--prompt`, `--seed`, or `--output` to make another video.
|
| 98 |
+
`infer.py` refuses to overwrite an existing MP4 and validates the generated
|
| 99 |
+
file by decoding every frame.
|
| 100 |
|
| 101 |
+
The verified protocol is **640×640, 53 frames, 8 FPS, 30 sampling steps,
|
| 102 |
+
FlowMatchEulerDiscreteScheduler shift 7, eta 0, and no classic CFG**. The
|
| 103 |
+
public pipeline takes `guidance_scale=6.018` as the user-facing value and
|
| 104 |
+
multiplies it by 1000 internally, yielding embedded guidance **6018**. Do not
|
| 105 |
+
pass 6018 as `guidance_scale`. The loader keeps the transformer in F32, runs
|
| 106 |
+
with BF16 autocast, loads both tokenizers from the pinned public base model,
|
| 107 |
+
and enables VAE tiling. The script prints the MP4 SHA256, decoded frame count,
|
| 108 |
+
resolution, FPS, and pixel statistics after a successful run.
|
| 109 |
|
| 110 |
+
## Terms and provenance
|
| 111 |
|
| 112 |
+
The package has no optimizer, RNG state, or training code. Tencent's original
|
| 113 |
+
HunyuanVideo weights and derivatives are subject to the **Tencent Hunyuan
|
| 114 |
+
Community License Agreement**. Read the included [LICENSE.txt](LICENSE.txt)
|
| 115 |
+
and [Notice](Notice) before use. The model card and inference helpers do not
|
| 116 |
+
change their terms. The original run was planned for 100 updates but stopped
|
| 117 |
+
at step 40; this release selects step 30 and does not claim 100 updates.
|
|
|
|
|
|
|
|
|
|
|
|
infer.py
ADDED
|
@@ -0,0 +1,200 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env python3
|
| 2 |
+
"""Generate one video with the pinned public HunyuanVideo inference pipeline."""
|
| 3 |
+
|
| 4 |
+
import argparse
|
| 5 |
+
import hashlib
|
| 6 |
+
import json
|
| 7 |
+
import os
|
| 8 |
+
import re
|
| 9 |
+
import sys
|
| 10 |
+
import time
|
| 11 |
+
from pathlib import Path
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
MODEL_ID = "vanzl/DiffusionART-HunyuanVideo-7-2-1"
|
| 15 |
+
BASE_ID = "hunyuanvideo-community/HunyuanVideo"
|
| 16 |
+
BASE_REVISION = "e8c2aaa66fe3742a32c11a6766aecbf07c56e773"
|
| 17 |
+
FRAMES = 53
|
| 18 |
+
WIDTH = HEIGHT = 640
|
| 19 |
+
FPS = 8
|
| 20 |
+
STEPS = 30
|
| 21 |
+
SHIFT = 7.0
|
| 22 |
+
GUIDANCE_SCALE = 6.018 # The public pipeline multiplies this by 1000.
|
| 23 |
+
|
| 24 |
+
|
| 25 |
+
def arguments():
|
| 26 |
+
parser = argparse.ArgumentParser(description=__doc__)
|
| 27 |
+
parser.add_argument("--prompt", required=True, help="Text prompt for one video")
|
| 28 |
+
parser.add_argument("--seed", type=int, default=42)
|
| 29 |
+
parser.add_argument("--output", required=True, type=Path, help="Output MP4 path")
|
| 30 |
+
parser.add_argument("--cache-dir", required=True, type=Path,
|
| 31 |
+
help="Writable directory for both pinned Hugging Face snapshots and runtime caches")
|
| 32 |
+
parser.add_argument("--revision", required=True,
|
| 33 |
+
help="Exact 40-character commit SHA of the private model release")
|
| 34 |
+
parser.add_argument("--cpu-offload", action="store_true",
|
| 35 |
+
help="Move inactive modules to CPU; the default H800 acceptance did not need this")
|
| 36 |
+
args = parser.parse_args()
|
| 37 |
+
if not args.prompt.strip():
|
| 38 |
+
parser.error("--prompt must contain text")
|
| 39 |
+
if not re.fullmatch(r"[0-9a-f]{40}", args.revision):
|
| 40 |
+
parser.error("--revision must be a fixed 40-character lowercase commit SHA")
|
| 41 |
+
if args.output.suffix.lower() != ".mp4":
|
| 42 |
+
parser.error("--output must end in .mp4")
|
| 43 |
+
if args.output.exists():
|
| 44 |
+
parser.error(f"output already exists: {args.output}")
|
| 45 |
+
return args
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
def configure_caches(root: Path):
|
| 49 |
+
root.mkdir(parents=True, exist_ok=True)
|
| 50 |
+
hf_home = root / "hf"
|
| 51 |
+
hub = hf_home / "hub"
|
| 52 |
+
for directory in (hf_home, hub, root / "xdg", root / "tmp", root / "triton"):
|
| 53 |
+
directory.mkdir(parents=True, exist_ok=True)
|
| 54 |
+
os.environ.update({
|
| 55 |
+
"HF_HOME": str(hf_home),
|
| 56 |
+
"HF_HUB_CACHE": str(hub),
|
| 57 |
+
"HUGGINGFACE_HUB_CACHE": str(hub),
|
| 58 |
+
"HF_ASSETS_CACHE": str(hf_home / "assets"),
|
| 59 |
+
"XDG_CACHE_HOME": str(root / "xdg"),
|
| 60 |
+
"TMPDIR": str(root / "tmp"),
|
| 61 |
+
"TRITON_CACHE_DIR": str(root / "triton"),
|
| 62 |
+
"TOKENIZERS_PARALLELISM": "false",
|
| 63 |
+
"HF_HUB_DISABLE_TELEMETRY": "1",
|
| 64 |
+
"PYTHONNOUSERSITE": "1",
|
| 65 |
+
})
|
| 66 |
+
return hub
|
| 67 |
+
|
| 68 |
+
|
| 69 |
+
def sha256(path: Path):
|
| 70 |
+
digest = hashlib.sha256()
|
| 71 |
+
with path.open("rb") as source:
|
| 72 |
+
for block in iter(lambda: source.read(16 * 1024 * 1024), b""):
|
| 73 |
+
digest.update(block)
|
| 74 |
+
return digest.hexdigest()
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def validate_video(path: Path):
|
| 78 |
+
import av
|
| 79 |
+
import numpy as np
|
| 80 |
+
|
| 81 |
+
frame_count = 0
|
| 82 |
+
pixel_sum = 0.0
|
| 83 |
+
squared_sum = 0.0
|
| 84 |
+
pixel_min = 255
|
| 85 |
+
pixel_max = 0
|
| 86 |
+
with av.open(str(path)) as container:
|
| 87 |
+
streams = [stream for stream in container.streams if stream.type == "video"]
|
| 88 |
+
if len(streams) != 1:
|
| 89 |
+
raise RuntimeError(f"expected one video stream, got {len(streams)}")
|
| 90 |
+
stream = streams[0]
|
| 91 |
+
width, height = stream.width, stream.height
|
| 92 |
+
fps = float(stream.average_rate)
|
| 93 |
+
for frame in container.decode(stream):
|
| 94 |
+
pixels = frame.to_ndarray(format="rgb24")
|
| 95 |
+
if pixels.shape != (HEIGHT, WIDTH, 3):
|
| 96 |
+
raise RuntimeError(f"unexpected frame shape: {pixels.shape}")
|
| 97 |
+
values = pixels.astype(np.float64)
|
| 98 |
+
pixel_sum += float(values.sum())
|
| 99 |
+
squared_sum += float(np.square(values).sum())
|
| 100 |
+
pixel_min = min(pixel_min, int(pixels.min()))
|
| 101 |
+
pixel_max = max(pixel_max, int(pixels.max()))
|
| 102 |
+
frame_count += 1
|
| 103 |
+
if (frame_count, width, height) != (FRAMES, WIDTH, HEIGHT):
|
| 104 |
+
raise RuntimeError(f"unexpected video geometry: {(frame_count, width, height)}")
|
| 105 |
+
if abs(fps - FPS) > 0.01:
|
| 106 |
+
raise RuntimeError(f"unexpected FPS: {fps}")
|
| 107 |
+
count = frame_count * width * height * 3
|
| 108 |
+
mean = pixel_sum / count
|
| 109 |
+
std = max(0.0, squared_sum / count - mean * mean) ** 0.5
|
| 110 |
+
if pixel_max <= pixel_min or pixel_max == 0 or std <= 1.0:
|
| 111 |
+
raise RuntimeError(f"blank or nearly constant video: range={pixel_min}:{pixel_max}, std={std}")
|
| 112 |
+
return {"decoded_frames": frame_count, "width": width, "height": height,
|
| 113 |
+
"fps": fps, "pixel_min": pixel_min, "pixel_max": pixel_max,
|
| 114 |
+
"pixel_mean": mean, "pixel_std": std}
|
| 115 |
+
|
| 116 |
+
|
| 117 |
+
def main():
|
| 118 |
+
args = arguments()
|
| 119 |
+
cache_root = args.cache_dir.expanduser().resolve()
|
| 120 |
+
hub_cache = configure_caches(cache_root)
|
| 121 |
+
output = args.output.expanduser().resolve()
|
| 122 |
+
output.parent.mkdir(parents=True, exist_ok=True)
|
| 123 |
+
|
| 124 |
+
# Import after setting cache variables, so neither library selects a home cache.
|
| 125 |
+
import torch
|
| 126 |
+
from diffusers import FlowMatchEulerDiscreteScheduler
|
| 127 |
+
from diffusers.utils import export_to_video
|
| 128 |
+
from fastvideo.models.hunyuan_hf.pipeline_hunyuan import HunyuanVideoPipeline
|
| 129 |
+
from fastvideo.utils.load import load_transformer
|
| 130 |
+
from huggingface_hub import snapshot_download
|
| 131 |
+
|
| 132 |
+
if not torch.cuda.is_available():
|
| 133 |
+
raise RuntimeError("A CUDA GPU is required")
|
| 134 |
+
if torch.version.cuda != "12.1":
|
| 135 |
+
raise RuntimeError(f"expected the verified PyTorch CUDA 12.1 build, got {torch.version.cuda}")
|
| 136 |
+
model = Path(snapshot_download(MODEL_ID, revision=args.revision,
|
| 137 |
+
cache_dir=str(hub_cache), token=True))
|
| 138 |
+
base = Path(snapshot_download(BASE_ID, revision=BASE_REVISION,
|
| 139 |
+
cache_dir=str(hub_cache)))
|
| 140 |
+
if model.name != args.revision or base.name != BASE_REVISION:
|
| 141 |
+
raise RuntimeError("Hugging Face snapshot revision differs from the requested commit")
|
| 142 |
+
|
| 143 |
+
started = time.perf_counter()
|
| 144 |
+
transformer = load_transformer("hunyuan_hf", str(model / "transformer"),
|
| 145 |
+
str(base), torch.float32)
|
| 146 |
+
if transformer.dtype != torch.float32:
|
| 147 |
+
raise RuntimeError(f"unexpected transformer dtype: {transformer.dtype}")
|
| 148 |
+
# The public base snapshot supplies both text tokenizers and encoders. This
|
| 149 |
+
# preserves the pipeline's default prompt template and avoids saved embeds.
|
| 150 |
+
pipe = HunyuanVideoPipeline.from_pretrained(
|
| 151 |
+
str(base), transformer=transformer, torch_dtype=torch.bfloat16)
|
| 152 |
+
if not isinstance(pipe.scheduler, FlowMatchEulerDiscreteScheduler):
|
| 153 |
+
raise RuntimeError(f"unexpected scheduler: {type(pipe.scheduler).__name__}")
|
| 154 |
+
if abs(float(pipe.scheduler.config.shift) - SHIFT) > 1e-6:
|
| 155 |
+
raise RuntimeError(f"unexpected scheduler shift: {pipe.scheduler.config.shift}")
|
| 156 |
+
pipe.enable_vae_tiling()
|
| 157 |
+
if args.cpu_offload:
|
| 158 |
+
pipe.enable_model_cpu_offload()
|
| 159 |
+
else:
|
| 160 |
+
pipe.to("cuda:0")
|
| 161 |
+
load_seconds = time.perf_counter() - started
|
| 162 |
+
|
| 163 |
+
generator = torch.Generator(device="cpu").manual_seed(args.seed)
|
| 164 |
+
torch.cuda.reset_peak_memory_stats(0)
|
| 165 |
+
started = time.perf_counter()
|
| 166 |
+
with torch.inference_mode(), torch.autocast("cuda", dtype=torch.bfloat16):
|
| 167 |
+
frames = pipe(prompt=[args.prompt], height=HEIGHT, width=WIDTH,
|
| 168 |
+
num_frames=FRAMES, num_inference_steps=STEPS,
|
| 169 |
+
guidance_scale=GUIDANCE_SCALE, generator=generator).frames[0]
|
| 170 |
+
inference_seconds = time.perf_counter() - started
|
| 171 |
+
peak_bytes = torch.cuda.max_memory_allocated(0)
|
| 172 |
+
|
| 173 |
+
partial = output.with_name(f"{output.stem}.partial.{os.getpid()}.mp4")
|
| 174 |
+
try:
|
| 175 |
+
export_to_video(frames, str(partial), fps=FPS)
|
| 176 |
+
del frames
|
| 177 |
+
decoded = validate_video(partial)
|
| 178 |
+
partial.replace(output)
|
| 179 |
+
finally:
|
| 180 |
+
if partial.exists():
|
| 181 |
+
partial.unlink()
|
| 182 |
+
result = {"output": str(output), "sha256": sha256(output),
|
| 183 |
+
"model_revision": args.revision, "base_revision": BASE_REVISION,
|
| 184 |
+
"prompt": args.prompt, "seed": args.seed,
|
| 185 |
+
"sampling": {"frames": FRAMES, "width": WIDTH, "height": HEIGHT,
|
| 186 |
+
"fps": FPS, "steps": STEPS, "shift": SHIFT, "eta": 0,
|
| 187 |
+
"classic_cfg": False, "guidance_scale": GUIDANCE_SCALE,
|
| 188 |
+
"embedded_guidance": 6018},
|
| 189 |
+
"load_seconds": round(load_seconds, 2),
|
| 190 |
+
"inference_seconds": round(inference_seconds, 2),
|
| 191 |
+
"peak_cuda_allocated_bytes": peak_bytes, "decoded": decoded}
|
| 192 |
+
print(json.dumps(result, ensure_ascii=False, indent=2), flush=True)
|
| 193 |
+
|
| 194 |
+
|
| 195 |
+
if __name__ == "__main__":
|
| 196 |
+
try:
|
| 197 |
+
main()
|
| 198 |
+
except Exception as error:
|
| 199 |
+
print(f"inference failed: {error}", file=sys.stderr, flush=True)
|
| 200 |
+
raise
|
install.sh
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
#!/usr/bin/env bash
|
| 2 |
+
set -euo pipefail
|
| 3 |
+
|
| 4 |
+
if [[ $# -ne 2 ]]; then
|
| 5 |
+
echo "usage: bash install.sh ABSOLUTE_VENV_DIR ABSOLUTE_PUBLIC_RUNTIME_DIR" >&2
|
| 6 |
+
exit 2
|
| 7 |
+
fi
|
| 8 |
+
VENV_DIR="$1"
|
| 9 |
+
PUBLIC_DIR="$2"
|
| 10 |
+
[[ "$VENV_DIR" = /* && "$PUBLIC_DIR" = /* ]] || {
|
| 11 |
+
echo "Both paths must be absolute." >&2
|
| 12 |
+
exit 2
|
| 13 |
+
}
|
| 14 |
+
[[ ! -e "$VENV_DIR" && ! -e "$PUBLIC_DIR" ]] || {
|
| 15 |
+
echo "Use fresh venv and public-runtime directories." >&2
|
| 16 |
+
exit 2
|
| 17 |
+
}
|
| 18 |
+
|
| 19 |
+
SCRIPT_DIR="$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")" && pwd)"
|
| 20 |
+
PUBLIC_COMMIT=15cc71d53cc2e6e18a68ee607d5fb6ba9a99e344
|
| 21 |
+
PUBLIC_ARCHIVE_SHA256=65e1e74b26989af16ba2232486d461d500249f69ebaa164eb210b69ed17cb1f6
|
| 22 |
+
WORK_DIR="$(dirname -- "$VENV_DIR")"
|
| 23 |
+
export PYTHONNOUSERSITE=1
|
| 24 |
+
unset PYTHONPATH
|
| 25 |
+
export PIP_CONFIG_FILE=/dev/null
|
| 26 |
+
export PIP_EXTRA_INDEX_URL=
|
| 27 |
+
export PIP_DISABLE_PIP_VERSION_CHECK=1
|
| 28 |
+
export PIP_CACHE_DIR="${PIP_CACHE_DIR:-$WORK_DIR/pip-cache}"
|
| 29 |
+
export TMPDIR="${TMPDIR:-$WORK_DIR/tmp}"
|
| 30 |
+
export XDG_CACHE_HOME="${XDG_CACHE_HOME:-$WORK_DIR/xdg-cache}"
|
| 31 |
+
export MAX_JOBS="${MAX_JOBS:-4}"
|
| 32 |
+
mkdir -p "$PIP_CACHE_DIR" "$TMPDIR" "$XDG_CACHE_HOME" "$(dirname -- "$PUBLIC_DIR")"
|
| 33 |
+
|
| 34 |
+
echo "STAGE create isolated Python 3.12 environment"
|
| 35 |
+
python3.12 -m venv "$VENV_DIR"
|
| 36 |
+
PYTHON="$VENV_DIR/bin/python"
|
| 37 |
+
|
| 38 |
+
echo "STAGE install PyTorch 2.5.0 CUDA 12.1"
|
| 39 |
+
timeout 1800s "$PYTHON" -m pip install --timeout 60 --retries 3 \
|
| 40 |
+
--index-url https://download.pytorch.org/whl/cu121 \
|
| 41 |
+
--extra-index-url https://pypi.org/simple \
|
| 42 |
+
'torch==2.5.0+cu121' 'torchvision==0.20.0+cu121'
|
| 43 |
+
|
| 44 |
+
echo "STAGE install pinned inference dependencies"
|
| 45 |
+
timeout 1800s "$PYTHON" -m pip install --timeout 60 --retries 3 \
|
| 46 |
+
--index-url https://pypi.org/simple \
|
| 47 |
+
-r "$SCRIPT_DIR/requirements-inference.txt"
|
| 48 |
+
|
| 49 |
+
echo "STAGE fetch fixed public DanceGRPO source"
|
| 50 |
+
ARCHIVE="$WORK_DIR/DanceGRPO-$PUBLIC_COMMIT.tar.gz"
|
| 51 |
+
timeout 600s curl --fail --location --retry 3 --retry-delay 2 \
|
| 52 |
+
"https://github.com/XueZeyue/DanceGRPO/archive/$PUBLIC_COMMIT.tar.gz" \
|
| 53 |
+
--output "$ARCHIVE"
|
| 54 |
+
printf '%s %s\n' "$PUBLIC_ARCHIVE_SHA256" "$ARCHIVE" | sha256sum --check --status
|
| 55 |
+
mkdir "$PUBLIC_DIR"
|
| 56 |
+
tar --extract --gzip --file "$ARCHIVE" --directory "$PUBLIC_DIR" --strip-components=1 \
|
| 57 |
+
--no-same-owner --no-same-permissions
|
| 58 |
+
test -f "$PUBLIC_DIR/fastvideo/models/hunyuan_hf/pipeline_hunyuan.py"
|
| 59 |
+
timeout 600s "$PYTHON" -m pip install --timeout 60 --retries 3 \
|
| 60 |
+
--index-url https://pypi.org/simple --no-deps --editable "$PUBLIC_DIR"
|
| 61 |
+
|
| 62 |
+
echo "STAGE build fixed flash-attn and install required public loader import"
|
| 63 |
+
timeout 3600s "$PYTHON" -m pip install --timeout 60 --retries 3 \
|
| 64 |
+
--index-url https://pypi.org/simple --no-build-isolation 'flash-attn==2.7.0.post2'
|
| 65 |
+
timeout 600s "$PYTHON" -m pip install --timeout 60 --retries 3 \
|
| 66 |
+
--index-url https://pypi.org/simple --no-deps 'liger-kernel==0.4.1'
|
| 67 |
+
|
| 68 |
+
echo "STAGE verify inference imports and installed versions"
|
| 69 |
+
"$PYTHON" - <<'PY'
|
| 70 |
+
import av, diffusers, fastvideo, huggingface_hub, torch, transformers
|
| 71 |
+
from fastvideo.models.hunyuan_hf.pipeline_hunyuan import HunyuanVideoPipeline
|
| 72 |
+
from fastvideo.utils.load import load_transformer
|
| 73 |
+
assert torch.__version__ == '2.5.0+cu121', torch.__version__
|
| 74 |
+
assert torch.version.cuda == '12.1', torch.version.cuda
|
| 75 |
+
assert diffusers.__version__ == '0.32.0', diffusers.__version__
|
| 76 |
+
assert transformers.__version__ == '4.46.1', transformers.__version__
|
| 77 |
+
print('verified:', torch.__version__, diffusers.__version__, transformers.__version__,
|
| 78 |
+
av.__version__, huggingface_hub.__version__, flush=True)
|
| 79 |
+
PY
|
| 80 |
+
echo "INSTALL COMPLETE: $VENV_DIR"
|
requirements-inference.txt
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Versions used by the independent public-pipeline inference acceptance.
|
| 2 |
+
# PyTorch, the fixed public runtime, flash-attn, and liger-kernel are installed
|
| 3 |
+
# in that order by install.sh because flash-attn builds against PyTorch.
|
| 4 |
+
accelerate==1.0.1
|
| 5 |
+
av==13.1.0
|
| 6 |
+
certifi==2026.7.22
|
| 7 |
+
charset-normalizer==3.5.2
|
| 8 |
+
diffusers==0.32.0
|
| 9 |
+
einops==0.8.0
|
| 10 |
+
filelock==4.0.12
|
| 11 |
+
fsspec==2026.9.0
|
| 12 |
+
hf_transfer==0.1.9
|
| 13 |
+
huggingface-hub==0.26.1
|
| 14 |
+
idna==3.20
|
| 15 |
+
imageio==2.36.0
|
| 16 |
+
imageio-ffmpeg==0.5.1
|
| 17 |
+
importlib_metadata==9.0.1
|
| 18 |
+
Jinja2==3.1.6
|
| 19 |
+
MarkupSafe==3.0.4
|
| 20 |
+
mpmath==1.3.0
|
| 21 |
+
networkx==3.7
|
| 22 |
+
ninja==1.11.1.1
|
| 23 |
+
numpy==1.26.3
|
| 24 |
+
packaging==26.3
|
| 25 |
+
peft==0.13.2
|
| 26 |
+
pillow==10.2.0
|
| 27 |
+
protobuf==5.28.3
|
| 28 |
+
psutil==7.2.2
|
| 29 |
+
PyYAML==6.0.3
|
| 30 |
+
regex==2026.9.29
|
| 31 |
+
requests==2.34.2
|
| 32 |
+
safetensors==0.8.0
|
| 33 |
+
sentencepiece==0.2.0
|
| 34 |
+
setuptools==84.0.0
|
| 35 |
+
sympy==1.13.1
|
| 36 |
+
tokenizers==0.20.3
|
| 37 |
+
typing_extensions==4.16.0
|
| 38 |
+
tqdm==4.70.1
|
| 39 |
+
transformers==4.46.1
|
| 40 |
+
urllib3==2.8.0
|
| 41 |
+
wheel==0.44.0
|
| 42 |
+
zipp==4.1.1
|