Spaces:
Running on Zero
Running on Zero
Update app.py
Browse files
app.py
CHANGED
|
@@ -37,7 +37,7 @@ GPU_SIZE = os.environ.get("H3_GPU_SIZE", "xlarge")
|
|
| 37 |
|
| 38 |
LORA_REPO = os.environ.get("H3_LORA_REPO", "dagloop5/LoRA")
|
| 39 |
LORA_FILES = {
|
| 40 |
-
"lora1": os.environ.get("
|
| 41 |
"loraa": os.environ.get("H3_LORA_A_FILE", "Mylo_lora_epoch31.safetensors"),
|
| 42 |
"lorab": os.environ.get("H3_LORA_B_FILE", "VBVR_H3_attn_only.safetensors"),
|
| 43 |
"lorac": os.environ.get("H3_LORA_C_FILE", "AIO_V2.safetensors"),
|
|
@@ -144,19 +144,27 @@ def _convert_diffusion_model_lora(raw: dict, base_shapes: dict) -> dict:
|
|
| 144 |
import re
|
| 145 |
|
| 146 |
out = {}
|
| 147 |
-
pattern = re.compile(r"^diffusion_model\.blocks\.(\d+)\.(attn|mlp)\.(\w+)\.(lora_[AB])\.weight$")
|
| 148 |
|
| 149 |
-
|
| 150 |
-
|
| 151 |
-
|
| 152 |
-
|
| 153 |
-
|
| 154 |
-
|
| 155 |
-
|
| 156 |
-
|
| 157 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 158 |
prefix = f"transformer_blocks.{block}."
|
| 159 |
-
|
| 160 |
if kind == "attn" and leaf == "qkv_proj":
|
| 161 |
if ab == "lora_A":
|
| 162 |
# Shared low-rank input side — identical for q, k, v.
|
|
@@ -168,28 +176,55 @@ def _convert_diffusion_model_lora(raw: dict, base_shapes: dict) -> dict:
|
|
| 168 |
k_out = base_shapes[f"{prefix}attn.to_k.weight"][0]
|
| 169 |
v_out = base_shapes[f"{prefix}attn.to_v.weight"][0]
|
| 170 |
assert tensor.shape[0] == q_out + k_out + v_out, (
|
| 171 |
-
f"{
|
| 172 |
-
f"got {tensor.shape[0]}"
|
| 173 |
)
|
| 174 |
out[f"{prefix}attn.to_q.{ab}.weight"] = tensor[:q_out].clone()
|
| 175 |
out[f"{prefix}attn.to_k.{ab}.weight"] = tensor[q_out:q_out + k_out].clone()
|
| 176 |
out[f"{prefix}attn.to_v.{ab}.weight"] = tensor[q_out + k_out:].clone()
|
| 177 |
-
|
| 178 |
elif kind == "attn" and leaf == "out_proj":
|
| 179 |
out[f"{prefix}attn.to_out.0.{ab}.weight"] = tensor
|
| 180 |
-
|
| 181 |
elif kind == "mlp" and leaf == "fc1":
|
| 182 |
if ab == "lora_B" and SWAP_FC1_HALVES:
|
| 183 |
-
# Some checkpoints store the SwiGLU gate/value halves in the opposite order diffusers expects.
|
| 184 |
half = tensor.shape[0] // 2
|
| 185 |
tensor = torch.cat([tensor[half:], tensor[:half]], dim=0)
|
| 186 |
out[f"{prefix}ff.net.0.proj.{ab}.weight"] = tensor
|
| 187 |
-
|
| 188 |
elif kind == "mlp" and leaf == "fc2":
|
| 189 |
out[f"{prefix}ff.net.2.{ab}.weight"] = tensor
|
| 190 |
-
|
|
|
|
| 191 |
else:
|
| 192 |
-
print(f"[lora-convert] no mapping for {
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 193 |
|
| 194 |
return out
|
| 195 |
|
|
|
|
| 37 |
|
| 38 |
LORA_REPO = os.environ.get("H3_LORA_REPO", "dagloop5/LoRA")
|
| 39 |
LORA_FILES = {
|
| 40 |
+
"lora1": os.environ.get("H3_LORA_1_FILE", "minimax_h3_turbo_v4_step600_ema.safetensors"),
|
| 41 |
"loraa": os.environ.get("H3_LORA_A_FILE", "Mylo_lora_epoch31.safetensors"),
|
| 42 |
"lorab": os.environ.get("H3_LORA_B_FILE", "VBVR_H3_attn_only.safetensors"),
|
| 43 |
"lorac": os.environ.get("H3_LORA_C_FILE", "AIO_V2.safetensors"),
|
|
|
|
| 144 |
import re
|
| 145 |
|
| 146 |
out = {}
|
|
|
|
| 147 |
|
| 148 |
+
# Family A: `[diffusion_model.]blocks.N.(attn|mlp|adaln_proj).LEAF.(lora_A|lora_B).weight` — covers Mylo,
|
| 149 |
+
# VBVR, AIO_V2, moawxx, the Furry Realism LoRA, and (minus its `diffusion_model.` prefix) the Turbo LoRA.
|
| 150 |
+
standard = re.compile(
|
| 151 |
+
r"^(?:diffusion_model\.)?blocks\.(\d+)\.(attn|mlp|adaln_proj)\.([\w.]+)\.(lora_[AB])\.weight$"
|
| 152 |
+
)
|
| 153 |
+
# Family B (Kohya-style): `lora_unet_blocks_N_TARGET.(lora_down|lora_up).weight` — covers SB and Fluid
|
| 154 |
+
# Enhancer. `lora_down`/`lora_up` are the same A/B convention under a different name.
|
| 155 |
+
kohya = re.compile(
|
| 156 |
+
r"^lora_unet_blocks_(\d+)_(attn_out_proj|attn_qkv_proj|mlp_fc1|mlp_fc2)\.(lora_down|lora_up)\.weight$"
|
| 157 |
+
)
|
| 158 |
+
kohya_targets = {
|
| 159 |
+
"attn_out_proj": ("attn", "out_proj"),
|
| 160 |
+
"attn_qkv_proj": ("attn", "qkv_proj"),
|
| 161 |
+
"mlp_fc1": ("mlp", "fc1"),
|
| 162 |
+
"mlp_fc2": ("mlp", "fc2"),
|
| 163 |
+
}
|
| 164 |
+
kohya_ab = {"lora_down": "lora_A", "lora_up": "lora_B"}
|
| 165 |
+
|
| 166 |
+
def emit(block: str, kind: str, leaf: str, ab: str, tensor) -> None:
|
| 167 |
prefix = f"transformer_blocks.{block}."
|
|
|
|
| 168 |
if kind == "attn" and leaf == "qkv_proj":
|
| 169 |
if ab == "lora_A":
|
| 170 |
# Shared low-rank input side — identical for q, k, v.
|
|
|
|
| 176 |
k_out = base_shapes[f"{prefix}attn.to_k.weight"][0]
|
| 177 |
v_out = base_shapes[f"{prefix}attn.to_v.weight"][0]
|
| 178 |
assert tensor.shape[0] == q_out + k_out + v_out, (
|
| 179 |
+
f"blocks.{block}.attn.qkv_proj.{ab}: expected {q_out + k_out + v_out} rows "
|
| 180 |
+
f"(q{q_out}+k{k_out}+v{v_out}), got {tensor.shape[0]}"
|
| 181 |
)
|
| 182 |
out[f"{prefix}attn.to_q.{ab}.weight"] = tensor[:q_out].clone()
|
| 183 |
out[f"{prefix}attn.to_k.{ab}.weight"] = tensor[q_out:q_out + k_out].clone()
|
| 184 |
out[f"{prefix}attn.to_v.{ab}.weight"] = tensor[q_out + k_out:].clone()
|
|
|
|
| 185 |
elif kind == "attn" and leaf == "out_proj":
|
| 186 |
out[f"{prefix}attn.to_out.0.{ab}.weight"] = tensor
|
|
|
|
| 187 |
elif kind == "mlp" and leaf == "fc1":
|
| 188 |
if ab == "lora_B" and SWAP_FC1_HALVES:
|
|
|
|
| 189 |
half = tensor.shape[0] // 2
|
| 190 |
tensor = torch.cat([tensor[half:], tensor[:half]], dim=0)
|
| 191 |
out[f"{prefix}ff.net.0.proj.{ab}.weight"] = tensor
|
|
|
|
| 192 |
elif kind == "mlp" and leaf == "fc2":
|
| 193 |
out[f"{prefix}ff.net.2.{ab}.weight"] = tensor
|
| 194 |
+
elif kind == "adaln_proj" and leaf == "linear":
|
| 195 |
+
out[f"{prefix}adaln_proj.linear.{ab}.weight"] = tensor
|
| 196 |
else:
|
| 197 |
+
print(f"[lora-convert] no mapping for blocks.{block}.{kind}.{leaf}.{ab}, skipping", flush=True)
|
| 198 |
+
|
| 199 |
+
for key, raw_tensor in raw.items():
|
| 200 |
+
# Some files (fp16-labeled ones especially) don't match the bf16 transformer's dtype; PEFT expects the
|
| 201 |
+
# adapter's dtype to match the wrapped base layer's.
|
| 202 |
+
tensor = raw_tensor.to(torch.bfloat16)
|
| 203 |
+
|
| 204 |
+
match = standard.match(key)
|
| 205 |
+
if match:
|
| 206 |
+
block, kind, leaf, ab = match.groups()
|
| 207 |
+
emit(block, kind, leaf, ab, tensor)
|
| 208 |
+
continue
|
| 209 |
+
|
| 210 |
+
match = kohya.match(key)
|
| 211 |
+
if match:
|
| 212 |
+
block, target, direction = match.groups()
|
| 213 |
+
kind, leaf = kohya_targets[target]
|
| 214 |
+
emit(block, kind, leaf, kohya_ab[direction], tensor)
|
| 215 |
+
continue
|
| 216 |
+
|
| 217 |
+
if key.endswith(".alpha"):
|
| 218 |
+
# Per-module rank/alpha scaling isn't threaded through — matched modules get PEFT's default scaling
|
| 219 |
+
# (scale 1.0), and the UI slider is what actually controls each LoRA's visible strength here. This
|
| 220 |
+
# is a known simplification: a file's built-in alpha may have scaled it up or down from its raw
|
| 221 |
+
# rank, so its slider range that "feels right" may not match what the file's author intended or
|
| 222 |
+
# tested at. It isn't a bug — the Furry Realism LoRA's `.alpha` keys were already dropped the same
|
| 223 |
+
# way and it loads and works fine — just worth knowing if a LoRA's effect seems unexpectedly
|
| 224 |
+
# weak/strong across its whole slider range rather than at a specific value.
|
| 225 |
+
continue
|
| 226 |
+
|
| 227 |
+
print(f"[lora-convert] skipping unrecognized key: {key}", flush=True)
|
| 228 |
|
| 229 |
return out
|
| 230 |
|