Image-Text-to-Video
Diffusers
Safetensors
text-to-video
image-to-video
text-to-audio-video
image-to-audio-video
reference-to-audio-video
audio-video-generation
multimodal
lora
Instructions to use multimodalart/MiniMax-H3-Pruned with libraries, inference providers, notebooks, and local apps. Follow these links to get started.
- Libraries
- Diffusers
How to use multimodalart/MiniMax-H3-Pruned with Diffusers:
pip install -U diffusers transformers accelerate
import torch from diffusers import DiffusionPipeline # switch to "mps" for apple devices pipe = DiffusionPipeline.from_pretrained("MiniMaxAI/MiniMax-H3", dtype=torch.bfloat16, device_map="cuda") pipe.load_lora_weights("multimodalart/MiniMax-H3-Pruned") prompt = "Astronaut in a jungle, cold color palette, muted colors, detailed, 8k" image = pipe(prompt).images[0] - Inference
- Notebooks
- Google Colab
- Kaggle
- Local Apps Settings
- Draw Things
Project released-checkpoint AdaLN LoRAs onto the pruned timestep coordinates
Browse files- README.md +79 -10
- transformer/adaln_affine.safetensors +3 -0
- transformer/diffusion_pytorch_model.safetensors.index.json +5 -3
- transformer/modeling_minimax_h3_pruned.py +215 -12
- transformer_ref/adaln_affine.safetensors +3 -0
- transformer_ref/diffusion_pytorch_model.safetensors.index.json +5 -3
- transformer_ref/modeling_minimax_h3_pruned.py +215 -12
README.md
CHANGED
|
@@ -21,7 +21,7 @@ tags:
|
|
| 21 |
|
| 22 |
[MiniMaxAI/MiniMax-H3](https://huggingface.co/MiniMaxAI/MiniMax-H3) in diffusers format with the AdaLN input
|
| 23 |
projections reduced to their reachable rank. **52 GB smaller** across the two DiT partitions, no visible change in
|
| 24 |
-
output, and pruned-trained
|
| 25 |
|
| 26 |
| | released | this repo |
|
| 27 |
|---|---|---|
|
|
@@ -131,9 +131,13 @@ be paged in and out per step. On a card with room for all of it, expect the two
|
|
| 131 |
|
| 132 |
## LoRAs
|
| 133 |
|
| 134 |
-
**
|
| 135 |
-
|
| 136 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 137 |
|
| 138 |
This works because the coordinates are **not ours**. A rank-8 basis is unique only up to sign and rotation, so a
|
| 139 |
pruned-trained LoRA is portable only between checkpoints that share the coordinate convention. Rather than derive a
|
|
@@ -154,11 +158,71 @@ The last row is why the table is copied and not recomputed: a re-derived basis s
|
|
| 154 |
accurate, and would still turn every existing LoRA into noise. Both files load with all 50 AdaLN projections wrapped
|
| 155 |
by PEFT at scale exactly 1.0, 362 modules in total.
|
| 156 |
|
| 157 |
-
|
| 158 |
-
|
| 159 |
-
|
| 160 |
-
|
| 161 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 162 |
|
| 163 |
LoRA loading for MiniMax-H3 landed in diffusers via
|
| 164 |
[huggingface/diffusers#14408](https://github.com/huggingface/diffusers/pull/14408). This repo declares the stock
|
|
@@ -288,7 +352,12 @@ ComfyUI's `*_int8_convrot` files quantize, arrived at from their tensor inventor
|
|
| 288 |
`MiniMaxH3ConvRotLinear` is a plain `nn.Linear` subclass, so PEFT wraps it as a `base_layer` and a LoRA's own
|
| 289 |
branch reads the **unrotated** input in bfloat16. A LoRA trained against the ordinary checkpoint is therefore
|
| 290 |
correct on top of a rotated, quantized base - and `adaln_proj`, which is where pruned-trained LoRAs put most of
|
| 291 |
-
their weight, is never rotated or quantized at all.
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 292 |
|
| 293 |
The order matters, and there is one thing not to do:
|
| 294 |
|
|
|
|
| 21 |
|
| 22 |
[MiniMaxAI/MiniMax-H3](https://huggingface.co/MiniMaxAI/MiniMax-H3) in diffusers format with the AdaLN input
|
| 23 |
projections reduced to their reachable rank. **52 GB smaller** across the two DiT partitions, no visible change in
|
| 24 |
+
output, and every published H3 LoRA loads - pruned-trained ones natively, released-trained ones projected.
|
| 25 |
|
| 26 |
| | released | this repo |
|
| 27 |
|---|---|---|
|
|
|
|
| 131 |
|
| 132 |
## LoRAs
|
| 133 |
|
| 134 |
+
**Every published H3 LoRA loads here** - the ones trained against a pruned checkpoint natively, the ones trained
|
| 135 |
+
against the released one by projection.
|
| 136 |
+
|
| 137 |
+
### Pruned-trained LoRAs, natively
|
| 138 |
+
|
| 139 |
+
ai-toolkit trains against a pruned checkpoint by default, so its H3 LoRAs carry `adaln_proj.linear.lora_A` of shape
|
| 140 |
+
`[rank, 8]`. That is a size mismatch against the released `Linear(2688, ...)` and an exact fit here.
|
| 141 |
|
| 142 |
This works because the coordinates are **not ours**. A rank-8 basis is unique only up to sign and rotation, so a
|
| 143 |
pruned-trained LoRA is portable only between checkpoints that share the coordinate convention. Rather than derive a
|
|
|
|
| 158 |
accurate, and would still turn every existing LoRA into noise. Both files load with all 50 AdaLN projections wrapped
|
| 159 |
by PEFT at scale exactly 1.0, 362 modules in total.
|
| 160 |
|
| 161 |
+
### Released-trained LoRAs, by projection
|
| 162 |
+
|
| 163 |
+
LoRAs trained against the **released** checkpoint's 2688-wide AdaLN projections - the 4-step turbo LoRA and its
|
| 164 |
+
conversions - are mapped onto these coordinates when they load. Nothing about them is approximated beyond what the
|
| 165 |
+
pruning already approximates: the identity that folded the weights folds the adapter.
|
| 166 |
+
|
| 167 |
+
A LoRA on the released projection contributes `lora_B @ (lora_A @ x)`, and `x = mean + c @ basis`, so
|
| 168 |
+
|
| 169 |
+
```
|
| 170 |
+
lora_B @ (lora_A @ x) = lora_B @ ((lora_A @ basis.T) @ c) + lora_B @ (lora_A @ mean)
|
| 171 |
+
```
|
| 172 |
+
|
| 173 |
+
`lora_A @ basis.T` is an `[rank, 8]` `lora_A` over the pruned coordinates, at the same rank; `lora_B` is unchanged;
|
| 174 |
+
and what is left over is a **constant**, which no `Linear(8 -> ...)` can produce. That constant is carried as a
|
| 175 |
+
float32 offset on the modulation, alongside the fold's own `folded_bias` and for the same reason - it is ~98% of
|
| 176 |
+
what these adapters do to the AdaLN path, so rounding it into bfloat16 would spend a full rounding step of the
|
| 177 |
+
modulation on almost the whole update. The factors are computed in float64.
|
| 178 |
+
|
| 179 |
+
`adaln_basis` and `adaln_mean` ship as `adaln_affine.safetensors` (97 KB) beside each partition's weights. They are
|
| 180 |
+
the map the projections were folded with, read back, not re-derived - for the same reason the table is copied and
|
| 181 |
+
not recomputed.
|
| 182 |
+
|
| 183 |
+
**Why not PEFT's `lora_bias`.** That is the obvious place to put a constant, and it is the wrong one here.
|
| 184 |
+
`lora_bias` is a `LoraConfig` flag, not a per-module one, so switching it on for the 51 AdaLN modules also switches
|
| 185 |
+
it on for the other 312: it warns on every bias-free target, creates zero biases on the attention and feed-forward
|
| 186 |
+
modules that then read as missing keys, and makes `fuse_lora` raise `Impossible to merge LoRA with lora_bias=True
|
| 187 |
+
because the base layer has no bias` on all 51 - `adaln_proj.linear` is deliberately bias-free, which is what makes
|
| 188 |
+
PEFT wrap it exactly as it wraps the released projection. It would also put the constant in bfloat16, on the wrong
|
| 189 |
+
side of the argument this repo already made for `folded_bias`. The float32 buffer beside `folded_bias` avoids all
|
| 190 |
+
four, and PEFT still owns the state that matters: the offsets are read through `active_adapters`, `scaling` and
|
| 191 |
+
`disable_adapters`, so they follow `set_adapters`, `disable_lora` and `delete_adapters` on their own.
|
| 192 |
+
|
| 193 |
+
Measured per AdaLN module over 200 off-grid timesteps, projected against full-space, both in float64:
|
| 194 |
+
|
| 195 |
+
| | worst of 51 | mean of 51 |
|
| 196 |
+
|---|---|---|
|
| 197 |
+
| rank-8 subspace residual on the timestep curve | 1.47e-5 | - |
|
| 198 |
+
| [`larryvrh/MiniMax-H3-Turbo-Lora`](https://huggingface.co/larryvrh/MiniMax-H3-Turbo-Lora) v4 step 600 EMA | **2.05e-5** | 1.26e-5 |
|
| 199 |
+
| [`InstantX/MiniMax-H3-Turbo-Lora-Diffusers`](https://huggingface.co/InstantX/MiniMax-H3-Turbo-Lora-Diffusers) | **1.53e-5** | 0.88e-5 |
|
| 200 |
+
|
| 201 |
+
which is the subspace residual and nothing else. Both files attach to **363 modules** - 312 attention/feed-forward
|
| 202 |
+
and token-refiner ones untouched, 51 AdaLN ones projected - all at scale exactly 1.0. Loaded on top of the released
|
| 203 |
+
weights and on top of these, the LoRA'd AdaLN function was compared against an exact float64 evaluation at three
|
| 204 |
+
probe modules: the pruned side is the closer of the two at all three, the same way the fold itself is.
|
| 205 |
+
|
| 206 |
+
End to end, 960x544, 124 frames, 7 steps, seed 42, `larryvrh` v4, against the identical request on the released
|
| 207 |
+
weights: **cosine 0.9958** with the LoRA on and **0.9992** with it off, and the on/off difference - the turbo effect
|
| 208 |
+
itself - is 0.9609 here against 0.9641 there. Frame for frame the two runs are the same video.
|
| 209 |
+
|
| 210 |
+
How much of that is the AdaLN half? On these particular files, not much - their AdaLN update is about 2e-4 of the
|
| 211 |
+
modulation, and simply *discarding* those 51 modules lands at 0.9960 against the released run where projecting
|
| 212 |
+
lands at 0.9958, which is the same number twice. What is not the same number is projecting them and dropping the
|
| 213 |
+
constant term: **0.9947**. The half-applied version is the only one of the three that is measurably wrong, which
|
| 214 |
+
is both the argument for where the constant is kept and the reason to do this exactly rather than approximately -
|
| 215 |
+
an adapter that leans harder on AdaLN than a turbo LoRA does has no other way to arrive intact.
|
| 216 |
+
|
| 217 |
+
A file whose AdaLN modules are not uniformly 8-wide or uniformly 2688-wide raises rather than loading some of them:
|
| 218 |
+
an adapter that applies to 50 of its 51 AdaLN projections is not the adapter anyone trained.
|
| 219 |
+
|
| 220 |
+
One caveat: the projection happens at load, and the constant terms are not PEFT parameters, so
|
| 221 |
+
`save_lora_weights` on a projected adapter writes the projected factors *without* them. Distribute the original
|
| 222 |
+
file, not a re-save of it.
|
| 223 |
+
|
| 224 |
+
Everything outside `adaln_proj` and `norm_out` - attention, feed-forward, the token refiner - is untouched by the
|
| 225 |
+
pruning and was always loading either way.
|
| 226 |
|
| 227 |
LoRA loading for MiniMax-H3 landed in diffusers via
|
| 228 |
[huggingface/diffusers#14408](https://github.com/huggingface/diffusers/pull/14408). This repo declares the stock
|
|
|
|
| 352 |
`MiniMaxH3ConvRotLinear` is a plain `nn.Linear` subclass, so PEFT wraps it as a `base_layer` and a LoRA's own
|
| 353 |
branch reads the **unrotated** input in bfloat16. A LoRA trained against the ordinary checkpoint is therefore
|
| 354 |
correct on top of a rotated, quantized base - and `adaln_proj`, which is where pruned-trained LoRAs put most of
|
| 355 |
+
their weight and where a released-trained one is projected, is never rotated or quantized at all.
|
| 356 |
+
|
| 357 |
+
The order below is unchanged by the AdaLN projection: the projection happens at load, before anything is rotated,
|
| 358 |
+
and neither `enable_convrot` nor `quantize_` touches the modules it wrote to. Verified on `larryvrh` v4 - the
|
| 359 |
+
AdaLN update at three probe modules is bit-identical before and after quantizing, with all 363 modules and all 51
|
| 360 |
+
constant terms still attached.
|
| 361 |
|
| 362 |
The order matters, and there is one thing not to do:
|
| 363 |
|
transformer/adaln_affine.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:34f285e7aeae741666868bf5912506ab4793098357a01a9d8358f6ce7704d532
|
| 3 |
+
size 96960
|
transformer/diffusion_pytorch_model.safetensors.index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"metadata": {
|
| 3 |
-
"total_size":
|
| 4 |
},
|
| 5 |
"weight_map": {
|
| 6 |
"audio_proj_in.bias": "diffusion_pytorch_model-00001-of-00014.safetensors",
|
|
@@ -637,6 +637,8 @@
|
|
| 637 |
"transformer_blocks.9.ff.net.0.proj.weight": "diffusion_pytorch_model-00003-of-00014.safetensors",
|
| 638 |
"transformer_blocks.9.ff.net.2.weight": "diffusion_pytorch_model-00003-of-00014.safetensors",
|
| 639 |
"transformer_blocks.9.norm1.weight": "diffusion_pytorch_model-00003-of-00014.safetensors",
|
| 640 |
-
"transformer_blocks.9.norm2.weight": "diffusion_pytorch_model-00003-of-00014.safetensors"
|
|
|
|
|
|
|
| 641 |
}
|
| 642 |
-
}
|
|
|
|
| 1 |
{
|
| 2 |
"metadata": {
|
| 3 |
+
"total_size": 40235463200
|
| 4 |
},
|
| 5 |
"weight_map": {
|
| 6 |
"audio_proj_in.bias": "diffusion_pytorch_model-00001-of-00014.safetensors",
|
|
|
|
| 637 |
"transformer_blocks.9.ff.net.0.proj.weight": "diffusion_pytorch_model-00003-of-00014.safetensors",
|
| 638 |
"transformer_blocks.9.ff.net.2.weight": "diffusion_pytorch_model-00003-of-00014.safetensors",
|
| 639 |
"transformer_blocks.9.norm1.weight": "diffusion_pytorch_model-00003-of-00014.safetensors",
|
| 640 |
+
"transformer_blocks.9.norm2.weight": "diffusion_pytorch_model-00003-of-00014.safetensors",
|
| 641 |
+
"adaln_basis": "adaln_affine.safetensors",
|
| 642 |
+
"adaln_mean": "adaln_affine.safetensors"
|
| 643 |
}
|
| 644 |
+
}
|
transformer/modeling_minimax_h3_pruned.py
CHANGED
|
@@ -17,12 +17,15 @@ Everything outside the timestep path is inherited from `MiniMaxH3Transformer3DMo
|
|
| 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 |
attention and feed-forward linears that makes 8-bit *compute* (int8 or fp8 dynamic activations, via torchao) land
|
| 22 |
-
within a rounding step of bfloat16.
|
|
|
|
|
|
|
| 23 |
"""
|
| 24 |
|
| 25 |
import math
|
|
|
|
| 26 |
|
| 27 |
import torch
|
| 28 |
import torch.nn as nn
|
|
@@ -36,10 +39,17 @@ from diffusers.models.transformers.transformer_minimax_h3 import (
|
|
| 36 |
MiniMaxH3Transformer3DModel,
|
| 37 |
MiniMaxH3TransformerBlock,
|
| 38 |
)
|
|
|
|
| 39 |
|
| 40 |
|
|
|
|
|
|
|
| 41 |
_HADAMARD_CACHE: dict = {}
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
# The linears ConvRot conditions: the block stack's attention and feed-forward projections, and nothing else.
|
| 44 |
# This is the set ComfyUI's `*_int8_convrot` checkpoints quantize (they carry one fused `qkv_proj`; the three
|
| 45 |
# split projections here share an input, so rotating each is the same transform). The AdaLN path, the patch
|
|
@@ -129,7 +139,58 @@ class MiniMaxH3PrunedTimeProj(nn.Module):
|
|
| 129 |
return timestep
|
| 130 |
|
| 131 |
|
| 132 |
-
class
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
r"""`MiniMaxH3AdaLayerNormModulation` over the pruned timestep coordinates.
|
| 134 |
|
| 135 |
Two differences from the released module. It applies no `silu` - the table already holds the coordinates of the
|
|
@@ -143,33 +204,31 @@ class MiniMaxH3PrunedAdaLayerNormModulation(nn.Module):
|
|
| 143 |
"""
|
| 144 |
|
| 145 |
def __init__(self, adaln_rank: int, hidden_size: int) -> None:
|
| 146 |
-
super().__init__()
|
| 147 |
-
self.hidden_size = hidden_size
|
| 148 |
out_features = 6 * hidden_size * MINIMAX_H3_MODALITY_NUM
|
|
|
|
|
|
|
| 149 |
self.linear = nn.Linear(adaln_rank, out_features, bias=False)
|
| 150 |
-
self.register_buffer("folded_bias", torch.zeros(out_features), persistent=True)
|
| 151 |
|
| 152 |
def forward(self, temb: torch.Tensor) -> tuple[torch.Tensor, ...]:
|
| 153 |
dtype = get_parameter_dtype(self.linear)
|
| 154 |
temb = self.linear(temb.to(dtype))
|
| 155 |
-
temb = (temb.float() + self.
|
| 156 |
temb = temb.view(-1, 6 * self.hidden_size)
|
| 157 |
return temb.chunk(6, dim=-1)
|
| 158 |
|
| 159 |
|
| 160 |
-
class MiniMaxH3PrunedAdaLayerNormOut(
|
| 161 |
r"""`MiniMaxH3AdaLayerNormOut` over the pruned timestep coordinates; see the modulation module above."""
|
| 162 |
|
| 163 |
def __init__(self, hidden_size: int, adaln_rank: int, eps: float) -> None:
|
| 164 |
-
super().__init__()
|
| 165 |
self.norm = nn.RMSNorm(hidden_size, eps=eps)
|
| 166 |
self.linear = nn.Linear(adaln_rank, 2 * hidden_size, bias=False)
|
| 167 |
-
self.register_buffer("folded_bias", torch.zeros(2 * hidden_size), persistent=True)
|
| 168 |
|
| 169 |
def forward(self, hidden_states: torch.Tensor, temb: torch.Tensor, timestep_indices: torch.Tensor) -> torch.Tensor:
|
| 170 |
dtype = get_parameter_dtype(self.linear)
|
| 171 |
temb = self.linear(temb.to(dtype))
|
| 172 |
-
shift, scale = (temb.float() + self.
|
| 173 |
hidden_states = self.norm(hidden_states)
|
| 174 |
return hidden_states * (1.0 + scale.index_select(0, timestep_indices)) + shift.index_select(
|
| 175 |
0, timestep_indices
|
|
@@ -188,7 +247,8 @@ class MiniMaxH3PrunedTransformer3DModel(MiniMaxH3Transformer3DModel):
|
|
| 188 |
Only what builds the timestep path differs from [`MiniMaxH3Transformer3DModel`]: `time_proj` becomes an identity,
|
| 189 |
`time_embedder` becomes [`MiniMaxH3PrunedTimeEmbedder`], and the AdaLN projections take `adaln_rank` inputs.
|
| 190 |
`forward` is inherited unchanged. The module names are the released ones, so LoRAs trained against a pruned
|
| 191 |
-
checkpoint - what the common trainers use by default - load natively
|
|
|
|
| 192 |
|
| 193 |
Args:
|
| 194 |
adaln_rank (`int`, defaults to `8`):
|
|
@@ -217,6 +277,8 @@ class MiniMaxH3PrunedTransformer3DModel(MiniMaxH3Transformer3DModel):
|
|
| 217 |
"audio_proj_out",
|
| 218 |
"rope",
|
| 219 |
"folded_bias",
|
|
|
|
|
|
|
| 220 |
]
|
| 221 |
|
| 222 |
@register_to_config
|
|
@@ -259,6 +321,13 @@ class MiniMaxH3PrunedTransformer3DModel(MiniMaxH3Transformer3DModel):
|
|
| 259 |
self.time_proj = MiniMaxH3PrunedTimeProj()
|
| 260 |
self.time_embedder = MiniMaxH3PrunedTimeEmbedder(table_size=time_table_size, adaln_rank=adaln_rank)
|
| 261 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
# 3. Rotary embedding over the packed (t, h, w) grid
|
| 263 |
self.rope = MiniMaxH3RotaryPosEmbed(rope_freq_dim=rope_freq_dim, rope_theta=rope_theta)
|
| 264 |
|
|
@@ -303,6 +372,140 @@ class MiniMaxH3PrunedTransformer3DModel(MiniMaxH3Transformer3DModel):
|
|
| 303 |
|
| 304 |
self.gradient_checkpointing = False
|
| 305 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
# -- 8-bit compute -----------------------------------------------------------------------------------------
|
| 307 |
#
|
| 308 |
# `enable_convrot` and `quantize_8bit` are opt-in and change nothing until called.
|
|
|
|
| 17 |
token refiner, the output heads and `forward` itself are the released implementation, unmodified. Only what feeds the
|
| 18 |
AdaLN projections changes.
|
| 19 |
|
| 20 |
+
Two other things this file adds. `enable_convrot` / `quantize_8bit`: an opt-in Hadamard conditioning of the
|
| 21 |
attention and feed-forward linears that makes 8-bit *compute* (int8 or fp8 dynamic activations, via torchao) land
|
| 22 |
+
within a rounding step of bfloat16. And `load_lora_adapter`, overridden to project a LoRA trained against the
|
| 23 |
+
*released* 2688-wide AdaLN projections onto these 8-wide ones. Both are inert unless used, so the plain pruned path
|
| 24 |
+
is byte for byte what it was.
|
| 25 |
"""
|
| 26 |
|
| 27 |
import math
|
| 28 |
+
import re
|
| 29 |
|
| 30 |
import torch
|
| 31 |
import torch.nn as nn
|
|
|
|
| 39 |
MiniMaxH3Transformer3DModel,
|
| 40 |
MiniMaxH3TransformerBlock,
|
| 41 |
)
|
| 42 |
+
from diffusers.utils import logging
|
| 43 |
|
| 44 |
|
| 45 |
+
logger = logging.get_logger(__name__)
|
| 46 |
+
|
| 47 |
_HADAMARD_CACHE: dict = {}
|
| 48 |
|
| 49 |
+
# The 51 AdaLN projections, as a LoRA state dict names them, with or without a `transformer.` / `transformer_ref.`
|
| 50 |
+
# component prefix. Group 1 is the module path the model itself knows the projection by.
|
| 51 |
+
ADALN_LORA_A_KEY = re.compile(r"(?:^|\.)((?:transformer_blocks\.\d+\.adaln_proj|norm_out)\.linear)\.lora_A\.weight$")
|
| 52 |
+
|
| 53 |
# The linears ConvRot conditions: the block stack's attention and feed-forward projections, and nothing else.
|
| 54 |
# This is the set ComfyUI's `*_int8_convrot` checkpoints quantize (they carry one fused `qkv_proj`; the three
|
| 55 |
# split projections here share an input, so rotating each is the same transform). The AdaLN path, the patch
|
|
|
|
| 139 |
return timestep
|
| 140 |
|
| 141 |
|
| 142 |
+
class MiniMaxH3PrunedAdaLN(nn.Module):
|
| 143 |
+
r"""Shared by both pruned AdaLN modules: the folded float32 bias, plus the LoRA offsets that ride alongside it.
|
| 144 |
+
|
| 145 |
+
A LoRA trained against the released 2688-wide projection contributes `lora_B @ (lora_A @ x)` to the modulation.
|
| 146 |
+
With `x = mean + c @ basis`, that splits into a coordinate term the projected factors reproduce and a *constant*
|
| 147 |
+
term, `lora_B @ (lora_A @ mean)`, which no `Linear(8 -> out)` can express. That constant is held here, as a
|
| 148 |
+
per-adapter float32 buffer added to `folded_bias` - the same place, and the same precision, the fold's own
|
| 149 |
+
constant term lives in. It is deliberately not the projection's `bias`: rounding it into bfloat16 would spend a
|
| 150 |
+
full rounding step of the modulation on a term that is most of what the adapter does to the AdaLN path.
|
| 151 |
+
"""
|
| 152 |
+
|
| 153 |
+
def __init__(self, out_features: int) -> None:
|
| 154 |
+
super().__init__()
|
| 155 |
+
self.register_buffer("folded_bias", torch.zeros(out_features), persistent=True)
|
| 156 |
+
# `{adapter name: buffer attribute}`. Plain state, not a submodule: the buffers themselves are what move
|
| 157 |
+
# with the module, and being non-persistent they stay out of the checkpoint, as an adapter should.
|
| 158 |
+
self._lora_adaln_offsets: dict[str, str] = {}
|
| 159 |
+
|
| 160 |
+
def register_lora_adaln_offset(self, adapter_name: str, offset: torch.Tensor) -> None:
|
| 161 |
+
r"""Attach one adapter's constant term, on the device and in the precision `folded_bias` is kept in."""
|
| 162 |
+
attribute = self._lora_adaln_offsets.get(adapter_name)
|
| 163 |
+
if attribute is None:
|
| 164 |
+
attribute = f"lora_adaln_offset_{len(self._lora_adaln_offsets)}"
|
| 165 |
+
self._lora_adaln_offsets[adapter_name] = attribute
|
| 166 |
+
value = offset.to(device=self.folded_bias.device, dtype=torch.float32)
|
| 167 |
+
self.register_buffer(attribute, value, persistent=False)
|
| 168 |
+
|
| 169 |
+
def lora_adaln_bias(self) -> torch.Tensor:
|
| 170 |
+
r"""`folded_bias` plus every active adapter's constant term at its current scaling.
|
| 171 |
+
|
| 172 |
+
PEFT owns everything this reads - `active_adapters`, `scaling`, `disable_adapters` - so the offsets follow
|
| 173 |
+
`set_adapters`, `disable_lora` and `delete_adapters` with no bookkeeping of their own. They apply whether or
|
| 174 |
+
not an adapter is merged: `fuse_lora` folds `lora_B @ lora_A` into the projection's weight, and there is
|
| 175 |
+
nowhere in a bias-free `Linear` for this term to be folded to.
|
| 176 |
+
"""
|
| 177 |
+
bias = self.folded_bias
|
| 178 |
+
offsets = self._lora_adaln_offsets
|
| 179 |
+
if not offsets:
|
| 180 |
+
return bias
|
| 181 |
+
layer = self.linear
|
| 182 |
+
scaling = getattr(layer, "scaling", None)
|
| 183 |
+
if not isinstance(scaling, dict) or getattr(layer, "disable_adapters", False):
|
| 184 |
+
return bias
|
| 185 |
+
for adapter_name in getattr(layer, "active_adapters", ()):
|
| 186 |
+
attribute = offsets.get(adapter_name)
|
| 187 |
+
if attribute is None or adapter_name not in scaling:
|
| 188 |
+
continue
|
| 189 |
+
bias = bias + getattr(self, attribute) * float(scaling[adapter_name])
|
| 190 |
+
return bias
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
class MiniMaxH3PrunedAdaLayerNormModulation(MiniMaxH3PrunedAdaLN):
|
| 194 |
r"""`MiniMaxH3AdaLayerNormModulation` over the pruned timestep coordinates.
|
| 195 |
|
| 196 |
Two differences from the released module. It applies no `silu` - the table already holds the coordinates of the
|
|
|
|
| 204 |
"""
|
| 205 |
|
| 206 |
def __init__(self, adaln_rank: int, hidden_size: int) -> None:
|
|
|
|
|
|
|
| 207 |
out_features = 6 * hidden_size * MINIMAX_H3_MODALITY_NUM
|
| 208 |
+
super().__init__(out_features)
|
| 209 |
+
self.hidden_size = hidden_size
|
| 210 |
self.linear = nn.Linear(adaln_rank, out_features, bias=False)
|
|
|
|
| 211 |
|
| 212 |
def forward(self, temb: torch.Tensor) -> tuple[torch.Tensor, ...]:
|
| 213 |
dtype = get_parameter_dtype(self.linear)
|
| 214 |
temb = self.linear(temb.to(dtype))
|
| 215 |
+
temb = (temb.float() + self.lora_adaln_bias()).to(dtype)
|
| 216 |
temb = temb.view(-1, 6 * self.hidden_size)
|
| 217 |
return temb.chunk(6, dim=-1)
|
| 218 |
|
| 219 |
|
| 220 |
+
class MiniMaxH3PrunedAdaLayerNormOut(MiniMaxH3PrunedAdaLN):
|
| 221 |
r"""`MiniMaxH3AdaLayerNormOut` over the pruned timestep coordinates; see the modulation module above."""
|
| 222 |
|
| 223 |
def __init__(self, hidden_size: int, adaln_rank: int, eps: float) -> None:
|
| 224 |
+
super().__init__(2 * hidden_size)
|
| 225 |
self.norm = nn.RMSNorm(hidden_size, eps=eps)
|
| 226 |
self.linear = nn.Linear(adaln_rank, 2 * hidden_size, bias=False)
|
|
|
|
| 227 |
|
| 228 |
def forward(self, hidden_states: torch.Tensor, temb: torch.Tensor, timestep_indices: torch.Tensor) -> torch.Tensor:
|
| 229 |
dtype = get_parameter_dtype(self.linear)
|
| 230 |
temb = self.linear(temb.to(dtype))
|
| 231 |
+
shift, scale = (temb.float() + self.lora_adaln_bias()).to(dtype).chunk(2, dim=-1)
|
| 232 |
hidden_states = self.norm(hidden_states)
|
| 233 |
return hidden_states * (1.0 + scale.index_select(0, timestep_indices)) + shift.index_select(
|
| 234 |
0, timestep_indices
|
|
|
|
| 247 |
Only what builds the timestep path differs from [`MiniMaxH3Transformer3DModel`]: `time_proj` becomes an identity,
|
| 248 |
`time_embedder` becomes [`MiniMaxH3PrunedTimeEmbedder`], and the AdaLN projections take `adaln_rank` inputs.
|
| 249 |
`forward` is inherited unchanged. The module names are the released ones, so LoRAs trained against a pruned
|
| 250 |
+
checkpoint - what the common trainers use by default - load natively, and `load_lora_adapter` projects LoRAs
|
| 251 |
+
trained against the released checkpoint's `time_embed_dim`-wide projections onto the same coordinates.
|
| 252 |
|
| 253 |
Args:
|
| 254 |
adaln_rank (`int`, defaults to `8`):
|
|
|
|
| 277 |
"audio_proj_out",
|
| 278 |
"rope",
|
| 279 |
"folded_bias",
|
| 280 |
+
"adaln_basis",
|
| 281 |
+
"adaln_mean",
|
| 282 |
]
|
| 283 |
|
| 284 |
@register_to_config
|
|
|
|
| 321 |
self.time_proj = MiniMaxH3PrunedTimeProj()
|
| 322 |
self.time_embedder = MiniMaxH3PrunedTimeEmbedder(table_size=time_table_size, adaln_rank=adaln_rank)
|
| 323 |
|
| 324 |
+
# 2b. The affine map the fold was performed with: `silu(time_embedder(t)) ~= adaln_mean + c(t) @ adaln_basis`.
|
| 325 |
+
# Nothing in `forward` reads these - the folded projections already carry them. They are stored so that a
|
| 326 |
+
# LoRA trained on the released 2688-wide projections can be mapped onto these coordinates at load time;
|
| 327 |
+
# see `load_lora_adapter`. 97 KB per partition.
|
| 328 |
+
self.register_buffer("adaln_basis", torch.zeros(adaln_rank, time_embed_dim), persistent=True)
|
| 329 |
+
self.register_buffer("adaln_mean", torch.zeros(time_embed_dim), persistent=True)
|
| 330 |
+
|
| 331 |
# 3. Rotary embedding over the packed (t, h, w) grid
|
| 332 |
self.rope = MiniMaxH3RotaryPosEmbed(rope_freq_dim=rope_freq_dim, rope_theta=rope_theta)
|
| 333 |
|
|
|
|
| 372 |
|
| 373 |
self.gradient_checkpointing = False
|
| 374 |
|
| 375 |
+
# -- LoRA ---------------------------------------------------------------------------------------------------
|
| 376 |
+
|
| 377 |
+
def project_adaln_lora(self, state_dict: dict, prefix: str | None = None) -> tuple[dict, dict]:
|
| 378 |
+
r"""Map a LoRA's AdaLN factors from the released timestep embedding onto the pruned coordinates.
|
| 379 |
+
|
| 380 |
+
The released projection reads `x = silu(time_embedder(t))`, so a LoRA on it contributes
|
| 381 |
+
|
| 382 |
+
lora_B @ (lora_A @ x) = lora_B @ (lora_A @ (mean + basis.T @ c))
|
| 383 |
+
= (lora_B @ (lora_A @ basis.T)) @ c + lora_B @ (lora_A @ mean)
|
| 384 |
+
|
| 385 |
+
which is a rank-preserving `[rank, adaln_rank]` `lora_A` over the pruned coordinates plus a constant output
|
| 386 |
+
offset. Both are computed in float64 from the file's own factors; the only error is the rank-8 subspace's
|
| 387 |
+
own residual on the timestep curve, 1.5e-5 relative, which is ~250x below one bfloat16 step of the weights
|
| 388 |
+
being adapted.
|
| 389 |
+
|
| 390 |
+
Returns `(state_dict, {module path: offset})`, the state dict unchanged and the offsets empty when the AdaLN
|
| 391 |
+
factors are already `adaln_rank`-wide (a LoRA trained against a pruned checkpoint - most of them).
|
| 392 |
+
|
| 393 |
+
Every AdaLN module in a file has to be one or the other. A file that mixes widths is not something this can
|
| 394 |
+
half-apply, so it raises.
|
| 395 |
+
|
| 396 |
+
`prefix` scopes this to one component's keys, the same way `load_lora_adapter` scopes the load - a file that
|
| 397 |
+
names both partitions holds two different adapters under one roof, and only one of them is going into this
|
| 398 |
+
module.
|
| 399 |
+
"""
|
| 400 |
+
adaln_rank = self.config.adaln_rank
|
| 401 |
+
time_embed_dim = self.config.time_embed_dim
|
| 402 |
+
|
| 403 |
+
modules = {}
|
| 404 |
+
for key in state_dict:
|
| 405 |
+
if prefix is not None and not key.startswith(f"{prefix}."):
|
| 406 |
+
continue
|
| 407 |
+
match = ADALN_LORA_A_KEY.search(key)
|
| 408 |
+
if match is not None:
|
| 409 |
+
modules[key] = match.group(1)
|
| 410 |
+
if not modules:
|
| 411 |
+
return state_dict, {}
|
| 412 |
+
|
| 413 |
+
widths = sorted({int(state_dict[key].shape[1]) for key in modules})
|
| 414 |
+
if widths == [adaln_rank]:
|
| 415 |
+
return state_dict, {}
|
| 416 |
+
if widths != [time_embed_dim]:
|
| 417 |
+
odd = sorted(
|
| 418 |
+
{modules[key] for key in modules if int(state_dict[key].shape[1]) != max(widths)},
|
| 419 |
+
key=lambda name: (name != "norm_out.linear", name),
|
| 420 |
+
)
|
| 421 |
+
raise ValueError(
|
| 422 |
+
f"This LoRA's {len(modules)} AdaLN projections read inputs of width {widths}. On this checkpoint they "
|
| 423 |
+
f"have to be uniformly {adaln_rank} wide (trained against a pruned checkpoint, loaded as they are) or "
|
| 424 |
+
f"uniformly {time_embed_dim} wide (trained against the released checkpoint, projected onto the pruned "
|
| 425 |
+
"coordinates at load). An adapter cannot be applied to some of its AdaLN modules and not others, so "
|
| 426 |
+
f"nothing was loaded. The minority width is on: {odd[:4]}{' ...' if len(odd) > 4 else ''}."
|
| 427 |
+
)
|
| 428 |
+
|
| 429 |
+
basis = self.adaln_basis
|
| 430 |
+
mean = self.adaln_mean
|
| 431 |
+
if basis.abs().sum() == 0:
|
| 432 |
+
raise ValueError(
|
| 433 |
+
"Projecting a released-checkpoint LoRA needs `adaln_basis` and `adaln_mean`, the affine map this "
|
| 434 |
+
"checkpoint's AdaLN projections were folded with, and this model was loaded without them. Re-download "
|
| 435 |
+
"the repository: they ship as `adaln_affine.safetensors` next to the weights."
|
| 436 |
+
)
|
| 437 |
+
basis = basis.double()
|
| 438 |
+
mean = mean.double()
|
| 439 |
+
|
| 440 |
+
projected = dict(state_dict)
|
| 441 |
+
offsets = {}
|
| 442 |
+
for key, module in modules.items():
|
| 443 |
+
lora_b_key = key[: -len("lora_A.weight")] + "lora_B.weight"
|
| 444 |
+
if lora_b_key not in state_dict:
|
| 445 |
+
raise ValueError(
|
| 446 |
+
f"{key} has no matching {lora_b_key}. The constant term of the projection is "
|
| 447 |
+
"`lora_B @ (lora_A @ mean)`, so both factors have to be present; nothing was loaded."
|
| 448 |
+
)
|
| 449 |
+
lora_a = state_dict[key].to(device=basis.device, dtype=torch.float64)
|
| 450 |
+
lora_b = state_dict[lora_b_key].to(device=basis.device, dtype=torch.float64)
|
| 451 |
+
# float32 rather than the file's bfloat16: the projection is exact arithmetic on the file's factors and
|
| 452 |
+
# there is no reason to round it twice. PEFT casts to the adapter's dtype when it loads them.
|
| 453 |
+
projected[key] = (lora_a @ basis.T).to(torch.float32).cpu().contiguous()
|
| 454 |
+
offsets[module] = (lora_b @ (lora_a @ mean)).to(torch.float32).cpu().contiguous()
|
| 455 |
+
return projected, offsets
|
| 456 |
+
|
| 457 |
+
def load_lora_adapter(self, pretrained_model_name_or_path_or_dict, prefix="transformer", hotswap=False, **kwargs):
|
| 458 |
+
r"""`PeftAdapterMixin.load_lora_adapter`, with the AdaLN projection of [`project_adaln_lora`] in front of it.
|
| 459 |
+
|
| 460 |
+
LoRAs trained against a pruned checkpoint pass through untouched. LoRAs trained against the released
|
| 461 |
+
checkpoint's 2688-wide AdaLN projections - the official turbo LoRA and its conversions - are mapped onto the
|
| 462 |
+
pruned coordinates here, which is the only thing that ever stopped them loading. Everything outside the AdaLN
|
| 463 |
+
path is identical between the two checkpoints and is neither inspected nor changed.
|
| 464 |
+
"""
|
| 465 |
+
state_dict = pretrained_model_name_or_path_or_dict
|
| 466 |
+
if not isinstance(state_dict, dict):
|
| 467 |
+
from diffusers.loaders.lora_base import _fetch_state_dict
|
| 468 |
+
|
| 469 |
+
state_dict, _ = _fetch_state_dict(
|
| 470 |
+
pretrained_model_name_or_path_or_dict=state_dict,
|
| 471 |
+
weight_name=kwargs.get("weight_name"),
|
| 472 |
+
use_safetensors=kwargs.get("use_safetensors", True),
|
| 473 |
+
local_files_only=kwargs.get("local_files_only"),
|
| 474 |
+
cache_dir=kwargs.get("cache_dir"),
|
| 475 |
+
force_download=kwargs.get("force_download", False),
|
| 476 |
+
proxies=kwargs.get("proxies"),
|
| 477 |
+
token=kwargs.get("token"),
|
| 478 |
+
revision=kwargs.get("revision"),
|
| 479 |
+
subfolder=kwargs.get("subfolder"),
|
| 480 |
+
user_agent={"file_type": "attn_procs_weights", "framework": "pytorch"},
|
| 481 |
+
allow_pickle=False,
|
| 482 |
+
metadata=kwargs.get("metadata"),
|
| 483 |
+
)
|
| 484 |
+
|
| 485 |
+
state_dict, offsets = self.project_adaln_lora(state_dict, prefix=prefix)
|
| 486 |
+
if offsets:
|
| 487 |
+
logger.info(
|
| 488 |
+
f"Projecting {len(offsets)} AdaLN LoRA modules from the released {self.config.time_embed_dim}-wide "
|
| 489 |
+
f"timestep embedding onto this checkpoint's {self.config.adaln_rank} pruned coordinates; each one's "
|
| 490 |
+
"constant term is carried as a float32 offset on the modulation."
|
| 491 |
+
)
|
| 492 |
+
|
| 493 |
+
before = set(getattr(self, "peft_config", None) or ())
|
| 494 |
+
super().load_lora_adapter(state_dict, prefix=prefix, hotswap=hotswap, **kwargs)
|
| 495 |
+
if not offsets:
|
| 496 |
+
return
|
| 497 |
+
|
| 498 |
+
added = set(getattr(self, "peft_config", None) or ()) - before
|
| 499 |
+
adapter_name = added.pop() if len(added) == 1 else kwargs.get("adapter_name")
|
| 500 |
+
if adapter_name is None:
|
| 501 |
+
raise RuntimeError(
|
| 502 |
+
"The AdaLN projection could not tell which adapter was just loaded, so its constant terms were not "
|
| 503 |
+
f"attached and the adapter is incomplete. Adapters before: {sorted(before)}, after: "
|
| 504 |
+
f"{sorted(getattr(self, 'peft_config', None) or ())}. Pass `adapter_name` explicitly."
|
| 505 |
+
)
|
| 506 |
+
for module, offset in offsets.items():
|
| 507 |
+
self.get_submodule(module.rsplit(".", 1)[0]).register_lora_adaln_offset(adapter_name, offset)
|
| 508 |
+
|
| 509 |
# -- 8-bit compute -----------------------------------------------------------------------------------------
|
| 510 |
#
|
| 511 |
# `enable_convrot` and `quantize_8bit` are opt-in and change nothing until called.
|
transformer_ref/adaln_affine.safetensors
ADDED
|
@@ -0,0 +1,3 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
version https://git-lfs.github.com/spec/v1
|
| 2 |
+
oid sha256:98fbeba868fd1d08b8232dce62abb3f5427dd49fb08462869633a3d53533b50f
|
| 3 |
+
size 96960
|
transformer_ref/diffusion_pytorch_model.safetensors.index.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
| 1 |
{
|
| 2 |
"metadata": {
|
| 3 |
-
"total_size":
|
| 4 |
},
|
| 5 |
"weight_map": {
|
| 6 |
"audio_proj_in.bias": "diffusion_pytorch_model-00001-of-00014.safetensors",
|
|
@@ -637,6 +637,8 @@
|
|
| 637 |
"transformer_blocks.9.ff.net.0.proj.weight": "diffusion_pytorch_model-00003-of-00014.safetensors",
|
| 638 |
"transformer_blocks.9.ff.net.2.weight": "diffusion_pytorch_model-00003-of-00014.safetensors",
|
| 639 |
"transformer_blocks.9.norm1.weight": "diffusion_pytorch_model-00003-of-00014.safetensors",
|
| 640 |
-
"transformer_blocks.9.norm2.weight": "diffusion_pytorch_model-00003-of-00014.safetensors"
|
|
|
|
|
|
|
| 641 |
}
|
| 642 |
-
}
|
|
|
|
| 1 |
{
|
| 2 |
"metadata": {
|
| 3 |
+
"total_size": 40235463200
|
| 4 |
},
|
| 5 |
"weight_map": {
|
| 6 |
"audio_proj_in.bias": "diffusion_pytorch_model-00001-of-00014.safetensors",
|
|
|
|
| 637 |
"transformer_blocks.9.ff.net.0.proj.weight": "diffusion_pytorch_model-00003-of-00014.safetensors",
|
| 638 |
"transformer_blocks.9.ff.net.2.weight": "diffusion_pytorch_model-00003-of-00014.safetensors",
|
| 639 |
"transformer_blocks.9.norm1.weight": "diffusion_pytorch_model-00003-of-00014.safetensors",
|
| 640 |
+
"transformer_blocks.9.norm2.weight": "diffusion_pytorch_model-00003-of-00014.safetensors",
|
| 641 |
+
"adaln_basis": "adaln_affine.safetensors",
|
| 642 |
+
"adaln_mean": "adaln_affine.safetensors"
|
| 643 |
}
|
| 644 |
+
}
|
transformer_ref/modeling_minimax_h3_pruned.py
CHANGED
|
@@ -17,12 +17,15 @@ Everything outside the timestep path is inherited from `MiniMaxH3Transformer3DMo
|
|
| 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 |
attention and feed-forward linears that makes 8-bit *compute* (int8 or fp8 dynamic activations, via torchao) land
|
| 22 |
-
within a rounding step of bfloat16.
|
|
|
|
|
|
|
| 23 |
"""
|
| 24 |
|
| 25 |
import math
|
|
|
|
| 26 |
|
| 27 |
import torch
|
| 28 |
import torch.nn as nn
|
|
@@ -36,10 +39,17 @@ from diffusers.models.transformers.transformer_minimax_h3 import (
|
|
| 36 |
MiniMaxH3Transformer3DModel,
|
| 37 |
MiniMaxH3TransformerBlock,
|
| 38 |
)
|
|
|
|
| 39 |
|
| 40 |
|
|
|
|
|
|
|
| 41 |
_HADAMARD_CACHE: dict = {}
|
| 42 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 43 |
# The linears ConvRot conditions: the block stack's attention and feed-forward projections, and nothing else.
|
| 44 |
# This is the set ComfyUI's `*_int8_convrot` checkpoints quantize (they carry one fused `qkv_proj`; the three
|
| 45 |
# split projections here share an input, so rotating each is the same transform). The AdaLN path, the patch
|
|
@@ -129,7 +139,58 @@ class MiniMaxH3PrunedTimeProj(nn.Module):
|
|
| 129 |
return timestep
|
| 130 |
|
| 131 |
|
| 132 |
-
class
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 133 |
r"""`MiniMaxH3AdaLayerNormModulation` over the pruned timestep coordinates.
|
| 134 |
|
| 135 |
Two differences from the released module. It applies no `silu` - the table already holds the coordinates of the
|
|
@@ -143,33 +204,31 @@ class MiniMaxH3PrunedAdaLayerNormModulation(nn.Module):
|
|
| 143 |
"""
|
| 144 |
|
| 145 |
def __init__(self, adaln_rank: int, hidden_size: int) -> None:
|
| 146 |
-
super().__init__()
|
| 147 |
-
self.hidden_size = hidden_size
|
| 148 |
out_features = 6 * hidden_size * MINIMAX_H3_MODALITY_NUM
|
|
|
|
|
|
|
| 149 |
self.linear = nn.Linear(adaln_rank, out_features, bias=False)
|
| 150 |
-
self.register_buffer("folded_bias", torch.zeros(out_features), persistent=True)
|
| 151 |
|
| 152 |
def forward(self, temb: torch.Tensor) -> tuple[torch.Tensor, ...]:
|
| 153 |
dtype = get_parameter_dtype(self.linear)
|
| 154 |
temb = self.linear(temb.to(dtype))
|
| 155 |
-
temb = (temb.float() + self.
|
| 156 |
temb = temb.view(-1, 6 * self.hidden_size)
|
| 157 |
return temb.chunk(6, dim=-1)
|
| 158 |
|
| 159 |
|
| 160 |
-
class MiniMaxH3PrunedAdaLayerNormOut(
|
| 161 |
r"""`MiniMaxH3AdaLayerNormOut` over the pruned timestep coordinates; see the modulation module above."""
|
| 162 |
|
| 163 |
def __init__(self, hidden_size: int, adaln_rank: int, eps: float) -> None:
|
| 164 |
-
super().__init__()
|
| 165 |
self.norm = nn.RMSNorm(hidden_size, eps=eps)
|
| 166 |
self.linear = nn.Linear(adaln_rank, 2 * hidden_size, bias=False)
|
| 167 |
-
self.register_buffer("folded_bias", torch.zeros(2 * hidden_size), persistent=True)
|
| 168 |
|
| 169 |
def forward(self, hidden_states: torch.Tensor, temb: torch.Tensor, timestep_indices: torch.Tensor) -> torch.Tensor:
|
| 170 |
dtype = get_parameter_dtype(self.linear)
|
| 171 |
temb = self.linear(temb.to(dtype))
|
| 172 |
-
shift, scale = (temb.float() + self.
|
| 173 |
hidden_states = self.norm(hidden_states)
|
| 174 |
return hidden_states * (1.0 + scale.index_select(0, timestep_indices)) + shift.index_select(
|
| 175 |
0, timestep_indices
|
|
@@ -188,7 +247,8 @@ class MiniMaxH3PrunedTransformer3DModel(MiniMaxH3Transformer3DModel):
|
|
| 188 |
Only what builds the timestep path differs from [`MiniMaxH3Transformer3DModel`]: `time_proj` becomes an identity,
|
| 189 |
`time_embedder` becomes [`MiniMaxH3PrunedTimeEmbedder`], and the AdaLN projections take `adaln_rank` inputs.
|
| 190 |
`forward` is inherited unchanged. The module names are the released ones, so LoRAs trained against a pruned
|
| 191 |
-
checkpoint - what the common trainers use by default - load natively
|
|
|
|
| 192 |
|
| 193 |
Args:
|
| 194 |
adaln_rank (`int`, defaults to `8`):
|
|
@@ -217,6 +277,8 @@ class MiniMaxH3PrunedTransformer3DModel(MiniMaxH3Transformer3DModel):
|
|
| 217 |
"audio_proj_out",
|
| 218 |
"rope",
|
| 219 |
"folded_bias",
|
|
|
|
|
|
|
| 220 |
]
|
| 221 |
|
| 222 |
@register_to_config
|
|
@@ -259,6 +321,13 @@ class MiniMaxH3PrunedTransformer3DModel(MiniMaxH3Transformer3DModel):
|
|
| 259 |
self.time_proj = MiniMaxH3PrunedTimeProj()
|
| 260 |
self.time_embedder = MiniMaxH3PrunedTimeEmbedder(table_size=time_table_size, adaln_rank=adaln_rank)
|
| 261 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 262 |
# 3. Rotary embedding over the packed (t, h, w) grid
|
| 263 |
self.rope = MiniMaxH3RotaryPosEmbed(rope_freq_dim=rope_freq_dim, rope_theta=rope_theta)
|
| 264 |
|
|
@@ -303,6 +372,140 @@ class MiniMaxH3PrunedTransformer3DModel(MiniMaxH3Transformer3DModel):
|
|
| 303 |
|
| 304 |
self.gradient_checkpointing = False
|
| 305 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 306 |
# -- 8-bit compute -----------------------------------------------------------------------------------------
|
| 307 |
#
|
| 308 |
# `enable_convrot` and `quantize_8bit` are opt-in and change nothing until called.
|
|
|
|
| 17 |
token refiner, the output heads and `forward` itself are the released implementation, unmodified. Only what feeds the
|
| 18 |
AdaLN projections changes.
|
| 19 |
|
| 20 |
+
Two other things this file adds. `enable_convrot` / `quantize_8bit`: an opt-in Hadamard conditioning of the
|
| 21 |
attention and feed-forward linears that makes 8-bit *compute* (int8 or fp8 dynamic activations, via torchao) land
|
| 22 |
+
within a rounding step of bfloat16. And `load_lora_adapter`, overridden to project a LoRA trained against the
|
| 23 |
+
*released* 2688-wide AdaLN projections onto these 8-wide ones. Both are inert unless used, so the plain pruned path
|
| 24 |
+
is byte for byte what it was.
|
| 25 |
"""
|
| 26 |
|
| 27 |
import math
|
| 28 |
+
import re
|
| 29 |
|
| 30 |
import torch
|
| 31 |
import torch.nn as nn
|
|
|
|
| 39 |
MiniMaxH3Transformer3DModel,
|
| 40 |
MiniMaxH3TransformerBlock,
|
| 41 |
)
|
| 42 |
+
from diffusers.utils import logging
|
| 43 |
|
| 44 |
|
| 45 |
+
logger = logging.get_logger(__name__)
|
| 46 |
+
|
| 47 |
_HADAMARD_CACHE: dict = {}
|
| 48 |
|
| 49 |
+
# The 51 AdaLN projections, as a LoRA state dict names them, with or without a `transformer.` / `transformer_ref.`
|
| 50 |
+
# component prefix. Group 1 is the module path the model itself knows the projection by.
|
| 51 |
+
ADALN_LORA_A_KEY = re.compile(r"(?:^|\.)((?:transformer_blocks\.\d+\.adaln_proj|norm_out)\.linear)\.lora_A\.weight$")
|
| 52 |
+
|
| 53 |
# The linears ConvRot conditions: the block stack's attention and feed-forward projections, and nothing else.
|
| 54 |
# This is the set ComfyUI's `*_int8_convrot` checkpoints quantize (they carry one fused `qkv_proj`; the three
|
| 55 |
# split projections here share an input, so rotating each is the same transform). The AdaLN path, the patch
|
|
|
|
| 139 |
return timestep
|
| 140 |
|
| 141 |
|
| 142 |
+
class MiniMaxH3PrunedAdaLN(nn.Module):
|
| 143 |
+
r"""Shared by both pruned AdaLN modules: the folded float32 bias, plus the LoRA offsets that ride alongside it.
|
| 144 |
+
|
| 145 |
+
A LoRA trained against the released 2688-wide projection contributes `lora_B @ (lora_A @ x)` to the modulation.
|
| 146 |
+
With `x = mean + c @ basis`, that splits into a coordinate term the projected factors reproduce and a *constant*
|
| 147 |
+
term, `lora_B @ (lora_A @ mean)`, which no `Linear(8 -> out)` can express. That constant is held here, as a
|
| 148 |
+
per-adapter float32 buffer added to `folded_bias` - the same place, and the same precision, the fold's own
|
| 149 |
+
constant term lives in. It is deliberately not the projection's `bias`: rounding it into bfloat16 would spend a
|
| 150 |
+
full rounding step of the modulation on a term that is most of what the adapter does to the AdaLN path.
|
| 151 |
+
"""
|
| 152 |
+
|
| 153 |
+
def __init__(self, out_features: int) -> None:
|
| 154 |
+
super().__init__()
|
| 155 |
+
self.register_buffer("folded_bias", torch.zeros(out_features), persistent=True)
|
| 156 |
+
# `{adapter name: buffer attribute}`. Plain state, not a submodule: the buffers themselves are what move
|
| 157 |
+
# with the module, and being non-persistent they stay out of the checkpoint, as an adapter should.
|
| 158 |
+
self._lora_adaln_offsets: dict[str, str] = {}
|
| 159 |
+
|
| 160 |
+
def register_lora_adaln_offset(self, adapter_name: str, offset: torch.Tensor) -> None:
|
| 161 |
+
r"""Attach one adapter's constant term, on the device and in the precision `folded_bias` is kept in."""
|
| 162 |
+
attribute = self._lora_adaln_offsets.get(adapter_name)
|
| 163 |
+
if attribute is None:
|
| 164 |
+
attribute = f"lora_adaln_offset_{len(self._lora_adaln_offsets)}"
|
| 165 |
+
self._lora_adaln_offsets[adapter_name] = attribute
|
| 166 |
+
value = offset.to(device=self.folded_bias.device, dtype=torch.float32)
|
| 167 |
+
self.register_buffer(attribute, value, persistent=False)
|
| 168 |
+
|
| 169 |
+
def lora_adaln_bias(self) -> torch.Tensor:
|
| 170 |
+
r"""`folded_bias` plus every active adapter's constant term at its current scaling.
|
| 171 |
+
|
| 172 |
+
PEFT owns everything this reads - `active_adapters`, `scaling`, `disable_adapters` - so the offsets follow
|
| 173 |
+
`set_adapters`, `disable_lora` and `delete_adapters` with no bookkeeping of their own. They apply whether or
|
| 174 |
+
not an adapter is merged: `fuse_lora` folds `lora_B @ lora_A` into the projection's weight, and there is
|
| 175 |
+
nowhere in a bias-free `Linear` for this term to be folded to.
|
| 176 |
+
"""
|
| 177 |
+
bias = self.folded_bias
|
| 178 |
+
offsets = self._lora_adaln_offsets
|
| 179 |
+
if not offsets:
|
| 180 |
+
return bias
|
| 181 |
+
layer = self.linear
|
| 182 |
+
scaling = getattr(layer, "scaling", None)
|
| 183 |
+
if not isinstance(scaling, dict) or getattr(layer, "disable_adapters", False):
|
| 184 |
+
return bias
|
| 185 |
+
for adapter_name in getattr(layer, "active_adapters", ()):
|
| 186 |
+
attribute = offsets.get(adapter_name)
|
| 187 |
+
if attribute is None or adapter_name not in scaling:
|
| 188 |
+
continue
|
| 189 |
+
bias = bias + getattr(self, attribute) * float(scaling[adapter_name])
|
| 190 |
+
return bias
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
class MiniMaxH3PrunedAdaLayerNormModulation(MiniMaxH3PrunedAdaLN):
|
| 194 |
r"""`MiniMaxH3AdaLayerNormModulation` over the pruned timestep coordinates.
|
| 195 |
|
| 196 |
Two differences from the released module. It applies no `silu` - the table already holds the coordinates of the
|
|
|
|
| 204 |
"""
|
| 205 |
|
| 206 |
def __init__(self, adaln_rank: int, hidden_size: int) -> None:
|
|
|
|
|
|
|
| 207 |
out_features = 6 * hidden_size * MINIMAX_H3_MODALITY_NUM
|
| 208 |
+
super().__init__(out_features)
|
| 209 |
+
self.hidden_size = hidden_size
|
| 210 |
self.linear = nn.Linear(adaln_rank, out_features, bias=False)
|
|
|
|
| 211 |
|
| 212 |
def forward(self, temb: torch.Tensor) -> tuple[torch.Tensor, ...]:
|
| 213 |
dtype = get_parameter_dtype(self.linear)
|
| 214 |
temb = self.linear(temb.to(dtype))
|
| 215 |
+
temb = (temb.float() + self.lora_adaln_bias()).to(dtype)
|
| 216 |
temb = temb.view(-1, 6 * self.hidden_size)
|
| 217 |
return temb.chunk(6, dim=-1)
|
| 218 |
|
| 219 |
|
| 220 |
+
class MiniMaxH3PrunedAdaLayerNormOut(MiniMaxH3PrunedAdaLN):
|
| 221 |
r"""`MiniMaxH3AdaLayerNormOut` over the pruned timestep coordinates; see the modulation module above."""
|
| 222 |
|
| 223 |
def __init__(self, hidden_size: int, adaln_rank: int, eps: float) -> None:
|
| 224 |
+
super().__init__(2 * hidden_size)
|
| 225 |
self.norm = nn.RMSNorm(hidden_size, eps=eps)
|
| 226 |
self.linear = nn.Linear(adaln_rank, 2 * hidden_size, bias=False)
|
|
|
|
| 227 |
|
| 228 |
def forward(self, hidden_states: torch.Tensor, temb: torch.Tensor, timestep_indices: torch.Tensor) -> torch.Tensor:
|
| 229 |
dtype = get_parameter_dtype(self.linear)
|
| 230 |
temb = self.linear(temb.to(dtype))
|
| 231 |
+
shift, scale = (temb.float() + self.lora_adaln_bias()).to(dtype).chunk(2, dim=-1)
|
| 232 |
hidden_states = self.norm(hidden_states)
|
| 233 |
return hidden_states * (1.0 + scale.index_select(0, timestep_indices)) + shift.index_select(
|
| 234 |
0, timestep_indices
|
|
|
|
| 247 |
Only what builds the timestep path differs from [`MiniMaxH3Transformer3DModel`]: `time_proj` becomes an identity,
|
| 248 |
`time_embedder` becomes [`MiniMaxH3PrunedTimeEmbedder`], and the AdaLN projections take `adaln_rank` inputs.
|
| 249 |
`forward` is inherited unchanged. The module names are the released ones, so LoRAs trained against a pruned
|
| 250 |
+
checkpoint - what the common trainers use by default - load natively, and `load_lora_adapter` projects LoRAs
|
| 251 |
+
trained against the released checkpoint's `time_embed_dim`-wide projections onto the same coordinates.
|
| 252 |
|
| 253 |
Args:
|
| 254 |
adaln_rank (`int`, defaults to `8`):
|
|
|
|
| 277 |
"audio_proj_out",
|
| 278 |
"rope",
|
| 279 |
"folded_bias",
|
| 280 |
+
"adaln_basis",
|
| 281 |
+
"adaln_mean",
|
| 282 |
]
|
| 283 |
|
| 284 |
@register_to_config
|
|
|
|
| 321 |
self.time_proj = MiniMaxH3PrunedTimeProj()
|
| 322 |
self.time_embedder = MiniMaxH3PrunedTimeEmbedder(table_size=time_table_size, adaln_rank=adaln_rank)
|
| 323 |
|
| 324 |
+
# 2b. The affine map the fold was performed with: `silu(time_embedder(t)) ~= adaln_mean + c(t) @ adaln_basis`.
|
| 325 |
+
# Nothing in `forward` reads these - the folded projections already carry them. They are stored so that a
|
| 326 |
+
# LoRA trained on the released 2688-wide projections can be mapped onto these coordinates at load time;
|
| 327 |
+
# see `load_lora_adapter`. 97 KB per partition.
|
| 328 |
+
self.register_buffer("adaln_basis", torch.zeros(adaln_rank, time_embed_dim), persistent=True)
|
| 329 |
+
self.register_buffer("adaln_mean", torch.zeros(time_embed_dim), persistent=True)
|
| 330 |
+
|
| 331 |
# 3. Rotary embedding over the packed (t, h, w) grid
|
| 332 |
self.rope = MiniMaxH3RotaryPosEmbed(rope_freq_dim=rope_freq_dim, rope_theta=rope_theta)
|
| 333 |
|
|
|
|
| 372 |
|
| 373 |
self.gradient_checkpointing = False
|
| 374 |
|
| 375 |
+
# -- LoRA ---------------------------------------------------------------------------------------------------
|
| 376 |
+
|
| 377 |
+
def project_adaln_lora(self, state_dict: dict, prefix: str | None = None) -> tuple[dict, dict]:
|
| 378 |
+
r"""Map a LoRA's AdaLN factors from the released timestep embedding onto the pruned coordinates.
|
| 379 |
+
|
| 380 |
+
The released projection reads `x = silu(time_embedder(t))`, so a LoRA on it contributes
|
| 381 |
+
|
| 382 |
+
lora_B @ (lora_A @ x) = lora_B @ (lora_A @ (mean + basis.T @ c))
|
| 383 |
+
= (lora_B @ (lora_A @ basis.T)) @ c + lora_B @ (lora_A @ mean)
|
| 384 |
+
|
| 385 |
+
which is a rank-preserving `[rank, adaln_rank]` `lora_A` over the pruned coordinates plus a constant output
|
| 386 |
+
offset. Both are computed in float64 from the file's own factors; the only error is the rank-8 subspace's
|
| 387 |
+
own residual on the timestep curve, 1.5e-5 relative, which is ~250x below one bfloat16 step of the weights
|
| 388 |
+
being adapted.
|
| 389 |
+
|
| 390 |
+
Returns `(state_dict, {module path: offset})`, the state dict unchanged and the offsets empty when the AdaLN
|
| 391 |
+
factors are already `adaln_rank`-wide (a LoRA trained against a pruned checkpoint - most of them).
|
| 392 |
+
|
| 393 |
+
Every AdaLN module in a file has to be one or the other. A file that mixes widths is not something this can
|
| 394 |
+
half-apply, so it raises.
|
| 395 |
+
|
| 396 |
+
`prefix` scopes this to one component's keys, the same way `load_lora_adapter` scopes the load - a file that
|
| 397 |
+
names both partitions holds two different adapters under one roof, and only one of them is going into this
|
| 398 |
+
module.
|
| 399 |
+
"""
|
| 400 |
+
adaln_rank = self.config.adaln_rank
|
| 401 |
+
time_embed_dim = self.config.time_embed_dim
|
| 402 |
+
|
| 403 |
+
modules = {}
|
| 404 |
+
for key in state_dict:
|
| 405 |
+
if prefix is not None and not key.startswith(f"{prefix}."):
|
| 406 |
+
continue
|
| 407 |
+
match = ADALN_LORA_A_KEY.search(key)
|
| 408 |
+
if match is not None:
|
| 409 |
+
modules[key] = match.group(1)
|
| 410 |
+
if not modules:
|
| 411 |
+
return state_dict, {}
|
| 412 |
+
|
| 413 |
+
widths = sorted({int(state_dict[key].shape[1]) for key in modules})
|
| 414 |
+
if widths == [adaln_rank]:
|
| 415 |
+
return state_dict, {}
|
| 416 |
+
if widths != [time_embed_dim]:
|
| 417 |
+
odd = sorted(
|
| 418 |
+
{modules[key] for key in modules if int(state_dict[key].shape[1]) != max(widths)},
|
| 419 |
+
key=lambda name: (name != "norm_out.linear", name),
|
| 420 |
+
)
|
| 421 |
+
raise ValueError(
|
| 422 |
+
f"This LoRA's {len(modules)} AdaLN projections read inputs of width {widths}. On this checkpoint they "
|
| 423 |
+
f"have to be uniformly {adaln_rank} wide (trained against a pruned checkpoint, loaded as they are) or "
|
| 424 |
+
f"uniformly {time_embed_dim} wide (trained against the released checkpoint, projected onto the pruned "
|
| 425 |
+
"coordinates at load). An adapter cannot be applied to some of its AdaLN modules and not others, so "
|
| 426 |
+
f"nothing was loaded. The minority width is on: {odd[:4]}{' ...' if len(odd) > 4 else ''}."
|
| 427 |
+
)
|
| 428 |
+
|
| 429 |
+
basis = self.adaln_basis
|
| 430 |
+
mean = self.adaln_mean
|
| 431 |
+
if basis.abs().sum() == 0:
|
| 432 |
+
raise ValueError(
|
| 433 |
+
"Projecting a released-checkpoint LoRA needs `adaln_basis` and `adaln_mean`, the affine map this "
|
| 434 |
+
"checkpoint's AdaLN projections were folded with, and this model was loaded without them. Re-download "
|
| 435 |
+
"the repository: they ship as `adaln_affine.safetensors` next to the weights."
|
| 436 |
+
)
|
| 437 |
+
basis = basis.double()
|
| 438 |
+
mean = mean.double()
|
| 439 |
+
|
| 440 |
+
projected = dict(state_dict)
|
| 441 |
+
offsets = {}
|
| 442 |
+
for key, module in modules.items():
|
| 443 |
+
lora_b_key = key[: -len("lora_A.weight")] + "lora_B.weight"
|
| 444 |
+
if lora_b_key not in state_dict:
|
| 445 |
+
raise ValueError(
|
| 446 |
+
f"{key} has no matching {lora_b_key}. The constant term of the projection is "
|
| 447 |
+
"`lora_B @ (lora_A @ mean)`, so both factors have to be present; nothing was loaded."
|
| 448 |
+
)
|
| 449 |
+
lora_a = state_dict[key].to(device=basis.device, dtype=torch.float64)
|
| 450 |
+
lora_b = state_dict[lora_b_key].to(device=basis.device, dtype=torch.float64)
|
| 451 |
+
# float32 rather than the file's bfloat16: the projection is exact arithmetic on the file's factors and
|
| 452 |
+
# there is no reason to round it twice. PEFT casts to the adapter's dtype when it loads them.
|
| 453 |
+
projected[key] = (lora_a @ basis.T).to(torch.float32).cpu().contiguous()
|
| 454 |
+
offsets[module] = (lora_b @ (lora_a @ mean)).to(torch.float32).cpu().contiguous()
|
| 455 |
+
return projected, offsets
|
| 456 |
+
|
| 457 |
+
def load_lora_adapter(self, pretrained_model_name_or_path_or_dict, prefix="transformer", hotswap=False, **kwargs):
|
| 458 |
+
r"""`PeftAdapterMixin.load_lora_adapter`, with the AdaLN projection of [`project_adaln_lora`] in front of it.
|
| 459 |
+
|
| 460 |
+
LoRAs trained against a pruned checkpoint pass through untouched. LoRAs trained against the released
|
| 461 |
+
checkpoint's 2688-wide AdaLN projections - the official turbo LoRA and its conversions - are mapped onto the
|
| 462 |
+
pruned coordinates here, which is the only thing that ever stopped them loading. Everything outside the AdaLN
|
| 463 |
+
path is identical between the two checkpoints and is neither inspected nor changed.
|
| 464 |
+
"""
|
| 465 |
+
state_dict = pretrained_model_name_or_path_or_dict
|
| 466 |
+
if not isinstance(state_dict, dict):
|
| 467 |
+
from diffusers.loaders.lora_base import _fetch_state_dict
|
| 468 |
+
|
| 469 |
+
state_dict, _ = _fetch_state_dict(
|
| 470 |
+
pretrained_model_name_or_path_or_dict=state_dict,
|
| 471 |
+
weight_name=kwargs.get("weight_name"),
|
| 472 |
+
use_safetensors=kwargs.get("use_safetensors", True),
|
| 473 |
+
local_files_only=kwargs.get("local_files_only"),
|
| 474 |
+
cache_dir=kwargs.get("cache_dir"),
|
| 475 |
+
force_download=kwargs.get("force_download", False),
|
| 476 |
+
proxies=kwargs.get("proxies"),
|
| 477 |
+
token=kwargs.get("token"),
|
| 478 |
+
revision=kwargs.get("revision"),
|
| 479 |
+
subfolder=kwargs.get("subfolder"),
|
| 480 |
+
user_agent={"file_type": "attn_procs_weights", "framework": "pytorch"},
|
| 481 |
+
allow_pickle=False,
|
| 482 |
+
metadata=kwargs.get("metadata"),
|
| 483 |
+
)
|
| 484 |
+
|
| 485 |
+
state_dict, offsets = self.project_adaln_lora(state_dict, prefix=prefix)
|
| 486 |
+
if offsets:
|
| 487 |
+
logger.info(
|
| 488 |
+
f"Projecting {len(offsets)} AdaLN LoRA modules from the released {self.config.time_embed_dim}-wide "
|
| 489 |
+
f"timestep embedding onto this checkpoint's {self.config.adaln_rank} pruned coordinates; each one's "
|
| 490 |
+
"constant term is carried as a float32 offset on the modulation."
|
| 491 |
+
)
|
| 492 |
+
|
| 493 |
+
before = set(getattr(self, "peft_config", None) or ())
|
| 494 |
+
super().load_lora_adapter(state_dict, prefix=prefix, hotswap=hotswap, **kwargs)
|
| 495 |
+
if not offsets:
|
| 496 |
+
return
|
| 497 |
+
|
| 498 |
+
added = set(getattr(self, "peft_config", None) or ()) - before
|
| 499 |
+
adapter_name = added.pop() if len(added) == 1 else kwargs.get("adapter_name")
|
| 500 |
+
if adapter_name is None:
|
| 501 |
+
raise RuntimeError(
|
| 502 |
+
"The AdaLN projection could not tell which adapter was just loaded, so its constant terms were not "
|
| 503 |
+
f"attached and the adapter is incomplete. Adapters before: {sorted(before)}, after: "
|
| 504 |
+
f"{sorted(getattr(self, 'peft_config', None) or ())}. Pass `adapter_name` explicitly."
|
| 505 |
+
)
|
| 506 |
+
for module, offset in offsets.items():
|
| 507 |
+
self.get_submodule(module.rsplit(".", 1)[0]).register_lora_adaln_offset(adapter_name, offset)
|
| 508 |
+
|
| 509 |
# -- 8-bit compute -----------------------------------------------------------------------------------------
|
| 510 |
#
|
| 511 |
# `enable_convrot` and `quantize_8bit` are opt-in and change nothing until called.
|