NYCU-MLLab's picture
Upload folder using huggingface_hub
4a28d4d verified
Raw
History Blame Contribute Delete
4.62 kB
import contextlib
import math
from typing import Dict, Iterable, List, Sequence
import torch
import torch.nn as nn
class LoRALinear(nn.Module):
"""
Lightweight LoRA wrapper for nn.Linear.
"""
def __init__(
self,
base_linear: nn.Linear,
rank: int = 16,
alpha: float = 16.0,
dropout: float = 0.0,
) -> None:
super().__init__()
if not isinstance(base_linear, nn.Linear):
raise TypeError("LoRALinear expects an nn.Linear.")
if rank <= 0:
raise ValueError("LoRA rank must be > 0.")
self.base = base_linear
self.rank = int(rank)
self.alpha = float(alpha)
self.scaling = self.alpha / float(self.rank)
self.enabled = True
self.dropout = nn.Dropout(dropout) if dropout > 0 else nn.Identity()
self.lora_A = nn.Linear(self.base.in_features, self.rank, bias=False)
self.lora_B = nn.Linear(self.rank, self.base.out_features, bias=False)
self.reset_parameters()
self.base.requires_grad_(False)
def reset_parameters(self) -> None:
nn.init.kaiming_uniform_(self.lora_A.weight, a=math.sqrt(5))
nn.init.zeros_(self.lora_B.weight)
def forward(self, x: torch.Tensor) -> torch.Tensor:
out = self.base(x)
if not self.enabled:
return out
delta = self.lora_B(self.lora_A(self.dropout(x)))
return out + self.scaling * delta
def _match_target(name: str, target_modules: Sequence[str]) -> bool:
leaf = name.split(".")[-1]
for target in target_modules:
if not target:
continue
if leaf == target or name.endswith(target):
return True
return False
def _find_parent_module(root: nn.Module, module_name: str):
parts = module_name.split(".")
parent = root
for part in parts[:-1]:
parent = getattr(parent, part)
return parent, parts[-1]
def inject_lora(
model: nn.Module,
target_modules: Sequence[str],
rank: int,
alpha: float,
dropout: float,
) -> List[str]:
named_modules = list(model.named_modules())
injected = []
for module_name, module in named_modules:
if not _match_target(module_name, target_modules):
continue
if isinstance(module, LoRALinear):
continue
if not isinstance(module, nn.Linear):
continue
parent, child_name = _find_parent_module(model, module_name)
setattr(parent, child_name, LoRALinear(module, rank=rank, alpha=alpha, dropout=dropout))
injected.append(module_name)
return injected
def has_lora(model: nn.Module) -> bool:
return any(isinstance(m, LoRALinear) for m in model.modules())
def lora_parameters(model: nn.Module) -> Iterable[nn.Parameter]:
for module in model.modules():
if isinstance(module, LoRALinear):
yield from module.lora_A.parameters()
yield from module.lora_B.parameters()
def freeze_non_lora_parameters(model: nn.Module) -> None:
model.requires_grad_(False)
for p in lora_parameters(model):
p.requires_grad_(True)
def lora_state_dict(model: nn.Module) -> Dict[str, torch.Tensor]:
state = {}
for name, module in model.named_modules():
if not isinstance(module, LoRALinear):
continue
state[f"{name}.lora_A.weight"] = module.lora_A.weight.detach().cpu()
state[f"{name}.lora_B.weight"] = module.lora_B.weight.detach().cpu()
return state
def load_lora_state_dict(model: nn.Module, state_dict: Dict[str, torch.Tensor], strict: bool = True) -> None:
missing = []
for name, module in model.named_modules():
if not isinstance(module, LoRALinear):
continue
key_a = f"{name}.lora_A.weight"
key_b = f"{name}.lora_B.weight"
if key_a not in state_dict or key_b not in state_dict:
missing.append(name)
continue
module.lora_A.weight.data.copy_(state_dict[key_a].to(module.lora_A.weight.device))
module.lora_B.weight.data.copy_(state_dict[key_b].to(module.lora_B.weight.device))
if strict and missing:
raise KeyError(f"Missing LoRA weights for modules: {missing[:8]}")
def set_lora_enabled(model: nn.Module, enabled: bool) -> None:
for module in model.modules():
if isinstance(module, LoRALinear):
module.enabled = bool(enabled)
@contextlib.contextmanager
def lora_disabled(model: nn.Module):
set_lora_enabled(model, enabled=False)
try:
yield
finally:
set_lora_enabled(model, enabled=True)