Files
2025-11-05 20:42:24 +08:00

375 lines
21 KiB
Python

# SPDX-FileCopyrightText: Copyright (c) 2025 NVIDIA CORPORATION & AFFILIATES. All rights reserved.
# SPDX-License-Identifier: Apache-2.0
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS,
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
# See the License for the specific language governing permissions and
# limitations under the License.
import torch
from safetensors.torch import load as safetensors_torch_load
import os
from chronoedit._ext.imaginaire.utils import log
from chronoedit._ext.imaginaire.utils.easy_io import easy_io
def load_state_dict_from_safetensors(file_path, torch_dtype=None, s3_credential_path=None):
backend_args = (
{"backend": "s3", "s3_credential_path": s3_credential_path} if file_path.startswith("s3://") else None
)
state_dict = {}
byte_stream = easy_io.load(file_path, backend_args=backend_args, file_format="byte")
state_dict = safetensors_torch_load(byte_stream)
return state_dict
def load_state_dict_from_folder(file_path, torch_dtype=None):
state_dict = {}
for file_name in os.listdir(file_path):
if "." in file_name and file_name.split(".")[-1] in ["safetensors", "bin", "ckpt", "pth", "pt"]:
state_dict.update(load_state_dict(os.path.join(file_path, file_name), torch_dtype=torch_dtype))
return state_dict
def load_state_dict(file_path, torch_dtype=None, s3_credential_path=None):
if file_path.endswith(".safetensors"):
return load_state_dict_from_safetensors(
file_path, torch_dtype=torch_dtype, s3_credential_path=s3_credential_path
)
else:
return load_state_dict_from_bin(file_path, torch_dtype=torch_dtype, s3_credential_path=s3_credential_path)
def load_state_dict_from_bin(file_path, torch_dtype=None, s3_credential_path=None):
backend_args = (
{"backend": "s3", "s3_credential_path": s3_credential_path} if file_path.startswith("s3://") else None
)
state_dict = easy_io.load(
file_path, backend_args=backend_args, file_format="pt", map_location="cpu", weights_only=False
)
if torch_dtype is not None:
for i in state_dict:
if isinstance(state_dict[i], torch.Tensor):
state_dict[i] = state_dict[i].to(torch_dtype)
return state_dict
# based on https://github.com/huggingface/diffusers/blob/b793debd9d09225582943a1e9cb4ccdab30f1b37/src/diffusers/loaders/lora_conversion_utils.py#L1817
# since our model is the same as non-diffusers Wan, we only need to change the lora keys:
# 1. add adapter_name to the key, 2. change lora keys
def _convert_non_diffusers_wan_lora_to_diffusers(state_dict, adapter_name="default"):
converted_state_dict = {}
if any("diffusion_model." in k for k in state_dict.keys()):
original_state_dict = {k[len("diffusion_model.") :]: v for k, v in state_dict.items()}
else:
original_state_dict = state_dict
block_numbers = {int(k.split(".")[1]) for k in original_state_dict if k.startswith("blocks.")}
min_block = min(block_numbers)
max_block = max(block_numbers)
is_i2v_lora = any("k_img" in k for k in original_state_dict) and any("v_img" in k for k in original_state_dict)
lora_down_key = "lora_A" if any("lora_A" in k for k in original_state_dict) else "lora_down"
lora_up_key = "lora_B" if any("lora_B" in k for k in original_state_dict) else "lora_up"
def get_alpha_scales(down_weight, key):
rank = down_weight.shape[0]
alpha = original_state_dict.pop(key + ".alpha").item()
scale = alpha / rank # LoRA is scaled by 'alpha / rank' in forward pass, so we need to scale it back here
scale_down = scale
scale_up = 1.0
while scale_down * 2 < scale_up:
scale_down *= 2
scale_up /= 2
return scale_down, scale_up
diff_keys = [k for k in original_state_dict if k.endswith((".diff_b", ".diff"))]
if diff_keys:
for diff_k in diff_keys:
param = original_state_dict[diff_k]
# The magnitudes of the .diff-ending weights are very low (most are below 1e-4, some are upto 1e-3,
# and 2 of them are about 1.6e-2 [the case with AccVideo lora]). The low magnitudes mostly correspond
# to norm layers. Ignoring them is the best option at the moment until a better solution is found. It
# is okay to ignore because they do not affect the model output in a significant manner.
threshold = 1.6e-2
absdiff = param.abs().max() - param.abs().min()
all_zero = torch.all(param == 0).item()
all_absdiff_lower_than_threshold = absdiff < threshold
if all_zero or all_absdiff_lower_than_threshold:
log.debug(
f"Removed {diff_k} key from the state dict as it's all zeros, or values lower than hardcoded threshold."
)
original_state_dict.pop(diff_k)
# For the `diff_b` keys, we treat them as lora_bias.
# https://huggingface.co/docs/peft/main/en/package_reference/lora#peft.LoraConfig.lora_bias
for i in range(min_block, max_block + 1):
# Self-attention
for o, c in zip(["q", "k", "v", "o"], ["q", "k", "v", "o"]):
original_key = f"blocks.{i}.self_attn.{o}.{lora_down_key}.weight"
converted_down_key = f"blocks.{i}.self_attn.{c}.lora_A.{adapter_name}.weight"
if original_key in original_state_dict:
converted_state_dict[converted_down_key] = original_state_dict.pop(original_key)
original_key = f"blocks.{i}.self_attn.{o}.{lora_up_key}.weight"
converted_up_key = f"blocks.{i}.self_attn.{c}.lora_B.{adapter_name}.weight"
if original_key in original_state_dict:
converted_state_dict[converted_up_key] = original_state_dict.pop(original_key)
alpha_key = f"blocks.{i}.self_attn.{o}.alpha"
if alpha_key in original_state_dict:
down_weight = converted_state_dict[converted_down_key]
up_weight = converted_state_dict[converted_up_key]
scale_down, scale_up = get_alpha_scales(down_weight, f"blocks.{i}.self_attn.{o}")
converted_state_dict[converted_down_key] = down_weight * scale_down
converted_state_dict[converted_up_key] = up_weight * scale_up
original_key = f"blocks.{i}.self_attn.{o}.diff_b"
converted_key = f"blocks.{i}.self_attn.{c}.lora_B.{adapter_name}.bias"
if original_key in original_state_dict:
converted_state_dict[converted_key] = original_state_dict.pop(original_key)
# Cross-attention
for o, c in zip(["q", "k", "v", "o"], ["q", "k", "v", "o"]):
original_key = f"blocks.{i}.cross_attn.{o}.{lora_down_key}.weight"
converted_down_key = f"blocks.{i}.cross_attn.{c}.lora_A.{adapter_name}.weight"
if original_key in original_state_dict:
converted_state_dict[converted_down_key] = original_state_dict.pop(original_key)
original_key = f"blocks.{i}.cross_attn.{o}.{lora_up_key}.weight"
converted_up_key = f"blocks.{i}.cross_attn.{c}.lora_B.{adapter_name}.weight"
if original_key in original_state_dict:
converted_state_dict[converted_up_key] = original_state_dict.pop(original_key)
alpha_key = f"blocks.{i}.cross_attn.{o}.alpha"
if alpha_key in original_state_dict:
down_weight = converted_state_dict[converted_down_key]
up_weight = converted_state_dict[converted_up_key]
scale_down, scale_up = get_alpha_scales(down_weight, f"blocks.{i}.cross_attn.{o}")
converted_state_dict[converted_down_key] = down_weight * scale_down
converted_state_dict[converted_up_key] = up_weight * scale_up
original_key = f"blocks.{i}.cross_attn.{o}.diff_b"
converted_key = f"blocks.{i}.cross_attn.{c}.lora_B.{adapter_name}.bias"
if original_key in original_state_dict:
converted_state_dict[converted_key] = original_state_dict.pop(original_key)
if is_i2v_lora:
for o, c in zip(["k_img", "v_img"], ["k_img", "v_img"]):
original_key = f"blocks.{i}.cross_attn.{o}.{lora_down_key}.weight"
converted_down_key = f"blocks.{i}.cross_attn.{c}.lora_A.{adapter_name}.weight"
if original_key in original_state_dict:
converted_state_dict[converted_down_key] = original_state_dict.pop(original_key)
original_key = f"blocks.{i}.cross_attn.{o}.{lora_up_key}.weight"
converted_up_key = f"blocks.{i}.cross_attn.{c}.lora_B.{adapter_name}.weight"
if original_key in original_state_dict:
converted_state_dict[converted_up_key] = original_state_dict.pop(original_key)
alpha_key = f"blocks.{i}.cross_attn.{o}.alpha"
if alpha_key in original_state_dict:
down_weight = converted_state_dict[converted_down_key]
up_weight = converted_state_dict[converted_up_key]
scale_down, scale_up = get_alpha_scales(down_weight, f"blocks.{i}.cross_attn.{o}")
converted_state_dict[converted_down_key] = down_weight * scale_down
converted_state_dict[converted_up_key] = up_weight * scale_up
original_key = f"blocks.{i}.cross_attn.{o}.diff_b"
converted_key = f"blocks.{i}.cross_attn.{c}.lora_B.{adapter_name}.bias"
if original_key in original_state_dict:
converted_state_dict[converted_key] = original_state_dict.pop(original_key)
# FFN
for o, c in zip(["ffn.0", "ffn.2"], ["ffn.0", "ffn.2"]):
original_key = f"blocks.{i}.{o}.{lora_down_key}.weight"
converted_down_key = f"blocks.{i}.{c}.lora_A.{adapter_name}.weight"
if original_key in original_state_dict:
converted_state_dict[converted_down_key] = original_state_dict.pop(original_key)
original_key = f"blocks.{i}.{o}.{lora_up_key}.weight"
converted_up_key = f"blocks.{i}.{c}.lora_B.{adapter_name}.weight"
if original_key in original_state_dict:
converted_state_dict[converted_up_key] = original_state_dict.pop(original_key)
alpha_key = f"blocks.{i}.{o}.alpha"
if alpha_key in original_state_dict:
down_weight = converted_state_dict[converted_down_key]
up_weight = converted_state_dict[converted_up_key]
scale_down, scale_up = get_alpha_scales(down_weight, f"blocks.{i}.{o}")
converted_state_dict[converted_down_key] = down_weight * scale_down
converted_state_dict[converted_up_key] = up_weight * scale_up
original_key = f"blocks.{i}.{o}.diff_b"
converted_key = f"blocks.{i}.{c}.lora_B.{adapter_name}.bias"
if original_key in original_state_dict:
converted_state_dict[converted_key] = original_state_dict.pop(original_key)
# Remaining.
if original_state_dict:
if any("time_projection" in k for k in original_state_dict):
original_key = f"time_projection.1.{lora_down_key}.weight"
converted_down_key = f"time_projection.1.lora_A.{adapter_name}.weight"
if original_key in original_state_dict:
converted_state_dict[converted_down_key] = original_state_dict.pop(original_key)
original_key = f"time_projection.1.{lora_up_key}.weight"
converted_up_key = f"time_projection.1.lora_B.{adapter_name}.weight"
if original_key in original_state_dict:
converted_state_dict[converted_up_key] = original_state_dict.pop(original_key)
alpha_key = f"time_projection.1.alpha"
if alpha_key in original_state_dict:
down_weight = converted_state_dict[converted_down_key]
up_weight = converted_state_dict[converted_up_key]
scale_down, scale_up = get_alpha_scales(down_weight, f"time_projection.1")
converted_state_dict[converted_down_key] = down_weight * scale_down
converted_state_dict[converted_up_key] = up_weight * scale_up
if "time_projection.1.diff_b" in original_state_dict:
converted_state_dict[f"time_projection.1.lora_B.{adapter_name}.bias"] = original_state_dict.pop(
"time_projection.1.diff_b"
)
if any("head.head" in k for k in state_dict):
converted_state_dict[f"head.head.lora_A.{adapter_name}.weight"] = original_state_dict.pop(
f"head.head.{lora_down_key}.weight"
)
converted_state_dict[f"head.head.lora_B.{adapter_name}.weight"] = original_state_dict.pop(f"head.head.{lora_up_key}.weight")
if "head.head.diff_b" in original_state_dict:
converted_state_dict[f"head.head.lora_B.{adapter_name}.bias"] = original_state_dict.pop("head.head.diff_b")
alpha_key = f"head.head.alpha"
if alpha_key in original_state_dict:
down_weight = converted_state_dict[f"head.head.lora_A.{adapter_name}.weight"]
up_weight = converted_state_dict[f"head.head.lora_B.{adapter_name}.weight"]
scale_down, scale_up = get_alpha_scales(down_weight, f"head.head")
converted_state_dict[f"head.head.lora_A.{adapter_name}.weight"] = down_weight * scale_down
converted_state_dict[f"head.head.lora_B.{adapter_name}.weight"] = up_weight * scale_up
for text_time in ["text_embedding", "time_embedding"]:
if any(text_time in k for k in original_state_dict):
for b_n in [0, 2]:
diffusers_b_n = b_n
diffusers_name = text_time
if any(f"{text_time}.{b_n}" in k for k in original_state_dict):
converted_state_dict[f"{diffusers_name}.{diffusers_b_n}.lora_A.{adapter_name}.weight"] = (
original_state_dict.pop(f"{text_time}.{b_n}.{lora_down_key}.weight")
)
converted_state_dict[f"{diffusers_name}.{diffusers_b_n}.lora_B.{adapter_name}.weight"] = (
original_state_dict.pop(f"{text_time}.{b_n}.{lora_up_key}.weight")
)
alpha_key = f"{text_time}.{b_n}.alpha"
if alpha_key in original_state_dict:
down_weight = converted_state_dict[f"{diffusers_name}.{diffusers_b_n}.lora_A.{adapter_name}.weight"]
up_weight = converted_state_dict[f"{diffusers_name}.{diffusers_b_n}.lora_B.{adapter_name}.weight"]
scale_down, scale_up = get_alpha_scales(down_weight, f"{text_time}.{b_n}")
converted_state_dict[f"{diffusers_name}.{diffusers_b_n}.lora_A.{adapter_name}.weight"] = down_weight * scale_down
converted_state_dict[f"{diffusers_name}.{diffusers_b_n}.lora_B.{adapter_name}.weight"] = up_weight * scale_up
if f"{text_time}.{b_n}.diff_b" in original_state_dict:
converted_state_dict[f"{diffusers_name}.{diffusers_b_n}.lora_B.{adapter_name}.bias"] = (
original_state_dict.pop(f"{text_time}.{b_n}.diff_b")
)
for img_ours, img_theirs in [
("img_emb.proj.1", "img_emb.proj.1"),
("img_emb.proj.3", "img_emb.proj.3"),
]:
original_key = f"{img_theirs}.{lora_down_key}.weight"
converted_key = f"{img_ours}.lora_A.{adapter_name}.weight"
if original_key in original_state_dict:
converted_state_dict[converted_key] = original_state_dict.pop(original_key)
original_key = f"{img_theirs}.{lora_up_key}.weight"
converted_key = f"{img_ours}.lora_B.{adapter_name}.weight"
if original_key in original_state_dict:
converted_state_dict[converted_key] = original_state_dict.pop(original_key)
alpha_key = f"{img_ours}.alpha"
if alpha_key in original_state_dict:
down_weight = converted_state_dict[f"{img_ours}.lora_A.{adapter_name}.weight"]
up_weight = converted_state_dict[f"{img_ours}.lora_B.{adapter_name}.weight"]
scale_down, scale_up = get_alpha_scales(down_weight, f"{img_ours}")
converted_state_dict[f"{img_ours}.lora_A.{adapter_name}.weight"] = down_weight * scale_down
converted_state_dict[f"{img_ours}.lora_B.{adapter_name}.weight"] = up_weight * scale_up
if len(original_state_dict) > 0:
diff = all(".diff" in k for k in original_state_dict)
if diff:
diff_keys = {k for k in original_state_dict if k.endswith(".diff")}
if not all("lora" not in k for k in diff_keys):
raise ValueError
log.info(
"The remaining `state_dict` contains `diff` keys which we do not handle yet. If you see performance issues, please file an issue: "
"https://github.com/huggingface/diffusers//issues/new"
)
else:
raise ValueError(f"`state_dict` should be empty at this point but has {original_state_dict.keys()=}")
return converted_state_dict
def _convert_musubi_wan_lora_to_non_diffusers_wan(state_dict):
# https://github.com/kohya-ss/musubi-tuner
converted_state_dict = {}
original_state_dict = {k[len("lora_unet_") :]: v for k, v in state_dict.items()}
num_blocks = len({k.split("blocks_")[1].split("_")[0] for k in original_state_dict})
is_i2v_lora = any("k_img" in k for k in original_state_dict) and any("v_img" in k for k in original_state_dict)
def get_alpha_scales(down_weight, key):
rank = down_weight.shape[0]
alpha = original_state_dict.pop(key + ".alpha").item()
scale = alpha / rank # LoRA is scaled by 'alpha / rank' in forward pass, so we need to scale it back here
scale_down = scale
scale_up = 1.0
while scale_down * 2 < scale_up:
scale_down *= 2
scale_up /= 2
return scale_down, scale_up
for i in range(num_blocks):
# Self-attention
for o, c in zip(["q", "k", "v", "o"], ["q", "k", "v", "o"]):
down_weight = original_state_dict.pop(f"blocks_{i}_self_attn_{o}.lora_down.weight")
up_weight = original_state_dict.pop(f"blocks_{i}_self_attn_{o}.lora_up.weight")
scale_down, scale_up = get_alpha_scales(down_weight, f"blocks_{i}_self_attn_{o}")
converted_state_dict[f"blocks.{i}.self_attn.{c}.lora_down.weight"] = down_weight * scale_down
converted_state_dict[f"blocks.{i}.self_attn.{c}.lora_up.weight"] = up_weight * scale_up
# Cross-attention
for o, c in zip(["q", "k", "v", "o"], ["q", "k", "v", "o"]):
down_weight = original_state_dict.pop(f"blocks_{i}_cross_attn_{o}.lora_down.weight")
up_weight = original_state_dict.pop(f"blocks_{i}_cross_attn_{o}.lora_up.weight")
scale_down, scale_up = get_alpha_scales(down_weight, f"blocks_{i}_cross_attn_{o}")
converted_state_dict[f"blocks.{i}.cross_attn.{c}.lora_down.weight"] = down_weight * scale_down
converted_state_dict[f"blocks.{i}.cross_attn.{c}.lora_up.weight"] = up_weight * scale_up
if is_i2v_lora:
for o, c in zip(["k_img", "v_img"], ["k_img", "v_img"]):
down_weight = original_state_dict.pop(f"blocks_{i}_cross_attn_{o}.lora_down.weight")
up_weight = original_state_dict.pop(f"blocks_{i}_cross_attn_{o}.lora_up.weight")
scale_down, scale_up = get_alpha_scales(down_weight, f"blocks_{i}_cross_attn_{o}")
converted_state_dict[f"blocks.{i}.cross_attn.{c}.lora_down.weight"] = down_weight * scale_down
converted_state_dict[f"blocks.{i}.cross_attn.{c}.lora_up.weight"] = up_weight * scale_up
# FFN
for o, c in zip(["ffn_0", "ffn_2"], ["ffn.0", "ffn.2"]):
down_weight = original_state_dict.pop(f"blocks_{i}_{o}.lora_down.weight")
up_weight = original_state_dict.pop(f"blocks_{i}_{o}.lora_up.weight")
scale_down, scale_up = get_alpha_scales(down_weight, f"blocks_{i}_{o}")
converted_state_dict[f"blocks.{i}.{c}.lora_down.weight"] = down_weight * scale_down
converted_state_dict[f"blocks.{i}.{c}.lora_up.weight"] = up_weight * scale_up
if len(original_state_dict) > 0:
raise ValueError(f"`state_dict` should be empty at this point but has {original_state_dict.keys()=}")
return converted_state_dict