LoRAとその関連技術(初版は2023年執筆)

# はじめに

汎用的な言語モデルをファインチューニングして、特殊な専門用語や特定の業務内容に対応できるものを作ることを考えます。

このとき、元の言語モデル全体の重みを直接ファインチューニングすると以下の問題が発生します。

- バックプロパゲーションに使う膨大な計算リソースが必要となる[^1]
- チューニング後のモデルが元と同じサイズになるため、大きすぎて配布しづらい

では、元の言語モデルの重みはそのままに、特定の用語や業務に対応するのに必要なプラスαの部分だけをうまく学習することはできないのでしょうか。

これから紹介するLoRA(Low-Rank Adaptation of Large Language Models)は、そのような都合のよい学習を実現する手法です。

[^1]: https://speakerdeck.com/hpprc/lun-jiang-zi-liao-lora-low-rank-adaptation-of-large-language-models?slide=29

# LoRAとは

LoRAの原著論文の初稿は2021年6月にarxivに投稿されました。

https://arxiv.org/abs/2106.09685

LoRAは"Low-Rank Adaptation of Large Language Models"の略です。文字通り大規模言語モデルに低ランク行列と呼ばれる比較的小さな行列を挿入し、元の言語モデルの重みを固定したまま低ランク行列(下図オレンジ部分)のみを学習するものです。これにより計算リソースを節約してファインチューニングできるというのがLoRAの論文の主張です。

![lora.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268034/5de23efd-143f-ff3e-285f-ae59a77abbee.png)
(図は原著論文より引用)

元の大規模言語モデルさえ共有されていれば、あとは学習済みの小さな行列だけを配布すればファインチューニング結果を共有できるのもありがたい点です。LoRAの論文では、1750億パラメタのGPT-3モデルをLoRAでファインチューニングしたところ、元の言語モデル全体を学習する場合に比べて必要なパラメタ数を10,000分の1まで削減できたことが報告されています。つまり、1750万パラメタだけ学習すればGPT-3のファインチューニングに十分な場合があるのです。この場合、LoRAの学習結果はなんと約35MBに収まります[^4]。

他方、LoRAでのGPT-3のファインチューニングに必要なGPUメモリはモデル全体を学習する場合に比べて3分の1までしか削減できず、約350GBのVRAMが必要です[^4]。ちなみにLoRAファインチューニングの時間は25%しか改善しません[^2]。

[^2]: 原著論文4.2節に"We also observe a 25% speedup during training on GPT-3 175B compared to full fine-tuning"とある。

[^4]: https://speakerdeck.com/hpprc/lun-jiang-zi-liao-lora-low-rank-adaptation-of-large-language-models?slide=37

したがって、我々一般人が利用可能な環境ではサイバーエージェントやrinnaが出しているような数GB～十数GBの言語モデル[^5][^6]を使ってLoRAを試してみるのが妥当なところだと思います。
ただし[databricks-dolly-15k-ja](https://huggingface.co/datasets/kunishou/databricks-dolly-15k-ja
)[^7]のような規模のコーパスをLoRAで1epoch分学習するだけでもGoogle Colabで数百円、計算時間は数時間かかります。1epochでは学習の効果は得られづらいですし、さりとてたくさん学習したところで却っておかしな結果になることもあります(詳細は後述)。したがってLoRAで学習の試行錯誤をするには相応の出費と時間が必要です。

詳細は後述しますが、ファインチューニングに必要なリソースをLoRA以上に削減する技術が出てきつつあります。我々が勉強目的で言語モデルのファインチューニングをするにはそれらを使ったほうが良いでしょう。

[^5]: https://huggingface.co/cyberagent/open-calm-7b
[^6]: https://huggingface.co/rinna/japanese-gpt-neox-3.6b
[^7]: https://huggingface.co/datasets/kunishou/databricks-dolly-15k-ja

## 低ランク行列の作り方

元の重み行列Wがn行m列のとき、n行r列の行列Aとr行m列の行列Bの積ABによって低ランク行列を作ることができます。
rはハイパーパラメタで、nやmに比べて非常に小さな値(ex. 2や4)を指定するのが通例です。

![lora_2.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268034/ef8752a3-3211-6b88-f818-72dca1d9de90.png)


