- 模型 17/17 文件经镜像全量缓存(gated+token,直连本机不通;sfx.md/setup.md/asset-pipeline.md 镜像结论同步更新) - 修复迁移遗留:stable_audio_3/models/ 源码子包被 .gitignore models/ 规则误伤丢失,从上游恢复并加白名单 - radio:arg_muffled/brake_screech 等叙事件落地(接线原有 L111/L154 零改动)+ phone_ring SYN→SA3 升级;dungeon 13 源 A/B 对齐(RMS 偏差 ≤0.3dB)全量同名替换,SYN 原版备份 tools/asset_fix_backup/sa3_ab_20260902/ - 关键玩法语义达标:echo 距离档 0.83<1.43<2.18<2.98s 严格递增;one-shot 真峰值全 ≤-3dBFS(解码回读法) - check_all_assets 8 游戏全绿(radio 254/254、dungeon 303/303);playtest radio 221/dungeon 1610 全绿
471 lines
23 KiB
Python
471 lines
23 KiB
Python
"""
|
|
References:
|
|
1) the official LoRA implementation released by Microsoft:
|
|
https://github.com/microsoft/LoRA/blob/main/loralib/layers.py
|
|
"""
|
|
|
|
import math
|
|
import re
|
|
from functools import partial
|
|
from itertools import product
|
|
|
|
import torch
|
|
import torch.nn.utils.parametrize as parametrize
|
|
from torch import nn
|
|
|
|
from ...verbose import vprint
|
|
|
|
|
|
def _canonicalize_svd_signs(U, Vh):
|
|
"""Enforce deterministic sign convention: largest-magnitude element of each U column is positive."""
|
|
max_abs_idx = U.abs().argmax(dim=0)
|
|
signs = U[max_abs_idx, torch.arange(U.shape[1])].sign()
|
|
signs[signs == 0] = 1
|
|
return U * signs.unsqueeze(0), Vh * signs.unsqueeze(1)
|
|
|
|
|
|
class LoRAParametrization(nn.Module):
|
|
def __init__(self, fan_in, fan_out, fan_in_fan_out=False, rank=4, lora_dropout_p=0.0, lora_alpha=1,
|
|
adapter_type="lora", W0=None, lora_index=0, svd_bases=None
|
|
):
|
|
super().__init__()
|
|
dtype = torch.get_default_dtype()
|
|
device = W0.device if W0 is not None else None
|
|
# if weight is stored as (fan_out, fan_in), the memory layout of A & B follows (W + BA)x
|
|
# otherwise, it's x(W + AB). This allows us to tie the weights between linear layers and embeddings
|
|
self.swap = (lambda x: (x[1], x[0])) if fan_in_fan_out else (lambda x: x)
|
|
self.lora_alpha, self.rank = lora_alpha, rank
|
|
self.scaling = lora_alpha / rank
|
|
self.lora_dropout = nn.Dropout(p=lora_dropout_p) if lora_dropout_p > 0 else lambda x: x
|
|
self.dropout_fn = self._dropout if lora_dropout_p > 0 else lambda x: x
|
|
self.register_buffer("lora_dropout_mask", torch.ones(self.swap((1, fan_in)), dtype=dtype, device=device))
|
|
self.register_buffer("lora_strength", torch.tensor(1.0, dtype=dtype, device=device), persistent=False)
|
|
self.adapter_type = adapter_type
|
|
self.lora_index = lora_index
|
|
|
|
if self.adapter_type == "lora":
|
|
self.lora_A = nn.Parameter(torch.zeros(self.swap((rank, fan_in)), device=device))
|
|
self.lora_B = nn.Parameter(torch.zeros(self.swap((fan_out, rank)), device=device))
|
|
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
|
|
self.forward_fn = self.lora_forward
|
|
self._adapter_forward_fn = self.forward_fn
|
|
|
|
elif self.adapter_type in ("dora-rows", "dora"):
|
|
# DoRA-rows: magnitude per row (per-output-neuron, dim=1) - paper-correct variant
|
|
# "dora" maps here as the default for backward compat
|
|
self.lora_A = nn.Parameter(torch.zeros(self.swap((rank, fan_in)), device=device))
|
|
self.lora_B = nn.Parameter(torch.zeros(self.swap((fan_out, rank)), device=device))
|
|
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
|
|
self._norm_dim = 1
|
|
if W0 is not None:
|
|
W0_2d = W0.view(W0.shape[0], -1)
|
|
self.magnitude = nn.Parameter(W0_2d.norm(dim=self._norm_dim).to(dtype))
|
|
else:
|
|
self.magnitude = nn.Parameter(torch.ones(fan_out, dtype=dtype, device=device))
|
|
self.forward_fn = self.dora_forward
|
|
self._adapter_forward_fn = self.forward_fn
|
|
|
|
elif self.adapter_type == "dora-cols":
|
|
# DoRA-cols: magnitude per column (per-input-feature, dim=0) - legacy variant
|
|
self.lora_A = nn.Parameter(torch.zeros(self.swap((rank, fan_in)), device=device))
|
|
self.lora_B = nn.Parameter(torch.zeros(self.swap((fan_out, rank)), device=device))
|
|
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
|
|
self._norm_dim = 0
|
|
if W0 is not None:
|
|
W0_2d = W0.view(W0.shape[0], -1)
|
|
self.magnitude = nn.Parameter(W0_2d.norm(dim=self._norm_dim).to(dtype))
|
|
else:
|
|
self.magnitude = nn.Parameter(torch.ones(fan_in, dtype=dtype, device=device))
|
|
self.forward_fn = self.dora_forward
|
|
self._adapter_forward_fn = self.forward_fn
|
|
|
|
elif self.adapter_type == "bora":
|
|
# BoRA: bi-dimensional DoRA - magnitude on both row and column axes
|
|
self.lora_A = nn.Parameter(torch.zeros(self.swap((rank, fan_in)), device=device))
|
|
self.lora_B = nn.Parameter(torch.zeros(self.swap((fan_out, rank)), device=device))
|
|
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
|
|
if W0 is not None:
|
|
W0_2d = W0.view(W0.shape[0], -1)
|
|
self.magnitude_r = nn.Parameter(W0_2d.norm(dim=1).to(dtype))
|
|
self.magnitude_c = nn.Parameter(W0_2d.norm(dim=0).to(dtype))
|
|
else:
|
|
self.magnitude_r = nn.Parameter(torch.ones(fan_out, dtype=dtype, device=device))
|
|
self.magnitude_c = nn.Parameter(torch.ones(fan_in, dtype=dtype, device=device))
|
|
self.forward_fn = self.bora_forward
|
|
self._adapter_forward_fn = self.forward_fn
|
|
|
|
elif self.adapter_type == "lora-xs":
|
|
# frozen bases U:(fan_out,r), V:(fan_in,r); tiny trainable core M:(r,r)
|
|
self.register_buffer("U", torch.empty(self.swap((fan_out, self.rank)), device=device), persistent=True)
|
|
self.register_buffer("V", torch.empty(self.swap((fan_in, self.rank)), device=device), persistent=True)
|
|
self.M_xs = nn.Parameter(torch.zeros(self.rank, self.rank, dtype=dtype, device=device))
|
|
if svd_bases is not None:
|
|
self.U.copy_(svd_bases["U"][:, :self.rank].to(dtype))
|
|
self.V.copy_(svd_bases["V"][:, :self.rank].to(dtype))
|
|
elif W0 is not None:
|
|
W0_2d = W0.view(W0.shape[0], -1).cpu().float()
|
|
U_full, S, Vh_full = torch.linalg.svd(W0_2d, full_matrices=False)
|
|
U_full, Vh_full = _canonicalize_svd_signs(U_full, Vh_full)
|
|
self.U.copy_(U_full[:, :self.rank].to(dtype))
|
|
self.V.copy_(Vh_full[:self.rank, :].T.to(dtype))
|
|
self.forward_fn = self.lora_xs_forward
|
|
self._adapter_forward_fn = self.forward_fn
|
|
|
|
elif self.adapter_type == "dora-rows-xs":
|
|
# DoRA-rows + LoRA-XS: frozen SVD bases + trainable M_xs core + row magnitude
|
|
self.register_buffer("U", torch.empty(self.swap((fan_out, self.rank)), device=device), persistent=True)
|
|
self.register_buffer("V", torch.empty(self.swap((fan_in, self.rank)), device=device), persistent=True)
|
|
self.M_xs = nn.Parameter(torch.zeros(self.rank, self.rank, dtype=dtype, device=device))
|
|
self._norm_dim = 1
|
|
if svd_bases is not None:
|
|
self.U.copy_(svd_bases["U"][:, :self.rank].to(dtype))
|
|
self.V.copy_(svd_bases["V"][:, :self.rank].to(dtype))
|
|
if W0 is not None:
|
|
W0_2d = W0.view(W0.shape[0], -1)
|
|
if svd_bases is None:
|
|
U_full, S, Vh_full = torch.linalg.svd(W0_2d.cpu().float(), full_matrices=False)
|
|
U_full, Vh_full = _canonicalize_svd_signs(U_full, Vh_full)
|
|
self.U.copy_(U_full[:, :self.rank].to(dtype))
|
|
self.V.copy_(Vh_full[:self.rank, :].T.to(dtype))
|
|
self.magnitude = nn.Parameter(W0_2d.norm(dim=self._norm_dim).to(dtype))
|
|
else:
|
|
self.magnitude = nn.Parameter(torch.ones(fan_out, dtype=dtype, device=device))
|
|
self.forward_fn = self.dora_xs_forward
|
|
self._adapter_forward_fn = self.forward_fn
|
|
|
|
elif self.adapter_type == "dora-cols-xs":
|
|
# DoRA-cols + LoRA-XS: frozen SVD bases + trainable M_xs core + column magnitude
|
|
self.register_buffer("U", torch.empty(self.swap((fan_out, self.rank)), device=device), persistent=True)
|
|
self.register_buffer("V", torch.empty(self.swap((fan_in, self.rank)), device=device), persistent=True)
|
|
self.M_xs = nn.Parameter(torch.zeros(self.rank, self.rank, dtype=dtype, device=device))
|
|
self._norm_dim = 0
|
|
if svd_bases is not None:
|
|
self.U.copy_(svd_bases["U"][:, :self.rank].to(dtype))
|
|
self.V.copy_(svd_bases["V"][:, :self.rank].to(dtype))
|
|
if W0 is not None:
|
|
W0_2d = W0.view(W0.shape[0], -1)
|
|
if svd_bases is None:
|
|
U_full, S, Vh_full = torch.linalg.svd(W0_2d.cpu().float(), full_matrices=False)
|
|
U_full, Vh_full = _canonicalize_svd_signs(U_full, Vh_full)
|
|
self.U.copy_(U_full[:, :self.rank].to(dtype))
|
|
self.V.copy_(Vh_full[:self.rank, :].T.to(dtype))
|
|
self.magnitude = nn.Parameter(W0_2d.norm(dim=self._norm_dim).to(dtype))
|
|
else:
|
|
self.magnitude = nn.Parameter(torch.ones(fan_in, dtype=dtype, device=device))
|
|
self.forward_fn = self.dora_xs_forward
|
|
self._adapter_forward_fn = self.forward_fn
|
|
|
|
elif self.adapter_type == "bora-xs":
|
|
# BoRA + LoRA-XS: frozen SVD bases + trainable M_xs core + row & column magnitudes
|
|
self.register_buffer("U", torch.empty(self.swap((fan_out, self.rank)), device=device), persistent=True)
|
|
self.register_buffer("V", torch.empty(self.swap((fan_in, self.rank)), device=device), persistent=True)
|
|
self.M_xs = nn.Parameter(torch.zeros(self.rank, self.rank, dtype=dtype, device=device))
|
|
if svd_bases is not None:
|
|
self.U.copy_(svd_bases["U"][:, :self.rank].to(dtype))
|
|
self.V.copy_(svd_bases["V"][:, :self.rank].to(dtype))
|
|
if W0 is not None:
|
|
W0_2d = W0.view(W0.shape[0], -1)
|
|
if svd_bases is None:
|
|
U_full, S, Vh_full = torch.linalg.svd(W0_2d.cpu().float(), full_matrices=False)
|
|
U_full, Vh_full = _canonicalize_svd_signs(U_full, Vh_full)
|
|
self.U.copy_(U_full[:, :self.rank].to(dtype))
|
|
self.V.copy_(Vh_full[:self.rank, :].T.to(dtype))
|
|
self.magnitude_r = nn.Parameter(W0_2d.norm(dim=1).to(dtype))
|
|
self.magnitude_c = nn.Parameter(W0_2d.norm(dim=0).to(dtype))
|
|
else:
|
|
self.magnitude_r = nn.Parameter(torch.ones(fan_out, dtype=dtype, device=device))
|
|
self.magnitude_c = nn.Parameter(torch.ones(fan_in, dtype=dtype, device=device))
|
|
self.forward_fn = self.bora_xs_forward
|
|
self._adapter_forward_fn = self.forward_fn
|
|
|
|
|
|
def lora_forward(self, W):
|
|
delta = torch.matmul(*self.swap((self.lora_B, self.dropout_fn(self.lora_A)))).view(W.shape)
|
|
delta = self.scaling * self.lora_strength * delta
|
|
return (W + delta.to(W.dtype))
|
|
|
|
def dora_forward(self, W):
|
|
if self.lora_strength == 0:
|
|
return W
|
|
# work in 2D so norm/magnitude ops don't broadcast badly for Conv1d/Conv2d
|
|
# Upcast W to LoRA param dtype for stable norm computation, cast result back.
|
|
orig_shape = W.shape
|
|
W_2d = W.view(W.shape[0], -1).to(self.lora_A.dtype)
|
|
# low-rank update on the *direction*
|
|
delta = torch.matmul(*self.swap((self.lora_B, self.dropout_fn(self.lora_A))))
|
|
V = W_2d + self.scaling * self.lora_strength * delta
|
|
# normalize along _norm_dim, then scale by per-element magnitude
|
|
V_hat = V / (V.norm(dim=self._norm_dim, keepdim=True) + 1e-12)
|
|
return (V_hat * self.magnitude.unsqueeze(self._norm_dim)).view(orig_shape).to(W.dtype)
|
|
|
|
def bora_forward(self, W):
|
|
if self.lora_strength == 0:
|
|
return W
|
|
orig_shape = W.shape
|
|
W_2d = W.view(W.shape[0], -1).to(self.lora_A.dtype)
|
|
delta = torch.matmul(*self.swap((self.lora_B, self.dropout_fn(self.lora_A))))
|
|
V = W_2d + self.scaling * self.lora_strength * delta
|
|
# Row-normalize, then scale by row magnitudes
|
|
V_r = V / (V.norm(dim=1, keepdim=True) + 1e-12)
|
|
intermediate = self.magnitude_r.unsqueeze(1) * V_r
|
|
# Column-normalize, then scale by column magnitudes
|
|
H_c = intermediate / (intermediate.norm(dim=0, keepdim=True) + 1e-12)
|
|
return (H_c * self.magnitude_c.unsqueeze(0)).view(orig_shape).to(W.dtype)
|
|
|
|
def lora_xs_forward(self, W):
|
|
delta = self.U @ self.M_xs.to(self.U.dtype) @ self.V.T
|
|
delta = delta.view(W.shape)
|
|
return (W + (self.scaling * self.lora_strength * delta).to(W.dtype))
|
|
|
|
def dora_xs_forward(self, W):
|
|
if self.lora_strength == 0:
|
|
return W
|
|
orig_shape = W.shape
|
|
W_2d = W.view(W.shape[0], -1).to(self.U.dtype)
|
|
delta = self.U @ self.M_xs.to(self.U.dtype) @ self.V.T
|
|
V = W_2d + self.scaling * self.lora_strength * delta
|
|
V_hat = V / (V.norm(dim=self._norm_dim, keepdim=True) + 1e-12)
|
|
return (V_hat * self.magnitude.unsqueeze(self._norm_dim)).view(orig_shape).to(W.dtype)
|
|
|
|
def bora_xs_forward(self, W):
|
|
if self.lora_strength == 0:
|
|
return W
|
|
orig_shape = W.shape
|
|
W_2d = W.view(W.shape[0], -1).to(self.U.dtype)
|
|
delta = self.U @ self.M_xs.to(self.U.dtype) @ self.V.T
|
|
V = W_2d + self.scaling * self.lora_strength * delta
|
|
V_r = V / (V.norm(dim=1, keepdim=True) + 1e-12)
|
|
intermediate = self.magnitude_r.unsqueeze(1) * V_r
|
|
H_c = intermediate / (intermediate.norm(dim=0, keepdim=True) + 1e-12)
|
|
return (H_c * self.magnitude_c.unsqueeze(0)).view(orig_shape).to(W.dtype)
|
|
|
|
def forward(self, X):
|
|
return self.forward_fn(X)
|
|
|
|
def _dropout(self, A):
|
|
# to mimic the original implementation: A @ dropout(x), we do (A * dropout(ones)) @ x
|
|
return A * self.lora_dropout(self.lora_dropout_mask)
|
|
|
|
def disable_lora(self):
|
|
self.forward_fn = lambda x: x
|
|
|
|
def enable_lora(self):
|
|
self.forward_fn = self._adapter_forward_fn
|
|
|
|
@classmethod
|
|
def _get_original_weight(cls, layer, attr_name="weight"):
|
|
"""Get the true original weight, even when the layer is already parametrized."""
|
|
if hasattr(layer, 'parametrizations') and hasattr(layer.parametrizations, attr_name):
|
|
return getattr(layer.parametrizations, attr_name).original.detach()
|
|
return getattr(layer, attr_name).detach()
|
|
|
|
@classmethod
|
|
def from_linear(cls, layer, rank=4, lora_dropout_p=0.0, lora_alpha=1, svd_bases=None, **kwargs):
|
|
fan_out, fan_in = layer.weight.shape
|
|
W0 = cls._get_original_weight(layer)
|
|
return cls(
|
|
fan_in, fan_out, fan_in_fan_out=False, rank=rank, lora_dropout_p=lora_dropout_p, lora_alpha=lora_alpha,
|
|
W0=W0, svd_bases=svd_bases, **kwargs
|
|
)
|
|
|
|
@classmethod
|
|
def from_conv2d(cls, layer, rank=4, lora_dropout_p=0.0, lora_alpha=1, svd_bases=None, **kwargs):
|
|
fan_out, fan_in = layer.weight.view(layer.weight.shape[0], -1).shape
|
|
W0 = cls._get_original_weight(layer).view(layer.weight.shape[0], -1)
|
|
return cls(
|
|
fan_in, fan_out, fan_in_fan_out=False, rank=rank, lora_dropout_p=lora_dropout_p, lora_alpha=lora_alpha,
|
|
W0=W0, svd_bases=svd_bases, **kwargs
|
|
)
|
|
|
|
@classmethod
|
|
def from_conv1d(cls, layer, rank=4, lora_dropout_p=0.0, lora_alpha=1, svd_bases=None, **kwargs):
|
|
fan_out, fan_in = layer.weight.view(layer.weight.shape[0], -1).shape
|
|
W0 = cls._get_original_weight(layer).view(layer.weight.shape[0], -1)
|
|
return cls(
|
|
fan_in, fan_out, fan_in_fan_out=False, rank=rank, lora_dropout_p=lora_dropout_p, lora_alpha=lora_alpha,
|
|
W0=W0, svd_bases=svd_bases, **kwargs
|
|
)
|
|
|
|
@classmethod
|
|
def from_embedding(cls, layer, rank=4, lora_dropout_p=0.0, lora_alpha=1, svd_bases=None, **kwargs):
|
|
fan_in, fan_out = layer.weight.shape
|
|
W0 = cls._get_original_weight(layer).view(layer.weight.shape[0], -1)
|
|
return cls(
|
|
fan_in, fan_out, fan_in_fan_out=True, rank=rank, lora_dropout_p=lora_dropout_p, lora_alpha=lora_alpha,
|
|
W0=W0, svd_bases=svd_bases, **kwargs
|
|
)
|
|
|
|
|
|
default_lora_config = { # specify which layers to add lora to, by default only add to linear layers
|
|
nn.Linear: {
|
|
"weight": partial(LoRAParametrization.from_linear, rank=4),
|
|
},
|
|
}
|
|
|
|
|
|
# --- layer name filtering helpers ---
|
|
|
|
def _expand(s):
|
|
"""Expand bracket notation in layer filter strings, e.g. 'layers[0-5]' -> ['layers0', ..., 'layers5']."""
|
|
t = re.split(r'\[(\d+)-(\d+)\]', s)
|
|
if len(t) == 1:
|
|
return [s]
|
|
lits, starts, ends = t[0::3], t[1::3], t[2::3]
|
|
pools = []
|
|
for a, b in zip(starts, ends):
|
|
ai, bi = int(a), int(b)
|
|
step = 1 if bi >= ai else -1
|
|
pools.append([str(n) for n in range(ai, bi + step, step)])
|
|
out = []
|
|
for combo in product(*pools):
|
|
pieces = []
|
|
for i, lit in enumerate(lits):
|
|
pieces.append(lit)
|
|
if i < len(combo):
|
|
pieces.append(combo[i])
|
|
out.append(''.join(pieces))
|
|
return out
|
|
|
|
|
|
def _matches_any(name, patterns):
|
|
"""Check if name contains any pattern substring (with bracket expansion)."""
|
|
if not patterns:
|
|
return False
|
|
for pattern in patterns:
|
|
for expanded in _expand(pattern.strip()):
|
|
if expanded in name:
|
|
return True
|
|
return False
|
|
|
|
|
|
# --- core LoRA application ---
|
|
|
|
def _match_layer_type(layer, lora_config):
|
|
"""Find the matching lora_config key for a layer, using isinstance to handle ParametrizedLinear etc."""
|
|
for layer_type in lora_config:
|
|
if isinstance(layer, layer_type):
|
|
return layer_type
|
|
return None
|
|
|
|
|
|
def apply_lora(layer, register=True, merge=False, lora_config=default_lora_config):
|
|
"""add lora parametrization to a layer, designed to be used with model.apply"""
|
|
if register:
|
|
matched_type = _match_layer_type(layer, lora_config)
|
|
if matched_type is not None:
|
|
for attr_name, parametrization in lora_config[matched_type].items():
|
|
parametrize.register_parametrization(layer, attr_name, parametrization(layer), unsafe=True)
|
|
else: # this will remove all parametrizations, use with caution
|
|
if hasattr(layer, "parametrizations"):
|
|
for attr_name in list(layer.parametrizations.keys()):
|
|
parametrize.remove_parametrizations(layer, attr_name, leave_parametrized=merge)
|
|
|
|
|
|
def add_lora(model, lora_config=default_lora_config, include=None, exclude=None, svd_bases=None):
|
|
"""Add LoRA parametrization to layers in a model.
|
|
|
|
Args:
|
|
model: The model to add LoRA to.
|
|
lora_config: Dict mapping nn.Module types to parametrization configs.
|
|
include: Optional list of substrings. If provided, only modules whose
|
|
name contains at least one pattern get LoRA. Supports bracket
|
|
expansion (e.g. "layers[0-11]").
|
|
exclude: Optional list of substrings. Modules matching any pattern are
|
|
skipped even if they match an include pattern.
|
|
svd_bases: Optional dict of pre-computed SVD bases keyed by
|
|
"layer_name.weight". When provided, -XS adapter types use
|
|
these instead of computing SVD.
|
|
"""
|
|
if svd_bases is not None or include is not None or exclude is not None:
|
|
# Named-module path: iterate by name so we can look up SVD bases per layer
|
|
n_cached = 0
|
|
n_recomputed = 0
|
|
for name, module in model.named_modules():
|
|
matched_type = _match_layer_type(module, lora_config)
|
|
if matched_type is None:
|
|
continue
|
|
|
|
if include is not None and not _matches_any(name, include):
|
|
continue
|
|
|
|
if exclude is not None and _matches_any(name, exclude):
|
|
continue
|
|
|
|
for attr_name, parametrization_fn in lora_config[matched_type].items():
|
|
layer_bases = None
|
|
if svd_bases is not None:
|
|
bases_key = f"{name}.{attr_name}"
|
|
layer_bases = svd_bases.get(bases_key)
|
|
param = parametrization_fn(module, svd_bases=layer_bases)
|
|
parametrize.register_parametrization(module, attr_name, param, unsafe=True)
|
|
if hasattr(param, 'U'): # -XS layer that needs SVD bases
|
|
if layer_bases is not None:
|
|
n_cached += 1
|
|
else:
|
|
n_recomputed += 1
|
|
if n_cached > 0:
|
|
vprint(f"SVD bases: {n_cached} layers loaded from cache")
|
|
if n_recomputed > 0:
|
|
vprint(f"SVD bases: {n_recomputed} layers RECOMPUTED (missing from cache)")
|
|
if svd_bases is not None and n_cached == 0 and n_recomputed == 0:
|
|
vprint("SVD bases: provided but no -XS layers found")
|
|
else:
|
|
# Fast path: original behavior, no name inspection needed
|
|
model.apply(partial(apply_lora, lora_config=lora_config))
|
|
|
|
def add_lora_by_name(model, target_module_names, lora_config=default_lora_config, svd_bases=None):
|
|
"""Add LoRA to specific layers by name. Convenience wrapper around add_lora()."""
|
|
add_lora(model, lora_config=lora_config, include=target_module_names, svd_bases=svd_bases)
|
|
|
|
|
|
def merge_lora(model):
|
|
"""merge lora parametrization to all layers in a model. This will remove all parametrization"""
|
|
model.apply(partial(apply_lora, register=False, merge=True))
|
|
|
|
def remove_lora(model):
|
|
"""remove lora parametrization to all layers in a model. This will remove all parametrization"""
|
|
model.apply(partial(apply_lora, register=False, merge=False))
|
|
|
|
def remove_lora_by_index(model, lora_index):
|
|
"""Remove all LoRA parametrizations with a specific lora_index.
|
|
|
|
For modules where other LoRA entries remain, only the matching entry is deleted.
|
|
For modules where the removed entry was the only one, the parametrization is
|
|
removed entirely and the weight reverts to its original value.
|
|
"""
|
|
for name, mod in model.named_modules():
|
|
if not hasattr(mod, 'parametrizations'):
|
|
continue
|
|
for attr in list(mod.parametrizations.keys()):
|
|
params = mod.parametrizations[attr]
|
|
to_remove = [i for i, p in enumerate(params)
|
|
if isinstance(p, LoRAParametrization) and p.lora_index == lora_index]
|
|
if not to_remove:
|
|
continue
|
|
if len(params) == len(to_remove):
|
|
# All entries being removed — remove parametrization entirely
|
|
parametrize.remove_parametrizations(mod, attr, leave_parametrized=False)
|
|
else:
|
|
for i in reversed(to_remove):
|
|
del params[i]
|
|
|
|
def set_lora_strength(model, strength: float, lora_index=None):
|
|
"""Set lora strength. If lora_index is None, sets all LoRAs. If specified, sets only that index."""
|
|
strength = float(strength)
|
|
if lora_index is None:
|
|
for name, buf in model.named_buffers(recurse=True):
|
|
if name.endswith("lora_strength"):
|
|
buf.fill_(strength)
|
|
else:
|
|
for p in _iter_lora_params(model):
|
|
if p.lora_index == lora_index:
|
|
p.lora_strength.fill_(strength)
|
|
|
|
def _iter_lora_params(model):
|
|
for _, mod in model.named_modules():
|
|
plist = getattr(getattr(mod, "parametrizations", None), "weight", None)
|
|
if plist is None:
|
|
continue
|
|
for p in plist:
|
|
if isinstance(p, LoRAParametrization):
|
|
yield p |