QLoRAを選ぶ理由

全パラメータ微調整(Full Fine-tuning)はモデルの全パラメータを更新する必要があります。70Bモデルの場合、FP16で140GB以上のGPUメモリが必要であり、8×A100(640GB)を使用してもテンソル並列が必要です。これはほとんどのチームにとって耐え難いコストです。LoRA(Low-Rank Adaptation)は、モデルの横に小さな低ランク行列を追加してパラメータ更新を近似することで、学習可能なパラメータを元の0.1%〜1%に削減し、メモリ要件を1/3に削減します。QLoRA(Quantized LoRA)はさらに進んで、ベースモデルを4ビット(NF4形式)に量子化してから、その上にLoRAを適用します。これにより、単一のRTX 4090(24GB)で13B規模のモデルを微調整できます。

QLoRAは品質を犠牲にした「貧者の解決策」ではありません。複数の研究により、適切に調整されたQLoRAは全パラメータ微調整の95%〜98%の性能を達成でき、一部のタスクでは量子化による正則化効果で全パラメータ微調整をわずかに上回ることさえあります。中小チームにとって、QLoRAは現在最もコストパフォーマンスの高い微調整手法です。

微調整データの準備

微調整データの品質は量よりもはるかに重要です。高品質で多様な1,000件の指示データは、低品質な10,000件よりも効果が上がることがよくあります。データ準備の要点:形式の標準化({"instruction":"...","input":"...","output":"..."}のJSONL形式を統一)、多様性の確保(モデルに期待するすべてのシナリオをカバー)、回答品質(各outputは人間によるレビューまたは高品質AIによる生成)、重複除去とノイズ除去(重複や明らかなエラーを削除)。500〜5,000件の指示データを推奨します。

# データ形式の例 (train.jsonl)
# {"instruction":"次の中国語を英語に翻訳してください","input":"人工智能正在改变世界","output":"Artificial intelligence is changing the world."}
# {"instruction":"次のコードの機能を説明してください","input":"def add(a,b): return a+b","output":"これは単純な加算関数で、2つの引数aとbを受け取り、それらの和を返します。"}

import json
from datasets import Dataset

def load_custom_dataset(jsonl_path):
    """カスタム指示データセットを読み込む"""
    data = []
    with open(jsonl_path, 'r', encoding='utf-8') as f:
        for line in f:
            item = json.loads(line.strip())
            text = f"### 指示:\n{item['instruction']}\n### 入力:\n{item['input']}\n### 出力:\n{item['output']}"
            data.append({"text": text})
    return Dataset.from_list(data)

dataset = load_custom_dataset("train.jsonl")
print(f"{len(dataset)} 件のトレーニングデータを読み込みました")

QLoRA微調整の実装

unslothまたは標準のtransformers + bitsandbytes + peftライブラリを使用してQLoRA微調整を行います。unslothは速度とメモリ効率に深く最適化されており、最初の選択肢として推奨されます。

# PEFT + bitsandbytesを使用したQLoRA微調整
import torch
from transformers import AutoTokenizer, AutoModelForCausalLM, BitsAndBytesConfig
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
from datasets import Dataset

model_name = "deepseek-ai/DeepSeek-V2-Lite"

# 4ビット量子化設定
bnb_config = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
    bnb_4bit_use_double_quant=True
)

# 量子化モデルの読み込み
tokenizer = AutoTokenizer.from_pretrained(model_name)
tokenizer.pad_token = tokenizer.eos_token
model = AutoModelForCausalLM.from_pretrained(
    model_name,
    quantization_config=bnb_config,
    device_map="auto",
    trust_remote_code=True
)
model = prepare_model_for_kbit_training(model)

# LoRA設定
lora_config = LoraConfig(
    r=16,  # LoRAランク、大きいほど表現力が高いがパラメータが増える(推奨8-32)
    lora_alpha=32,  # LoRAスケーリング係数(通常rの2倍)
    target_modules=["q_proj","k_proj","v_proj","o_proj","gate_proj","up_proj","down_proj"],
    lora_dropout=0.05,
    bias="none",
    task_type="CAUSAL_LM"
)
model = get_peft_model(model, lora_config)
print(f"学習可能なパラメータ: {model.print_trainable_parameters()}")

# トレーニング引数(簡略版、実際はTrainerを使用)
# from transformers import TrainingArguments, Trainer
# training_args = TrainingArguments(
#     output_dir="./qlora-deepseek",
#     per_device_train_batch_size=4,
#     gradient_accumulation_steps=4,
#     learning_rate=2e-4,
#     num_train_epochs=3,
#     logging_steps=10,
#     save_strategy="epoch",
#     bf16=True,
# )
# trainer = Trainer(model=model, args=training_args, train_dataset=dataset)
# trainer.train()

# 保存と読み込み
# model.save_pretrained("./ql

