LoRA Redux: 신호 처리 관점의 PEFT 설계 및 최적화 원칙

서론

대규모 언어 모델(LLM)을 실무 도메인에 적용하려고 할 때 마주치는 가장 큰 장벽은 역시 비용과 자원입니다. 수십억 개의 파라미터를 가진 모델을 Full Fine-tuning하는 것은 대부분의 기업이나 연구실에서 감당하기 어려운 GPU 메모리와 시간을 요구합니다. 이러한 상황에서 Low-Rank Adaptation(LoRA)는 파라미터 효율적 파인튜닝(PEFT)의 표준(de facto standard)으로 자리 잡았습니다. 단지 적은 수의 랭크(Rank) 행렬만을 추가하여 원본 모델의 지식을 유지하면서도 특정 태스크에 맞게 모델을 적응시킬 수 있기 때문입니다.

하지만 LoRA가 널리 쓰이게 되면서 “LoRA를 어떤 Rank로 설정해야 하는가?”, “학습이 잘 수렴하지 않을 때 어떻게 최적화해야 하는가?“와 같은 심층적인 질문들이 제기되었습니다. 최근 arXiv에 게재된 논문 Low-Rank Adaptation Redux for Large Models은 이 질문에 대해 흥미로운 시각을 제시합니다. 바로 신호 처리(Signal Processing) 관점에서 LoRA를 재조명하는 것입니다.

단순히 “저차원 행렬을 더한다"는 수학적 표현을 넘어, LoRA를 고전적인 신호 처리의 이론인 특이값 분해(SVD), 역문제(Inverse Problem) 해결, 그리고 게이지 불변성(Gauge Invariance)과 연결하여 분석합니다. 이 글에서는 이러한 신호 처리 관점의 설계 원칙이 LoRA의 성능을 극대화하고 서빙 단계까지 효율성을 높이는지 기술적으로 심도 있게 다루고자 합니다.

본론: 신호 처리 관점으로 본 LoRA의 설계와 최적화

LoRA의 핵심 아이디어는 사전 학습된 가중치 행렬 $W$를 동결시키고, 낮은 랭크 $r$을 가진 두 개의 행렬 $A$와 $B$를 통해 업데이트 $\Delta W = BA$를 수행하는 것입니다. 신호 처리 관점에서 볼 때, 이는 **“고차원 신호에서 잡음을 제거하고 중요한 저주파 성분(Principal Components)만을 추출하여 재구성하는 과정”**으로 해석할 수 있습니다.

1. 아키텍처 설계: SVD 기반 분해와 텐서화

기존의 LoRA는 $A$를 무작위(Gaussian)로 초기화하고 $B$를 0으로 초기화하여 학습 시작 시 트레이닝에 영향을 주지 않도록 설계되었습니다. 하지만 신호 처리 이론은 더 효율적인 진입점을 제공합니다. 특히 특이값 분해(SVD)는 데이터의 중요한 구조를 파악하는 데 필수적입니다.

LoRA Redux에서 제안하는 SVD 기반 초기화는 학습하려는 데이터의 분포를 미리 분석하여 $A$와 $B$를 설정합니다. 또한, 단일 계층의 랭크만 줄이는 것을 넘어 Cross-layer Tensorization(교차 계층 텐서화) 기법을 통해 여러 계층의 LoRA 가중치를 공유하는 방식이 연구되고 있습니다. 이는 각 계층의 업데이트가 서로 독립적이라는 가정을 깨고, 전체 모델의 파라미터 공간에서 저차원 다양체(Low-dimensional Manifold)를 찾는다는 점에서 의의가 있습니다.

다음은 LoRA의 업데이트 메커니즘과 SVD를 이용한 최적화 과정을 간단히 도식화한 것입니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
graph LR
    A[Input x] --> B[Frozen Pretrained W]
    A --> C[LoRA Adapter Path]
    C --> D[Low Rank Matrix A]
    C --> E[Low Rank Matrix B]
    D --> F[Product BA]
    E --> F
    F --> G[Delta W]
    B --> H[Add Operation]
    G --> H
    H --> I[Output y]
    I --> J[SVD Analysis for Optimization]
    J --> D
    J --> E

2. 게이지 불변 최적화 (Gauge-Invariant Optimization)

LoRA 학습에서 흔히 발생하는 문제 중 하나는 **게이지 자유도(Gauge Freedom)**입니다. 수식으로 보면 $W + BA$는 스케일링 상수 $\alpha$에 대해 $W + (\alpha B)(A/\alpha)$와 완전히 동일한 결과를 냅니다. 즉, $B$를 크게 키우고 $A$를 작게 줄여도 출력은 변하지 않습니다.

