Python LLM ファインチューニング深掘り(2026):QLoRA 全工程
クイックプレビュー
7B モデルの全パラメータ微調には A100 4 枚が必要——大多数のチームには非現実的。QLoRA は 4-bit 量子化・低ランク適応・ページ化オプティマイザを組み合わせ、単一の民生用 GPUで大規模モデルを微調可能にする。
本記事は NF4 量子化理論から PEFT 設定、TRL 学習、vLLM 展開までの再現可能な全工程を提供する。全ステップにコード・データ・注意点付き。
深層原理:LoRA と QLoRA の実際
LoRA:ベース凍結、バイパスのみ学習
LoRA は事前学習済み重み W を変更しない。各注意層の脇に 2 つの学習可能な低ランク行列 A、B を挿入する:
h = W·x + (B·A)·x
^^^^ ^^^^^^^^^
凍結 学習可能(r << d、通常 8–64)
逆伝播時は A と B のみが非ゼロ勾配を持つ——W は常にゼロ(凍結)。最適化状態は A と B だけ追跡すればよく W 全体は不要。これがメモリ効率の根本理由。
初期化テクニック:A を Kaiming/Xavier 正規分布で、B を全ゼロで初期化。学習開始時 BA=0 で、モデルの初期出力は元の事前学習モデルと完全一致——学習安定性を保証する。
7B モデルの場合、学習可能パラメータは全体の 0.1%–0.5% に抑えられる。
QLoRA:ベースも圧縮
QLoRA は凍結ベース重み W を 4-bit NormalFloat(NF4) に量子化する。
NF4 が INT4 に勝つ理由:INT4 は均等間隔の均一量子化で、重みが一様分布すると仮定する。しかし実際の事前学習済み重みはゼロ平均正規分布に近似——平均付近に集中し、両端は疎。NF4 は情報理論的に最適な非均一量子化であり、確率密度が高い領域により多くの量子化レベルを、裾野には少なく割り当てる。実験では INT4 比で情報損失が約 1/2–1/3。
二重量子化:スケーリング係数(FP32)自体を FP8 に再量子化し、約 0.4 bit/パラメータ追加節約。
ページ化オプティマイザ:VRAM 逼迫時に optimizer state を固定サイズのページに分割し、CPU-GPU 間で自動ページング——OS の仮想メモリ機構に類似。
VRAM 計算
Llama 3.1-8B、BF16 約 16 GB:
| 構成要素 | 全パラメータ | QLoRA |
|---|---|---|
| モデル重み | 16 GB (BF16) | ~3.8 GB (NF4) |
| Optimizer (AdamW) | 32 GB (m+v) | ~0.08 GB |
| 勾配 | 16 GB | ~0.08 GB |
| 活性値 (batch=4,seq=2048) | ~12 GB | ~12 GB |
| 合計 | ~76 GB ❌ | ~16 GB ✅ |
環境構築
pip install torch transformers accelerate peft bitsandbytes trl datasets tensorboard
バージョン注意:bitsandbytes は CUDA バージョンと一致させる。trl 0.9+ は SFTConfig 使用。Windows は WSL2 推奨。
4-bit 量子化モデル読込
import torch
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
bnb_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(
"meta-llama/Llama-3.1-8B",
quantization_config=bnb_config, device_map="auto", torch_dtype=torch.bfloat16,
)
tokenizer = AutoTokenizer.from_pretrained("meta-llama/Llama-3.1-8B")
tokenizer.pad_token = tokenizer.eos_token
LoRA 設定
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
model = prepare_model_for_kbit_training(model)
lora_config = LoraConfig(
r=16, lora_alpha=32, lora_dropout=0.05,
target_modules=["q_proj", "k_proj", "v_proj", "o_proj",
"gate_proj", "up_proj", "down_proj"],
bias="none", task_type="CAUSAL_LM",
)
model = get_peft_model(model, lora_config)
# trainable: ~42M / ~8B = 0.52%
ハイパーパラメータガイド
| パラメータ | 推奨 | 根拠 |
|---|---|---|
r |
8–32 | ランク。r=8 で ΔW を 8 基底ベクトルで表現。多くのタスクで十分。32 超で収穫逓減。 |
lora_alpha |
r×1–2 | 実効学習率 ∝ alpha/r。r=16, alpha=32 でスケーリング 2.0。 |
lora_dropout |
0.05 | 正則化。小データ(<500)→0.1。大データ→0。 |
target_modules |
多いほど良 | Llama: q/k/v/o+gate/up/down。他アーキテクチャは state_dict 確認。 |
データセット準備
from datasets import load_dataset
dataset = load_dataset("tatsu-lab/alpaca")
def format_instruction(example):
prompt = f"### Instruction:\n{example['instruction']}\n\n### Response:\n{example['output']}"
return {"text": prompt}
dataset = dataset.map(format_instruction)
💡 200–500 件で検証 → loss 低下確認 → 2k–5k で効果実感 → 10k+ で競争力。品質 >> 量。
学習:TRL SFTTrainer
from trl import SFTTrainer, SFTConfig
training_args = SFTConfig(
output_dir="./qlora-llama3-chat",
per_device_train_batch_size=4, gradient_accumulation_steps=4,
num_train_epochs=3, max_seq_length=2048,
logging_steps=10, save_steps=200,
learning_rate=2e-4, lr_scheduler_type="cosine", warmup_ratio=0.03,
optim="paged_adamw_8bit", bf16=True, gradient_checkpointing=True,
report_to="tensorboard",
)
trainer = SFTTrainer(model=model, args=training_args,
train_dataset=dataset["train"], tokenizer=tokenizer)
trainer.train()
tensorboard --logdir ./qlora-llama3-chat で監視:loss(0.5–1.5 に安定低下)、grad_norm(0.1–1.0 が理想)、GPU 利用率(100% 付近)。
評価
from peft import PeftModel
base = AutoModelForCausalLM.from_pretrained(
"meta-llama/Llama-3.1-8B", torch_dtype=torch.bfloat16, device_map="auto"
)
base = PeftModel.from_pretrained(base, "./qlora-llama3-chat/checkpoint-600")
base = base.merge_and_unload()
def generate(prompt, max_new=256):
inputs = tokenizer(prompt, return_tensors="pt").to("cuda")
outputs = base.generate(**inputs, max_new_tokens=max_new, temperature=0.7)
return tokenizer.decode(outputs[0], skip_special_tokens=True)
定量的には GPT-4 を judge に、正確性・完全性・有用性を 1–5 評価。平均 > 4.0 で合格。
保存と展開
trainer.save_model("./qlora-llama3-chat-final") # 約 60 MB、アダプタのみ
model = PeftModel.from_pretrained(base, "./qlora-llama3-chat-final")
model = model.merge_and_unload()
model.save_pretrained("./merged-model")
vLLM
python -m vllm.entrypoints.openai.api_server \
--model ./merged-model --dtype bfloat16 --max-model-len 4096 --port 8000
FAQ
Q1:QLoRA と全パラメータの品質差?——多くのタスクで 96–99%。極端な場合のみ全パラメータ必要。
Q2:r=8 vs r=64?——中規模データで r=8 十分。r に計算資源を浪費する前にデータ品質を。
Q3:複数アダプタ切替?——model.set_adapter() ミリ秒切替、ベース再ロード不要。
Q4:loss 低いが回答悪い?——プロンプトテンプレート過適合。多様性追加・epoch 減・dropout 増。
Q5:CPU 学習?——極めて遅い。Colab 無料 T4 / Kaggle 無料 P100 推奨。
結論
推奨パス:200 件検証 → TensorBoard 調整 → 手動テスト → データ追加(r ではない) → merge → vLLM 展開。