Text-to-Video
VideoX Fun
hkunzhe commited on
Commit
d41cfc8
·
verified ·
1 Parent(s): 7c897c1

Upload MiniMax-H3 acceleration LoRAs and examples

Browse files

Upload the MiniMax-H3 FL2VA and Ref2VA acceleration LoRAs, inference examples, documentation, assets, and generated comparison videos. Excludes __pycache__ and samples.

.gitattributes CHANGED
@@ -33,3 +33,24 @@ saved_model/**/* filter=lfs diff=lfs merge=lfs -text
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
33
  *.zip filter=lfs diff=lfs merge=lfs -text
34
  *.zst filter=lfs diff=lfs merge=lfs -text
35
  *tfevents* filter=lfs diff=lfs merge=lfs -text
36
+ asset/ref2va_test_5_2.jpg filter=lfs diff=lfs merge=lfs -text
37
+ asset/ref2va_test_5_3.jpg filter=lfs diff=lfs merge=lfs -text
38
+ asset/ref2va_test_5_4.jpg filter=lfs diff=lfs merge=lfs -text
39
+ results/minimax_h3_fl2va_acc_8step_v1_1.mp4 filter=lfs diff=lfs merge=lfs -text
40
+ results/minimax_h3_fl2va_acc_8step_v1_2.mp4 filter=lfs diff=lfs merge=lfs -text
41
+ results/minimax_h3_fl2va_acc_8step_v1_3.mp4 filter=lfs diff=lfs merge=lfs -text
42
+ results/minimax_h3_fl2va_baseline_1.mp4 filter=lfs diff=lfs merge=lfs -text
43
+ results/minimax_h3_fl2va_baseline_2.mp4 filter=lfs diff=lfs merge=lfs -text
44
+ results/minimax_h3_fl2va_baseline_3.mp4 filter=lfs diff=lfs merge=lfs -text
45
+ results/minimax_h3_fl2va_turbo_4step_v1.1_768p_1.mp4 filter=lfs diff=lfs merge=lfs -text
46
+ results/minimax_h3_fl2va_turbo_4step_v1.1_768p_2.mp4 filter=lfs diff=lfs merge=lfs -text
47
+ results/minimax_h3_fl2va_turbo_4step_v1.1_768p_3.mp4 filter=lfs diff=lfs merge=lfs -text
48
+ results/minimax_h3_ref2va_acc_8step_v1_1.mp4 filter=lfs diff=lfs merge=lfs -text
49
+ results/minimax_h3_ref2va_acc_8step_v1_2.mp4 filter=lfs diff=lfs merge=lfs -text
50
+ results/minimax_h3_ref2va_acc_8step_v1_3.mp4 filter=lfs diff=lfs merge=lfs -text
51
+ results/minimax_h3_ref2va_baseline_1.mp4 filter=lfs diff=lfs merge=lfs -text
52
+ results/minimax_h3_ref2va_baseline_2.mp4 filter=lfs diff=lfs merge=lfs -text
53
+ results/minimax_h3_ref2va_baseline_3.mp4 filter=lfs diff=lfs merge=lfs -text
54
+ results/minimax_h3_ref2va_turbo_4step_v0.1_768p_1.mp4 filter=lfs diff=lfs merge=lfs -text
55
+ results/minimax_h3_ref2va_turbo_4step_v0.1_768p_2.mp4 filter=lfs diff=lfs merge=lfs -text
56
+ results/minimax_h3_ref2va_turbo_4step_v0.1_768p_3.mp4 filter=lfs diff=lfs merge=lfs -text
MiniMax-H3-FL2VA-Acc-8Step.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:0b29be7042d883970eb0c20774a9ba03d95669ed80a721bb4d21be8ea0d0a196
3
+ size 1372450680
MiniMax-H3-Ref2VA-Acc-8Step.safetensors ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:111c82e669f6e20e628228172edf39395f1a9fc3ad049793895e542c0f55b18c
3
+ size 1372450680
README.md CHANGED
@@ -1,3 +1,126 @@
1
  ---
2
  license: apache-2.0
 
 
 
 
 
3
  ---
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
  ---
2
  license: apache-2.0
3
+ base_model:
4
+ - alibaba-pai/MiniMax-H3-Acc-LoRAs
5
+ - MiniMaxAI/MiniMax-H3
6
+ library_name: videox_fun
7
+ pipeline_tag: text-to-video
8
  ---
