LoRA・QLoRA・Full Fine-tuningの選定と実装ガイド
この記事でわかること
- Full Fine-tuning・LoRA・QLoRAの3手法の技術的な違いとGPUメモリ・精度・速度のトレードオフ
- LoRAのハイパーパラメータ(rank, alpha, target modules)の選び方と実験に基づく指針
- Hugging Face PEFT + TRLを使った実装コード(LoRA / QLoRA両対応)
- DoRAなどLoRA派生手法の位置づけと使い分け
- 本番環境でのアダプタデプロイ戦略(マージ vs 動的ロード)
対象読者
- 想定読者: LLMのFine-tuningをこれから始める、または手法選定に迷っているMLエンジニア
-
必要な前提知識:
- PyTorchの基本的な使い方(
torch.nn.Module、Trainerクラスなど) - Transformerアーキテクチャの基礎(Attention、線形層の役割)
- Hugging Face
transformersライブラリの基本操作
- PyTorchの基本的な使い方(
結論・成果
GPU予算とタスク要件で手法を選べば、7Bモデルでも6〜120GBの範囲でVRAM使用量をコントロールできます。 Sebastian Raschkaの数百回に及ぶ実験(出典)によると、LoRA(r=256, alpha=512, 全線形層適用)はFull Fine-tuningと同等以上の精度を約17GBのVRAMで達成しています。QLoRAはさらにメモリを33%削減し約14GBで動作しますが、訓練速度は39%低下します。
以下の表が手法選定の出発点になります。
| 手法 | 7B VRAM目安 | Full FT比の品質 | 訓練速度 | 主な用途 |
|---|---|---|---|---|
| Full Fine-tuning | 60〜120 GB | 100%(基準) | 基準 | 最高精度が必要な場合 |
| LoRA | 16〜21 GB | 90〜95% | 基準の約0.5倍 | 実験・プロダクション両方 |
| QLoRA | 6〜14 GB | 80〜90% | 基準の約0.36倍 | GPU制約が厳しい環境 |
Full Fine-tuning・LoRA・QLoRAの仕組みを理解する
3つの手法は「モデルのどの部分を、どの精度で更新するか」が異なります。ここではそれぞれの原理を整理します。
Full Fine-tuningの仕組み
Full Fine-tuningは事前学習済みモデルの全パラメータを対象データで更新する手法です。7Bモデルの場合、約70億個のパラメータすべてに勾配を計算し、オプティマイザの状態(AdamWならパラメータ数×2のモーメンタム)も保持する必要があります。
# Full Fine-tuningの概念コード
from transformers import AutoModelForCausalLM, TrainingArguments, Trainer
model = AutoModelForCausalLM.from_pretrained("meta-llama/Llama-3.1-8B")
# 全パラメータが学習対象(requires_grad=True)
trainable_params = sum(p.numel() for p in model.parameters() if p.requires_grad)
print(f"学習対象パラメータ: {trainable_params:,}") # 約80億
training_args = TrainingArguments(
output_dir="./full-ft-output",
per_device_train_batch_size=1, # VRAMが足りなければ1
gradient_accumulation_steps=16,
learning_rate=2e-5,
num_train_epochs=1, # 過学習防止のため1エポックが基本
bf16=True,
gradient_checkpointing=True, # メモリ節約
)
trainer = Trainer(model=model, args=training_args, train_dataset=dataset)
trainer.train()
メモリの内訳(7Bモデル、bf16の場合):
- モデルパラメータ: 約14 GB(7B × 2バイト)
- 勾配: 約14 GB
- オプティマイザ状態(AdamW): 約28 GB(パラメータ×4バイト×2)
- 合計: 約56 GB(+活性化メモリでさらに増加)
なぜFull Fine-tuningを選ぶか:
- タスク固有の精度が最優先で、GPU予算に制約がない場合
- ドメイン特化モデルを作り込む場合(医療、法律など)
制約条件:
Full Fine-tuningは7Bモデルでも実質的にA100 80GB×2台以上が必要です。H100を使っても1回の学習で数百ドルのコストがかかるため、実験の反復には向きません。
LoRAの仕組み
LoRA(Low-Rank Adaptation)は、事前学習済みモデルの重みを凍結し、各線形層に小さな低ランク行列ペア(A, B)を挿入する手法です。元の重み行列W(d×d)に対して、ΔW = B×A(d×r と r×d、r << d)だけを学習します。
核心的なアイデアは、Fine-tuning時の重み更新ΔWが低ランク構造を持つという仮説です。例えばrank=16であれば、7Bモデルの学習対象パラメータは全体の0.1〜0.3%程度(約400万〜2000万パラメータ)に抑えられます。
import torch
from peft import LoraConfig, get_peft_model
from transformers import AutoModelForCausalLM
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-8B",
torch_dtype=torch.bfloat16,
device_map="auto",
)
# LoRA設定
lora_config = LoraConfig(
r=64, # ランク: 低ランク行列の次元
lora_alpha=128, # スケーリング係数(alpha/r で適用)
target_modules="all-linear", # 全線形層に適用
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
# 例: trainable params: 83,886,080 || all params: 8,113,311,744 || trainable%: 1.034%
なぜ全線形層に適用するのか:
Raschkaの実験(出典)によると、従来のQuery・Value行列のみへの適用(学習パラメータ約420万)に対し、全線形層への適用(約2030万パラメータ)は性能を大幅に向上させます。メモリ増加は14.18 GB→16.62 GBと軽微です。
QLoRAの仕組み
QLoRA(Quantized LoRA)は、LoRAの技術にさらに3つのイノベーションを加えた手法です。
- 4-bit NormalFloat(NF4)量子化: 正規分布に最適化された4ビットデータ型で事前学習済みモデルを圧縮
- Double Quantization: 量子化の定数自体も量子化し、メモリをさらに削減(パラメータあたり約0.37ビット節約)
- Paged Optimizers: GPU↔CPUのメモリスワップでOOMを防止
from transformers import AutoModelForCausalLM, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model
import torch
# QLoRA用の4bit量子化設定
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4", # NormalFloat4を使用
bnb_4bit_compute_dtype=torch.bfloat16, # 計算はbf16で実行
bnb_4bit_use_double_quant=True, # Double Quantization有効化
)
model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-8B",
quantization_config=bnb_config,
device_map="auto",
)
# LoRAアダプタの設定はLoRAと同じ
lora_config = LoraConfig(
r=64,
lora_alpha=128,
target_modules="all-linear",
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)
model.print_trainable_parameters()
QLoRAのメモリ内訳(7Bモデルの場合):
- ベースモデル(4-bit): 約3.5 GB(7B × 0.5バイト)
- LoRAアダプタ(bf16): 約0.1〜0.5 GB
- オプティマイザ + 活性化: 約2〜10 GB
- 合計: 約6〜14 GB
QLoRAは量子化→逆量子化→計算→逆量子化のオーバーヘッドにより、LoRAと比べて約39%の訓練速度低下が報告されています(出典)。推論時の速度は影響を受けません(アダプタをマージして通常精度に戻すため)。
LoRAハイパーパラメータの選び方を実験データから学ぶ
LoRAの性能はハイパーパラメータに大きく依存します。ここでは数百回の実験結果に基づく知見を整理します。
rankの選び方
rankはLoRAの表現力を直接制御するパラメータです。大きいほど表現力が増しますが、パラメータ数・メモリ使用量も増加します。
| rank | 学習パラメータ(7B全線形層) | VRAMの目安 | 推奨シナリオ |
|---|---|---|---|
| 8 | 約800万 | 14〜15 GB | 簡単なタスク、高速実験 |
| 16 | 約1600万 | 15〜16 GB | 一般的な開始点 |
| 64 | 約6400万 | 16〜18 GB | 中〜高複雑度タスク |
| 128 | 約1.3億 | 17〜20 GB | 高複雑度タスク |
| 256 | 約2.6億 | 18〜22 GB | 最高精度が必要な場合 |
Raschkaの実験(出典)では、r=256が多くのベンチマークで最高スコアを記録しました。しかし、rankを上げすぎると過学習のリスクがあるため、バリデーションセットでの評価が必須です。
実践的な手順:
- r=16から開始し、ベースラインを確立
- r=64に上げて精度向上を確認
- 精度が飽和するポイントを見つける(多くの場合r=64〜128)
- バリデーション損失が上昇し始めたらrankを下げる
alphaの設定
alphaはLoRAの更新をスケーリングする係数です。実際の更新は ΔW × (alpha / rank) としてスケーリングされます。
基本ルール: alpha = 2 × rank(Lightning AIの実験で推奨)
ただし、Raschkaの実験ではr=256, alpha=128(alpha = 0.5 × rank)が最高精度を示したケースもあり、タスク依存であることに注意してください。
# 推奨設定パターン
configs = [
{"r": 16, "lora_alpha": 32}, # alpha = 2r(基本)
{"r": 64, "lora_alpha": 128}, # alpha = 2r(基本)
{"r": 256, "lora_alpha": 512}, # alpha = 2r(基本)
{"r": 256, "lora_alpha": 128}, # alpha = 0.5r(高rankで有効な場合も)
]
よくある間違い:
最初はalpha=1に固定してrankだけを上げていましたが、高rankでは収束が不安定になりました。alphaを適切にスケーリングしないと、勾配が極端に小さくなり学習が進みません。alpha/rankの比率を意識することが重要です。
学習率とオプティマイザ
UnslothのハイパーパラメータガイドおよびRaschkaの実験に基づく推奨値です。
| パラメータ | 推奨値 | 備考 |
|---|---|---|
| 学習率 | 1e-4〜3e-4 | QLoRAの場合は2e-4が多い |
| オプティマイザ | AdamW(8-bit) | SGDとの精度差は小さいがAdamWが安定 |
| スケジューラ | cosine | SGD使用時は特に有効 |
| エポック数 | 1〜3 | 過学習防止のため少なめ |
| warmup比率 | 0.03〜0.1 | 学習率の安定化 |
過学習への注意:
Raschkaの実験では、50Kサンプルで2エポック以上訓練すると性能が低下しました。特に算術タスクでは「基本的な算数を積極的にアンラーニングしている」兆候が見られたと報告されています。データセットの反復回数は慎重に設定してください。
target_modulesの選択
従来はQuery(q_proj)とValue(v_proj)のみにLoRAを適用するのが一般的でしたが、現在は全線形層への適用が推奨されています。
# 2024年以前の設定(非推奨)
old_config = LoraConfig(
target_modules=["q_proj", "v_proj"], # Attention Q/Vのみ
# ...
)
# 2025年〜の推奨設定
new_config = LoraConfig(
target_modules="all-linear", # 全線形層
# ...
)
Philipp Schmidの2025年ガイド(出典)でも lora_target_modules: "all-linear" が標準構成として採用されています。
SFTTrainerを使った実装の全体像を組み立てる
ここでは、Hugging Face TRL(公式ドキュメント)のSFTTrainerを使った実装例を示します。LoRAとQLoRAの切り替えが最小限のコード変更で可能な構成です。
完全な実装例
# train_lora.py
import torch
from datasets import load_dataset
from transformers import (
AutoModelForCausalLM,
AutoTokenizer,
BitsAndBytesConfig,
TrainingArguments,
)
from peft import LoraConfig
from trl import SFTTrainer
# --- 設定 ---
MODEL_NAME = "meta-llama/Llama-3.1-8B-Instruct"
USE_QLORA = True # False にするとLoRA(16-bit)モードになる
LORA_R = 64
LORA_ALPHA = 128
MAX_SEQ_LENGTH = 2048
OUTPUT_DIR = "./output"
# --- トークナイザの準備 ---
tokenizer = AutoTokenizer.from_pretrained(MODEL_NAME)
tokenizer.pad_token = tokenizer.eos_token
tokenizer.padding_side = "right"
# --- モデルの準備 ---
model_kwargs = {
"torch_dtype": torch.bfloat16,
"device_map": "auto",
"attn_implementation": "flash_attention_2", # 高速化
}
if USE_QLORA:
model_kwargs["quantization_config"] = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.bfloat16,
bnb_4bit_use_double_quant=True,
)
model = AutoModelForCausalLM.from_pretrained(MODEL_NAME, **model_kwargs)
# --- データセットの準備 ---
dataset = load_dataset("json", data_files="train.jsonl", split="train")
def format_chat(example):
"""チャット形式にフォーマット"""
messages = [
{"role": "system", "content": "あなたは有用なアシスタントです。"},
{"role": "user", "content": example["instruction"]},
{"role": "assistant", "content": example["output"]},
]
return {"text": tokenizer.apply_chat_template(messages, tokenize=False)}
dataset = dataset.map(format_chat)
# --- LoRA設定 ---
peft_config = LoraConfig(
r=LORA_R,
lora_alpha=LORA_ALPHA,
target_modules="all-linear",
lora_dropout=0.05,
bias="none",
task_type="CAUSAL_LM",
)
# --- トレーニング設定 ---
training_args = TrainingArguments(
output_dir=OUTPUT_DIR,
per_device_train_batch_size=4,
gradient_accumulation_steps=4, # 実効バッチサイズ = 16
learning_rate=2e-4,
num_train_epochs=1,
lr_scheduler_type="cosine",
warmup_ratio=0.05,
bf16=True,
gradient_checkpointing=True,
gradient_checkpointing_kwargs={"use_reentrant": False},
logging_steps=10,
save_strategy="steps",
save_steps=500,
optim="adamw_8bit" if USE_QLORA else "adamw_torch",
)
# --- トレーニング実行 ---
trainer = SFTTrainer(
model=model,
args=training_args,
train_dataset=dataset,
peft_config=peft_config,
max_seq_length=MAX_SEQ_LENGTH,
dataset_text_field="text",
)
trainer.train()
trainer.save_model(OUTPUT_DIR)
ライブラリバージョン(Philipp Schmid 2025ガイドに基づく):
transformers>=4.46.3peft>=0.13.2trl>=0.12.1bitsandbytes>=0.44.1accelerate>=1.1.1
Unslothによる高速化
Unslothは、LoRA/QLoRAの訓練を最適化するフレームワークで、カスタムカーネルにより2.7倍の高速化と74%のメモリ削減を報告しています(NVIDIA Blog)。
# Unslothを使った場合の例
from unsloth import FastLanguageModel
model, tokenizer = FastLanguageModel.from_pretrained(
model_name="unsloth/Meta-Llama-3.1-8B-Instruct",
max_seq_length=2048,
load_in_4bit=True, # QLoRAモード
)
model = FastLanguageModel.get_peft_model(
model,
r=64,
lora_alpha=128,
target_modules=[
"q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj",
],
lora_dropout=0,
bias="none",
)
なぜUnslothを検討すべきか:
- Hugging Face TRLとの互換性が高く、
SFTTrainerをそのまま使える - 同じGPUでバッチサイズを増やせるため、実効的なスループットが向上
- RTX 4090などの消費者GPUでも7Bモデルの学習が現実的になる
注意点:
Unslothは全GPUアーキテクチャに対応しているわけではなく、特にAMD GPUや古いCUDAバージョンでは動作しない場合があります。また、分散学習のサポートはエンタープライズ版に限定されています(2026年3月時点)。
LoRA派生手法を比較し使い分けを判断する
LoRAの登場以降、多くの派生手法が提案されています。ここでは実用上重要なものを整理します。
DoRA(Weight-Decomposed Low-Rank Adaptation)
DoRAはNVIDIA Researchが提案し、ICML 2024でOral採択された手法です。重みの更新を**大きさ(magnitude)と方向(direction)**に分解し、方向の更新にLoRAを使います。
DoRAの利点(NVIDIA公式ブログ):
- 低rankでもLoRAより高品質を維持(r=8でLoRAのr=32相当の性能)
- 推論時のオーバーヘッドなし(LoRAと同様にマージ可能)
- ドメイン適応(専門用語・文体の変更)で特に有効
# DoRAの設定(PEFT >= 0.13.0で対応)
from peft import LoraConfig
dora_config = LoraConfig(
r=16,
lora_alpha=32,
target_modules="all-linear",
use_dora=True, # DoRAを有効化
task_type="CAUSAL_LM",
)
手法比較表
| 手法 | 提案年 | 精度(低rank) | メモリ追加 | 推論オーバーヘッド | 成熟度 |
|---|---|---|---|---|---|
| LoRA | 2021 | 基準 | 基準 | なし(マージ後) | 高(プロダクション実績多数) |
| QLoRA | 2023 | LoRA比やや低下 | -33% | なし(マージ後) | 高(広く採用) |
| DoRA | 2024 | LoRA比向上 | +数% | なし(マージ後) | 中(PEFT対応済み) |
| AdaLoRA | 2023 | LoRA比向上 | +10〜20% | なし(マージ後) | 中 |
| rsLoRA | 2024 | 高rankで向上 | 同等 | なし(マージ後) | 中 |
手法選定の指針:
- まずLoRAから始めて、ベースラインを確立する
- GPU制約が厳しければQLoRAに切り替え
- 低rankで精度が不足する場合はDoRAを試す
- 高rankで性能が飽和する場合はrsLoRAを試す
本番デプロイ戦略を設計する
学習したLoRAアダプタを本番環境にデプロイする方法は、ユースケースに応じて大きく2つに分かれます。
アダプタマージ(単一タスク向け)
アダプタを元のモデルに統合し、通常のモデルとしてデプロイする方法です。
from peft import PeftModel
from transformers import AutoModelForCausalLM
# ベースモデルのロード
base_model = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-8B-Instruct",
torch_dtype=torch.bfloat16,
)
# アダプタの読み込みとマージ
model = PeftModel.from_pretrained(base_model, "./output")
merged_model = model.merge_and_unload()
# マージ済みモデルの保存
merged_model.save_pretrained("./merged-model")
マージのメリット:
- 推論時にPEFTライブラリが不要
- レイヤーごとの追加計算がなくなり、レイテンシが改善
- 通常のTransformersモデルと同じように扱える
マージのデメリット:
- モデル全体のサイズ(7Bなら約14GB)が必要
- 複数のアダプタを切り替えられない
動的アダプタローディング(マルチテナント向け)
ベースモデルを共有し、リクエストに応じてアダプタを動的に切り替える方法です。vLLM、LoRAX、TGI(Text Generation Inference)などの推論フレームワークが対応しています。
# vLLMでのマルチLoRAサービングの概念例
from vllm import LLM, SamplingParams
from vllm.lora.request import LoRARequest
llm = LLM(
model="meta-llama/Llama-3.1-8B-Instruct",
enable_lora=True,
max_lora_rank=64,
)
# リクエストごとに異なるアダプタを指定
output_customer_a = llm.generate(
"こんにちは",
sampling_params=SamplingParams(max_tokens=256),
lora_request=LoRARequest("customer_a", 1, "./adapters/customer_a"),
)
output_customer_b = llm.generate(
"こんにちは",
sampling_params=SamplingParams(max_tokens=256),
lora_request=LoRARequest("customer_b", 2, "./adapters/customer_b"),
)
動的ローディングのメリット:
- アダプタは数MB〜数十MBで、ベースモデルを共有するためGPUメモリ効率が高い
- 顧客ごとにパーソナライズされたモデルを提供できる
トレードオフ:
動的ローディングはContinuous Batchingと組み合わせることで効率的に運用できます。ただし、アダプタの切り替え頻度が高すぎると、キャッシュヒット率が下がりスループットが低下する可能性があります。Together AI(出典)は、数百のアダプタを同時にサービングするServerless Multi-LoRAを提供しています。
よくある問題と解決方法
| 問題 | 原因 | 解決方法 |
|---|---|---|
| OOM(Out of Memory)が発生する | バッチサイズが大きすぎる |
per_device_train_batch_size=1 + gradient_accumulation_stepsを増やす |
| 損失が下がらない | alpha/rank比が不適切 | alpha = 2 × rank を基本に調整。学習率を1e-4〜3e-4の範囲で試す |
| 検証損失が上昇する | 過学習 | エポック数を減らす(1〜2)。dropout=0.05〜0.1を追加 |
| QLoRAでNaN損失が出る | bf16と4bit量子化の相性問題 |
bnb_4bit_compute_dtype=torch.float16に変更。gradient_checkpointingを有効化 |
| アダプタマージ後に品質が低下する | マージ時の精度落ち |
model.merge_and_unload(safe_merge=True)を使用。マージ前後で推論結果を比較検証 |
| LoRA適用後もベースモデルと変わらない | 学習率が低すぎる、データが少なすぎる | 学習率を5e-4まで上げて実験。最低1000サンプル以上のデータを用意 |
まとめと次のステップ
まとめ:
- Full Fine-tuningは全パラメータを更新し最高精度だが、7Bモデルで60GB以上のVRAMが必要でコストが高い
- LoRAは低ランク行列で効率的にFine-tuningし、90〜95%の品質を16〜21GBで達成する
- QLoRAは4-bit量子化を組み合わせ、6〜14GBまでVRAMを削減するが訓練速度は39%低下する
- ハイパーパラメータはrank=16〜256、alpha=2×rank、全線形層適用が基本方針
-
DoRAは低rankでの精度向上に有効で、PEFTライブラリで
use_dora=Trueのみで利用可能 - 本番デプロイはアダプタマージ(単一タスク)と動的ローディング(マルチテナント)を使い分ける
次にやるべきこと:
- 自身のデータセットでLoRA(r=16)を試し、ベースラインを確立する
- 精度が不足すればrankを上げるか、DoRAに切り替えて比較する
- GPU制約がある場合はQLoRAに切り替え、訓練速度と精度のトレードオフを確認する
参考
- Sebastian Raschka - Practical Tips for Finetuning LLMs Using LoRA
- Hugging Face PEFT - LoRA Conceptual Guide
- Philipp Schmid - How to fine-tune open LLMs in 2025 with Hugging Face
- Lightning AI - Finetuning LLMs with LoRA and QLoRA: Insights from Hundreds of Experiments
- QLoRA: Efficient Finetuning of Quantized LLMs(原論文)
- LoRA: Low-Rank Adaptation of Large Language Models(原論文)
- NVIDIA - Introducing DoRA
- Unsloth - LoRA Hyperparameters Guide
- Together AI - Serverless Multi-LoRA
- Hugging Face - Making LLMs even more accessible with bitsandbytes, 4-bit quantization and QLoRA
注意: この記事はAI(Claude Code)により自動生成されました。内容の正確性については複数の情報源で検証していますが、実際の利用時は公式ドキュメントもご確認ください。