Aratako's picture
Upload folder using huggingface_hub
d505298 verified
Raw
History Blame Contribute Delete
8.99 kB
from __future__ import annotations
from collections.abc import Mapping, Sequence
from pathlib import Path
from typing import Any
import torch
from .config import TrainConfig
from .model import TextToLatentRFDiT
LORA_TRAIN_CONFIG_FIELDS = (
"lora_enabled",
"lora_r",
"lora_alpha",
"lora_dropout",
"lora_bias",
"lora_target_modules",
"lora_modules_to_save",
)
LORA_ADAPTER_CONFIG_NAME = "adapter_config.json"
LORA_ADAPTER_STATE_NAMES = ("adapter_model.safetensors", "adapter_model.bin")
LORA_TRAINER_STATE_NAME = "trainer_state.pt"
LORA_METADATA_NAME = "irodori_lora_metadata.json"
LORA_TARGET_PRESETS: dict[str, str] = {
"text_attn_mlp": (
r"^text_encoder\.blocks\.\d+\."
r"(attention\.(wq|wk|wv|wo|gate)|mlp\.(w1|w2|w3))$"
),
"caption_attn_mlp": (
r"^caption_encoder\.blocks\.\d+\."
r"(attention\.(wq|wk|wv|wo|gate)|mlp\.(w1|w2|w3))$"
),
"speaker_attn_mlp": (
r"^(speaker_encoder\.in_proj"
r"|speaker_encoder\.blocks\.\d+\."
r"(attention\.(wq|wk|wv|wo|gate)|mlp\.(w1|w2|w3)))$"
),
"diffusion_attn": (
r"^blocks\.\d+\.attention\."
r"(wq|wk|wv|wo|wk_text|wv_text|wk_speaker|wv_speaker|wk_caption|wv_caption|gate)$"
),
"diffusion_attn_mlp": (
r"^blocks\.\d+\."
r"(attention\.(wq|wk|wv|wo|wk_text|wv_text|wk_speaker|wv_speaker|wk_caption|wv_caption|gate)"
r"|mlp\.(w1|w2|w3))$"
),
"all_attn": (
r"^(text_encoder\.blocks\.\d+\.attention\.(wq|wk|wv|wo|gate)"
r"|caption_encoder\.blocks\.\d+\.attention\.(wq|wk|wv|wo|gate)"
r"|speaker_encoder\.blocks\.\d+\.attention\.(wq|wk|wv|wo|gate)"
r"|blocks\.\d+\.attention\.(wq|wk|wv|wo|wk_text|wv_text|wk_speaker|wv_speaker|wk_caption|wv_caption|gate))$"
),
"diffusion_full": (
r"^(cond_module\.(0|2|4)"
r"|in_proj"
r"|out_proj"
r"|blocks\.\d+\."
r"(attention\.(wq|wk|wv|wo|wk_text|wv_text|wk_speaker|wv_speaker|wk_caption|wv_caption|gate)"
r"|mlp\.(w1|w2|w3)"
r"|attention_adaln\.(shift_down|scale_down|gate_down|shift_up|scale_up|gate_up)"
r"|mlp_adaln\.(shift_down|scale_down|gate_down|shift_up|scale_up|gate_up)))$"
),
"adaln": (
r"^blocks\.\d+\."
r"(attention_adaln\.(shift_down|scale_down|gate_down|shift_up|scale_up|gate_up)"
r"|mlp_adaln\.(shift_down|scale_down|gate_down|shift_up|scale_up|gate_up))$"
),
"conditioning": (
r"^(cond_module\.(0|2|4)"
r"|speaker_encoder\.in_proj"
r"|blocks\.\d+\.attention\.(wk_text|wv_text|wk_speaker|wv_speaker|wk_caption|wv_caption))$"
),
"all_attn_mlp": (
r"^(text_encoder\.blocks\.\d+\."
r"(attention\.(wq|wk|wv|wo|gate)|mlp\.(w1|w2|w3))"
r"|caption_encoder\.blocks\.\d+\."
r"(attention\.(wq|wk|wv|wo|gate)|mlp\.(w1|w2|w3))"
r"|speaker_encoder\.in_proj"
r"|speaker_encoder\.blocks\.\d+\."
r"(attention\.(wq|wk|wv|wo|gate)|mlp\.(w1|w2|w3))"
r"|blocks\.\d+\."
r"(attention\.(wq|wk|wv|wo|wk_text|wv_text|wk_speaker|wv_speaker|wk_caption|wv_caption|gate)"
r"|mlp\.(w1|w2|w3)))$"
),
"all_linear": (
r"^(speaker_encoder\.in_proj"
r"|cond_module\.(0|2|4)"
r"|in_proj"
r"|out_proj"
r"|text_encoder\.blocks\.\d+\."
r"(attention\.(wq|wk|wv|wo|gate)|mlp\.(w1|w2|w3))"
r"|caption_encoder\.blocks\.\d+\."
r"(attention\.(wq|wk|wv|wo|gate)|mlp\.(w1|w2|w3))"
r"|speaker_encoder\.blocks\.\d+\."
r"(attention\.(wq|wk|wv|wo|gate)|mlp\.(w1|w2|w3))"
r"|blocks\.\d+\."
r"(attention\.(wq|wk|wv|wk_text|wv_text|wk_speaker|wv_speaker|wk_caption|wv_caption|gate|wo)"
r"|mlp\.(w1|w2|w3)"
r"|attention_adaln\.(shift_down|scale_down|gate_down|shift_up|scale_up|gate_up)"
r"|mlp_adaln\.(shift_down|scale_down|gate_down|shift_up|scale_up|gate_up)))$"
),
}
def _require_peft():
try:
from peft import LoraConfig, PeftModel, get_peft_model
except ImportError as exc:
raise RuntimeError(
"LoRA fine-tuning requires `peft`. Install with `pip install peft` or `uv sync`."
) from exc
return LoraConfig, PeftModel, get_peft_model
def _lookup_config_value(raw: TrainConfig | Mapping[str, Any] | None, field: str) -> Any:
if raw is None:
return getattr(TrainConfig(), field)
if isinstance(raw, TrainConfig):
return getattr(raw, field)
if isinstance(raw, Mapping):
if field in raw:
return raw[field]
return getattr(TrainConfig(), field)
raise TypeError(f"Unsupported LoRA config source: {type(raw)!r}")
def train_config_uses_lora(raw: TrainConfig | Mapping[str, Any] | None) -> bool:
return bool(_lookup_config_value(raw, "lora_enabled"))
def checkpoint_state_uses_lora(model_state: Mapping[str, torch.Tensor]) -> bool:
return any(key.startswith("base_model.model.") or ".lora_" in key for key in model_state)
def resolve_lora_target_modules(spec: str | Sequence[str] | None) -> str | list[str]:
if spec is None:
spec = TrainConfig().lora_target_modules
if isinstance(spec, str):
value = spec.strip()
if not value:
raise ValueError("lora_target_modules must not be empty.")
preset = LORA_TARGET_PRESETS.get(value)
if preset is not None:
return preset
if "," in value:
modules = [chunk.strip() for chunk in value.split(",") if chunk.strip()]
if not modules:
raise ValueError(f"Invalid LoRA target_modules list: {spec!r}")
return modules
return value
modules = [str(item).strip() for item in spec if str(item).strip()]
if not modules:
raise ValueError("LoRA target_modules sequence must not be empty.")
return modules
def resolve_lora_modules_to_save(
spec: str | Sequence[str] | None,
*,
use_duration_predictor: bool,
) -> list[str] | None:
if spec is None:
return None
if isinstance(spec, str):
value = spec.strip()
if not value or value.lower() == "none":
return None
if value.lower() == "auto":
if use_duration_predictor:
return ["duration_predictor"]
return None
modules = [chunk.strip() for chunk in value.split(",") if chunk.strip()]
else:
modules = [str(item).strip() for item in spec if str(item).strip()]
if not modules:
return None
return modules
def build_lora_config_kwargs(
raw: TrainConfig | Mapping[str, Any],
*,
use_duration_predictor: bool = False,
) -> dict[str, Any]:
bias = str(_lookup_config_value(raw, "lora_bias")).strip().lower()
if bias not in {"none", "all", "lora_only"}:
raise ValueError(f"Unsupported lora_bias={bias!r}. Expected one of: none, all, lora_only.")
kwargs = {
"r": int(_lookup_config_value(raw, "lora_r")),
"lora_alpha": int(_lookup_config_value(raw, "lora_alpha")),
"lora_dropout": float(_lookup_config_value(raw, "lora_dropout")),
"bias": bias,
"target_modules": resolve_lora_target_modules(
_lookup_config_value(raw, "lora_target_modules")
),
}
modules_to_save = resolve_lora_modules_to_save(
_lookup_config_value(raw, "lora_modules_to_save"),
use_duration_predictor=use_duration_predictor,
)
if modules_to_save is not None:
kwargs["modules_to_save"] = modules_to_save
return kwargs
def apply_lora(
model: TextToLatentRFDiT,
raw: TrainConfig | Mapping[str, Any],
) -> torch.nn.Module:
if not train_config_uses_lora(raw):
return model
lora_config_cls, _, get_peft_model = _require_peft()
peft_model = get_peft_model(
model,
lora_config_cls(
task_type=None,
inference_mode=False,
**build_lora_config_kwargs(
raw,
use_duration_predictor=bool(model.cfg.use_duration_predictor),
),
),
)
return peft_model
def is_lora_adapter_dir(path: str | Path) -> bool:
candidate = Path(path)
if not candidate.is_dir():
return False
if not (candidate / LORA_ADAPTER_CONFIG_NAME).is_file():
return False
return any((candidate / name).is_file() for name in LORA_ADAPTER_STATE_NAMES)
def load_lora_adapter(
model: TextToLatentRFDiT,
adapter_path: str | Path,
*,
is_trainable: bool,
) -> torch.nn.Module:
_, peft_model_cls, _ = _require_peft()
return peft_model_cls.from_pretrained(model, str(adapter_path), is_trainable=is_trainable)
def count_parameters(model: torch.nn.Module) -> tuple[int, int]:
trainable = sum(int(param.numel()) for param in model.parameters() if param.requires_grad)
total = sum(int(param.numel()) for param in model.parameters())
return trainable, total