ABが低ランク行列と呼ばれるのは、このrの値によってABのランクが決まるからです。行列の積のランクには下記の性質があるため、ABのランクは高々rです。

```math
rank(AB) \leq rank(A)\\
rank(AB) \leq rank(B)
```

低ランク行列を学習に使えば、元のままだと $n \times m$ 個のパラメタを学習する必要があったのに対し、$(n \times r)+(r \times m)$個のパラメタを学習するだけで済みます。$n=m=1000$、$r=4$だとすると、従来は100万パラメタを学習するべきところ、LoRAでは8000パラメタの学習だけで済みます。

つまり、ファインチューニングで微修正をするだけなのだから、見た目こそn行m列の行列でも中身がたかが数ランクでスカスカな行列ABを学習すればいいだろうというのがLoRA論文の主張なわけです。

## 低ランク行列の挿入先

低ランク行列はファインチューニングしたい重み行列ごとに用意する必要があります。そのため、学習対象の言語モデルにあるどのレイヤを学習するか選定することが、リソース節約の観点で重要になります。

原著論文に書かれた実験によると、各self attentionレイヤのKey, Query, Valueをそれぞれ求める重みである$Wk$, $Wq$, $Wv$に加え、self attentionレイヤの最後の出力にかける重みである$Wo$に低ランク行列を用意するのがベストであったとしています。しかも低ランク行列のランク数は4以下でも十分であったとしています。必ずしもこれらの値があらゆるタスクにベストであるとは限りませんが、非常に小さなランクを設定してもLoRAが機能する場合があることは知っていて損はないと思います。

![table_6.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268034/d1dc2d14-5278-81b9-0108-58d064ef3994.jpeg)
(表は原著論文から引用)

なおself attentionの仕組みはどうなっていたか？ということを知りたい方は、例えば下記が参考になると思います。(私も参照しました)

https://towardsdatascience.com/illustrated-self-attention-2d627e33b20a

## LoRAの適用例

では、実際に言語モデルにLoRAを適用したらどのような結果が得られるのでしょうか。

