사실 간단하지 않음


시작하기 앞서, 이 글은

 1. 행렬이 어떤 것인지 알고, 행렬간 연산을 할 수 있는 사람

 2. 정규분포에 대해서 알고, 각 독립 변수의 분포의 합을 구할 수 있는 사람

 3. (선택사항) 행렬의 rank가 무엇인지 아는 사람

이 아니라면 이해하기 힘들 수 있음을 알림


우선 LoRA는 2021년 10월 microsoft에서 발표한 논문

LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS(https://arxiv.org/pdf/2106.09685.pdf)에서 공개된 것이고


비슷하게 읽어볼 논문으론 (내 추측으론) SD model의 하이퍼네트워크의 기반이 된

2016년에 발표된 HyperNetwork (https://arxiv.org/pdf/1609.09106)이 있음.


------------------------------------------시작------------------------------------------


1. LoRA는?

LoRA는 Low-Rank 어쩌구 저쩌구이기 때문에 Low Rank가 무엇인지 알고 시작을 해야함


Low는 낮은 거고 Rank는 행렬의 rank를 지칭하는 것임


행렬의 rank는 간단히 행렬의 행이나 열에서 서로 독립인 행과 열의 개수를 가리키는데,


우리가 사용할 d * r, r * k(r은 d나 k보다 매우 작음)의 행렬에선 그냥 행이나 열 중에 작은 값이라고 생각하면 됨. 


그래서 종합하면 LoRA는 낮은 rank의 행렬을 이용해 거대 모델(우리는 SD 모델)을 튜닝하는 방법이라고 할 수 있다



2. 그래서 어떻게 튜닝되는데?

LORA: LOW-RANK ADAPTATION OF LARGE LANGUAGE MODELS 중 일부.


영어가 좀 길지만, 우리가 볼 것은 3번 식만 보면 된다.


W0는 원래 모델(행렬 d * k)이고 ΔW는 파인 튜닝을 진행했을 때 원래 모델과의 차이를 가리킴(당연히 원래 모델과 같은 크기(d*k)의 행렬이겠지?)


근데 dreambooth처럼 ΔW를 전부 계산하면 VRAM도 많이 잡아 먹고 학습 결과물도 새로운 모델 그 자체가 나오기 때문에 저장공간도 잡아먹음


근데 LoRA는 여기서 ΔW를 B * A의 행렬 곱으로 나눔과 동시에 B를 d * r행렬, A를 r * k행렬로, 즉 low-rank 행렬 두 개의 곱으로 나눴다.


그렇게 되면 parameter 수도 원랜 W0랑 같이 d * k만큼 가지고 있을 것이,


B, A 모두 합쳐 (d + k) * r 만큼의 파라미터를 가지게 됨(적은 용량, 빠른 계산) (r << d, k 이므로 당연히 (d + k) * r < d ∗ k임)


그리고 원본모델 W0는 가만히 냅두고 BA에 대해서만 학습을 진행하기 때문에, 적은 용량 빠른 계산 모든 이점을 취할 수 있게 됨


또한 원래 모델과 합친다고 할 때, 원래 모델 W0은 내비 두고 학습된 BA 부분만 더해주면 되기 때문에,


파인 튜닝한 결과가 모듈처럼 붙였다 뗐다가 할 수 있는 거( <lora:이름:가중치>, 여러 개 써서 lora 여러 개 불러오는 게 이거 때문에 가능)


근데 행렬 쪼개서 계산한다고 파인튜닝이 잘되냐? microsoft가 잘된대 ㅇㅇ 그냥 하셈


빌게이츠가 나보다 똑똑할거니깐 코런갑다 하고 LoRA잘 쓰면 된다


여기 까지만 읽으면 사실 거의 다 이해한 거임. 3부터는 학습에 관련된 얘기라서 안 할 거면 몰?라도 됨



이 밑에서부터는

 1. 딥러닝 모델이 어떻게 학습되는지 개략적으로 이해하는 사람

이 아니라면 따라오기 힘들 수 있음


3. α/r 값을 곱해준다?


다만 논문에서 잘 보면 ΔW에다가 α/r 값을 곱해준다고 했음. 왜? 그냥 BA값을 모델을 그냥 더하지 않는 이유가 있을까?


3-1. 1/r

답은 당연히 있음, 우선 1/r로 나누는 부분부터 살펴본다면


BA는 두 low-rank, rank(B)=rank(A)=r 행렬임. 그래서 생성된 ΔW 행렬의 성분은 두 행렬 BA의 행렬곱으로 만들어 지게 됨


예를 들어, ΔW의 (1, 1) 성분은 ∑B_1i * A_i1, i = 1, 2, ... , r임


근데 A행렬은 정규분포를 따른다고 했기 때문에 ΔW의 (1, 1) 성분은 r개의 독립변수의 정규분포의 합이 될 수밖에 없음


따라서 ΔW의 각 성분의 분포는 ΣN(0, 1) = N(0, r)이 되기 때문에, ΔW는 더 이상 표준 정규분포가 아니게 되어버림


그래서 1/r을 곱해서 ΔW을 표준정규분포로 바꿔주는 작업을 하게 된거임


3-2. α값은?


1/r은 왜 하는 지 알겠는데 α은 왜 나왔냐? 하면 설명이 긴데 간단히 두 가지 측면으로 나눠보면,


1) 1/r 을 곱했기 때문에 상대적으로 높은 rank를 가진 LoRA는 학습되는 속도가 느려진다.


2) α값을 곱함으로써 ΔW를 더 빨리 내가 원하는 방향으로 학습할 수 있음.