# アダプターを保存 model.save_pretrained("./qlora-adapter") # tokenizer.save_pretrained("./qlora-adapter")

ファインチューニングのハイパーパラメータ調整

QLoRAの主要なハイパーパラメータと推奨範囲:LoRA Rank(r):8-32。rが大きいほど表現力は高まりますが過学習のリスクも増加し、一般的には16が良い出発点です。LoRA Alpha:通常はrの2倍に設定します(例:r=16ならalpha=32)。学習率:2e-4から5e-4。QLoRAは全パラメータのファインチューニングよりも高い学習率を使用できます。エポック数:3-5回。小規模データセット(<1000件)では5-10回でも良いですが、過学習に注意してください。バッチサイズ:GPUメモリに制限されるため、通常はバッチサイズ4-8と勾配累積ステップ4-8を組み合わせて、より大きなバッチサイズをシミュレートします。ターゲットモジュール:DeepSeekモデルの場合、すべての線形層(q/k/v/o/gate/up/down proj)を含めることをお勧めします。より良い適合が得られます。

評価とデプロイ

ファインチューニング後は体系的な評価が必要です:保持したテストセットでベースモデルとファインチューニングモデルの効果を比較します。人間による評価(アノテーターにベースモデルとファインチューニングモデルの出力をブラインド評価させ、勝率を計算します)。破滅的忘却が発生していないか確認します(汎用能力テストセットで性能が大幅に低下していないか評価)。デプロイ時、LoRAアダプターは数十MBしかなく、ベースモデルと分離して保存できます。推論時にはvLLMのLoRA動的ロード機能を使用して、同じ推論サービス上で異なるユーザーに異なるLoRAアダプターをロードできます。

破滅的忘却の検出と予防

ファインチューニングの最大のリスクの1つは「破滅的忘却」です。モデルが新しいタスクに適応する一方で、元の汎用能力を失います。例えば、モデルをカスタマーサービスエキスパートにファインチューニングした後、コード生成を忘れてしまう可能性があります。予防策:トレーニングデータに5%-10%の汎用能力データ(翻訳、要約、コード生成など)を混ぜて、モデルの基本能力を維持します。より小さい学習率と少ないトレーニングエポックを使用します(過学習よりも過学習不足を優先)。ファインチューニング中に定期的に汎用テストセットで性能を評価し、劣化の兆候が見られたらトレーニングを停止します。LoRAのマージと長期メンテナンス:ファインチューニング後、LoRAアダプターをベースモデルとマージするか、分離したままにするかを選択できます。マージの利点は、推論時に追加のアダプターをロードする必要がなく、よりシンプルです。分離したままの利点は、異なるアダプターを簡単に切り替えられることです(1つのベースモデル+複数のアダプターで、複数のファインチューニングバージョンを提供するシナリオに適しています)。ビジネスニーズの変化に応じて、ファインチューニングモデルは定期的な更新が必要になる場合があります。「毎月のファインチューニング」のリズムを確立し、毎月追加された高品質なデータで再トレーニングまたは継続トレーニングすることをお勧めします。また、すべての過去バージョンのアダプターを保持し、問題が発生した場合に迅速にロールバックできるようにします。

マルチタスクファインチューニング戦略

多くの実際のシナリオでは、モデルが複数のタスクを同時に得意とする必要があります。例えば、意図認識、感情分析、エンティティ抽出などです。いくつかのマルチタスクファインチューニング戦略があります:データ混合戦略(すべてのタスクのデータを混ぜてトレーニングする。最もシンプルですが、タスク間の干渉を引き起こす可能性があります)、タスク識別戦略(各データの指示にタスクタイプの識別子を追加します。例:「【意図認識】以下のユーザーメッセージの意図を判断してください」)、およびマルチアダプター戦略(各タスク用に独立したLoRAアダプターをトレーニングし、推論時にタスクタイプに応じて対応するアダプターをロードします)。タスクの差が大きいシナリオ(コード生成+カスタマーサービスなど)では、マルチアダプター戦略をお勧めします。タスクが類似しているシナリオ(感情分析+意図認識など)では、データ混合戦略で通常十分です。

まとめ:QLoRAファインチューニングはモデルカスタマイズのハードルを大幅に下げますが、「ファインチューニングできる」ことは「うまくファインチューニングできる」ことを意味しません。成功するファインチューニングには、高品質なデータ、適切なハイパーパラメータの選択、厳密な評価システム、明確な最適化目標が必要です。小規模な実験(100-500件のデータ、1-2エポック)から始めて、方向性を検証してから規模を拡大することをお勧めします。ファインチューニングは実践的な技術です。多く試し、多く比較し、多く記録して、自分自身のファインチューニング経験を蓄積してください。

このスキルチェーンを自分で編成してみませんか?

スキルチェーンで開く →