multimodalart HF Staff commited on
Commit
b0b040c
·
verified ·
1 Parent(s): de482f9

Config, modular index and the pruned transformer class

Browse files
modular_model_index.json ADDED
@@ -0,0 +1,131 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "MiniMaxH3ModularPipeline",
3
+ "_diffusers_version": "0.40.0.dev0",
4
+ "_blocks_class_name": "MiniMaxH3Blocks",
5
+ "text_encoder": [
6
+ "transformers",
7
+ "Qwen3VLForConditionalGeneration",
8
+ {
9
+ "type_hint": [
10
+ "transformers",
11
+ "Qwen3VLForConditionalGeneration"
12
+ ],
13
+ "pretrained_model_name_or_path": "MiniMaxAI/MiniMax-H3",
14
+ "subfolder": "text_encoder",
15
+ "variant": null,
16
+ "revision": null
17
+ }
18
+ ],
19
+ "tokenizer": [
20
+ "transformers",
21
+ "Qwen2TokenizerFast",
22
+ {
23
+ "type_hint": [
24
+ "transformers",
25
+ "Qwen2TokenizerFast"
26
+ ],
27
+ "pretrained_model_name_or_path": "MiniMaxAI/MiniMax-H3",
28
+ "subfolder": "tokenizer",
29
+ "variant": null,
30
+ "revision": null
31
+ }
32
+ ],
33
+ "processor": [
34
+ "transformers",
35
+ "Qwen3VLProcessor",
36
+ {
37
+ "type_hint": [
38
+ "transformers",
39
+ "Qwen3VLProcessor"
40
+ ],
41
+ "pretrained_model_name_or_path": "MiniMaxAI/MiniMax-H3",
42
+ "subfolder": "processor",
43
+ "variant": null,
44
+ "revision": null
45
+ }
46
+ ],
47
+ "vae": [
48
+ "diffusers",
49
+ "AutoencoderKLMiniMaxH3",
50
+ {
51
+ "type_hint": [
52
+ "diffusers",
53
+ "AutoencoderKLMiniMaxH3"
54
+ ],
55
+ "pretrained_model_name_or_path": "MiniMaxAI/MiniMax-H3",
56
+ "subfolder": "vae",
57
+ "variant": null,
58
+ "revision": null
59
+ }
60
+ ],
61
+ "audio_vae": [
62
+ "diffusers",
63
+ "AutoencoderKLMiniMaxH3Audio",
64
+ {
65
+ "type_hint": [
66
+ "diffusers",
67
+ "AutoencoderKLMiniMaxH3Audio"
68
+ ],
69
+ "pretrained_model_name_or_path": "MiniMaxAI/MiniMax-H3",
70
+ "subfolder": "audio_vae",
71
+ "variant": null,
72
+ "revision": null
73
+ }
74
+ ],
75
+ "transformer": [
76
+ "diffusers",
77
+ "AutoModel",
78
+ {
79
+ "type_hint": [
80
+ "diffusers",
81
+ "AutoModel"
82
+ ],
83
+ "pretrained_model_name_or_path": "multimodalart/MiniMax-H3-Pruned",
84
+ "subfolder": "transformer",
85
+ "variant": null,
86
+ "revision": null
87
+ }
88
+ ],
89
+ "transformer_ref": [
90
+ "diffusers",
91
+ "AutoModel",
92
+ {
93
+ "type_hint": [
94
+ "diffusers",
95
+ "AutoModel"
96
+ ],
97
+ "pretrained_model_name_or_path": "multimodalart/MiniMax-H3-Pruned",
98
+ "subfolder": "transformer_ref",
99
+ "variant": null,
100
+ "revision": null
101
+ }
102
+ ],
103
+ "scheduler": [
104
+ "diffusers",
105
+ "MiniMaxH3Scheduler",
106
+ {
107
+ "type_hint": [
108
+ "diffusers",
109
+ "MiniMaxH3Scheduler"
110
+ ],
111
+ "pretrained_model_name_or_path": "MiniMaxAI/MiniMax-H3",
112
+ "subfolder": "scheduler",
113
+ "variant": null,
114
+ "revision": null
115
+ }
116
+ ],
117
+ "audio_scheduler": [
118
+ "diffusers",
119
+ "MiniMaxH3Scheduler",
120
+ {
121
+ "type_hint": [
122
+ "diffusers",
123
+ "MiniMaxH3Scheduler"
124
+ ],
125
+ "pretrained_model_name_or_path": "MiniMaxAI/MiniMax-H3",
126
+ "subfolder": "audio_scheduler",
127
+ "variant": null,
128
+ "revision": null
129
+ }
130
+ ]
131
+ }
transformer/config.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "MiniMaxH3PrunedTransformer3DModel",
3
+ "_diffusers_version": "0.40.0.dev0",
4
+ "adaln_rank": 8,
5
+ "attention_head_dim": 128,
6
+ "audio_in_channels": 32,
7
+ "auto_map": {
8
+ "AutoModel": "modeling_minimax_h3_pruned.MiniMaxH3PrunedTransformer3DModel"
9
+ },
10
+ "ffn_dim": 14336,
11
+ "final_norm_eps": 1e-05,
12
+ "freq_dim": 256,
13
+ "hidden_size": 5376,
14
+ "in_channels": 24,
15
+ "norm_eps": 1e-05,
16
+ "num_attention_heads": 56,
17
+ "num_layers": 50,
18
+ "num_refiner_layers": 2,
19
+ "patch_size": [
20
+ 1,
21
+ 2,
22
+ 2
23
+ ],
24
+ "qk_norm_eps": 1e-05,
25
+ "rope_freq_dim": 16,
26
+ "rope_theta": 10000.0,
27
+ "text_dim": 5120,
28
+ "time_embed_dim": 2688,
29
+ "time_embed_hidden_dim": 5376,
30
+ "time_table_size": 1025
31
+ }
transformer/modeling_minimax_h3_pruned.py ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 The MiniMax Team and The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """AdaLN-pruned MiniMax-H3 transformer.
15
+
16
+ Everything outside the timestep path is inherited from `MiniMaxH3Transformer3DModel`: the attention, the blocks, the
17
+ token refiner, the output heads and `forward` itself are the released implementation, unmodified. Only what feeds the
18
+ AdaLN projections changes.
19
+ """
20
+
21
+ import torch
22
+ import torch.nn as nn
23
+ from diffusers.configuration_utils import register_to_config
24
+ from diffusers.models.modeling_utils import get_parameter_dtype
25
+ from diffusers.models.transformers.transformer_minimax_h3 import (
26
+ MINIMAX_H3_MODALITY_NUM,
27
+ MiniMaxH3RotaryPosEmbed,
28
+ MiniMaxH3TokenRefiner,
29
+ MiniMaxH3Transformer3DModel,
30
+ MiniMaxH3TransformerBlock,
31
+ )
32
+
33
+
34
+ class MiniMaxH3PrunedTimeEmbedder(nn.Module):
35
+ r"""The released timestep MLP, replaced by an interpolated table of AdaLN coordinates.
36
+
37
+ Every AdaLN projection in the released model consumes `silu(time_embedder(time_proj(t)))`, which depends on the
38
+ scalar timestep alone: over `t` in `[0, 1]` it traces a one-dimensional curve in `R^{time_embed_dim}`. A rank-8
39
+ affine subspace reproduces that curve to about 1.5e-5 relative RMS, so only the curve's coordinates in that
40
+ subspace are stored - sampled on a uniform grid of `table_size` timesteps and linearly interpolated in between.
41
+ The subspace offset is folded into the AdaLN biases and its basis into the AdaLN weights, which is why the
42
+ projections take an `adaln_rank`-wide input here instead of `time_embed_dim`.
43
+
44
+ The module stands in for `time_proj` and `time_embedder` together: it consumes the raw timestep, so the released
45
+ `forward` needs no change once `time_proj` is an identity.
46
+ """
47
+
48
+ def __init__(self, table_size: int = 1025, adaln_rank: int = 8) -> None:
49
+ super().__init__()
50
+ self.register_buffer("table", torch.zeros(table_size, adaln_rank), persistent=True)
51
+
52
+ def forward(self, timestep: torch.Tensor) -> torch.Tensor:
53
+ table = self.table
54
+ steps = table.shape[0] - 1
55
+ position = timestep.to(table.dtype).flatten().clamp(0.0, 1.0) * steps
56
+ lower = position.floor().clamp(max=steps - 1).long()
57
+ weight = (position - lower).unsqueeze(-1)
58
+ return torch.lerp(table.index_select(0, lower), table.index_select(0, lower + 1), weight)
59
+
60
+
61
+ class MiniMaxH3PrunedTimeProj(nn.Module):
62
+ r"""Identity stand-in for `Timesteps`: the pruned time embedder indexes the raw timestep."""
63
+
64
+ def forward(self, timestep: torch.Tensor) -> torch.Tensor:
65
+ return timestep
66
+
67
+
68
+ class MiniMaxH3PrunedAdaLayerNormModulation(nn.Module):
69
+ r"""`MiniMaxH3AdaLayerNormModulation` over the pruned timestep coordinates.
70
+
71
+ Two differences from the released module. It applies no `silu` - the table already holds the coordinates of the
72
+ activated curve. And the folded bias is a float32 buffer applied outside the projection rather than the
73
+ projection's own bias: it carries almost the entire modulation (the coordinate term contributes a few tenths of
74
+ it), so storing it in bfloat16 would put a full output-scale rounding step into every evaluation. Kept in
75
+ float32 it costs 0.4 MB per block and leaves the pruned AdaLN function closer to an exact float64 evaluation
76
+ than the released bfloat16 checkpoint's own arithmetic is.
77
+
78
+ `linear` stays a bias-free `nn.Linear` so PEFT wraps it exactly as it wraps the released projection.
79
+ """
80
+
81
+ def __init__(self, adaln_rank: int, hidden_size: int) -> None:
82
+ super().__init__()
83
+ self.hidden_size = hidden_size
84
+ out_features = 6 * hidden_size * MINIMAX_H3_MODALITY_NUM
85
+ self.linear = nn.Linear(adaln_rank, out_features, bias=False)
86
+ self.register_buffer("folded_bias", torch.zeros(out_features), persistent=True)
87
+
88
+ def forward(self, temb: torch.Tensor) -> tuple[torch.Tensor, ...]:
89
+ dtype = get_parameter_dtype(self.linear)
90
+ temb = self.linear(temb.to(dtype))
91
+ temb = (temb.float() + self.folded_bias).to(dtype)
92
+ temb = temb.view(-1, 6 * self.hidden_size)
93
+ return temb.chunk(6, dim=-1)
94
+
95
+
96
+ class MiniMaxH3PrunedAdaLayerNormOut(nn.Module):
97
+ r"""`MiniMaxH3AdaLayerNormOut` over the pruned timestep coordinates; see the modulation module above."""
98
+
99
+ def __init__(self, hidden_size: int, adaln_rank: int, eps: float) -> None:
100
+ super().__init__()
101
+ self.norm = nn.RMSNorm(hidden_size, eps=eps)
102
+ self.linear = nn.Linear(adaln_rank, 2 * hidden_size, bias=False)
103
+ self.register_buffer("folded_bias", torch.zeros(2 * hidden_size), persistent=True)
104
+
105
+ def forward(self, hidden_states: torch.Tensor, temb: torch.Tensor, timestep_indices: torch.Tensor) -> torch.Tensor:
106
+ dtype = get_parameter_dtype(self.linear)
107
+ temb = self.linear(temb.to(dtype))
108
+ shift, scale = (temb.float() + self.folded_bias).to(dtype).chunk(2, dim=-1)
109
+ hidden_states = self.norm(hidden_states)
110
+ return hidden_states * (1.0 + scale.index_select(0, timestep_indices)) + shift.index_select(
111
+ 0, timestep_indices
112
+ )
113
+
114
+
115
+ class MiniMaxH3PrunedTransformer3DModel(MiniMaxH3Transformer3DModel):
116
+ r"""MiniMax-H3's DiT with the AdaLN input projections reduced to their reachable rank.
117
+
118
+ The released checkpoint spends 13.03B of its 33.14B parameters on the 50 per-block `adaln_proj.linear` matrices
119
+ plus `norm_out.linear`, all of which read the same 2688-wide timestep embedding. Because that embedding is a
120
+ function of the scalar timestep, its reachable set is a curve an 8-dimensional affine subspace covers to ~1.5e-5
121
+ relative RMS - far below one bfloat16 rounding step of the weights themselves. Folding the subspace into the
122
+ projections leaves an 8-wide input and removes 26 GB per partition.
123
+
124
+ Only what builds the timestep path differs from [`MiniMaxH3Transformer3DModel`]: `time_proj` becomes an identity,
125
+ `time_embedder` becomes [`MiniMaxH3PrunedTimeEmbedder`], and the AdaLN projections take `adaln_rank` inputs.
126
+ `forward` is inherited unchanged. The module names are the released ones, so LoRAs trained against a pruned
127
+ checkpoint - what the common trainers use by default - load natively.
128
+
129
+ Args:
130
+ adaln_rank (`int`, defaults to `8`):
131
+ The width of the timestep coordinates every AdaLN projection consumes.
132
+ time_table_size (`int`, defaults to `1025`):
133
+ The number of uniformly spaced timesteps the coordinate table holds; values in between are interpolated
134
+ linearly.
135
+
136
+ Every other argument is [`MiniMaxH3Transformer3DModel`]'s and carries the same meaning. `freq_dim` and
137
+ `time_embed_hidden_dim` are kept in the config, unused, so a pruned config still records the shape of the
138
+ released timestep MLP it was folded from.
139
+ """
140
+
141
+ _supports_gradient_checkpointing = True
142
+ _no_split_modules = ["MiniMaxH3TransformerBlock", "MiniMaxH3TokenRefinerBlock", "MiniMaxH3PrunedAdaLayerNormOut"]
143
+ _repeated_blocks = ["MiniMaxH3TransformerBlock", "MiniMaxH3TokenRefinerBlock"]
144
+ _skip_layerwise_casting_patterns = ["norm"]
145
+ # The released checkpoint's mixed-precision split - patch projections, output heads and the timestep path in
146
+ # float32, the block stack in bfloat16 - plus the folded AdaLN biases, for the reason given on the modulation
147
+ # module. Entries are matched against the dot-separated segments of each parameter name.
148
+ _keep_in_fp32_modules = [
149
+ "proj_in",
150
+ "audio_proj_in",
151
+ "time_embedder",
152
+ "proj_out",
153
+ "audio_proj_out",
154
+ "rope",
155
+ "folded_bias",
156
+ ]
157
+
158
+ @register_to_config
159
+ def __init__(
160
+ self,
161
+ num_attention_heads: int = 56,
162
+ attention_head_dim: int = 128,
163
+ hidden_size: int = 5376,
164
+ num_layers: int = 50,
165
+ num_refiner_layers: int = 2,
166
+ ffn_dim: int = 14336,
167
+ in_channels: int = 24,
168
+ audio_in_channels: int = 32,
169
+ patch_size: tuple[int, int, int] = (1, 2, 2),
170
+ text_dim: int = 5120,
171
+ freq_dim: int = 256,
172
+ time_embed_hidden_dim: int = 5376,
173
+ time_embed_dim: int = 2688,
174
+ rope_freq_dim: int = 16,
175
+ rope_theta: float = 10000.0,
176
+ norm_eps: float = 1e-5,
177
+ qk_norm_eps: float = 1e-5,
178
+ final_norm_eps: float = 1e-5,
179
+ adaln_rank: int = 8,
180
+ time_table_size: int = 1025,
181
+ ) -> None:
182
+ # `MiniMaxH3Transformer3DModel.__init__` is itself wrapped by `register_to_config`, so calling it would
183
+ # register the released config over this one - and would allocate the 26 GB of AdaLN projections this class
184
+ # exists to avoid. The module tree is built here instead; everything but the timestep path is verbatim.
185
+ nn.Module.__init__(self)
186
+
187
+ video_patch_dim = in_channels * patch_size[0] * patch_size[1] * patch_size[2]
188
+
189
+ # 1. Per-modality input projections
190
+ self.proj_in = nn.Linear(video_patch_dim, hidden_size, bias=True)
191
+ self.audio_proj_in = nn.Linear(audio_in_channels, hidden_size, bias=True)
192
+ self.context_embedder = nn.Linear(text_dim, hidden_size, bias=True)
193
+
194
+ # 2. Timestep coordinates, shared by every AdaLN projection
195
+ self.time_proj = MiniMaxH3PrunedTimeProj()
196
+ self.time_embedder = MiniMaxH3PrunedTimeEmbedder(table_size=time_table_size, adaln_rank=adaln_rank)
197
+
198
+ # 3. Rotary embedding over the packed (t, h, w) grid
199
+ self.rope = MiniMaxH3RotaryPosEmbed(rope_freq_dim=rope_freq_dim, rope_theta=rope_theta)
200
+
201
+ # 4. Text stream refiner
202
+ self.token_refiner = MiniMaxH3TokenRefiner(
203
+ hidden_size=hidden_size,
204
+ num_attention_heads=num_attention_heads,
205
+ attention_head_dim=attention_head_dim,
206
+ ffn_dim=ffn_dim,
207
+ num_layers=num_refiner_layers,
208
+ norm_eps=norm_eps,
209
+ qk_norm_eps=qk_norm_eps,
210
+ final_norm_eps=final_norm_eps,
211
+ )
212
+
213
+ # 5. The block stack, with each block's AdaLN projection narrowed to the timestep coordinates. The block is
214
+ # built with `time_embed_dim=adaln_rank` so its own projection is already the right shape, then swapped
215
+ # for the pruned module, which drops the `silu` and moves the bias to float32.
216
+ self.transformer_blocks = nn.ModuleList(
217
+ [
218
+ MiniMaxH3TransformerBlock(
219
+ hidden_size=hidden_size,
220
+ num_attention_heads=num_attention_heads,
221
+ attention_head_dim=attention_head_dim,
222
+ ffn_dim=ffn_dim,
223
+ time_embed_dim=adaln_rank,
224
+ norm_eps=norm_eps,
225
+ qk_norm_eps=qk_norm_eps,
226
+ )
227
+ for _ in range(num_layers)
228
+ ]
229
+ )
230
+ for block in self.transformer_blocks:
231
+ block.adaln_proj = MiniMaxH3PrunedAdaLayerNormModulation(adaln_rank=adaln_rank, hidden_size=hidden_size)
232
+
233
+ # 6. Shared output norm and the two per-modality output heads
234
+ self.norm_out = MiniMaxH3PrunedAdaLayerNormOut(
235
+ hidden_size=hidden_size, adaln_rank=adaln_rank, eps=final_norm_eps
236
+ )
237
+ self.proj_out = nn.Linear(hidden_size, video_patch_dim, bias=True)
238
+ self.audio_proj_out = nn.Linear(hidden_size, audio_in_channels, bias=True)
239
+
240
+ self.gradient_checkpointing = False
241
+
242
+
243
+ MiniMaxH3PrunedTransformer3DModel.register_for_auto_class("AutoModel")
transformer_ref/config.json ADDED
@@ -0,0 +1,31 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ {
2
+ "_class_name": "MiniMaxH3PrunedTransformer3DModel",
3
+ "_diffusers_version": "0.40.0.dev0",
4
+ "adaln_rank": 8,
5
+ "attention_head_dim": 128,
6
+ "audio_in_channels": 32,
7
+ "auto_map": {
8
+ "AutoModel": "modeling_minimax_h3_pruned.MiniMaxH3PrunedTransformer3DModel"
9
+ },
10
+ "ffn_dim": 14336,
11
+ "final_norm_eps": 1e-05,
12
+ "freq_dim": 256,
13
+ "hidden_size": 5376,
14
+ "in_channels": 24,
15
+ "norm_eps": 1e-05,
16
+ "num_attention_heads": 56,
17
+ "num_layers": 50,
18
+ "num_refiner_layers": 2,
19
+ "patch_size": [
20
+ 1,
21
+ 2,
22
+ 2
23
+ ],
24
+ "qk_norm_eps": 1e-05,
25
+ "rope_freq_dim": 16,
26
+ "rope_theta": 10000.0,
27
+ "text_dim": 5120,
28
+ "time_embed_dim": 2688,
29
+ "time_embed_hidden_dim": 5376,
30
+ "time_table_size": 1025
31
+ }
transformer_ref/modeling_minimax_h3_pruned.py ADDED
@@ -0,0 +1,243 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ # Copyright 2025 The MiniMax Team and The HuggingFace Team. All rights reserved.
2
+ #
3
+ # Licensed under the Apache License, Version 2.0 (the "License");
4
+ # you may not use this file except in compliance with the License.
5
+ # You may obtain a copy of the License at
6
+ #
7
+ # http://www.apache.org/licenses/LICENSE-2.0
8
+ #
9
+ # Unless required by applicable law or agreed to in writing, software
10
+ # distributed under the License is distributed on an "AS IS" BASIS,
11
+ # WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12
+ # See the License for the specific language governing permissions and
13
+ # limitations under the License.
14
+ """AdaLN-pruned MiniMax-H3 transformer.
15
+
16
+ Everything outside the timestep path is inherited from `MiniMaxH3Transformer3DModel`: the attention, the blocks, the
17
+ token refiner, the output heads and `forward` itself are the released implementation, unmodified. Only what feeds the
18
+ AdaLN projections changes.
19
+ """
20
+
21
+ import torch
22
+ import torch.nn as nn
23
+ from diffusers.configuration_utils import register_to_config
24
+ from diffusers.models.modeling_utils import get_parameter_dtype
25
+ from diffusers.models.transformers.transformer_minimax_h3 import (
26
+ MINIMAX_H3_MODALITY_NUM,
27
+ MiniMaxH3RotaryPosEmbed,
28
+ MiniMaxH3TokenRefiner,
29
+ MiniMaxH3Transformer3DModel,
30
+ MiniMaxH3TransformerBlock,
31
+ )
32
+
33
+
34
+ class MiniMaxH3PrunedTimeEmbedder(nn.Module):
35
+ r"""The released timestep MLP, replaced by an interpolated table of AdaLN coordinates.
36
+
37
+ Every AdaLN projection in the released model consumes `silu(time_embedder(time_proj(t)))`, which depends on the
38
+ scalar timestep alone: over `t` in `[0, 1]` it traces a one-dimensional curve in `R^{time_embed_dim}`. A rank-8
39
+ affine subspace reproduces that curve to about 1.5e-5 relative RMS, so only the curve's coordinates in that
40
+ subspace are stored - sampled on a uniform grid of `table_size` timesteps and linearly interpolated in between.
41
+ The subspace offset is folded into the AdaLN biases and its basis into the AdaLN weights, which is why the
42
+ projections take an `adaln_rank`-wide input here instead of `time_embed_dim`.
43
+
44
+ The module stands in for `time_proj` and `time_embedder` together: it consumes the raw timestep, so the released
45
+ `forward` needs no change once `time_proj` is an identity.
46
+ """
47
+
48
+ def __init__(self, table_size: int = 1025, adaln_rank: int = 8) -> None:
49
+ super().__init__()
50
+ self.register_buffer("table", torch.zeros(table_size, adaln_rank), persistent=True)
51
+
52
+ def forward(self, timestep: torch.Tensor) -> torch.Tensor:
53
+ table = self.table
54
+ steps = table.shape[0] - 1
55
+ position = timestep.to(table.dtype).flatten().clamp(0.0, 1.0) * steps
56
+ lower = position.floor().clamp(max=steps - 1).long()
57
+ weight = (position - lower).unsqueeze(-1)
58
+ return torch.lerp(table.index_select(0, lower), table.index_select(0, lower + 1), weight)
59
+
60
+
61
+ class MiniMaxH3PrunedTimeProj(nn.Module):
62
+ r"""Identity stand-in for `Timesteps`: the pruned time embedder indexes the raw timestep."""
63
+
64
+ def forward(self, timestep: torch.Tensor) -> torch.Tensor:
65
+ return timestep
66
+
67
+
68
+ class MiniMaxH3PrunedAdaLayerNormModulation(nn.Module):
69
+ r"""`MiniMaxH3AdaLayerNormModulation` over the pruned timestep coordinates.
70
+
71
+ Two differences from the released module. It applies no `silu` - the table already holds the coordinates of the
72
+ activated curve. And the folded bias is a float32 buffer applied outside the projection rather than the
73
+ projection's own bias: it carries almost the entire modulation (the coordinate term contributes a few tenths of
74
+ it), so storing it in bfloat16 would put a full output-scale rounding step into every evaluation. Kept in
75
+ float32 it costs 0.4 MB per block and leaves the pruned AdaLN function closer to an exact float64 evaluation
76
+ than the released bfloat16 checkpoint's own arithmetic is.
77
+
78
+ `linear` stays a bias-free `nn.Linear` so PEFT wraps it exactly as it wraps the released projection.
79
+ """
80
+
81
+ def __init__(self, adaln_rank: int, hidden_size: int) -> None:
82
+ super().__init__()
83
+ self.hidden_size = hidden_size
84
+ out_features = 6 * hidden_size * MINIMAX_H3_MODALITY_NUM
85
+ self.linear = nn.Linear(adaln_rank, out_features, bias=False)
86
+ self.register_buffer("folded_bias", torch.zeros(out_features), persistent=True)
87
+
88
+ def forward(self, temb: torch.Tensor) -> tuple[torch.Tensor, ...]:
89
+ dtype = get_parameter_dtype(self.linear)
90
+ temb = self.linear(temb.to(dtype))
91
+ temb = (temb.float() + self.folded_bias).to(dtype)
92
+ temb = temb.view(-1, 6 * self.hidden_size)
93
+ return temb.chunk(6, dim=-1)
94
+
95
+
96
+ class MiniMaxH3PrunedAdaLayerNormOut(nn.Module):
97
+ r"""`MiniMaxH3AdaLayerNormOut` over the pruned timestep coordinates; see the modulation module above."""
98
+
99
+ def __init__(self, hidden_size: int, adaln_rank: int, eps: float) -> None:
100
+ super().__init__()
101
+ self.norm = nn.RMSNorm(hidden_size, eps=eps)
102
+ self.linear = nn.Linear(adaln_rank, 2 * hidden_size, bias=False)
103
+ self.register_buffer("folded_bias", torch.zeros(2 * hidden_size), persistent=True)
104
+
105
+ def forward(self, hidden_states: torch.Tensor, temb: torch.Tensor, timestep_indices: torch.Tensor) -> torch.Tensor:
106
+ dtype = get_parameter_dtype(self.linear)
107
+ temb = self.linear(temb.to(dtype))
108
+ shift, scale = (temb.float() + self.folded_bias).to(dtype).chunk(2, dim=-1)
109
+ hidden_states = self.norm(hidden_states)
110
+ return hidden_states * (1.0 + scale.index_select(0, timestep_indices)) + shift.index_select(
111
+ 0, timestep_indices
112
+ )
113
+
114
+
115
+ class MiniMaxH3PrunedTransformer3DModel(MiniMaxH3Transformer3DModel):
116
+ r"""MiniMax-H3's DiT with the AdaLN input projections reduced to their reachable rank.
117
+
118
+ The released checkpoint spends 13.03B of its 33.14B parameters on the 50 per-block `adaln_proj.linear` matrices
119
+ plus `norm_out.linear`, all of which read the same 2688-wide timestep embedding. Because that embedding is a
120
+ function of the scalar timestep, its reachable set is a curve an 8-dimensional affine subspace covers to ~1.5e-5
121
+ relative RMS - far below one bfloat16 rounding step of the weights themselves. Folding the subspace into the
122
+ projections leaves an 8-wide input and removes 26 GB per partition.
123
+
124
+ Only what builds the timestep path differs from [`MiniMaxH3Transformer3DModel`]: `time_proj` becomes an identity,
125
+ `time_embedder` becomes [`MiniMaxH3PrunedTimeEmbedder`], and the AdaLN projections take `adaln_rank` inputs.
126
+ `forward` is inherited unchanged. The module names are the released ones, so LoRAs trained against a pruned
127
+ checkpoint - what the common trainers use by default - load natively.
128
+
129
+ Args:
130
+ adaln_rank (`int`, defaults to `8`):
131
+ The width of the timestep coordinates every AdaLN projection consumes.
132
+ time_table_size (`int`, defaults to `1025`):
133
+ The number of uniformly spaced timesteps the coordinate table holds; values in between are interpolated
134
+ linearly.
135
+
136
+ Every other argument is [`MiniMaxH3Transformer3DModel`]'s and carries the same meaning. `freq_dim` and
137
+ `time_embed_hidden_dim` are kept in the config, unused, so a pruned config still records the shape of the
138
+ released timestep MLP it was folded from.
139
+ """
140
+
141
+ _supports_gradient_checkpointing = True
142
+ _no_split_modules = ["MiniMaxH3TransformerBlock", "MiniMaxH3TokenRefinerBlock", "MiniMaxH3PrunedAdaLayerNormOut"]
143
+ _repeated_blocks = ["MiniMaxH3TransformerBlock", "MiniMaxH3TokenRefinerBlock"]
144
+ _skip_layerwise_casting_patterns = ["norm"]
145
+ # The released checkpoint's mixed-precision split - patch projections, output heads and the timestep path in
146
+ # float32, the block stack in bfloat16 - plus the folded AdaLN biases, for the reason given on the modulation
147
+ # module. Entries are matched against the dot-separated segments of each parameter name.
148
+ _keep_in_fp32_modules = [
149
+ "proj_in",
150
+ "audio_proj_in",
151
+ "time_embedder",
152
+ "proj_out",
153
+ "audio_proj_out",
154
+ "rope",
155
+ "folded_bias",
156
+ ]
157
+
158
+ @register_to_config
159
+ def __init__(
160
+ self,
161
+ num_attention_heads: int = 56,
162
+ attention_head_dim: int = 128,
163
+ hidden_size: int = 5376,
164
+ num_layers: int = 50,
165
+ num_refiner_layers: int = 2,
166
+ ffn_dim: int = 14336,
167
+ in_channels: int = 24,
168
+ audio_in_channels: int = 32,
169
+ patch_size: tuple[int, int, int] = (1, 2, 2),
170
+ text_dim: int = 5120,
171
+ freq_dim: int = 256,
172
+ time_embed_hidden_dim: int = 5376,
173
+ time_embed_dim: int = 2688,
174
+ rope_freq_dim: int = 16,
175
+ rope_theta: float = 10000.0,
176
+ norm_eps: float = 1e-5,
177
+ qk_norm_eps: float = 1e-5,
178
+ final_norm_eps: float = 1e-5,
179
+ adaln_rank: int = 8,
180
+ time_table_size: int = 1025,
181
+ ) -> None:
182
+ # `MiniMaxH3Transformer3DModel.__init__` is itself wrapped by `register_to_config`, so calling it would
183
+ # register the released config over this one - and would allocate the 26 GB of AdaLN projections this class
184
+ # exists to avoid. The module tree is built here instead; everything but the timestep path is verbatim.
185
+ nn.Module.__init__(self)
186
+
187
+ video_patch_dim = in_channels * patch_size[0] * patch_size[1] * patch_size[2]
188
+
189
+ # 1. Per-modality input projections
190
+ self.proj_in = nn.Linear(video_patch_dim, hidden_size, bias=True)
191
+ self.audio_proj_in = nn.Linear(audio_in_channels, hidden_size, bias=True)
192
+ self.context_embedder = nn.Linear(text_dim, hidden_size, bias=True)
193
+
194
+ # 2. Timestep coordinates, shared by every AdaLN projection
195
+ self.time_proj = MiniMaxH3PrunedTimeProj()
196
+ self.time_embedder = MiniMaxH3PrunedTimeEmbedder(table_size=time_table_size, adaln_rank=adaln_rank)
197
+
198
+ # 3. Rotary embedding over the packed (t, h, w) grid
199
+ self.rope = MiniMaxH3RotaryPosEmbed(rope_freq_dim=rope_freq_dim, rope_theta=rope_theta)
200
+
201
+ # 4. Text stream refiner
202
+ self.token_refiner = MiniMaxH3TokenRefiner(
203
+ hidden_size=hidden_size,
204
+ num_attention_heads=num_attention_heads,
205
+ attention_head_dim=attention_head_dim,
206
+ ffn_dim=ffn_dim,
207
+ num_layers=num_refiner_layers,
208
+ norm_eps=norm_eps,
209
+ qk_norm_eps=qk_norm_eps,
210
+ final_norm_eps=final_norm_eps,
211
+ )
212
+
213
+ # 5. The block stack, with each block's AdaLN projection narrowed to the timestep coordinates. The block is
214
+ # built with `time_embed_dim=adaln_rank` so its own projection is already the right shape, then swapped
215
+ # for the pruned module, which drops the `silu` and moves the bias to float32.
216
+ self.transformer_blocks = nn.ModuleList(
217
+ [
218
+ MiniMaxH3TransformerBlock(
219
+ hidden_size=hidden_size,
220
+ num_attention_heads=num_attention_heads,
221
+ attention_head_dim=attention_head_dim,
222
+ ffn_dim=ffn_dim,
223
+ time_embed_dim=adaln_rank,
224
+ norm_eps=norm_eps,
225
+ qk_norm_eps=qk_norm_eps,
226
+ )
227
+ for _ in range(num_layers)
228
+ ]
229
+ )
230
+ for block in self.transformer_blocks:
231
+ block.adaln_proj = MiniMaxH3PrunedAdaLayerNormModulation(adaln_rank=adaln_rank, hidden_size=hidden_size)
232
+
233
+ # 6. Shared output norm and the two per-modality output heads
234
+ self.norm_out = MiniMaxH3PrunedAdaLayerNormOut(
235
+ hidden_size=hidden_size, adaln_rank=adaln_rank, eps=final_norm_eps
236
+ )
237
+ self.proj_out = nn.Linear(hidden_size, video_patch_dim, bias=True)
238
+ self.audio_proj_out = nn.Linear(hidden_size, audio_in_channels, bias=True)
239
+
240
+ self.gradient_checkpointing = False
241
+
242
+
243
+ MiniMaxH3PrunedTransformer3DModel.register_for_auto_class("AutoModel")