23. 少ないリソースで大きなAIを育てる!LoRAとQLoRAの魔法を徹底解説
前回まで、AIモデルのデータ型や量子化について学んできました。大規模モデルをどうやって小さく圧縮するか、その技術的な仕組みがわかりましたよね。
でも、ここで実践的な疑問が浮かびます。「データセンター規模の予算がない私たちが、70億パラメータや700億パラメータのモデルを実際にどうやってファインチューニング(調整訓練)するの?」
この疑問への答えが、今回紹介するLoRA(Low-Rank Adaptation)とQLoRA(Quantized Low-Rank Adaptation)という革命的な技術なんです!
これらの技術を使えば、モデルのパラメータのわずか0.1%だけを更新するだけで、完全なファインチューニングに匹敵するパフォーマンスを出せるんですよ。しかも、GPUのメモリにすら収まらないような巨大モデルでもファインチューニングできちゃいます。
今回は、そんな夢のような技術の仕組みを、できるだけわかりやすく解説していきます!
アクセシビリティの問題:誰もがAIを扱える時代へ
まず、なぜLoRAやQLoRAが必要だったのか、その背景から理解しましょう。
こんな状況を想像してみてください。あなたは強力な70億パラメータのモデル(LLaMAやMistralのようなもの)を持っていて、自分のタスクに合わせてファインチューニングしたいと思っています。ファインチューニングがパフォーマンスを向上させることはわかっていますが、大きな課題があります。予算が限られているんです。
従来のファインチューニングの問題点
従来のファインチューニングでは、すべてのパラメータを更新する必要があります。つまり、重み、勾配、オプティマイザの状態をすべて保存しなければなりません。
70億パラメータのモデルの場合、16ビット精度で重みを保存するだけで14GBのVRAMが必要です。でも待ってください。勾配とオプティマイザの状態を加えると、合計メモリ消費量は40~50GBになります!これはNVIDIA A100のような最上位GPUでは可能ですが、高価すぎます。
さらに、700億パラメータのモデルのトレーニングを想像してみてください。16ビット精度では、重みだけで約140GBのメモリが必要で、トレーニングのオーバーヘッドを含めると300GB以上になります。予算があったとしても、このような規模のハードウェアは個人や小規模チームには手が届きません。
本当の問題:アクセシビリティ
ここでの本当の問題は、コストだけでなくアクセシビリティでした。大手テック企業だけがこれらの巨大なモデルをファインチューニングするリソースを持っている場合、AIコミュニティの他のメンバーはどうやって実験し、革新を起こせるのでしょうか?
ここでParameter-Efficient Fine-Tuning(PEFT、パラメータ効率的ファインチューニング)手法がすべてを変えました。PEFTを使えば、大企業の予算がなくても大規模モデルをファインチューニングでき、これらのモデルをすべての人にとってアクセス可能にしたんです!
Parameter-Efficient Fine-Tuningとは?
PEFTの天才的な点は非常にシンプルでありながら強力です。それは「モデルをファインチューニングするためにすべてのパラメータを更新する必要はない」という発想です。
こう考えてみてください。事前学習済みモデルをファインチューニングする際、ドメイン固有の情報でモデルをガイドしているんです。タスクに合わせて言語を調整したり、トーンを微調整したり、専門用語を教えたりしています。
でも、覚えておいてください。モデルはすでに言語構造や推論パターンなどの基本を知っています。ゼロから始めているわけじゃないんです。あなたのニーズに向けてモデルを微調整しているだけなんですね。
PEFTの仕組み
PEFTは元のモデルの重みを凍結し、小さな学習可能なアダプター(適応層)を追加することで機能します。これらのアダプターはタスク固有の調整だけを学習し、モデルのコア知識はそのまま保たれます。
数十億のパラメータを更新する代わりに、ほんのわずかな割合、時にはモデルの全パラメータのわずか0.1%だけを訓練します。
LoRAとQLoRAは最も人気のある2つのPEFT手法で、今回はこれらに焦点を当てます!
LoRAの紹介:Low-Rank Adaptation
LoRA(Low-Rank Adaptation of Large Language Models)は、Microsoftの研究者による2021年の論文で紹介され、ファインチューニングについての考え方を根本的に変えました。
LoRAの核心的アイデア
ファインチューニング中に重み行列全体を更新する代わりに、LoRAは巧妙なことをします。元の重みを凍結したまま、重み更新の低ランク分解を学習するんです。
実際にこれが何を意味するか見てみましょう。
モデル内に重み行列W(例えばアテンション層のクエリ投影)があるとしましょう。通常、ファインチューニング中にこの行列全体を更新します。でもLoRAはより効率的なアプローチを導入します。
Wを直接変更する代わりに、Wを凍結したままにし、ΔW(デルタW)と呼ばれる更新行列を学習します。これは新しいタスクのためにWに適用したい変更を表します。
低ランク分解の魔法
ΔWはWと同じ形状(m × n)ですが、そのすべてのパラメータを訓練する代わりに、LoRAはこれを2つのはるかに小さい行列AとBの積としてパラメータ化します:
- A:m × r
- B:r × n
ここでrは分解のランクであり、r << min(m, n)です。これはメモリとパフォーマンスのトレードオフを制御するために調整できる重要なハイパーパラメータです。
具体例で理解する
数字で見てみましょう:
- Wがm × n行列(例:5000 × 4000)であるとすると、行列は2000万パラメータを持ちます
- ΔWの2000万すべてを訓練する代わりに、LoRAはアダプター行列のみを訓練します
- r = 8を選択するとします:
- A(5000 × 8)= 40,000パラメータ
- B(8 × 4000)= 32,000パラメータ
これは、この1つの層で更新する必要があるパラメータ数の99.6%削減です!
推論中、モデルは次のように計算します:
出力 = (W + A × B) × 入力
これは、事前学習済みの重みWはそのまま保たれ、アダプターが新しいタスクにモデルを特化させる小さく効率的な修正を適用することを意味します。
この大幅な削減により、はるかに少ないメモリと計算能力で大規模モデルをファインチューニングできるんです!
でもトレードオフはないの?
この時点で、あなたは考えているかもしれません。「すごい、ファインチューニングから多くのパラメータを節約できた。何か落とし穴があるに違いない、そうでしょう?」
正解です。タダ飯はありません!
LoRAの効率性は、完全なファインチューニングと比較してわずかなパフォーマンス低下を伴います。
でも、研究によると、このギャップは驚くほど小さく、特にメモリ節約と計算効率の大きな利点を考えると小さいんです。
実際には、品質の損失は無視できるほど小さく、特に指示追従やドメイン適応などのタスクでは顕著です。巨大なモデル(70億、130億、または700億パラメータ)でも、控えめなハードウェアでファインチューニングでき、ほぼ完全精度のパフォーマンスを維持できます。
結論:LoRAを使用すると、膨大なモデルを効率的かつ効果的にファインチューニングでき、リソースのごく一部でほぼ同じ結果を達成できるんです!
LoRAハイパーパラメータ:制御する「ノブ」
では、特定のタスクに対してLoRAをどのように機能させるのでしょうか?ここでハイパーパラメータを調整します。
主な4つのハイパーパラメータがあります:
1. ランク(r)- 「容量」ノブ
これはアダプターがどれだけ学習できるかを制御します。本にどれだけの付箋を使えるかを決めるようなものです。
- 高いランク(64や128):より多くの学習容量を与えますが、より多くのメモリを使用し、小さなデータセットで過学習する可能性があります
- 低いランク(4や8):メモリを少なく使用し、より速く訓練しますが、タスクに必要な複雑な変更を捉えられない可能性があります
- 最適な点:r = 8:ほとんどのタスクでうまく機能し、人気のあるデフォルトです
2. アルファ(α)- 「ボリューム」ノブ
これは元のモデルと比較してアダプターがどれだけ「大声で話す」かを制御します。付箋の修正のボリュームを調整するようなものです。
- 低すぎる:アダプターがモデルにほとんど影響を与えません(ささやくように)
- 高すぎる:アダプターが元のモデルを圧倒します(叫ぶように)
- 経験則:α = 2 × r:r = 8の場合、α = 16を使用します
3. ターゲットモジュール – 「どこ」の選択
LoRAはモデル全体を適応させるわけではありません。どの層をターゲットにするかを選択します。
トランスフォーマーモデルでは、各アテンションブロックに4つの重み行列が含まれます:
- Query(Wq):入力をクエリベクトルに投影
- Key(Wk):入力をキーベクトルに投影
- Value(Wv):入力をバリューベクトルに投影
- Output(Wo):アテンション出力をモデル次元に投影し戻す
最も一般的な設定:q_projとv_proj(QueryとValue)をターゲットにします。これにより、最小限のメモリオーバーヘッドで優れた適応力が得られます。
より多くの容量のために:k_projとo_proj(KeyとOutput)を追加します。これにより訓練可能なパラメータがおよそ2倍になりますが、困難なタスクにより多くの適応力を提供します。
実用的な戦略:非常に高いランクを少数のモジュールに使用するよりも、中程度のランクを複数のモジュールに分散させます。例えば、4つのモジュールでr = 8は、類似のパラメータ予算を使用しながら、1つのモジュールでr = 32よりも典型的に優れたパフォーマンスを発揮します。
4. LoRAドロップアウト – 正規化装置
LoRAドロップアウトは、特に小さいまたは狭いデータセットで訓練する際に、ファインチューニング中の過学習を防ぐためにLoRA層内で使用される正規化パラメータです。
訓練中にアダプターの有効容量を減らし、訓練データに過学習するのではなく、より堅牢で一般化された表現を学習するよう促します。
一般的な値:
– 0.0:ドロップアウトなし(高速だが過学習のリスク)
– 0.05~0.1:ほとんどのセットアップに適したデフォルト
– 0.1以上:データセットが非常に小さいか過学習が明確な場合のみ
LoRAのハイパーパラメータの選択
ほとんどのファインチューニングタスクで機能する信頼性の高い設定は次のとおりです:
- ランク(r):8
- アルファ(α):16
- ターゲットモジュール:q_proj, v_proj
- ドロップアウト:正規化のために0.05または0.1
この設定は70億パラメータのモデルで約400~800万パラメータを訓練し、最小限のメモリを使用し、幅広いタスクで優れた結果を達成します。
そこから特定のニーズに基づいて調整します:
- モデルが不適合?:ランクを16に増やすか、より多くのターゲットモジュールを追加
- メモリ不足?:ランクを4に減らすか、より少ないモジュールをターゲットに
- 小さなデータセット(< 1000例)?:過学習を避けるためにランクを低く保つ
- 大きなドメインシフト?:ランク、アルファを増やし、より多くのモジュールをターゲットに
- 破壊的忘却(一般的パフォーマンスの劣化)?:一般化を保つためにランクとアルファを下げる
QLoRA:Quantized Low-Rank Adaptation
LoRAは訓練可能なパラメータの問題を解決しましたが、1つのボトルネックが残りました。凍結されたベースモデルは依然として完全な精度でGPUメモリに収まる必要がありました。
16ビット精度の70億パラメータのモデルは約14GBのVRAMを必要とします。これは現代のGPUで管理可能です。でも130億パラメータのモデルは約26GB、330億パラメータのモデルは約66GB、700億パラメータのモデルは約140GBを必要とします。LoRAの効率性があっても、ほとんどの実務者は70億パラメータを超えるモデルをファインチューニングできませんでした。
2023年に導入されたQLoRAは、LoRAアプローチに量子化を追加することでこれを解決しました!
QLoRAの3つの重要技術
1. NF4(NormalFloat-4)量子化
前回のレッスンで学びましたよね!標準的な量子化スキームは、利用可能なビン全体に値を均等に分散させます。でも、ニューラルネットワークの重みは均等に分散されていません。ほぼ正規分布でゼロの周りに集中しているんです。
NF4は、正規分布データ専用に設計された量子化スキームです。ゼロ付近(ほとんどの重みが存在する場所)により多くのビンを割り当て、裾にはより少ないビンを割り当てます。これにより、ナイーブな4ビット量子化よりもモデルの品質がはるかに良く保たれます。
2. ダブル量子化
重みを4ビットに量子化した後でも、量子化定数自体からのメモリオーバーヘッドがあります。
重みのブロックを量子化するとき、そのブロックのスケーリング係数とゼロ点を保存します。大規模モデルの場合、これらの定数は数百メガバイトに達する可能性があります。
ダブル量子化は、これらの量子化定数自体を量子化し、通常は8ビットにします。これにより、モデルの品質に影響を与えることなく、オーバーヘッドがさらに約30%削減されます。
3. ページドオプティマイザ
トレーニング中、オプティマイザの状態(Adamのモーメンタムと分散など)は、特に大きなバッチサイズや長いシーケンスで、一時的なメモリスパイクを引き起こす可能性があります。
ページドオプティマイザは、必要に応じてオプティマイザの状態をGPUとCPUメモリ間で自動的に移動することでこれを解決します。GPUメモリの圧力が高まると、頻繁に使用されないオプティマイザの状態がCPU RAMにページアウトされます。再び必要になると、ページインされます。
これはバックグラウンドで透過的に行われ、メモリ不足にならずにより大きな有効バッチサイズで訓練できるんです。
QLoRAトレーニングの仕組み
QLoRAトレーニング中、次のことが起こります:
- ベースモデルの重みは4ビットNF4形式でディスクに保存されます
- 層が計算に必要な場合、その重みがロードされてbfloat16に逆量子化されます
- 順伝播と逆伝播は数値安定性のためにbfloat16で行われます
- LoRAアダプターのみが勾配を蓄積し更新されます
- ページドオプティマイザがメモリを自動的に管理します
結果:ほぼ完全精度の品質を維持しながら、48GB未満のVRAMで700億パラメータのモデルをファインチューニングできます!
LoRA vs QLoRA:それぞれをいつ使うか
LoRAとQLoRAの間の決定は簡単です。ベースモデルがGPUメモリに収まるかどうかにかかっています。
LoRAを使用する場合
- ベースモデルがGPUメモリに快適に収まる(例:24GB GPUで70億パラメータのモデル、40GB GPUで130億パラメータ)
- 最速の反復で最もシンプルなセットアップが必要
- 頻繁にファインチューニングし、最小限のオーバーヘッドが必要
QLoRAを使用する場合
- ベースモデルが完全精度でGPUメモリに収まらない
- コンシューマーハードウェアで330億、650億、または700億パラメータのモデルをファインチューニングしたい
- 大幅なメモリ節約のために約10~20%遅いトレーニングを受け入れる意思がある
実用的なリファレンス:
| モデルサイズ | LoRA (BF16) | QLoRA (4-bit) | 推奨 |
|---|---|---|---|
| 7B | 約16GB | 約6GB | 24GB GPUならどちらでも |
| 13B | 約26GB | 約10GB | 24GB GPUならQLoRA |
| 33B | 約66GB | 約20GB | 40GB GPUでもQLoRA推奨 |
| 70B | 約140GB | 約35GB | QLoRA必須 |
結論:収まるならLoRAを使用。収まらないならQLoRAを使用。どちらも優れた結果をもたらします!
実装:コード例
Hugging Faceのライブラリを使用して両方のアプローチを実装する方法を見てみましょう。
LoRA実装
from peft import LoraConfig, get_peft_model
from transformers import AutoModelForCausalLM, AutoTokenizer
# モデルとトークナイザーを通常通りロード
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-2-7b-hf")
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")
# LoRAを設定
lora_config = LoraConfig(
r=8, # ランク
lora_alpha=16, # スケーリング係数(2 × r)
target_modules=["q_proj", "v_proj"], # 適応する層
lora_dropout=0.05, # 正規化のためのドロップアウト
bias="none", # バイアス項を適応しない
task_type="CAUSAL_LM" # タスクタイプを指定
)
# モデルにLoRAアダプターを適用
model = get_peft_model(model, lora_config)
# 訓練可能なパラメータを確認
model.print_trainable_parameters()
# 出力: trainable params: 4,194,304 || all params: 6,742,609,920 || trainable%: 0.0622
以上です!モデルは標準的なトレーニングループで訓練する準備ができました。
QLoRA実装
次に、さらに大きなメモリ効率のためにQLoRAを適用する方法を見てみましょう。
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
import torch
# 4ビット量子化を設定
bnb_config = BitsAndBytesConfig(
load_in_4bit=True, # 4ビットロードを有効化
bnb_4bit_quant_type="nf4", # NF4量子化を使用
bnb_4bit_compute_dtype=torch.bfloat16, # bfloat16で計算
bnb_4bit_use_double_quant=True, # ダブル量子化を有効化
)
# 量子化してモデルをロード
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=bnb_config,
device_map="auto", # GPU間で自動的に分散
trust_remote_code=True
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-2-7b-hf")
# トレーニング用にモデルを準備(勾配チェックポイントなどを処理)
model = prepare_model_for_kbit_training(model)
# 以前と同じLoRA設定
lora_config = LoraConfig(
r=8,
lora_alpha=16,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
# 量子化されたモデルにLoRAアダプターを適用
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# 出力: trainable params: 4,194,304 || all params: 3,752,071,168 || trainable%: 0.1118
主な違いは、QLoRAはモデルをロードする際にBitsAndBytesConfigを追加し、LoRAを適用する前にprepare_model_for_kbit_training()を呼び出すことです。他はすべて同じです。
メモリの違いは?LoRAはこの70億パラメータのモデルに約16GBを使用し、QLoRAは約6GBを使用します。これが4ビット量子化の威力です!
よくある落とし穴とベストプラクティス
LoRAとQLoRAを使う際に注意すべき点をまとめます:
保守的に始めて、スケールアップ
常にr = 8、α = 16で始め、q_projとv_projをターゲットにします。これはほとんどのタスクで機能します。明確な不適合が見られる場合にのみ、ランクを増やすかモジュールを追加します。
ランクとアルファを独立して調整しない
ランクを16に増やす場合は、アルファを32に増やします。α/r比は約2のままにする必要があります。アルファを調整せずにランクを変更すると、パフォーマンスが低下することが多いです。
トレーニングの不安定性に注意
損失が不規則になったり急にスパイクしたりする場合、おそらくアルファが高すぎます。下げて学習率を確認してください。
小さなデータセットでの過学習
LoRAとQLoRAは、完全なファインチューニングと比較して過学習を自然に減らしますが、それでも発生する可能性があります。1000例未満のデータセットでは、ランクを低く(4~8)保ち、ドロップアウト(0.05~0.1)を使用します。
保存とロード
LoRAとQLoRAアダプターは小さい(通常10~50MB)です。アダプターの重みのみを保存し、後でベースモデルの上にロードできます。これにより、完全なモデルチェックポイントを配布するよりも、モデルの配布とバージョン管理がはるかに簡単になります。
まとめ:次のステップ
LoRAとQLoRA、大規模言語モデルをはるかに少ないメモリと計算能力でファインチューニングするための2つの強力な手法をマスターしました!これらの手法は、大規模モデルをニーズに合わせて効率的に適応させるための画期的なものです。
重要なポイントを振り返りましょう:
- LoRA:元の重みを凍結し、小さな低ランクアダプターを追加することで、パラメータの0.1%だけを訓練
- QLoRA:LoRAに4ビット量子化を組み合わせ、さらに大幅なメモリ削減を実現
- ハイパーパラメータ:r=8、α=16、q_proj/v_projターゲットがほとんどのタスクで有効
- 選択基準:モデルがメモリに収まるならLoRA、収まらないならQLoRA
来週は、今週のすべてを統合し、これらの技術を実際のタスクのためにモデルをファインチューニングすることに適用します。学んだことを実践的で影響力のある方法で適用する、実践的なプロジェクトの準備をしてくださいね!
構築を続けましょう!

コメント