이 있다


다만, α을 수정하는 것 자체는 learning rate를 바꾸는 결과와 유사하게 내가 손 대야 할 hyperparameter가 늘어남을 유도함


그래서 microsoft가 공개했을 당시는 α값을 그냥 1로 고정하고 진행했다. (수정할 hyperparameter 수를 줄이기 위해서)


예제들만 봐도 r=4, r=8 이런 식이라 1/r 부분이 큰 영향을 주지 않았기 때문에, 아주 튜닝도 잘되고 용량도 작은 LoRA가 나왔음


그러나 SD 모델에 LoRA 모델을 학습시키는 과정에서 왜인진 모르겠는데 r=128 이상 쓰는(내 생각엔 이정도면 low-rank아님) 모델들이 나왔고


sd 1.x 모델 용량이 2.0GB인데, r=128 LoRA모델은 약 144MB니깐 충분히 작?은 용량으로도 파인튜닝이 되니 r=128이 유행타기 시작한 것 같음


그러다 보니 논문대로 α값은 1로 뒀는데 1/r 부분은 그대로 두는 사태가 벌어져 실제 학습률이 1/128토막나서 학습되는 경우가 생기게 되었고 


몇몇 high rank 모델들이 underflow문제가 발생하게 됐음.(LoRA가 작동 안 함)


그렇게 되어 LoRA repo에서도 α값을 hyperparameter로 추가하는 일이 벌어졌다고 볼 수 있다.



이에 대한 해결책으로 두 가지 방법이 있다고 생각되는데


1. microsoft 논문의 예제대로 r=4, r=8 등 low-rank를 쓰면서 α=1로 두고  learning rate값만 hyperparameter로 둔다.


2. high rank의 경우, α = r로 두어서 ΔW의 스케일링 효과를 아예 없애고 learning rate값만 hyperparameter로 둔다.


난 LoRA 취지에 맞게 r=4, r=8로 low rank로 하는 걸 선호하긴 하는데, 둘이 뭔 차이냐고 물어보면


나도 ㅁ?ㄹ


학습 시 적정 hyperparameter를 설정할 수 있는 마법의 방법이 있다면 나도 좀 알고 싶네


끝.