multimodalart's picture
multimodalart HF Staff
Upload folder using huggingface_hub
ccf7a99 verified
Raw History Blame Contribute Delete
1.98 kB
"""LoRA adapter for a frozen nn.Linear."""
from __future__ import annotations
import math
import torch
import torch.nn as nn
class LoRALinear(nn.Module):
def __init__(
self,
base_linear: nn.Linear,
rank: int = 8,
alpha: float | None = None,
dropout: float = 0.0,
):
super().__init__()
if rank <= 0:
raise ValueError(f"LoRA rank must be positive; got {rank}")
if alpha is None:
alpha = float(rank)
self.rank = rank
self.alpha = float(alpha)
self.scale = self.alpha / rank
self.base = base_linear
in_f = base_linear.in_features
out_f = base_linear.out_features
# Match base's device/dtype so post-construction LoRA wrapping
# (e.g. apply_lora_and_freeze after the model is on GPU) doesn't leave
# the adapter params on CPU.
device = base_linear.weight.device
dtype = base_linear.weight.dtype
self.lora_A = nn.Parameter(torch.empty(rank, in_f, device=device, dtype=dtype))
self.lora_B = nn.Parameter(torch.zeros(out_f, rank, device=device, dtype=dtype))
nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
self.lora_dropout = nn.Dropout(dropout) if dropout > 0 else nn.Identity()
# When False the adapter is skipped and forward == base(x). Lets callers
# evaluate the pristine frozen base (e.g. a self-distillation teacher)
# without unwrapping. Defaults True — backward-compatible.
self.enabled = True
@property
def in_features(self) -> int:
return self.base.in_features
@property
def out_features(self) -> int:
return self.base.out_features
def forward(self, x: torch.Tensor) -> torch.Tensor:
out = self.base(x)
if not self.enabled:
return out
update = self.lora_dropout(x) @ self.lora_A.t() @ self.lora_B.t()
return out + update * self.scale