신호 처리에서 이는 최적화 경상(Landscape)에 평평한 구간을 만들어 학습 속도를 늦추거나 수렴을 어렵게 만드는 요인이 됩니다. 이를 해결하기 위해 게이지 불변 최적화 기법이 제안됩니다. 이는 파라미터 공간의 대칭성을 깨고 특정 방향으로 학습이 집중되도록 유도합니다. 실무적으로는 초기화 전략(예: $B=0$ 시작)이나 정규화(Regularization) 항을 추가하여 행렬 $A$와 $B$의 균형을 맞추는 것이 중요합니다.

다음은 PyTorch로 구현한 게이지 불변성을 고려한 LoRA 레이어의 간단한 예시입니다. SVD를 활용한 초기화 로직을 포함하여 더 안정적인 학습을 유도합니다.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
import torch
import torch.nn as nn
import torch.nn.functional as F

class SVDInitializedLoRA(nn.Module):
    def __init__(self, in_features, out_features, rank, alpha=1.0):
        super().__init__()
        self.in_features = in_features
        self.out_features = out_features
        self.rank = rank
        self.alpha = alpha
        
        # Random initialization for A (Standard)
        self.lora_A = nn.Parameter(torch.randn(in_features, rank))
        
        # Zero initialization for B (Standard)
        self.lora_B = nn.Parameter(torch.zeros(rank, out_features))
        
        self.scaling = self.alpha / self.rank

    def reset_parameters(self):
        # Optional: SVD-based reset if initial data is available
        nn.init.kaiming_uniform_(self.lora_A, a=math.sqrt(5))
        nn.init.zeros_(self.lora_B)

    def forward(self, x):
        # Original frozen forward pass would be handled by the parent module
        # Here we only calculate the delta W
        result = (x @ self.lora_A @ self.lora_B) * self.scaling
        return result

# Example usage within a Linear Layer
class LinearWithLoRA(nn.Module):
    def __init__(self, in_features, out_features, rank=4):
        super().__init__()
        # Pre-trained weight (Frozen)
        self.weight = nn.Parameter(torch.randn(out_features, in_features), requires_grad=False)
        self.bias = nn.Parameter(torch.zeros(out_features), requires_grad=False)
        
        # LoRA adapter
        self.lora = SVDInitializedLoRA(in_features, out_features, rank)

    def forward(self, x):
        # Standard Linear: xW^T + b
        base_output = F.linear(x, self.weight, self.bias)
        
        # LoRA update: x(BA)^T * scaling
        lora_output = self.lora(x)
        
        return base_output + lora_output

3. 실무 적용 가이드 및 성능 비교

신호 처리 원칙을 기반으로 LoRA를 설계하고 최적화하면, 단순한 랜덤 초기화를 사용했을 때보다 더 빠른 수렴 속도와 더 높은 성능을 기대할 수 있습니다. 특히 Rank를 설정할 때, 무조건 큰 Rank를 설정하는 것보다 SVD 분석을 통해 데이터의 실제 유효 랭크(Effective Rank)를 예측하여 설정하는 것이 메모리 효율성에 훨씬 유리합니다.

아래는 기존 LoRA 접근법과 신호 처리 기반 최적화(SVD + Gauge-Invariant) 방식의 비교입니다.

| 비교 항목 | 기존 LoRA (Random Init) | SP 기반 LoRA (SVD Init & Gauge Invariant) | | :— | :— | :— | | 초기화 전략 | A: Gaussian, B: Zero | A/B: SVD of Initial Residual or Data Covariance | | 최적화 안정성 | 중간 (Gauge Degeneracy 발생 가능) | 높음 (Gauge 불변성 보장) | | 수렴 속도 | 상대적으로 느림 | 빠름 (Principal Direction 선행 학습) | | 메모리 사용량 | 동일 (Rank $r$에 의존) | 동일 (혹은 Cross-layer로 더 낮춤 가능) | | 서빙 비용 | $W + BA$ 합체 필요 | 동일하나, 더 낮은 Rank로 유사 성능 달성 가능 |

4. Step-by-Step 가이드: 효율적인 LoRA 파인튜닝

연구 결과를 바탕으로 실제 프로덕션 환경에서 LoRA를 적용할 때 권장하는 단계별 가이드입니다


출처: http://arxiv.org/abs/2604.21905v1

Hugo로 만듦
JimmyStack 테마 사용 중