ChatGPTやClaude、Llamaといった大規模言語モデル(LLM)は汎用的な能力を持っていますが、特定のドメインや業務に特化させるにはファインチューニングが必要です。例えば、社内の技術文書に基づいたQ&A;ボット、特定の文体でのコンテンツ生成、専門用語を正確に扱うタスクなどでは、ベースモデルをそのまま使うより、ファインチューニングを施したモデルのほうが遥かに高い精度を発揮します。
しかし、LLMのフルファインチューニングは莫大なGPUメモリを必要とします。70億パラメータのモデルでさえ、フルファインチューニングにはA100 80GBが複数枚必要です。この課題を解決するのが、LoRA(Low-Rank Adaptation)とQLoRA(Quantized LoRA)です。
LoRAは、モデルの重み行列を低ランク行列の積に分解して、追加の小さな行列だけを学習する手法です。元のモデルの重みは凍結したまま、学習可能なパラメータ数を大幅に削減します。通常、全パラメータの1%未満の追加パラメータだけで、フルファインチューニングに匹敵する性能を達成できます。
# 必要なライブラリのインストール
pip install transformers peft datasets accelerate bitsandbytes
PEFTライブラリを使ったLoRAの設定例を見てみましょう。
from peft import LoraConfig, get_peft_model, TaskType
from transformers import AutoModelForCausalLM, AutoTokenizer
# ベースモデルの読み込み
model_name = "meta-llama/Llama-2-7b-hf"
tokenizer = AutoTokenizer.from_pretrained(model_name)
model = AutoModelForCausalLM.from_pretrained(
model_name,
torch_dtype=torch.float16,
device_map="auto"
)
# LoRAの設定
lora_config = LoraConfig(
task_type=TaskType.CAUSAL_LM,
r=16, # ランクの次元数
lora_alpha=32, # スケーリング係数
lora_dropout=0.05, # ドロップアウト率
target_modules=["q_proj", "v_proj", "k_proj", "o_proj"]
)
# LoRAモデルの作成
peft_model = get_peft_model(model, lora_config)
peft_model.print_trainable_parameters()
重要なハイパーパラメータはr(ランク)とlora_alphaです。ランクが大きいほど表現力は増しますが、メモリ消費も増えます。私の経験では、多くのタスクでr=16からr=64の範囲が最適です。target_modulesにはAttention層のプロジェクション行列を指定するのが一般的です。
QLoRAは、LoRAに4bit量子化を組み合わせた手法です。ベースモデルを4bitに量子化することで、GPUメモリの使用量をさらに削減します。70億パラメータのモデルでも、16GBのGPU1枚でファインチューニングが可能になります。
from transformers import BitsAndBytesConfig
import torch
# 4bit量子化の設定
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(
model_name,
quantization_config=bnb_config,
device_map="auto"
)
# LoRA設定を適用(QLoRA)
peft_model = get_peft_model(model, lora_config)
bnb_4bit_quant_type="nf4"は正規分布に基づく4bit量子化で、均一量子化よりも精度劣化が小さいことが知られています。bnb_4bit_use_double_quant=Trueは量子化パラメータ自体もさらに量子化する二重量子化で、追加のメモリ削減を実現します。
ファインチューニングの成否は、学習データの品質に大きく依存します。データの形式はタスクに応じて異なりますが、指示応答形式が最も汎用的です。
from datasets import Dataset
from transformers import TrainingArguments, Trainer
# データセットの準備例
train_data = [
{"instruction": "Pythonでリスト内包表記を説明してください",
"output": "リスト内包表記は..."},
# ... 他のデータ
]
dataset = Dataset.from_list(train_data)
# トレーニング引数の設定
training_args = TrainingArguments(
output_dir="./results",
num_train_epochs=3,
per_device_train_batch_size=4,
gradient_accumulation_steps=4,
learning_rate=2e-4,
warmup_ratio=0.03,
logging_steps=10,
save_strategy="epoch",
fp16=True,
optim="paged_adamw_8bit"
)
# トレーナーの作成と実行
trainer = Trainer(
model=peft_model,
args=training_args,
train_dataset=dataset,
tokenizer=tokenizer
)
trainer.train()
学習率は2e-4前後が出発点として適切です。paged_adamw_8bitオプティマイザはメモリ効率が良く、QLoRAとの相性が優れています。
ファインチューニング後のモデル評価では、定量的指標と定性的評価の両方が重要です。定量的にはPerplexityやタスク固有のベンチマークを使用しますが、実務では人間による評価も不可欠です。特にLLMの出力品質は自動指標だけでは捉えきれない部分が多いため、実際のユースケースに沿ったサンプルで手動評価を行いましょう。
LoRAとQLoRAは、限られたハードウェアリソースでLLMをカスタマイズするための強力な手法です。GPU予算が潤沢ならLoRA、コンシューマGPUで実験したいならQLoRAが適しています。どちらの場合も、高品質な学習データの準備に最も多くの時間を投じるべきです。データの量より質を重視し、数百件の厳選されたデータから始めることを推奨します。