Skip to content

26. Lora

import math
import os

import torch
import torch.nn as nn
from torch_judge import check

torch.set_default_device(os.environ.get("TORCH_DEVICE", "cpu"))


class LoRALinear(nn.Module):
    """LoRA (Low-Rank Adaptation) for Linear layers.

    h = W x + alpha/r * A B x

    where W is the frozen weight of the original linear layer, A and B are learnable
    low-rank matrices, alpha is a scaling factor, and r is the rank of the low-rank matrices.
    """

    def __init__(self, in_features: int, out_features: int, rank: int, alpha: float = 1.0):
        super().__init__()

        self.in_features = in_features
        self.out_feature = out_features

        if rank > max(in_features, self.out_feature) or rank < 1:
            raise ValueError(
                f"Invalid rank, should be [1, {max(in_features, self.out_feature)}), got {rank}"
            )
        self.rank = rank
        self.alpha = alpha
        self.scaling = self.alpha / self.rank

        # Create a dense layer
        self.linear = nn.Linear(
            in_features=in_features,
            out_features=out_features,
        )
        for param in self.linear.parameters():
            param.requires_grad = False

        self.lora_A = nn.Parameter(torch.empty(rank, in_features))
        self.lora_B = nn.Parameter(torch.empty(out_features, rank))

        self._init_lora_parameters()

    def _init_lora_parameters(self):
        nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
        nn.init.zeros_(self.lora_B)

    def forward(self, x):
        lin_out = self.linear(x)

        # x [n x in_features]
        # A [rank x in_features]
        # B [out_features x rank]

        lora_out = x @ (self.lora_B @ self.lora_A).T

        return lin_out + self.scaling * lora_out


if __name__ == "__main__":
    layer = LoRALinear(16, 8, rank=4)
    x = torch.randn(2, 16)
    print("Output:", layer(x).shape)
    print("Trainable:", sum(p.numel() for p in layer.parameters() if p.requires_grad))
    print("Total:    ", sum(p.numel() for p in layer.parameters()))
    check("lora")