その一例を示したものに、[計212件のずんだもんの対話データ](https://github.com/takaaki-inada/rinna-3.6b-hello-zundamon-ai/blob/main/datasets/zundamon_prof_homu_20230615.csv)をLoRAで学習させたときの結果を記したQiita記事があります。

https://qiita.com/takaaki_inada/items/9a9c07e85e46ec0e872e#%E5%8B%95%E4%BD%9C%E7%A2%BA%E8%AA%8D

LoRAの結果、ずんだもんらしい口調がLLMから返りやすくなったものの、日本の首都を聞かれたときすら「ずんだもんなのだ。」と返してしまうようになってしまいました。

これを踏まえると、どうやらLoRAを使って数多く学習させれば期待する回答が出やすくなるという単純な話では済まなさそうです。実際、1.5B～13Bくらいのパラメタ数の言語モデルでは教師データの猿真似が精一杯で、教師データの守備範囲にあまりないタスクではほとんど学習ができていないとする論文もあります[^10]。

他方、[上記記事](https://qiita.com/takaaki_inada/items/9a9c07e85e46ec0e872e)でも紹介のあったLIMA(Zhou et al. 2023)[^3]や、"Textbooks Are All You Need"(Gunasekar et al. 2023)[^9]のように、教師データを厳選したり大規模言語モデルに作成させれば十分な精度で所定のタスクをこなせると主張する論文もあります。

後述の方法で私がLoRAを試してみた限り[^11]では、規模の小さなモデルに関しては前者の論文の通り「猿真似が精一杯」な感があります。

小さめのモデルでもファインチューニングさえできれば企業や組織に特化したタスクを何でもこなせるかのように思っている人が多い気がしますが、融通の効かない役に立たないモデルができただけということに気づかず炎上する事例が出てしまわないか危惧しています[^14]。

[^3]: https://arxiv.org/abs/2305.11206
[^9]: https://arxiv.org/abs/2306.11644
[^10]: https://arxiv.org/abs/2305.15717
[^11]: https://huggingface.co/datasets/kunishou/databricks-dolly-15k-ja のコーパスをOpenCalm 1B + LoRAで学習させたところ、コーパスにある「ラクダはなぜ水なしで長く生きられるのか？」という問いに「ラクーダは砂を食べるから」のような文章を出してきました。絶望のあまり書いたコードを投げ捨てました。
[^14]: 本記事を追記して一般公開した2024年11月時点でも、私はこの危惧を持ち続けています。

## LoRAをPythonで実行するには

LoRAを実装したライブラリにHuggingFaceの開発したPEFTがあります。LoRAをPythonで実行するのはこれを使うのが2023年6月現在はスタンダードだと思います。

https://github.com/huggingface/peft

PEFTを利用したLoRAの実行例はいくつも見つかります。例えば以下です。

https://note.com/npaka/n/na5b8e6f749ce

ただしWindows上でPEFTをインストールして環境構築するのは私が知る限り、一緒にインストールされるbitsandbytesのビルドでそこそこ厄介[^8]なことになります。
個人的には自分で環境構築するかわりに、[text-generation-webui](https://github.com/oobabooga/text-generation-webui)のWindows版をインストールしてできるconda環境を使うのがおすすめです。この環境にはPEFTもbitsandbytesも既にインストールされていますので、何も考えなくて済みます。
あるいは素直にLinux環境を使って導入するのが良いと思います。

Windows版text-generation-webuiのzipファイルは以下からダウンロードできます。ダウンロードしたらzipファイルを展開して、できたフォルダの中のstart_windows.batを実行すれば何も考えずにPEFTの入った環境が手に入ります。

https://github.com/oobabooga/text-generation-webui/releases

ただし繰り返しにはなりますが、言語モデルのファインチューニングほど結果がおぼつかないものはないと思っています。特に我々がすぐ試せるような数十億パラメタのモデルでは特にそうだと思います。

ファインチューニングを実用化するための茨の道にあえて足を踏み込みたいのでなければ[^12]、実際にLoRAを動かしたらどうなるか体感するのに留めておくのが安全だと思います。

[^8]: https://qiita.com/selllous/items/fbaa2c3d2d504e436b17
[^12]: 逆に言えば、その茨の道を踏破できればIT業界の世界的な有名人になれる可能性も十分あると思います。

# Stable DiffusionへのLoRAの応用

LoRAは元々言語モデル向けの手法でしたが、その仕組からするとattentionを使っているモデルなら何にでも応用できそうな気がします。そのため、内部でattentionを使っているStable DiffusionでもLoRAが応用できるようになっています。

## LoRAを適用するレイヤ

Stable Diffusionでは、与えられたランダムノイズから画像を復元する処理の中でattentionレイヤがいくつか使われています。そのレイヤのQuery, Key, Valueを求める各重み行列にLoRAの低ランク行列を差し挟むことで、Stable DiffusionもLoRAでファインチューニングできるようになります。(下図の赤字部分)

![makoto-shing-stability-ai-image-model-finetuning-wandbevent230525pdf-16-2048.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268034/6d1460ef-9fb3-fdc1-4511-953aea3a8b44.png)
(図は下記スライドより引用)
https://www.slideshare.net/hijiki_s/makoto-shing-stability-ai-image-model-finetuning-wandbevent230525pdf

## 倫理的な問題

Stable Diffusion 1.5をLoRAでファインチューニングをするのに必要な画像は数十枚程度でも十分という話があります。

https://murasan-net.com/index.php/2023/06/01/lora-dataset-zunko/

例えばあなたの顔写真がそれだけの数あれば、自分自身の顔をDiffusion Modelで生成することができるようになるというわけです。

ということは写真が数多く出回っている有名人の顔ならばLoRAで学習するためのデータを集めるのは容易いということです。実際に本物と見分けがつきにくい画像を生成した事例もあります。下記のキャプチャはLoRAモデルを配布するサイトのものですが、明らかにあの俳優さんの顔を生成するのに特化したモデルになっています。

![the_rock_lora.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268034/b65a78ac-0cea-4a8f-47f3-47762a6a7ffb.png)

このような画像を公表すると、ご本人の肖像権に抵触する可能性があります。

実際、肖像権絡みの議論が既に発生した事例があります。
実在しない女性である「さつきあい」の写真集を集英社が2023年5月にリリースしたのですが、「『さつきあい』は実在の女優さんに似ている。肖像権侵害では？」との指摘が多数あり、発売中止になりました。

https://kai-you.net/article/86949

# LoRAの発展形

LoRAを使えば、元のモデルを全部再学習するよりも少ないリソースや時間でファインチューニングができることをこれまでに示しました。またLoRAのアウトプットは学習済みの低ランク行列のみとなるため、その配布も容易であることを示しました。
他方で、言語モデルのファインチューニング自体が安定して実用できるレベルではないことへの危惧も示しました。

加えて、Stable DiffusionのattentionレイヤにLoRAを適用することで特定の事物に特化した画像を生成できることを示しました。その他方で著名人の肖像権侵害につながるおそれがあることも示しました。

このように、LoRAに関する基礎研究や応用においてはまだまだ課題が多い状況ではありますが、関連技術は文字通り日々進化しています。本記事の締めとして、2023年6月現在で比較的新し目と思われるLoRA関連のアルゴリズムやその実装を紹介します。

## QLoRA

QLoRAは、元のモデルのものも含めて浮動小数点型の重みを4bit整数に量子化することで、ファインチューニングするときに必要となるVRAMのサイズを大幅に減らす手法です。

https://arxiv.org/abs/2305.14314

QLoRAの量子化は4bit Normal Floatというデータ型を用いています。こちらについて軽く説明します。

4bit Normal Floatでは1つの重みに使える記憶域が文字通り4bitしかないので、重みの浮動小数点の値を下記の16通りの値に限定します。

```python
[-1.0, -0.6961928009986877, -0.5250730514526367, -0.39491748809814453,
-0.28444138169288635, -0.18477343022823334, -0.09105003625154495, 0.0,
0.07958029955625534, 0.16093020141124725, 0.24611230194568634, 0.33791524171829224, 0.44070982933044434, 0.5626170039176941, 0.7229568362236023, 1.0]
```

0付近の値は多めに用意されていますが、0から離れるほどまばらになっています。モデルの重みは正規分布していると仮定できるので、これにより頻出しやすい値を優先して割り当てることができます。

このことを示す概念図を下記に示します[^13]。下図の色の境目は4bit Normal Floatで表すことのできる小数点の値に対応しています。すべての浮動小数点の値を一番近い境目に寄せれば4bit Normal Floatになります。

![q_ssd.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268034/1146f0f1-dd32-9089-ca14-151a2bd95ce8.png)

加えてQLoRAではVRAMのメモリ管理の工夫などを行い、限られたリソース下でもファインチューニングができるような工夫がなされています。もう少し詳しい話は下記などを参照ください。

https://zenn.dev/syoyo/articles/6918defde8a807

[^13]: ちなみに正規分布の量子化のグラフはGPT-4に作らせました。その際のログを共有します。　https://chat.openai.com/share/a4244f13-e704-4b30-b8a0-9c5c11a10b4c

QLoRAを使った言語モデルのファインチューニングのコード例は下記が詳しいです。
下記で使われているtrlというライブラリは、gpt-3.5などの学習において人が好む回答を学習するのに使われているRLHF(Reinforcement Learning from Human Feedback)を実行するためのものです。

https://note.com/npaka/n/n4ef8865ca3f9

## LyCORIS

LyCORISはStable Diffusion向けのLoRAの発展手法を実装したPythonプログラム群です。

https://github.com/KohakuBlueleaf/LyCORIS

LyCORISで実装されている手法の解説は以下に詳しいです。

https://ai-joyful.com/lycoris/

LyCORISで学習してできた重みファイルは、[以前に勉強会向けに限定で共有した記事](https://qiita.com/kzkymn/private/e773b567213cddf3cdbe)で紹介した[stable-diffusion-webui](https://github.com/AUTOMATIC1111/stable-diffusion-webui)で利用できます。

LyCORISを利用するためには、プラグインをstable-diffusion-webui経由でインストールする必要があります。詳しい導入手順は下記を参照ください。

https://www.ipentec.com/document/software-stable-diffusion-install-a1111-sd-webui-lycoris

# 【おまけ】本文で引用しなかった参考文献

最後に、本文で引用しきれなかった参考文献を順不同で雑多に並べていきます。

https://speakerdeck.com/koharite/lun-wen-jie-shuo-lora-low-rank-adaptation-of-large-language-models

https://dosuex.com/entry/2023/03/30/115101

https://zenn.dev/fusic/articles/paper-reading-lora

https://note.com/te_ftef/n/n096ef64cb6c1

https://huggingface.co/blog/lora