9
+ # MiniMax-H3-Acc-LoRAs
10
+ ## Introduction
11
+ We apply Parallel Decoding Distillation (PDD) <sup>[1](#ref1)</sup> to [MiniMax-H3](https://huggingface.co/MiniMaxAI/MiniMax-H3), enabling efficient video generation in only a few inference steps.
12
+
13
+ For more details, please refer to our [GitHub repo](https://github.com/aigc-apps/VideoX-Fun).
14
+
15
+ | Name | Base Model | Hugging Face | Description |
16
+ |--|--|--|--|
17
+ | MiniMax-H3-FL2VA-Acc-8Step.safetensors | [MiniMax-H3 (FL2VA)](https://huggingface.co/MiniMaxAI/MiniMax-H3/tree/main/FL2VA) | [🤗Link](https://huggingface.co/alibaba-pai/MiniMax-H3-Acc-LoRAs/resolve/main/MiniMax-H3-FL2VA-Acc-8Step.safetensors) | Official 8 Step Acc LoRA (`rank=64` and `network_alpha=64` in BF16) for MiniMax-H3 (FL2VA).|
18
+ | MiniMax-H3-Ref2VA-Acc-8Step.safetensors | [MiniMax-H3 (Ref2VA)](https://huggingface.co/MiniMaxAI/MiniMax-H3/tree/main/Ref2VA) | [🤗Link](https://huggingface.co/alibaba-pai/MiniMax-H3-Acc-LoRAs/resolve/main/MiniMax-H3-Ref2VA-Acc-8Step.safetensors) | Official 8 Step Acc LoRA (`rank=64` and `network_alpha=64` in BF16) for MiniMax-H3 (Ref2VA).|
19
+
20
+ ## Demo
21
+
22
+ ### FL2VA (768p)
23
+
24
+ <table border="0" style="width: 100%; text-align: center; margin-top: 20px;">
25
+ <thead>
26
+ <tr>
27
+ <th style="text-align: center;" width="33%">MiniMax-H3-FL2VA</th>
28
+ <th style="text-align: center;" width="33%"><a href="https://huggingface.co/lightx2v/Minimax-h3-Turbo/blob/main/minimax_h3_fl2v_turbo_4step_v1.1_768p_bf16.safetensors">Minimax-h3-Turbo <br> (fl2v_turbo_4step_v1.1_768p)</a></th>
29
+ <th style="text-align: center;" width="33%">MiniMax-H3-FL2VA-Acc-8Step </th>
30
+ </tr>
31
+ </thead>
32
+ <tr>
33
+ <td>
34
+ <video src="results/minimax_h3_fl2va_baseline_1.mp4" width="100%" controls autoplay loop></video>
35
+ </td>
36
+ <td>
37
+ <video src="results/minimax_h3_fl2va_turbo_4step_v1.1_768p_1.mp4" width="100%" controls autoplay loop></video>
38
+ </td>
39
+ <td>
40
+ <video src="results/minimax_h3_fl2va_acc_8step_v1_1.mp4" width="100%" controls autoplay loop></video>
41
+ </td>
42
+ </tr>
43
+ <tr>
44
+ <td>
45
+ <video src="results/minimax_h3_fl2va_baseline_2.mp4" width="100%" controls autoplay loop></video>
46
+ </td>
47
+ <td>
48
+ <video src="results/minimax_h3_fl2va_turbo_4step_v1.1_768p_2.mp4" width="100%" controls autoplay loop></video>
49
+ </td>
50
+ <td>
51
+ <video src="results/minimax_h3_fl2va_acc_8step_v1_2.mp4" width="100%" controls autoplay loop></video>
52
+ </td>
53
+ </tr>
54
+ <tr>
55
+ <td>
56
+ <video src="results/minimax_h3_fl2va_baseline_3.mp4" width="100%" controls autoplay loop></video>
57
+ </td>
58
+ <td>
59
+ <video src="results/minimax_h3_fl2va_turbo_4step_v1.1_768p_3.mp4" width="100%" controls autoplay loop></video>
60
+ </td>
61
+ <td>
62
+ <video src="results/minimax_h3_fl2va_acc_8step_v1_3.mp4" width="100%" controls autoplay loop></video>
63
+ </td>
64
+ </tr>
65
+ </table>
66
+
67
+ ### Ref2VA
68
+
69
+ <table border="0" style="width: 100%; text-align: center; margin-top: 20px;">
70
+ <thead>
71
+ <tr>
72
+ <th style="text-align: center;" width="33%">MiniMax-H3-Ref2VA</th>
73
+ <th style="text-align: center;" width="33%"><a href="https://huggingface.co/lightx2v/Minimax-h3-Turbo/blob/main/minimax_h3_ref2v_turbo_4step_v0.1_bf16.safetensors">Minimax-h3-Turbo <br> (ref2v_turbo_4step_v0.1)</a></th>
74
+ <th style="text-align: center;" width="33%">MiniMax-H3-Ref2VA-Acc-8Step</th>
75
+ </tr>
76
+ </thead>
77
+ <tr>
78
+ <td>
79
+ <video src="results/minimax_h3_ref2va_baseline_1.mp4" width="100%" controls autoplay loop></video>
80
+ </td>
81
+ <td>
82
+ <video src="results/minimax_h3_ref2va_turbo_4step_v0.1_768p_1.mp4" width="100%" controls autoplay loop></video>
83
+ </td>
84
+ <td>
85
+ <video src="results/minimax_h3_ref2va_acc_8step_v1_1.mp4" width="100%" controls autoplay loop></video>
86
+ </td>
87
+ </tr>
88
+ <tr>
89
+ <td>
90
+ <video src="results/minimax_h3_ref2va_baseline_2.mp4" width="100%" controls autoplay loop></video>
91
+ </td>
92
+ <td>
93
+ <video src="results/minimax_h3_ref2va_turbo_4step_v0.1_2.mp4" width="100%" controls autoplay loop></video>
94
+ </td>
95
+ <td>
96
+ <video src="results/minimax_h3_ref2va_acc_8step_v1_2.mp4" width="100%" controls autoplay loop></video>
97
+ </td>
98
+ </tr>
99
+ <tr>
100
+ <td>
101
+ <video src="results/minimax_h3_ref2va_baseline_3.mp4" width="100%" controls autoplay loop></video>
102
+ </td>
103
+ <td>
104
+ <video src="results/minimax_h3_ref2v_turbo_4step_v0.1_768p_3.mp4" width="100%" controls autoplay loop></video>
105
+ </td>
106
+ <td>
107
+ <video src="asset/minimax_h3_ref2va_acc_8step_v1_3.mp4" width="100%" controls autoplay loop></video>
108
+ </td>
109
+ </tr>
110
+ </table>
111
+
112
+
113
+ > [!NOTE]
114
+ > The above test cases are from <a href="https://github.com/ModelTC/Minimax-H3-Turbo/tree/main/examples">Minimax-H3-Turbo</a>.
115
+ > Videos are generated with a LoRA weight of 1.0 at both 4 and 8 NFE.
116
+
117
+ ## Quick Start
118
+ Set `model_path` and `pdd_lora_path` to the MiniMax-H3 model and the matching acceleration LoRA checkpoint in [predict_t2v.py](https://github.com/aigc-apps/VideoX-Fun/blob/main/examples/MiniMax-H3-Acc-LoRAs/predict_t2v.py) for FL2VA or [predict_ref2v.py](https://github.com/aigc-apps/VideoX-Fun/blob/main/examples/MiniMax-H3-Acc-LoRAs/predict_ref2v.py) for Ref2VA, then run the corresponding script. Each example uses `apply_pdd_lora` to load the checkpoint and derive the required number of inference steps from its configuration.
119
+
120
+ > [!NOTE]
121
+ > These scripts use Diffusers' MiniMax-H3 `ModularPipeline` and require `diffusers >= 0.40.0`.
122
+
123
+ ## Reference
124
+ <ol>
125
+ <li id="ref1">Neta Shaul, et al. "Parallel Decoding Distillation for Fast Image and Video Generation.". arXiv preprint arXiv:2607.26004 (2026).</li>
126
+ </ol>
asset/ref2va_test_5_1.jpg ADDED
asset/ref2va_test_5_2.jpg ADDED

Git LFS Details

  • SHA256: afda45ed8f06a176c19fa9b224cb6c07467dd0d0ab2564a14c3014cc7ae34ca4
  • Pointer size: 131 Bytes
  • Size of remote file: 378 kB
asset/ref2va_test_5_3.jpg ADDED

Git LFS Details

  • SHA256: 97e0835a1bf64ee4c4ae0ca0d94e3262bc6bfaff79a77ec2c7155ccb26110b8c
  • Pointer size: 131 Bytes
  • Size of remote file: 341 kB
asset/ref2va_test_5_4.jpg ADDED

Git LFS Details

  • SHA256: 939856c9964d33c9ddecac30b145b82993c5d6ba9ce6a2afcf169233ecc177e6
  • Pointer size: 131 Bytes
  • Size of remote file: 189 kB
minimax_h3_pdd.py ADDED
@@ -0,0 +1,290 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """PDD LoRA adapter for MiniMax-H3 on Diffusers 0.40.
2
+
3
+ Parallel Decoding Distillation (PDD, arXiv 2607.26004) is not a PEFT LoRA: the
4
+ backbone gets low-rank updates, and the two final heads (`proj_out`,
5
+ `audio_proj_out`) are repeated once per interval of a length-`N` grid. Each
6
+ generation step fuses a block of those heads into one Euler velocity, so
7
+ `NFE = N / L` transformer evaluations cover the whole trajectory.
8
+
9
+ This module is inference-only. It mutates a loaded
10
+ `MiniMaxH3Transformer3DModel` in place and arms the fused heads on every
11
+ forward, so the stock Diffusers modular pipeline does not need a step callback.
12
+ """
13
+
14
+ from __future__ import annotations
15
+
16
+ import json
17
+ from pathlib import Path
18
+ from typing import Sequence
19
+
20
+ import torch
21
+ import torch.nn as nn
22
+ import torch.nn.functional as F
23
+
24
+ DEFAULT_PDD_CONFIG = {
25
+ "pdd_num_steps": 32,
26
+ "pdd_block_size": 4,
27
+ "lora_rank": 64,
28
+ "lora_alpha": 64.0,
29
+ "lora_targets": "to_q,to_k,to_v,to_out.0,ff.net.0.proj,ff.net.2,adaln_proj.linear",
30
+ }
31
+
32
+
33
+ def shifted_sigma(shift: float, sigma: torch.Tensor) -> torch.Tensor:
34
+ return shift * sigma / (1 + (shift - 1) * sigma)
35
+
36
+
37
+ def pdd_time_grid(shift: float, num_steps: int) -> torch.Tensor:
38
+ """Ascending grid `0 = t_0 < ... < t_N = 1` of one MiniMax-H3 schedule."""
39
+ sigma = torch.linspace(1.0, 0.0, num_steps + 1, dtype=torch.float64)
40
+ return 1.0 - shifted_sigma(shift, sigma)
41
+
42
+
43
+ def pdd_sampling_plan(step_sizes: torch.Tensor, start: int, block_size: int) -> torch.Tensor:
44
+ """Mean velocity of one block, which an Euler step over the block boundaries consumes."""
45
+ plan = torch.zeros(1, step_sizes.shape[0], dtype=step_sizes.dtype, device=step_sizes.device)
46
+ span = step_sizes[start : start + block_size].sum()
47
+ plan[0, start : start + block_size] = step_sizes[start : start + block_size] / span
48
+ return plan
49
+
50
+
51
+ class MiniMaxH3ParallelHead(nn.Module):
52
+ """`N` per-interval output heads in place of one final linear layer."""
53
+
54
+ def __init__(self, source: nn.Linear, num_steps: int):
55
+ super().__init__()
56
+ self.num_steps = num_steps
57
+ self.in_features = source.in_features
58
+ self.out_features = source.out_features
59
+ self.weight = nn.Parameter(source.weight.detach()[None].repeat(num_steps, 1, 1).clone())
60
+ self.bias = (
61
+ None if source.bias is None else nn.Parameter(source.bias.detach()[None].repeat(num_steps, 1).clone())
62
+ )
63
+ self.plan = torch.zeros(1, num_steps)
64
+ self.plan[0, 0] = 1.0
65
+
66
+ def set_plan(self, plan: torch.Tensor) -> None:
67
+ if plan.ndim != 2 or plan.shape[1] != self.num_steps:
68
+ raise ValueError(f"A PDD plan must be `(num_directions, {self.num_steps})`, got {list(plan.shape)}.")
69
+ self.plan = plan
70
+
71
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
72
+ plan = self.plan.to(device=self.weight.device, dtype=self.weight.dtype)
73
+ weight = torch.einsum("pn,noi->poi", plan, self.weight).flatten(0, 1)
74
+ bias = None if self.bias is None else torch.einsum("pn,no->po", plan, self.bias).flatten()
75
+ return F.linear(hidden_states, weight, bias)
76
+
77
+
78
+ class LoRALinear(nn.Module):
79
+ """Frozen `nn.Linear` plus `y += (alpha / rank) * B A x`."""
80
+
81
+ # Diffusers' AdaLN casts activations with `get_parameter_dtype(linear)`. That
82
+ # helper returns the first floating parameter, so skip the float32 adapters
83
+ # and expose the frozen backbone dtype.
84
+ _keep_in_fp32_modules = ["lora_down", "lora_up"]
85
+
86
+ def __init__(self, base: nn.Linear, rank: int, alpha: float):
87
+ super().__init__()
88
+ self.base = base
89
+ self.base.requires_grad_(False)
90
+ self.scaling = alpha / rank
91
+ self.lora_down = nn.Parameter(torch.empty(rank, base.in_features, dtype=torch.float32))
92
+ self.lora_up = nn.Parameter(torch.zeros(base.out_features, rank, dtype=torch.float32))
93
+ nn.init.kaiming_uniform_(self.lora_down, a=5**0.5)
94
+
95
+ @property
96
+ def weight(self) -> torch.Tensor:
97
+ return self.base.weight
98
+
99
+ @property
100
+ def bias(self):
101
+ return self.base.bias
102
+
103
+ @property
104
+ def in_features(self) -> int:
105
+ return self.base.in_features
106
+
107
+ @property
108
+ def out_features(self) -> int:
109
+ return self.base.out_features
110
+
111
+ def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
112
+ out = self.base(hidden_states)
113
+ update = F.linear(
114
+ F.linear(hidden_states, self.lora_down.to(device=hidden_states.device, dtype=hidden_states.dtype)),
115
+ self.lora_up.to(device=hidden_states.device, dtype=hidden_states.dtype),
116
+ )
117
+ return out + self.scaling * update.to(out.dtype)
118
+
119
+
120
+ def attach_parallel_decoder(transformer: nn.Module, num_steps: int) -> None:
121
+ transformer.proj_out = MiniMaxH3ParallelHead(transformer.proj_out, num_steps)
122
+ transformer.audio_proj_out = MiniMaxH3ParallelHead(transformer.audio_proj_out, num_steps)
123
+
124
+
125
+ def add_lora(module: nn.Module, target_names: Sequence[str], rank: int, alpha: float) -> int:
126
+ targets = [
127
+ (name, child)
128
+ for name, child in module.named_modules()
129
+ if isinstance(child, nn.Linear) and any(name.endswith(suffix) for suffix in target_names)
130
+ ]
131
+ for name, child in targets:
132
+ parent_name, _, attribute = name.rpartition(".")
133
+ parent = module.get_submodule(parent_name) if parent_name else module
134
+ setattr(parent, attribute, LoRALinear(child, rank, alpha))
135
+ return len(targets)
136
+
137
+
138
+ def set_parallel_plan(transformer: nn.Module, video_plan: torch.Tensor, audio_plan: torch.Tensor) -> None:
139
+ transformer.proj_out.set_plan(video_plan)
140
+ transformer.audio_proj_out.set_plan(audio_plan)
141
+
142
+
143
+ def _load_pdd_config(config_path: Path | None) -> dict:
144
+ config = dict(DEFAULT_PDD_CONFIG)
145
+ if config_path is None or not config_path.is_file():
146
+ return config
147
+ with config_path.open(encoding="utf-8") as handle:
148
+ saved = json.load(handle)
149
+ for key in config:
150
+ if key in saved:
151
+ config[key] = saved[key]
152
+ if not isinstance(config["lora_targets"], str):
153
+ config["lora_targets"] = ",".join(config["lora_targets"])
154
+ return config
155
+
156
+
157
+ def _load_state_dict(path: Path) -> dict[str, torch.Tensor]:
158
+ if path.suffix.lower() == ".safetensors":
159
+ from safetensors.torch import load_file
160
+
161
+ checkpoint = load_file(path, device="cpu")
162
+ else:
163
+ try:
164
+ checkpoint = torch.load(path, map_location="cpu", weights_only=True, mmap=True)
165
+ except TypeError:
166
+ checkpoint = torch.load(path, map_location="cpu", weights_only=True)
167
+ if isinstance(checkpoint, dict) and isinstance(checkpoint.get("state_dict"), dict):
168
+ checkpoint = checkpoint["state_dict"]
169
+ if not isinstance(checkpoint, dict):
170
+ raise TypeError(f"Expected a state-dict mapping in {path}, got {type(checkpoint).__name__}.")
171
+ return checkpoint
172
+
173
+
174
+ def resolve_pdd_checkpoint(path: str) -> tuple[Path, Path | None]:
175
+ """Return `(weights_path, config_path)` from a local file/folder or a Hub repo id."""
176
+ local = Path(path).expanduser()
177
+ if local.is_file():
178
+ return local.resolve(), local.resolve().parent / "pdd_config.json"
179
+ if local.is_dir():
180
+ for name in ("pdd.pt", "pdd.safetensors"):
181
+ candidate = local / name
182
+ if candidate.is_file():
183
+ return candidate.resolve(), (local / "pdd_config.json").resolve()
184
+ raise FileNotFoundError(f"No pdd.pt or pdd.safetensors in {local}")
185
+ if "/" not in path:
186
+ raise FileNotFoundError(
187
+ f"PDD checkpoint not found: {path}. Pass a local pdd.pt / folder, or a Hugging Face repo id."
188
+ )
189
+
190
+ from huggingface_hub import hf_hub_download
191
+ from huggingface_hub.utils import EntryNotFoundError, RepositoryNotFoundError
192
+
193
+ try:
194
+ weights = Path(hf_hub_download(path, "pdd.pt"))
195
+ except (EntryNotFoundError, RepositoryNotFoundError):
196
+ weights = Path(hf_hub_download(path, "pdd.safetensors"))
197
+ try:
198
+ config = Path(hf_hub_download(path, "pdd_config.json"))
199
+ except EntryNotFoundError:
200
+ config = None
201
+ return weights, config
202
+
203
+
204
+ class _PDDStepArm:
205
+ """Arm the fused heads before each transformer forward, then advance one block."""
206
+
207
+ def __init__(
208
+ self,
209
+ transformer: nn.Module,
210
+ video_steps: torch.Tensor,
211
+ audio_steps: torch.Tensor,
212
+ block_size: int,
213
+ nfe: int,
214
+ ):
215
+ self.transformer = transformer
216
+ self.video_steps = video_steps
217
+ self.audio_steps = audio_steps
218
+ self.block_size = block_size
219
+ self.nfe = nfe
220
+ self.index = 0
221
+ self.arm(0)
222
+
223
+ def arm(self, step_index: int) -> None:
224
+ start = step_index * self.block_size
225
+ set_parallel_plan(
226
+ self.transformer,
227
+ pdd_sampling_plan(self.video_steps, start, self.block_size).float(),
228
+ pdd_sampling_plan(self.audio_steps, start, self.block_size).float(),
229
+ )
230
+
231
+ def __call__(self, _module, _args, output):
232
+ self.index += 1
233
+ if self.index < self.nfe:
234
+ self.arm(self.index)
235
+ else:
236
+ self.index = 0
237
+ self.arm(0)
238
+ return output
239
+
240
+
241
+ def apply_pdd_lora(transformer: nn.Module, checkpoint: str, video_shift: float, audio_shift: float) -> int:
242
+ """Inject PDD LoRA + parallel heads, load weights, and arm them on every forward.
243
+
244
+ Args:
245
+ transformer: MiniMax-H3 `transformer` or `transformer_ref`.
246
+ checkpoint: Local `pdd.pt` / folder / Hugging Face repo id.
247
+ video_shift, audio_shift: Scheduler shifts (12.0 / 3.0 as released).
248
+
249
+ Returns:
250
+ `nfe`, the number of transformer evaluations. Diffusers' MiniMax-H3
251
+ scheduler counts the terminal sigma, so call the pipeline with
252
+ `num_inference_steps=nfe + 1`.
253
+ """
254
+ weights_path, config_path = resolve_pdd_checkpoint(checkpoint)
255
+ config = _load_pdd_config(config_path)
256
+ num_steps = int(config["pdd_num_steps"])
257
+ block_size = int(config["pdd_block_size"])
258
+ if block_size < 1 or num_steps % block_size != 0:
259
+ raise ValueError(f"pdd_num_steps={num_steps} must be divisible by pdd_block_size={block_size}.")
260
+ nfe = num_steps // block_size
261
+
262
+ add_lora(
263
+ transformer,
264
+ config["lora_targets"].split(","),
265
+ int(config["lora_rank"]),
266
+ float(config["lora_alpha"]),
267
+ )
268
+ attach_parallel_decoder(transformer, num_steps)
269
+
270
+ state_dict = _load_state_dict(weights_path)
271
+ incompatible = transformer.load_state_dict(state_dict, strict=False)
272
+ if incompatible.unexpected_keys:
273
+ preview = ", ".join(incompatible.unexpected_keys[:3])
274
+ raise RuntimeError(
275
+ f"{weights_path} holds keys the parallel decoder does not have, e.g. {preview}."
276
+ )
277
+ print(
278
+ f"Loaded PDD LoRA: {weights_path} ({len(state_dict)} tensors, "
279
+ f"grid={num_steps}, block={block_size}, nfe={nfe})",
280
+ flush=True,
281
+ )
282
+
283
+ video_steps = pdd_time_grid(float(video_shift), num_steps).diff()
284
+ audio_steps = pdd_time_grid(float(audio_shift), num_steps).diff()
285
+ controller = _PDDStepArm(transformer, video_steps, audio_steps, block_size, nfe)
286
+ transformer.register_forward_hook(controller)
287
+ transformer._pdd_step_arm = controller
288
+ transformer.requires_grad_(False)
289
+ transformer.eval()
290
+ return nfe
predict_ref2v.py ADDED
@@ -0,0 +1,51 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+
4
+ import torch
5
+ from diffusers import ComponentsManager, ModularPipeline
6
+ from diffusers.modular_pipelines.minimax_h3 import MiniMaxH3ImageReference
7
+ from diffusers.utils.export_utils import encode_video
8
+
9
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
10
+ from minimax_h3_pdd import apply_pdd_lora
11
+
12
+ here = Path(__file__).resolve().parent
13
+ model_path = "MiniMaxAI/MiniMax-H3"
14
+ pdd_lora_path = str(here / "MiniMax-H3-Ref2VA-Acc-8Step.safetensors")
15
+ reference_images = [here / "asset" / f"ref2va_test_5_{i}.jpg" for i in range(1, 5)]
16
+ prompt = "subject_definitions:\n<Subject 1> is the East Asian woman sword fighter shown in <Picture 1>, preserving her facial identity, black hair tied in a high bun, focused expression, cream martial-arts jacket, and black-handled katana.\n<Subject 2> is the pale-skinned woman fighter shown in <Picture 2>, preserving her facial identity, short layered black hair with bangs, dark eye makeup, black lipstick, black leather jacket, and black clothing. The motorcycle is not present in the target video.\n<Subject 3> is the outdoor competition swimming pool shown in <Picture 3>, preserving the bright turquoise water, blue-and-white lane dividers, pale pool deck, and strong daylight.\n<Subject 4> is the underwater pool environment shown in <Picture 4>, preserving the cyan water, tiled pool floor, dark lane marking, moving caustic light, suspended bubbles, and refracted surface light.\n\nsummary:\n[reference generation] In one continuous 10-second action shot, <Subject 1> and <Subject 2> sprint into a violent exchange beside <Subject 3>. A rolling dodge leads into a left-fist versus right-hand-sword collision, a jumping counter, and a connected two-body tumble across the wet deck. Their momentum throws both women into the pool, where the confrontation finishes underwater inside <Subject 4>.\n\nretention_analysis:\n<Subject 1> (appears throughout): fully_preserved - retain the same face, high bun, cream jacket, body proportions, and single black-handled katana before and after entering the water.\n<Subject 2> (appears throughout): fully_preserved - retain the same face, short black hair, dark makeup, black lipstick, black leather jacket, black clothing, and body proportions before and after entering the water.\n<Subject 3> (appears before the underwater transition): fully_preserved - retain the competition-pool geometry, turquoise water, blue-and-white lane ropes, sunlit deck, and bright daylight.\n<Subject 4> (appears after the underwater transition): fully_preserved - retain the tiled floor, dark lane marking, cyan water, caustic illumination, bubbles, and refracted sunlight.\n\ndetailed_description:\nPhotorealistic live-action action cinema, 16:9 composition, hard bright daylight above water and luminous cyan light below water. Use one continuous aggressively tracking shot with no cuts: low lateral sprint tracking, one fast camera roll during the deck tumble, then a seamless plunge through the waterline. Keep both adult women clearly distinguishable by face, hairstyle, clothing color, and silhouette. <Subject 1> holds exactly one katana in her right hand throughout. <Subject 2> remains unarmed. Any fist-to-sword contact strikes the flat near the guard, never the sharpened edge. No blood, wounds, severed contact, duplicate people, duplicate weapons, motorcycle, readable text, logos, or costume changes.\n\n[Shot 1, 00:00.000-00:02.000] A low handheld camera races parallel to the wet edge of <Subject 3>. <Subject 1> in the cream jacket and <Subject 2> in black leather sprint toward each other from opposite ends of the pool deck. <Subject 1> carries the single katana low in her right hand, blade trailing safely behind. She plants one foot on a low starting block, leaps forward, and brings the sword into a fast diagonal arc. <Subject 2> drops beneath it into a forward shoulder roll, slides across the wet tiles, and rises inside the sword's range as water sprays from both fighters' shoes.\n\n[Shot 1, 00:02.000-00:04.200] <Subject 2> drives upward from the roll with one explosive left punch. <Subject 1> snaps the katana across in her right hand so the punch collides with the flat of the blade immediately beside the guard. The impact produces one sharp metallic crack, a compact spray of sparks, and a ring of displaced droplets. Their faces pass within centimeters. <Subject 2> hooks the sword wrist with her right hand and whips a low leg sweep; <Subject 1> jumps over it, twists in the air, and lands one step from the pool edge without releasing the sword.\n\n[Shot 1, 00:04.200-00:06.800] <Subject 2> immediately tackles <Subject 1> around the waist. <Subject 1> braces her left forearm across <Subject 2>'s shoulder and extends her right sword arm away from both bodies. Locked together, they crash onto the wet deck and complete two fast connected shoulder-over-shoulder rolls. The camera rolls with them at tile level as sky, lane ropes, faces, black leather, and cream fabric whip across frame. <Subject 1> hooks <Subject 2>'s ankle during the second rotation; their combined momentum carries both bodies over the pool lip.\n\n[Shot 1, 00:06.800-00:07.900] Both women rotate once in open air and strike the turquoise surface together in an enormous splash. The camera plunges between them without a cut, crossing the violently folding waterline as daylight becomes refracted cyan and a dense wall of bubbles engulfs the frame.\n\n[Shot 1, 00:07.900-00:10.000] Inside <Subject 4>, their clothes and hair surge in opposite currents while both identities remain clear. <Subject 2> throws one slowed left punch through the bubbles. <Subject 1>, still gripping the single katana in her right hand, blocks with the hilt and her left forearm while keeping the blade pointed safely away. The impact rolls both women apart above the dark lane marking. They kick backward, regain facing guard positions, and end suspended on opposite sides of a rising column of bubbles as caustic light flashes across their faces and the tiled floor.\n\noverall_soundscape:\nRapid wet footfalls, starting-block impact, sword whoosh, rolling bodies, one metallic fist-to-blade crack, sparks, tile scrapes, a heavy synchronized splash, dense bubbles, muffled underwater impact, and filtered pool ambience.\n\nnon_diegetic_music:\nFast taiko-like percussion accelerates through the sprint and deck rolls, cuts completely at the splash, then returns underwater as two muffled bass pulses ending on one suspended metallic note.\n"
17
+ sample_size = [768, 1344]
18
+ video_length = 243
19
+
20
+ manager = ComponentsManager()
21
+ manager.enable_auto_cpu_offload(device="cuda", memory_reserve_margin="12GB")
22
+ pipeline = ModularPipeline.from_pretrained(model_path, workflow="ref2va", components_manager=manager)
23
+ pipeline.load_components(dtype=torch.bfloat16, pretrained_model_name_or_path=model_path)
24
+
25
+ nfe = apply_pdd_lora(
26
+ pipeline.transformer_ref, pdd_lora_path, pipeline.scheduler.shift, pipeline.audio_scheduler.shift
27
+ )
28
+
29
+ generator = torch.Generator().manual_seed(42)
30
+ result = pipeline(
31
+ prompt=prompt,
32
+ references=[MiniMaxH3ImageReference.from_file(str(path)) for path in reference_images],
33
+ height=sample_size[0],
34
+ width=sample_size[1],
35
+ num_frames=video_length,
36
+ num_inference_steps=nfe + 1,
37
+ generator=generator,
38
+ output_type="np",
39
+ output=["videos", "audio", "sampling_rate"],
40
+ )
41
+
42
+ output_path = here / "samples" / "ref2v.mp4"
43
+ output_path.parent.mkdir(parents=True, exist_ok=True)
44
+ encode_video(
45
+ result["videos"][0],
46
+ fps=24,
47
+ output_path=str(output_path),
48
+ audio=result["audio"][0],
49
+ audio_sample_rate=int(result["sampling_rate"]),
50
+ )
51
+ print(f"Saved {output_path}", flush=True)
predict_t2v.py ADDED
@@ -0,0 +1,60 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import sys
2
+ from pathlib import Path
3
+
4
+ import torch
5
+ from diffusers import ComponentsManager, ModularPipeline
6
+ from diffusers.utils.export_utils import encode_video
7
+
8
+ sys.path.insert(0, str(Path(__file__).resolve().parent))
9
+ from minimax_h3_pdd import apply_pdd_lora
10
+
11
+ model_path = "MiniMaxAI/MiniMax-H3"
12
+ pdd_lora_path = str(Path(__file__).resolve().parent / "MiniMax-H3-FL2VA-Acc-8Step.safetensors")
13
+ prompt = (
14
+ "[Shot 1] Cinematic wide shot, low angle, camera pushing in fast. A knight in dented steel plate armour "
15
+ "charges across a muddy tournament field toward a shaggy brown warhorse-mounted rival, his tattered blue "
16
+ "surcoat streaming behind him. He raises a notched longsword in both hands and brings it down; the rival "
17
+ "parries with a round wooden shield that splinters, chips of wood spinning into the air. Clods of wet earth "
18
+ "fly up from their boots and hooves. Grey banners snap in a strong crosswind under an overcast sky, and a "
19
+ "crowd of blurred spectators presses against a wooden barricade in the background. The camera swings around "
20
+ "the clash as the knight shoulders into the shield and both men stagger sideways."
21
+ "Ringing steel-on-steel impacts with long metallic decay, the dry crack of splitting wood, heavy plate "
22
+ "rattling with every stride, boots sucking out of deep mud, a horse snorting and stamping, wind buffeting the "
23
+ "microphone and a roaring crowd swelling behind it."
24
+ "Full orchestral battle cue, fast ostinato strings under blaring low brass, timpani accenting each sword "
25
+ "impact."
26
+ )
27
+ sample_size = [704, 1280]
28
+ video_length = 124
29
+
30
+ manager = ComponentsManager()
31
+ manager.enable_auto_cpu_offload(device="cuda", memory_reserve_margin="12GB")
32
+ pipeline = ModularPipeline.from_pretrained(model_path, workflow="t2va", components_manager=manager)
33
+ pipeline.load_components(dtype=torch.bfloat16, pretrained_model_name_or_path=model_path)
34
+
35
+ nfe = apply_pdd_lora(
36
+ pipeline.transformer, pdd_lora_path, pipeline.scheduler.shift, pipeline.audio_scheduler.shift
37
+ )
38
+
39
+ generator = torch.Generator().manual_seed(42)
40
+ result = pipeline(
41
+ prompt=prompt,
42
+ height=sample_size[0],
43
+ width=sample_size[1],
44
+ num_frames=video_length,
45
+ num_inference_steps=nfe + 1,
46
+ generator=generator,
47
+ output_type="np",
48
+ output=["videos", "audio", "sampling_rate"],
49
+ )
50
+
51
+ output_path = Path(__file__).resolve().parent / "samples" / "t2v.mp4"
52
+ output_path.parent.mkdir(parents=True, exist_ok=True)
53
+ encode_video(
54
+ result["videos"][0],
55
+ fps=24,
56
+ output_path=str(output_path),
57
+ audio=result["audio"][0],
58
+ audio_sample_rate=int(result["sampling_rate"]),
59
+ )
60
+ print(f"Saved {output_path}", flush=True)
results/minimax_h3_fl2va_acc_8step_v1_1.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6fcef5076fec4cfad72b9e1a2104744ace87a6c0c2f804088b45d9817841741e
3
+ size 851760
results/minimax_h3_fl2va_acc_8step_v1_2.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:e15ed74a9883384f3a3662f959067de0793e8aefff20e6fd55885fb882529d23
3
+ size 8798213
results/minimax_h3_fl2va_acc_8step_v1_3.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:cf02dd4516a9453bb1bc012fb912f1ef83f854d07151686dfbcd16757cab0219
3
+ size 1961347
results/minimax_h3_fl2va_baseline_1.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:afcbd76e7e5b6107df3b5b0a77fbdc09253d2e20beca55403730d45c55032584
3
+ size 835589
results/minimax_h3_fl2va_baseline_2.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:6673f964eda77ada13384af36cca1fb3fa058a45a788780ff9da154569cdfd44
3
+ size 7719897
results/minimax_h3_fl2va_baseline_3.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7cc312bdafbf84b3a9d9da6c0af8a5b5cd75888b3d695d33f6bb5016c811d253
3
+ size 1814474
results/minimax_h3_fl2va_turbo_4step_v1.1_768p_1.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b92514db1f7a59517a1ef165748e5d7fab6e317fc51e8212e1b95645e3912f18
3
+ size 893312
results/minimax_h3_fl2va_turbo_4step_v1.1_768p_2.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:f21d92548605a8e05c2d8bee608df81c47a584ed483d3b7c9aea17f1ddaeaa0c
3
+ size 9806891
results/minimax_h3_fl2va_turbo_4step_v1.1_768p_3.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:831a974e4f25cb01c60845f1b6dc5720966c12f88801d5aefe8a077d76f99fda
3
+ size 2326695
results/minimax_h3_ref2va_acc_8step_v1_1.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:c38d56649aeb5420e6e595808d4f5f92aabdcc059fc52350ce09806fa7f5e1a6
3
+ size 6085204
results/minimax_h3_ref2va_acc_8step_v1_2.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:49db1d2d853a189c5f04987439fe2f4ca7b8d99827cddf9e5cf93ce0a0cafa4b
3
+ size 2006767
results/minimax_h3_ref2va_acc_8step_v1_3.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:87955def3dbb1ea49726ae9c07fc8709dbb15f9026ecc38a15ee300771d944f6
3
+ size 4151912
results/minimax_h3_ref2va_baseline_1.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:b06369445bba07ed87581b49e163ec47ea4219c4624f95ff59d0c8870d4fad09
3
+ size 5930077
results/minimax_h3_ref2va_baseline_2.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8bd6e9b1b8e992d00d9c7754215ce47314be76a81d461b5d50a1ad250b808afc
3
+ size 1659004
results/minimax_h3_ref2va_baseline_3.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:233d92ae3bedad301581c1a7df1c19fef6746967620902c9330950a52c1ae30c
3
+ size 4891750
results/minimax_h3_ref2va_turbo_4step_v0.1_768p_1.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:fa71f4bc139274893d2ffcf07a1c461bc04afd54f8959c94843a2c6418f7222f
3
+ size 5273075
results/minimax_h3_ref2va_turbo_4step_v0.1_768p_2.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:67b13c561a0d34387c0fc46904435edfc618bce23923fcebaab8102806638cc7
3
+ size 1012341
results/minimax_h3_ref2va_turbo_4step_v0.1_768p_3.mp4 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:bfce9c6dd70388c1eef57ae8c2455a492d2d2104a55c6ee31ad5188c0e3dcf9c
3
+ size 3734857