一文要約: LoRAは全重みではなく重み更新の低ランク分解を学習する。QLoRAは凍結ベースモデルを4ビットに量子化して、同じ手法をRTX 3090でも動かせるようにする。
26.1 効率的なFine-Tuningが生まれた理由
26.1.1 Full Fine-Tuningの計算コスト
LLaMA-7Bをコードレビュー専用アシスタントにFine-Tuningしたいとします。素朴なアプローチ---Full Fine-Tuning---は70億の全パラメータを更新します。
実際にGPUメモリにかかるコストを計算してみましょう:
- モデル重み (fp16): 7B × 2バイト = 14 GB
- 勾配 (重みと同じサイズ): 14 GB
- オプティマイザ状態 (Adamはパラメータごとに2つのモーメントを保持): 28 GB
合計: アクティベーションやバッチデータに触れる前から56 GB。 スタートラインに立つだけでA100 80GBが必要です。複数のハイパーパラメータ設定を並列で試したければ、A100 80GBが複数台必要になります。
26.1.2 ストレージ問題
Fine-Tuningのバリアントごとに重みのフルコピーが生まれます:
- コードレビューアシスタント: 14 GB
- セキュリティ監査アシスタント: 14 GB
- ドキュメント作成アシスタント: 14 GB
3バリアントで42 GB。20個のダウンストリームアプリケーションを運用するチームなら、ほぼ同一のパラメータファイルが280 GBになります。馬鹿げていますよね。
26.1.3 鍵となる洞察
Fine-Tuningのダイナミクスを研究していた研究者たちが、一貫して気づいたことがあります: Fine-Tuning中の重み変化ΔWは低ランクである、ということです。モデルはすべてを学び直しているわけではありません。各重み行列の小さな部分空間を調整しているだけなのです。
ΔWが本質的に低ランクであれば、N×Dの完全な行列でパラメータ化する必要はありません。その積が同じランクを持つ2つの小さな行列で近似できます。この観察こそがLoRAです。
26.2 LoRAのコアアイデア: 低ランク分解
26.2.1 数学的な説明
任意の行列は2つの小さな行列の積で近似できます:
ΔW (N × D) ≈ B (N × r) @ A (r × D)
ここで r はランクであり、r ≪ min(N, D) です。
N=1024, D=512, r=32 の場合のパラメータ数の比較:
- 元の行列: 1024 × 512 = 524,288
- 低ランク: 32 × (1024 + 512) = 49,152 (約9.4%)
26.2.2 LoRAの学習設定
LoRAは事前学習済みの重みWを変更しません。Wを凍結し、更新分を別に学習します:
W_new = W_original + B @ A
学習中:
- W_originalは凍結---勾配なし、オプティマイザ状態なし
- BとAだけが更新される
推論中:
- 一度だけマージ:
W_merged = W_original + (α/r) × B @ A - W_mergedを元のモデルとまったく同じように使う
- マージ後の推論オーバーヘッドはゼロ
26.2.3 初期化戦略
行列B: ゼロ初期化
B = zeros(N, r)
行列A: ランダム初期化 (Kaiming)
A = randn(r, D) * sqrt(2 / r)
学習開始時、B @ A = zeros @ randn = 0 なので W_new = W_original になります。モデルは事前学習済みモデルの完全なコピーからスタートします。トレーニングショックなし、不安定性なし。これは慎重に設計されたプロパティです。
26.2.4 スケーリング係数アルファ
LoRAを含む完全なフォワードパス:
y = W @ x + (α/r) × B @ (A @ x)
α/r という項は、異なるランクの選択でも学習ダイナミクスを安定に保ちます。rが大きくなるにつれ、B @ A の積は大きくなりますが、α/r がそれを打ち消します。実践では:
- α = r で中立スケール (α/r = 1)
- α = 2r でLoRAの貢献度を強める
- HuggingFace PEFTのデフォルトは α = 8
26.3 ランクの選び方
26.3.1 ランクが制御するもの
ランク r は更新の「自由度」を決めます:
- r = 1-4: 学習可能パラメータが非常に少なく、シンプルなスタイルや形式の適応に適している
- r = 8-16: ほとんどの命令追従Fine-Tuningでコミュニティが使うスイートスポット
- r = 32-64: 複雑なドメイン適応には大きな容量が必要
- r > 64: ほとんど不要。小さなデータセットで過学習する可能性あり
26.3.2 実験データ: ランクと品質
元のLoRA論文がテキスト生成ベンチマーク (val_lossとダウンストリームメトリクス) で報告しています:
| ランク r | val_loss | BLEU | NIST | METEOR | ROUGE-L | CIDEr |
|---|---|---|---|---|---|---|
| 1 | 1.23 | 68.72 | 8.7215 | 0.4565 | 0.7052 | 2.4329 |
| 2 | 1.21 | 69.17 | 8.7413 | 0.4590 | 0.7052 | 2.4639 |
| 4 | 1.18 | 70.38 | 8.8439 | 0.4689 | 0.7186 | 2.5349 |
| 8 | 1.17 | 69.57 | 8.7457 | 0.4636 | 0.7196 | 2.5196 |
| 16 | 1.16 | 69.61 | 8.7483 | 0.4629 | 0.7177 | 2.4985 |
| 32 | 1.16 | 69.33 | 8.7736 | 0.4642 | 0.7105 | 2.5255 |
| 64 | 1.16 | 69.24 | 8.7174 | 0.4651 | 0.7180 | 2.5070 |
| 128 | 1.16 | 68.73 | 8.6718 | 0.4628 | 0.7127 | 2.5030 |
| 256 | 1.16 | 68.92 | 8.6982 | 0.4629 | 0.7128 | 2.5012 |
| 512 | 1.16 | 68.78 | 8.6857 | 0.4637 | 0.7128 | 2.5025 |
| 1024 | 1.17 | 69.37 | 8.7495 | 0.4659 | 0.7149 | 2.5090 |
3つのポイントに注目してください:
- r=4がいくつかの生成メトリクスでトップ。大きければ良いわけではない。
- val_lossはr=16以降で改善が止まる。
- r=512はr=4と生成品質は同等なのに、学習がずっと遅い。
26.3.3 実践的なランク選択
| タスク | 推奨ランク | 理由 |
|---|---|---|
| シンプルな命令追従 | 4-8 | タスクの複雑度が低い |
| 要約、翻訳 | 8-16 | 中程度の適応が必要 |
| コード生成、複雑な推論 | 16-64 | 高い容量が必要 |
| 不確かな場合 | 8 | 安全なデフォルト、コミュニティの慣行に沿っている |
私が使うルール: r=8から始めます。評価メトリクスが早すぎるか収束しない場合は、r=16かr=32を試します。実際のFine-Tuningでr=64を超える必要があったことはほとんどありません。
26.4 LoRAをどこに適用するか
26.4.1 Attentionプロジェクション
元のLoRA論文は各AttentionレイヤーのQ, K, V, 出力プロジェクションにアダプタを適用します:
# 各Attentionレイヤーに、Q, K, VプロジェクションにLoRAを追加:
lora_query_B = nn.Parameter(torch.zeros(d, r))
lora_query_A = nn.Parameter(torch.randn(r, d))
lora_key_B = nn.Parameter(torch.zeros(d, r))
lora_key_A = nn.Parameter(torch.randn(r, d))
lora_value_B = nn.Parameter(torch.zeros(d, r))
lora_value_A = nn.Parameter(torch.randn(r, d))
# 実効的な更新:
lora_Wq = lora_query_B @ lora_query_A
lora_Wk = lora_key_B @ lora_key_A
lora_Wv = lora_value_B @ lora_value_A
26.4.2 モデルファミリー別のターゲットモジュール
| モデル | 一般的なLoRAターゲット |
|---|---|
| LLaMA / Mistral | q_proj, k_proj, v_proj, o_proj, gate_proj, up_proj, down_proj |
| GPT-2 | c_attn, c_proj, c_fc |
| BERT | query, key, value, dense |
保守的な出発点: QとVプロジェクションのみ。ほとんどのタスクに有効。 推奨の拡張: 出力プロジェクションとKを追加。コストは限界的で、結果が改善することが多い。 積極的なオプション: 全FFN線形レイヤーを含める。最大容量だが、小データでの過学習リスクが高い。
HuggingFace PEFTの設定:
from peft import LoraConfig, TaskType
lora_config = LoraConfig(
r=8,
lora_alpha=16,
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"
],
lora_dropout=0.05,
bias="none",
task_type=TaskType.CAUSAL_LM,
)
26.4.3 学習可能パラメータの割合
上記の設定で典型的な7Bモデルの場合、約5%のパラメータが学習可能です:
- 凍結 (ベースモデル): 66.5Bパラメータ、勾配なし
- 学習可能 (LoRA): 約350Mパラメータ、完全な勾配フロー
これがメモリ要件を56 GBから扱いやすいサイズに下げる仕組みです。
26.5 LoRAの完全なコード
26.5.1 LoRA Fine-Tuning
from transformers import AutoModelForCausalLM, AutoTokenizer
from peft import LoraConfig, get_peft_model, TaskType
import torch
# 1. ベースモデルの読み込み
model_name = "meta-llama/Llama-2-7b-hf"
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="auto"
)
tokenizer = AutoTokenizer.from_pretrained(model_name)
# 2. LoRAの設定
lora_config = LoraConfig(
r=8,
lora_alpha=16,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"],
lora_dropout=0.05,
bias="none",
task_type=TaskType.CAUSAL_LM
)
# 3. LoRAの適用
model = get_peft_model(model, lora_config)
# 4. パラメータ数の確認
model.print_trainable_parameters()
# trainable params: 4,194,304 || all params: 6,742,609,920 || trainable%: 0.06%
26.5.2 学習ループ
from transformers import TrainingArguments, Trainer
training_args = TrainingArguments(
output_dir="./lora-output",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=2e-4,
fp16=True,
save_steps=100,
logging_steps=10,
)
trainer = Trainer(
model=model,
args=training_args,
train_dataset=train_dataset,
tokenizer=tokenizer,
)
trainer.train()
# アダプタの重みだけを保存 (14 GBではなく数十MB)
model.save_pretrained("./lora-weights")
26.5.3 読み込みとマージ
from peft import PeftModel
base_model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
torch_dtype=torch.float16,
device_map="auto"
)
# ベースモデルの上にアダプタを読み込む
model = PeftModel.from_pretrained(base_model, "./lora-weights")
# ゼロオーバーヘッドの推論のためにアダプタをベース重みにマージ
model = model.merge_and_unload()
26.6 QLoRA: コンシューマ向けハードウェアでのFine-Tuning
26.6.1 残ったボトルネック
LoRAは学習可能パラメータを約0.1%に削減します。しかし凍結されたベースモデルは依然としてGPUメモリを占有しています。7BモデルのFP16は14 GBを必要とし、LoRAアダプタやオプティマイザ状態を加える前からそうなのです。
24 GBのRTX 3090では、ベースだけで14 GBを消費するのでヘッドルームはほとんどありません。13Bや70Bではそもそも不可能です。
QLoRAの答え: 凍結ベースモデルを4ビットに量子化し、LoRAアダプタをフル精度で適用する。
QLoRA = 凍結ベース (4ビット int4) + 学習可能アダプタ (bf16/fp32)
26.6.2 メモリ比較
| 手法 | 7Bのメモリ | 実用的なGPU |
|---|---|---|
| Full Fine-Tuning (fp16) | 80 GB以上 | A100 80GB |
| LoRA (fp16ベース) | 16-20 GB | A100 40GB, RTX 4090 |
| QLoRA (4ビットベース) | 6-8 GB | RTX 3090, RTX 4080 |
ほとんどの実践者にとって、これが重要な数字です。QLoRAは本格的なFine-Tuningを1枚の24 GB GPUで可能にしました。
26.6.3 QLoRAの3つのイノベーション
元のQLoRA論文 (Dettmers et al., 2023) が導入したもの:
1. NF4 (NormalFloat4): ニューラルネットワークの重みの分布に特化した4ビットデータ型。標準のint4は一様分布を仮定しています。NF4は正規分布下での期待誤差を最小化するように16の量子化レベルを配置しており、重み行列の実際の形状に合っています。
2. ダブル量子化: 量子化定数自体を量子化します。64個の重みのブロックはスケール係数 (float32) を共有します。NF4はint4 (重みあたり0.5バイト) として保存されますが、スケール係数に余分なコストがかかります。ダブル量子化はスケール係数を8ビットに圧縮し、重みあたり平均約0.37ビット節約します。
3. ページングされたオプティマイザ: オプティマイザ状態 (Adamの1次・2次モーメント) は長いシーケンスでGPUメモリをオーバーフローさせることがあります。QLoRAはNVIDIAのユニファイドメモリを使ってこれらの状態をクラッシュではなく自動的にCPU RAMにページングします。
26.6.4 QLoRAのコード
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
import torch
# 1. 4ビット量子化の設定
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # NormalFloat4
bnb_4bit_compute_dtype=torch.float16, # 速度のためfp16で計算
bnb_4bit_use_double_quant=True, # ダブル量子化
)
# 2. 量子化されたベースモデルの読み込み
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-2-7b-hf",
quantization_config=bnb_config,
device_map="auto"
)
# 3. kビット学習の準備 (レイヤー正規化のキャスト、勾配チェックポインティングの有効化)
model = prepare_model_for_kbit_training(model)
# 4. フル精度でLoRAアダプタを適用
lora_config = LoraConfig(
r=8,
lora_alpha=16,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
# 学習は通常のLoRAと同じように進む
26.7 LoRAとFull Fine-Tuningの比較
26.7.1 品質の比較
| メトリクス | Full Fine-Tuning | LoRA (r=8) | LoRA (r=16) |
|---|---|---|---|
| タスク品質 | 100% (ベースライン) | 95-98% | 97-99% |
| 学習可能パラメータ | 100% | ~0.1% | ~0.2% |
| GPUメモリ | 非常に高い | 低い | 低い |
| 学習時間 | 遅い | 速い | 速い |
2-5%の品質ギャップはデータ品質の改善と丁寧なモジュール選択でかなり縮まります。ほとんどのプロダクションユースケースでは、このギャップは無視できます。
26.7.2 どちらを使うべきか
LoRA / QLoRAを使う場合:
- コンシューマまたはミッドティアのGPU (24 GB, 40 GB)
- ハイパーパラメータ設定での高速なイテレーション
- 1つのベースモデルを共有する複数のダウンストリームバリアント
- タスクが事前学習の分布に近い
Full Fine-Tuningを検討する場合:
- ターゲットドメインが事前学習から大きく乖離している (非常に専門的な語彙、根本的に異なる形式)
- A100またはH100のGPU時間が十分にある
- 最終的なプロダクション品質がコストを正当化し、数パーセントのギャップが重要な場合
26.7.3 アダプタエコシステムの利点
LoRAを使えば、1つのベースモデルが多くのアダプタをホストできます。数百MBのアダプタ重みを入れ替えるだけで、コードレビューアダプタ、ドキュメント作成アダプタ、セキュリティ監査アダプタを実行時に切り替えられます。14 GBのベースはロードしたままです。これがマルチテナントのFine-Tuningを経済的に実用的にするアーキテクチャパターンです。
26.8 よくある質問とベストプラクティス
26.8.1 ランクはどう決めればいい?
r=8から始めましょう。val lossが早すぎるタイミングでプラトーになるなら、r=16かr=32を試してください。実際にはr=64を超える必要はほとんどありませんでした。
26.8.2 アルファはどう設定すればいい?
- α = r は中立な選択
- α = 2r はLoRAが凍結重みに対してより強く影響する
- PEFTのデフォルト値はほとんどのタスクで問題ない
26.8.3 どのモジュールをターゲットにすべき?
保守的: QとVのみ。 推奨: Q、K、V、出力プロジェクション。 積極的: FFNを含む全線形レイヤー。
26.8.4 GPUサイズ別のLoRA / QLoRA選択
| GPU (VRAM) | モデル | 推奨 |
|---|---|---|
| RTX 3090 / 4090 (24 GB) | 7B | QLoRA |
| RTX 3090 / 4090 (24 GB) | 13B | QLoRA |
| A100 (40 GB) | 7B | LoRA |
| A100 (40 GB) | 13B | QLoRA |
| A100 (80 GB) | 7B-13B | LoRA |
| A100 (80 GB) | 70B | QLoRA |
26.8.5 学習が不安定。どうすれば?
最初に試すべきこと、順番通りに:
- 学習率を下げる (2e-4 → 1e-4)
- ウォームアップステップを追加する
- バッチサイズを下げ、勾配累積で補う
- データ品質を確認する---ノイズの多いラベルはFull Fine-Tuningよりもずっと早くLoRAを不安定にします。アダプタには平均化する容量が少ないからです
26.9 第26章まとめ
26.9.1 キーコンセプト
| コンセプト | 説明 | 主要な式 |
|---|---|---|
| LoRA | 低ランク適応。更新分だけ学習する | W = W_orig + (α/r) × B @ A |
| ランク (r) | アダプタの容量。パラメータ数を制御する | r は4-64の範囲 |
| アルファ (α) | スケール係数。学習ダイナミクスを一貫させる | 通常 r または 2r |
| QLoRA | 4ビットベース + フル精度のLoRAアダプタ | 7Bモデルで6-8 GB |
26.9.2 パラメータ数の計算式
N × D の重み行列の場合:
- 元の行列: N × D パラメータ
- LoRAアダプタ: N × r + r × D = r × (N + D) パラメータ
- 圧縮率: r × (N + D) / (N × D)
N=1024, D=512, r=8の場合: アダプタは元のパラメータ数の1.5%を保持。
26.9.3 初期化のまとめ
- B行列: ゼロ初期化 --- アダプタがno-opとして始まるように
- A行列: ランダム初期化 --- 最初のステップから勾配が流れるように
- 結果: 学習は事前学習済みモデルの完全なコピーから始まる
26.9.4 核心的な学び
LoRAは1つの経験的な観察の上に成り立っています: Fine-Tuningの重み変化は低ランクである。完全なΔWではなく2つの小さな行列BとAを学習することで、パラメータ更新コストを0.1%に抑えながら品質の95%以上を維持できます。QLoRAはこれをコンシューマ向けハードウェアに拡張し、凍結ベースモデルを4ビットで保存します。この2つが合わさって、本格的なFine-Tuningをデータセンター規模を超えたところで民主化しました。
チャプターチェックリスト
このチャプターを終えたら、以下ができるはずです:
- 7BモデルのFull Fine-Tuningに56 GB以上のGPUメモリが必要な理由を説明できる。
- LoRAの重み計算式を説明し、Bがゼロ初期化される理由を解説できる。
- ランクが何を制御するかを説明し、新しいタスクの開始値を推奨できる。
- QLoRAの3つのイノベーション (NF4、ダブル量子化、ページングされたオプティマイザ) を挙げられる。
- HuggingFaceライブラリを使った最小限のPEFT LoRAとQLoRAのセットアップを書ける。
- 利用可能なGPUメモリとモデルサイズに基づいてLoRAとQLoRAを選択できる。
次の章へ
これでモデル全体を再学習せずに大型モデルを適応させる方法を押さえました。
次の問いはシンプルですが、実装は難しいです: 学習後にモデルを安く動かすにはどうすればいいか?第27章ではGPTQ、AWQ、GGUFを扱います---モデルがGPUに収まるか、ラップトップで動くか、クラウドの請求書を左右する量子化フォーマットです。