관심 있는 NLP 논문을 읽어보고 간단히 정리했습니다.
혹시 부족하거나 잘못된 내용이 있다면 댓글 부탁드립니다 🙇♂️
[Microsoft]
- 학습 동안에 residual path를 추가하고, 추론 시에는 extra path를 제거하는 merging을 적용한 ResLoRA
- LoRA와 residual path를 결합한 최초의 방법론
출처 : https://arxiv.org/abs/2402.18039
1. Introduction
LLM이 뛰어난 성능으로 주목을 받게 되었지만, 이를 학습하는데 필요한 비용이 너무 많이 든다는 문제점이 있습니다.
이를 해결하기 위한 아주 대표적인 방법 중 하나가 Low-Rank Adpatation (LoRA) 입니다.
지금까지 LoRA를 바탕에 두고 이를 발전시켜온 다양한 방법론들이 있습니다.
하지만 LoRA block이 여러 개로 쌓여있는 구조에서 backward path가 길어진다는 문제점은 여전히 해결되지 않고 있었죠.
즉, layer의 개수만큼 block은 쌓여있고, 최초의 입력까지 gradient가 제대로 전달되기를 기대하는 것은 어렵다는 뜻입니다.
본 논문에서는 이러한 한계를 극복하기 위해 ResNet에서 제시되었던 shortcut 개념을 LoRA에 접목했습니다.
하지만 LoRA의 경우 두 개로 나눠진 행렬 A, B를 학습한 뒤, 추론 단계에서 둘을 merging 하는 과정을 거칩니다.
이로 인해 residual path를 추가하는 본 방식은 단순한 merging을 적용할 수 없게 되는데, 이에 대한 방안도 본 논문에서는 두 가지를 제시합니다.
결과적으로 기존 LoRA와 비교해보면 추가적인 파라미터를 사용하지 않으면서 약 1~20%의 성능 향상이 있었으며 학습 동안에 loss가 더욱 빠르게 낮은 값으로 convergence하게 되는 결과가 나타났다고 합니다.
2. Related Works
- Parameter-efficient fine-tuning (PEFT)
- Low-rank training method (LoRA)
- DyLoRA, ReLoRA, LoHA, LoKr, AdaLoRA, AdaMix, QLoRA, LoRAHub, LoRAMoE
- Residual Network (ResNet)
3. Method
3.1. LoRA Blocks
사전학습된 모델의 linear layer의 가중치 행렬은 $W_{n} \in \mathbb{R}^{p \times q}$의 형상을 가집니다.
이때 $p$는 출력 차원을, $q$는 입력 차원을 뜻합니다.
linear layer에서는 이 가중치 행렬에 입력을 곱해 output hidden vector $h_{n}=W_{n}x_{n}$ 를 구합니다.
그리고 LoRA에서는 이 가중치 행렬을 그대로 두고 down-projection $A \in \mathbb{R}^{r \times p}$, up-projection $B \in \mathbb{R}^{q \times r}$ 로 구성된 LoRA block만을 업데이트 합니다.
이때 $r\ll min(p, q)$ 입니다.
추론 단계에서는 원래의 파라미터에 additional 파라미터를 더해줌으로써 latency가 높아지지 않도록 합니다.
이를 식으로 표현하면 다음과 같습니다.
$$h_{n}=W_{n}x_{n}+B_{n}A_{n}x_{n}$$
3.2. ResLoRA Blocks
본 논문은 residual path를 LoRA와 결합하는 방식을 제시하고 있습니다.
하지만 위에서 언급한 것처럼 기존 linear layer에 그대로 결합할 수 없기 때문에 여기에서는 세 가지 방식을 제안하고 각각 실험한 결과를 비교합니다.
세 가지 방식은 input-shorcut (is), block-shortcut (bs), middle-shorcut (ms) 입니다.
(1) Input-shorcut structure
이전 LoRA block의 입력 벡터를 현재 LoRA block의 입력에 그대로 더해주는 방식입니다.
이를 input-shortcut이라고 부르며 수식은 다음과 같습니다.
$$h_{n}=W_{n}x_{n}+B_{n}A_{n}(x_{n}+x_{n-1})$$
이때 $n \in [1, L]$이며 $L$은 원래 모델의 layer 개수입니다.
$n=0$인 경우에는 $x_{n-1}=x_{n}$으로 설정하여 첫 번째 layer와 이후 layer의 크기를 동일하게 유지한다고 합니다.
(2) Block-shortcut structure
이번에는 입력 벡터를 더하는 것이 아니라 LoRA block의 가중치 행렬을 더해줍니다.
즉, 현재 layer의 입력 벡터를 곱하는 가중치 행렬은 이전 layer들에 속한 가중치 행렬의 합입니다.
몇 개의 layer를 사용할지는 'pre_num'이라는 하이퍼 파라미터를 정해 $m$의 값을 조정함으로써 정합니다.
$$h_{n} = W_{n}x_{n} + (\sum_{k=0}^{m}B_{n-k}A_{n-k})x_{n}$$
m+1개 layer의 LoRA weight를 참조합니다.
단, out-of-index error가 발생하지 않을 수 있도록 매 layer마다 $m$의 값을 $m=min(m,n-1)$ 으로 정합니다.
(3) Middle-shorcut structure
기존 방법론들은 LoRA block 내의 A, B 행렬 중에서 A 행렬에 더 큰 영향을 주게 됩니다.
shortcut과 상대적으로 더 가까운 위치이기 때문이죠.
따라서 이 단계에서는 shorcut을 중간에 추가함으로써 B 행렬 역시 동일한 이득을 볼 수 있도록 합니다.
이를 위해 이전 layer의 A 행렬 output을 현재 layer의 A 행렬 output에 더해준 값을 B 행렬의 입력으로 전달하게 됩니다.
식은 다음과 같습니다.
$$h_{n}=W_{n}x_{n}+B_{n}(\sum_{k=0}^{m}A_{n-k}x_{n-k})$$
Block-shortcut에서와 마찬가지로 m+1 개의 layer를 참조하는 것을 알 수 있습니다.
이를 결정하는 하이퍼 파라미터는 'pre_num'입니다.
3.3. Merging Approaches
이제 학습된 A, B 행렬을 merge하는 과정에 대해 소개합니다.
사실 논문을 좀 열심히 봤는데도 명확히 이해된 내용은 아니라서.. 굵직한 내용들을 위주로 설명드리겠습니다.
헷갈리는 내용들이 조금 있는데, 궁금하시다면 직접 논문을 찾아보시는 것이 도움이 될 수도 있습니다..!
우선 세 가지의 shorcut 방식 중에서, block-shortcut은 LoRA blcok 내에 추가적인 forward path가 없기 때문에 단순히 가중치 행렬의 값들을 누적합하는 방식을 취할 수 있습니다. (그림을 참고해주세요)
이를 식으로 나타내면 다음과 같습니다.
$$W_{n}^{*}=W_{n}+\sum_{k=0}^{m}A_{n-k}B_{n-k}$$
하지만 나머지 두 방식, input-/middel shortcut 방식은 이전 layer의 입력 또는 출력을 현재 layer에 더해주는 과정이 포함되어 있어 추가적인 forward path가 존재하기 때문에, 단순히 A, B 행렬을 더할 수가 없게 됩니다. (그림을 참고해주세요)
이에 따라 본 논문에서는 input 기반의 merge, weight 기반의 merge, 두 가지 방식을 제안하고 있습니다.
이때 사용되는 주요 개념은 Frobenius norm입니다.
$$||A||_F=\sqrt{\sum_{i=1}^m\sum_{j=1}^n|a_{ij}|^2}$$
식을 보면 행렬의 모든 값들을 제곱하여 더한 값에 루트를 취해주었다는 것을 알 수 있습니다.
즉, 행렬이 가진 값들의 크기를 나타내는 norm으로 이해할 수 있습니다.
이 개념을 활용하기에 앞서, 다른 layer로부터 이어지는 forward path를 해석하는 관점에 대해 간단히 살펴보겠습니다.
layer를 통과해 얻게 되는 output hidden vector는 다음과 같이 이해될 수 있습니다.
$$\begin{align}
h_{n} &= W_{n}x_{n}+B_{n}A_{n}(x_{n}+x_{n-1}) \\
&\approx W_{n}x_{n}+B_{n}A_{n}(x_{n}+\alpha^* x_{n}) \\
&= W_{n}x_{n}+ (1+\alpha^*)B_{n}A_{n}x_{n}
\end{align}$$
이전 layer의 입력값을 현재 layer 입력값의 scale 된 값이라고 가정하는 것입니다.
이때 사용되는 scaling 계수 $\alpha^*$를 input 기반으로 구할지, weight 기반으로 구할지에 따라 방법을 구분합니다.
Merge Based on Input 방식에서는 각 sliding window (몇 개 layer를 참조하고 있는가)에 대한 Frobenius norm을 구하여 입력을 나눠준 값을 비교합니다.
이를 sliding window별로 비교한 결과는 유사할 것이라고 가정하는데, 이를 이용하여 scaling factor $\alpha^*$를 구합니다.
식으로 표현하면 다음과 같습니다.
$$\begin{align}
& \frac{x_{n}}{f_n}\approx \frac{x_{n-1}}{f_{n-1}} \\
& \alpha^*=\frac{f_{n-1}}{f_n}
\end{align}$$
한편, Merge Based on Weights of ResLoRA Blocks 방식에서는 말 그대로 ResLoRA Block 내에 포함된 가중치 행렬을 이용합니다.
즉, 다음 layer로 전달되는 input의 weight를 대상으로 Frobenius norm을 구하는 것이 아니라, block 내부의 가중치 행렬의 Frobenius norm을 구하여 $\alpha^*$를 구하게 되는 것입니다.
이를 식으로 표현하면 아래와 같습니다.
$$\begin{align}
& \frac{x_{n}}{x_{n-1}}\approx \frac{f_{n-1}^*}{f_{n-2}^*} \\
& \alpha^*=\frac{f_{n-2}^*}{f_{n-1}^*}
\end{align}$$
이때 $f$가 아니라 $f^*$이 쓰였고, 이것은 ResLoRA Block 내부 행렬의 Frobenius norm을 의미하게 됩니다.
이제 layer별로 구한 $\alpha^*$값들을 바탕으로 merge를 수행해주면 되겠습니다.
$$A_n^*=A_n+\sum_{k=1}^m \alpha^*_{n-k}A_{n-k}$$
논문에서는 추가적으로 backward pass에 관하여 chain rule로 수식을 전개하여 학습 과정을 설명하는데, 자세한 내용이 궁금하신 분들은 논문을 참고하시기 바랍니다.
4. Experiments & Results
결국 중요한 것은 ResLoRA가 기존 LoRA 대비 효율적인 방법론이 맞는지를 확인하는 것이기 때문에, 이를 비교한 세 개의 실험 및 그 결과에 대해 간단히 정리하겠습니다.
우선 사용된 비교한 방법론 목록은 다음과 같습니다.
- LoRA, AdaLoRA, LoHA, LoKr
4.1. Natural Language Generating
- Models and Datasets
- LLaMA2-7B
- mathematical and commonsense reasoning
- Results
- 베이스라인 중에서 LoRA의 경우 $r=16$일 때 가장 효과적인 것으로 확인되었습니다.
- LoRA와 비교했을 때, ResLoRA(is), ResLoRA(bs) 방식 모두 더 좋은 결과를 보입니다.
4.2. Natural Language Understanding
- Models and Datasets
- RoBERTa-large
- GLUE benchmark
- Results
- 마찬가지로 LoRA의 경우 $r=16$일 때 성능이 가장 좋습니다.
- 또한 ResLoRA가 NLG task 뿐만 아니라 NLU task에도 적용 가능한 방식임을, 즉 이 방식의 general applicability를 확인할 수 있었습니다.
4.3. Text to Image
- Models and Datasets
- Stable-Diffusion-v2
- Poketmon Blip Captions: 모델이 cartoon style을 배울 수 있는지 확인하는 데이터셋
- Results
- 200-step의 결과를 보면, ResLoRA(is)의 경우 만화 스타일을 잘 살릴 수 있게 되었으나 LoRA는 그렇지 않음이 확인됨
- ResLoRA(is)의 결과가 더 좋을 뿐만 아니라, 더 좋은 결과까지 빠르게 도달할 수 있다는 결론.
5. Limitations
저자는 본 연구의 한계점으로 크게 두 가지를 제시하고 있습니다.
첫째로 연산량입니다.
물론 단순 fine-tuning과 비교하면 아주 효율적이고 비용을 감소시킬 수 있는 좋은 방식일 수 있겠습니다만, LoRA와 비교해보면 추가적인 연산이 발생합니다.
parameter의 개수가 추가되는 것은 아니지만, 이전 layer의 입력 또는 출력값을 참조하여 현재 layer에 전달해야 하기 때문입니다.
둘째로 performance degradation입니다.
학습이 끝난 두 행렬 A, B를 merge 함으로써 inference 과정을 간단하게 만들어 줄 수 있다고 언급했습니다. (추가적인 latency가 발생하지 않을 수 있도록)
그런데 결국 이를 merge하게 되면 성능이 그 전에 비해 하락하게 됩니다.
이를 방지하고자 input/weight 기반의 두 가지 merge 방법론을 제시했음에도 불구하고 이 현상을 피해가지 못했다고 밝혔습니다.
향후 연구는 이러한 현상을 막을 수 있는 방향으로 이뤄져도 의미가 있을 것 같습니다.