Spaces:
Running on Zero
Running on Zero
| """Fold the MiniMax-H3 Turbo LoRA (`larryvrh/MiniMax-H3-Turbo-Lora`) into the diffusers transformer. | |
| The LoRA ships against the *reference* (ComfyUI) module tree — `blocks.N.attn.qkv_proj`, `blocks.N.mlp.fc1`, | |
| `token_refiner.blocks.N`, `final_layer.adaln_proj.linear` — with `alpha == rank`, so the update is exactly | |
| `W + lora_B @ lora_A`. The diffusers checkpoint is the same weights under different names and two layout transforms | |
| (see `scripts/convert_minimax_h3_to_diffusers.py` in huggingface/diffusers#14371), so each delta gets the same | |
| transform the base weight got: | |
| * fused `attn.qkv_proj` rows are `[q_all; k_all; v_all]` in both in-memory layouts -> split into contiguous thirds | |
| onto `attn.to_q` / `to_k` / `to_v`; | |
| * fused `mlp.fc1` is `[gate; value]` while diffusers' `SwiGLU` fuses `[value; gate]` -> swap the halves onto | |
| `ff.net.0.proj`; | |
| * `mlp.fc2` -> `ff.net.2`, `attn.out_proj` -> `attn.to_out.0`, `blocks.` -> `transformer_blocks.`, | |
| `token_refiner.blocks.` -> `token_refiner.refiner_blocks.`, `final_layer.adaln_proj.linear` -> `norm_out.linear`; | |
| * the `adaln_proj.linear` modulation tables share the `[timestep][modality][param]` row layout in both trees, so | |
| they map name-for-name with no reordering. | |
| The delta is folded into the bf16 weights rather than applied as a runtime wrapper for one reason: the AoTI block | |
| package (`h3_aoti`) reads each block's live weights, and a wrapper module would be invisible to it. The fold computes | |
| `lora_B @ lora_A` in float32 and rounds once on the way back into bf16. | |
| """ | |
| from __future__ import annotations | |
| import os | |
| import torch | |
| LORA_REPO = os.environ.get("H3_LORA_REPO", "larryvrh/MiniMax-H3-Turbo-Lora") | |
| # The trained weights; the `_ema` variant is an immature time-averaged snapshot at this checkpoint. `off` disables. | |
| LORA_FILE = os.environ.get("H3_LORA", "minimax_h3_turbo_4step.safetensors") | |
| def _delta_targets(name: str, delta: torch.Tensor, inner_dim: int) -> list[tuple[str, torch.Tensor]]: | |
| """Map one reference-tree LoRA base name and its `lora_B @ lora_A` delta onto diffusers parameter key(s).""" | |
| if name.startswith("token_refiner.blocks."): | |
| target = name.replace("token_refiner.blocks.", "token_refiner.refiner_blocks.", 1) | |
| elif name.startswith("blocks."): | |
| target = name.replace("blocks.", "transformer_blocks.", 1) | |
| else: | |
| target = name | |
| target = target.replace("final_layer.adaln_proj.linear", "norm_out.linear") | |
| if target.endswith(".attn.qkv_proj"): | |
| prefix = target.removesuffix("qkv_proj") | |
| return [ | |
| (f"{prefix}to_{kind}.weight", part.contiguous()) | |
| for kind, part in zip(("q", "k", "v"), delta.split(inner_dim, dim=0)) | |
| ] | |
| if target.endswith(".mlp.fc1"): | |
| gate, value = delta.chunk(2, dim=0) | |
| return [(target.replace(".mlp.fc1", ".ff.net.0.proj") + ".weight", torch.cat([value, gate]).contiguous())] | |
| if target.endswith(".mlp.fc2"): | |
| return [(target.replace(".mlp.fc2", ".ff.net.2") + ".weight", delta)] | |
| if target.endswith(".attn.out_proj"): | |
| return [(target.replace(".attn.out_proj", ".attn.to_out.0") + ".weight", delta)] | |
| # `adaln_proj.linear` (block-level and the final `norm_out.linear`): identical row layout on both sides. | |
| return [(target + ".weight", delta)] | |
| def apply_lora(transformer) -> str | None: | |
| """Fold the configured Turbo LoRA into `transformer` in place. Returns a status line, or `None` when disabled.""" | |
| if LORA_FILE.lower() in ("", "off", "none"): | |
| return None | |
| from huggingface_hub import hf_hub_download | |
| from safetensors.torch import load_file | |
| path = hf_hub_download(LORA_REPO, LORA_FILE) | |
| lora = load_file(path) | |
| bases = sorted({key.rsplit(".lora_", 1)[0] for key in lora}) | |
| config = transformer.config | |
| inner_dim = config.num_attention_heads * config.attention_head_dim | |
| params = dict(transformer.named_parameters()) | |
| folded = 0 | |
| for name in bases: | |
| a = lora[f"{name}.lora_A.weight"].float() | |
| b = lora[f"{name}.lora_B.weight"].float() | |
| delta = b @ a # alpha == rank, so the scale is 1 | |
| for key, converted in _delta_targets(name, delta, inner_dim): | |
| param = params.get(key) | |
| if param is None: | |
| raise KeyError(f"LoRA target `{key}` (from `{name}`) not found in the transformer") | |
| param.data = (param.data.float() + converted).to(param.dtype) | |
| folded += 1 | |
| return f"LoRA `{LORA_REPO}/{LORA_FILE}` folded into {folded} weights ({len(bases)} modules)" | |