Spaces:
Paused
Paused
File size: 7,335 Bytes
1d9183b | 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 138 139 140 | """10Eros-Max (TenStrip) beta_5 support: replace the diffusers transformer's weights with the Eros checkpoint.
`TenStrip/10Eros-Max` ships full bf16 checkpoints in the *reference* (ComfyUI) module tree, not LoRAs, so the update is
a straight weight replacement rather than a fold. Each tensor is renamed and row-transformed exactly like the
`larryvrh` LoRA deltas in `h3_lora` (fused-QKV thirds onto `attn.to_q/k/v`, the `SwiGLU` gate/value swap onto
`ff.net.0.proj`, `blocks.` -> `transformer_blocks.`, ...). The mapping was checked against the base weights: block 0
q/k/v/out_proj/fc1 all sit within ~1% cosine of MiniMax-H3 after that transform, and fc1 does not match without the swap.
AdaLN: the reference file stores the projections rank-8 compressed over the 1025-step timestep grid — `adaln_t_table[i] =
adaln_basis @ (silu(temb(i / 1024)) - adaln_mean)` and `*.adaln_proj.linear` of shape `[out, 8]`. Diffusers computes the
full `linear(silu(temb))`, so each projection is rebuilt as `W = W_ref @ basis`, `b = b_ref - W @ mean`. Checked against
the base model (time embedder from MiniMax-H3, which the file does not carry): the table matches at cos 0.9998 and the
rebuilt output matches the reference form at cos 1.0000 (0.9993-0.9999 vs. base weights). Exact on the sampler's
timestep grid, a rank-8 approximation off it; the time embedder itself stays base. `rope.inv_freq` is skipped.
Tensors are streamed one at a time with `safe_open`, so peak CPU memory is one tensor rather than the 40 GB file.
`H3_EROS=off` skips it, `H3_EROS_REPO` / `H3_EROS_FILE` pick the checkpoint. A `TURBO` file has the 4-step delta baked
in, which is why `app.py` then defaults the LoRA selector to `off` — stacking larry/lightx on top double-applies it.
"""
from __future__ import annotations
import os
import torch
EROS_REPO = os.environ.get("H3_EROS_REPO", "TenStrip/10Eros-Max")
EROS_FILE = os.environ.get("H3_EROS_FILE", "10Eros_Max_h3_TURBO-hybrid_beta5.safetensors")
_SKIP_EXACT = {"rope.inv_freq", "adaln_basis", "adaln_mean", "adaln_t_table"}
_DIRECT = {
"video_patch_proj": "proj_in",
"audio_patch_proj": "audio_proj_in",
"condition_proj": "context_embedder",
"final_layer.video_out": "proj_out",
"final_layer.audio_out": "audio_proj_out",
"final_layer.norm": "norm_out.norm",
}
def enabled() -> bool:
return EROS_FILE.lower() not in ("", "off", "none") and os.environ.get("H3_EROS", "on").lower() not in ("", "off", "none")
def turbo_baked() -> bool:
return enabled() and "turbo" in EROS_FILE.lower()
def adaln_target(name: str) -> str | None:
"""Diffusers module prefix for a compressed reference AdaLN projection tensor, else `None`."""
if name.startswith("final_layer.adaln_proj.linear."):
return "norm_out.linear"
if name.startswith("blocks.") and ".adaln_proj.linear." in name:
return name.replace("blocks.", "transformer_blocks.", 1).rsplit(".", 1)[0]
return None
def rebuild_adaln(w_ref: torch.Tensor, b_ref: torch.Tensor, basis: torch.Tensor, mean: torch.Tensor):
"""Rank-8 reference projection -> full `[out, time_embed_dim]` weight and bias (float32)."""
w = w_ref.float() @ basis.float()
return w, b_ref.float() - w @ mean.float()
def map_tensor(name: str, tensor: torch.Tensor, inner_dim: int) -> list[tuple[str, torch.Tensor]]:
"""Reference-tree tensor -> [(diffusers key, tensor)]. Returns [] for tensors with no diffusers counterpart."""
if name in _SKIP_EXACT or adaln_target(name) is not None:
return []
stem, _, leaf = name.rpartition(".") # leaf is `weight` / `bias`
if stem in _DIRECT:
return [(f"{_DIRECT[stem]}.{leaf}", tensor)]
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 # token_refiner.final_norm.weight
if target.endswith(".attn.qkv_proj.weight"):
prefix = target.removesuffix("qkv_proj.weight")
return [
(f"{prefix}to_{kind}.weight", part.contiguous())
for kind, part in zip(("q", "k", "v"), tensor.split(inner_dim, dim=0))
]
if target.endswith(".mlp.fc1.weight"):
gate, value = tensor.chunk(2, dim=0)
return [(target.replace(".mlp.fc1.weight", ".ff.net.0.proj.weight"), torch.cat([value, gate]).contiguous())]
if target.endswith(".mlp.fc2.weight"):
return [(target.replace(".mlp.fc2.weight", ".ff.net.2.weight"), tensor)]
if target.endswith(".attn.out_proj.weight"):
return [(target.replace(".attn.out_proj.weight", ".attn.to_out.0.weight"), tensor)]
if target.endswith(".attn.q_norm.weight"):
return [(target.replace(".attn.q_norm.weight", ".attn.norm_q.weight"), tensor)]
if target.endswith(".attn.k_norm.weight"):
return [(target.replace(".attn.k_norm.weight", ".attn.norm_k.weight"), tensor)]
return [(target, tensor)] # norm1 / norm2 / token_refiner.final_norm
def apply_eros(transformer) -> str | None:
"""Replace the transformer's weights in place with the Eros checkpoint. Returns a status line, or `None` if off."""
if not enabled():
return None
from huggingface_hub import hf_hub_download
from safetensors import safe_open
inner_dim = transformer.config.num_attention_heads * transformer.config.attention_head_dim
params = dict(transformer.named_parameters())
path = hf_hub_download(EROS_REPO, EROS_FILE)
replaced, skipped = 0, [] # skipped: reference-only tensors (basis / mean / table / rope)
with safe_open(path, framework="pt", device="cpu") as f:
basis, mean = f.get_tensor("adaln_basis"), f.get_tensor("adaln_mean")
for name in f.keys():
if name.endswith("adaln_proj.linear.weight") and (prefix := adaln_target(name)):
w, b = rebuild_adaln(f.get_tensor(name), f.get_tensor(name.replace(".weight", ".bias")), basis, mean)
for key, tensor in ((f"{prefix}.weight", w), (f"{prefix}.bias", b)):
if key not in params or tuple(params[key].shape) != tuple(tensor.shape):
raise ValueError(f"AdaLN `{key}`: transformer vs Eros {tuple(tensor.shape)} mismatch")
params[key].data.copy_(tensor.to(params[key].dtype))
replaced += 1
continue
targets = map_tensor(name, f.get_tensor(name), inner_dim)
if not targets:
skipped.append(name)
continue
for key, tensor in targets:
param = params.get(key)
if param is None:
raise KeyError(f"Eros tensor `{name}` -> `{key}` not found in the transformer")
if tuple(param.shape) != tuple(tensor.shape):
raise ValueError(f"`{key}`: transformer {tuple(param.shape)} vs Eros {tuple(tensor.shape)}")
param.data.copy_(tensor.to(param.dtype))
replaced += 1
return (
f"Eros `{EROS_REPO}/{EROS_FILE}`: {replaced} weights replaced, {len(skipped)} reference-only tensors skipped "
f"(AdaLN rebuilt from rank-8){' · turbo baked in' if turbo_baked() else ''}"
)
|