# Llama ファインチューニング実践：LoRA/QLoRA で自社データに特化させ本番投入する

> オープンウェイトの強みは『重みを自社データで微調整できる』こと。Llama を LoRA/QLoRA でファインチューニングする手順を、まず『本当に必要か（RAG vs FT）』の判断から、データ準備・torchtune/TRL実装・評価ゲート・マージ＆デプロイ・ライセンス命名規約まで、本番運用の観点で実コードで解説します。

- 公開日: 2026-06-25
- 著者: 友田 陽大
- タグ: Llama, ファインチューニング, LoRA, 生成AI, AWS Bedrock, Python, MLOps
- URL: https://tomodahinata.com/blog/llama-fine-tuning-lora-qlora-production-guide

## 要点

- 微調整の前に疑う：知識の追加は RAG、振る舞い・出力形式・口調・専門語彙の固定が FT。多くのケースは RAG＋プロンプトで足りる
- 現実的な対象は密モデル（Llama 3.3 8B/70B 等）。LoRA/QLoRA なら 4bit 量子化で単一GPUでも回せる。Llama 4 MoE のフルFTは別物の重さ
- 成否の9割はデータ。instruction 形式の JSONL・品質優先・重複排除・train/eval 分割・リーク防止を設計する
- 公式は torchtune（PyTorchネイティブ）。HF TRL+PEFT も定番。マネージドは Bedrock（Llama 2/3.2）/ SageMaker＋Custom Model Import
- 評価ゲート無しに出さない。FT前後をホールドアウトで比較し回帰を検出。配布する派生モデル名は『Llama』接頭辞が必須（ライセンス）

---

## この記事のゴール

[Llama を本番投入する全体像](/blog/meta-llama-open-weight-llm-production-guide)で述べたとおり、オープンウェイトを選ぶ最大の理由のひとつが「**重みを自社データで微調整（ファインチューニング, FT）できる**」ことです。本稿はその FT を、**デモではなく本番**——再現性・評価・コスト・ライセンスまで——の観点で、実コードで最後まで通します。

読み終えたとき、次の3つができる状態を目指します。

1. **そもそも FT が必要かを判断**できる（多くの場合は RAG で足りる、を含めて）。
2. **LoRA/QLoRA で実際に回せる**（torchtune と Hugging Face TRL の両経路）。
3. **評価ゲートを通してから安全にデプロイ**でき、**ライセンスの命名規約**まで守れる。

> **信頼性の開示**：私は AWS Bedrock / Vercel AI SDK を土台に生成AIシステムを本番運用しており、ドメイン特化（型番・専門語彙・出力フォーマットの固定）は[音声接客の事例](/blog/production-voice-ai-sales-agent-bedrock-pgvector)でも中心的な課題でした。本稿は「**FT は最後の手段**」という実務の順序に忠実です。盛った精度向上の数字は出しません。

---

## まず疑う：その課題、本当にFTが要りますか？

FT は強力ですが、**最初に手を出すべき道具ではありません**。コスト・運用負荷・陳腐化リスクが大きいからです。次の順で検討してください。

| 手法 | 効くもの | 効かないもの | コスト/運用 |
| --- | --- | --- | --- |
| **プロンプト/Few-shot** | 形式の指示、簡単な振る舞い | 大量の知識、強い様式固定 | 最小 |
| **RAG（検索拡張）** | **最新知識・社内文書の参照**、出典提示 | 口調・出力様式の根本変更 | 中（[pgvector RAG](/blog/pgvector-postgres-production-rag-hybrid-search)） |
| **ファインチューニング** | **振る舞い・出力形式・口調・専門語彙の固定**、レイテンシ短縮 | 知識の鮮度（学習時点で凍結） | 大（データ・学習・評価・再学習） |

**鉄則**：
- **知識を足したい** → まず **RAG**。モデルの中に事実を焼き込むのは陳腐化と幻覚の温床。
- **いつも同じ形式・口調・判断で答えさせたい**、**プロンプトが長くなりすぎた**、**専門語彙の取りこぼしを消したい** → **FT** の出番。
- 実務では **RAG ＋ 軽い FT** の併用が最も費用対効果が高い（知識は RAG、様式は FT）。

> 💡 「FT すれば賢くなる」は誤解です。FT が変えるのは主に**振る舞いの分布**であって、**新しい事実の信頼できる記憶**ではありません。事実は RAG に持たせるのが本番の定石です。

---

## 何を、どう微調整するか（現実的な対象とLoRA/QLoRA）

### 対象モデル：まず密モデルから

[Llama 4 は MoE](/blog/meta-llama-open-weight-llm-production-guide#llama-4-とは何かネイティブマルチモーダル-moe)で総パラメータが巨大（Scout 109B / Maverick 400B）です。これを**フルFT**するのは相当な分散学習基盤を要し、最初の一歩には向きません。**実務の現実的な対象は密（dense）モデル**——**Llama 3.3 8B / 70B** や用途特化の小型版——で、ここに **LoRA/QLoRA** を当てるのが王道です。

### LoRA と QLoRA を一言で

- **LoRA（Low-Rank Adaptation）**：巨大な重みを凍結し、**小さな低ランク行列だけを学習**する。更新対象がごく一部なので、**省メモリ・高速・成果物（アダプタ）が数十MB**。
- **QLoRA**：ベースモデルを **4bit 量子化**して載せ、その上で LoRA を学習する。**単一GPUでも 70B 級に手が届く**のが利点。精度劣化は実用上小さいことが多い。

| 手法 | メモリ | 速度 | 成果物 | 向く場面 |
| --- | --- | --- | --- | --- |
| フルFT | 最大 | 最遅 | モデル全体 | 大規模・基盤を作り替える |
| **LoRA** | 小 | 速 | アダプタ（小） | 標準。様式・語彙の固定 |
| **QLoRA** | **最小** | 速 | アダプタ（小） | **単一GPUで大きめモデル** |

---

## データ準備：成否の9割はここ

FT の品質は**データの品質で決まります**。モデルやハイパラの差は誤差です。

- **形式**：instruction/chat 形式の **JSONL**。1行＝1サンプル。
- **品質 > 量**：ノイズだらけの10万件より、**手で磨いた1,000件**。誤りは“正しく学習されてしまう”。
- **重複排除**：near-dup を除く。同じ例の重複は過学習を招く。
- **train/eval 分割**：必ず**評価用を切り分ける**。これを混ぜると「自分の宿題を採点」する状態（リーク）になり、精度を錯覚する。
- **多様性**：本番で来る入力分布をカバーする。エッジケース・断り方（「分かりません」）も学習対象に入れる。

```json
{"messages":[
  {"role":"system","content":"あなたは当社の見積もりアシスタント。数値は推測せず、無い情報は『不明』と返す。"},
  {"role":"user","content":"型番 TX-200 の標準納期は？"},
  {"role":"assistant","content":"TX-200 の標準納期は5営業日です。在庫状況により前後します。"}
]}
```

> ⚠️ **リークは“静かに”精度を盛る最悪のバグ**。評価セットの一部が学習に混ざると、ベンチは上がるのに本番で滑ります。**分割は最初に、決定的に**（ハッシュで振り分ける等）行ってください。

---

## 実装A：torchtune（PyTorchネイティブ・公式）

Meta/PyTorch 公式の **[torchtune](https://github.com/meta-pytorch/torchtune)** は、「**トレーナも抽象も挟まない、素のPyTorch**」が思想です。YAML で recipe を設定し、CLI で回します。

```bash
pip install torchtune

# 重みを取得（ライセンス同意済みのHFアカウントが必要）
tune download meta-llama/Llama-3.3-70B-Instruct \
  --output-dir ./Llama-3.3-70B-Instruct

# LoRA で単一デバイス学習（recipe と config を指定するだけ）
tune run lora_finetune_single_device \
  --config llama3_3/70B_lora_single_device \
  dataset.source=json \
  dataset.data_files=./data/train.jsonl
```

torchtune は config に学習率・LoRA rank・エポック・量子化などが**宣言的にまとまっており**、差分管理（誰がどの設定で回したか）が効きます。これは再現性＝本番運用の生命線です。

---

## 実装B：Hugging Face TRL + PEFT（QLoRA）

最も普及している経路。`TRL` の `SFTTrainer` に `PEFT` の `LoraConfig` を渡すだけで QLoRA になります。

```python
# pip install trl peft transformers bitsandbytes datasets
import torch
from datasets import load_dataset
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig
from peft import LoraConfig
from trl import SFTTrainer, SFTConfig

MODEL_ID = "meta-llama/Llama-3.3-70B-Instruct"  # 密モデルが現実的な対象

# QLoRA：ベースを 4bit(NF4) で載せる＝単一GPUでも大きめモデルに届く
bnb = BitsAndBytesConfig(
    load_in_4bit=True,
    bnb_4bit_quant_type="nf4",
    bnb_4bit_compute_dtype=torch.bfloat16,
)
model = AutoModelForCausalLM.from_pretrained(MODEL_ID, quantization_config=bnb, device_map="auto")
tokenizer = AutoTokenizer.from_pretrained(MODEL_ID)

# 学習するのは低ランクのアダプタだけ。注意機構の射影行列を対象にするのが定番。
lora = LoraConfig(
    r=16, lora_alpha=32, lora_dropout=0.05,
    target_modules=["q_proj", "k_proj", "v_proj", "o_proj"],
    task_type="CAUSAL_LM",
)

dataset = load_dataset("json", data_files={"train": "data/train.jsonl"})["train"]

trainer = SFTTrainer(
    model=model,
    train_dataset=dataset,
    peft_config=lora,
    args=SFTConfig(
        output_dir="out/llama-domain-lora",
        num_train_epochs=2,                 # 入れすぎは過学習。1〜3で様子を見る
        per_device_train_batch_size=1,
        gradient_accumulation_steps=8,      # 実効バッチを稼ぐ
        learning_rate=2e-4,
        bf16=True,
        logging_steps=10,
        save_strategy="epoch",
    ),
)
trainer.train()
trainer.save_model("out/llama-domain-lora")  # 成果物はアダプタ（小さい）
```

ポイントは **学習するのがアダプタだけ**なこと。ベース 70B は凍結・4bit のまま動くので、必要VRAMが現実的な水準に収まります。

---

## 実装C：マネージド（運用を AWS に寄せる）

学習基盤を持ちたくないなら **AWS にマネージドで寄せます**。

- **Bedrock のマネージドFT**：本稿執筆時点で **Llama 2 / Llama 3.2**（Vision 含む）等が対象。学習データを S3 に置き、コンソール/APIでジョブを回すと**カスタムモデル**が払い出される。GPU 運用ゼロ。
- **SageMaker ＋ Bedrock Custom Model Import**：torchtune/TRL で作った重み（Llama 2/3/3.1/3.2 系アーキ）を **Bedrock に取り込み**、既存の Converse API でそのまま叩く。

> 📌 **正確性のための注記**：マネージドFTの**対応モデル・リージョンは更新されます**。最新は [Bedrock のモデルカスタマイズ対応表](https://docs.aws.amazon.com/bedrock/latest/userguide/custom-model-supported.html) を必ず確認してください。「やりたいモデルがマネージドFTの対象か」を**先に**確かめるのが事故防止になります。

---

## 評価ゲート：これ無しに本番へ出さない

FT で最も危険なのは「**なんとなく良くなった気がする**」で出すことです。**ホールドアウトでFT前後を機械比較**し、回帰を検出してから初めてデプロイします。

```python
# evaluate.py — ベース vs FT後 を同じ評価セットで比較し、合否を判定する
import json, statistics
from typing import Callable

def run_eval(generate: Callable[[str], str], eval_path: str) -> float:
    """各サンプルを採点し平均スコアを返す。採点は完全一致/数値一致/LLM-judge等、用途で選ぶ。"""
    scores = []
    with open(eval_path) as f:
        for line in f:
            ex = json.loads(line)
            out = generate(ex["input"])
            scores.append(score(out, ex["expected"]))  # 0.0〜1.0
    return statistics.mean(scores)

base = run_eval(generate_base, "data/eval.jsonl")
tuned = run_eval(generate_tuned, "data/eval.jsonl")

# 改善が閾値未満、または既存能力の回帰があれば“不合格”としてデプロイを止める。
assert tuned >= base + 0.03, f"改善不足: base={base:.3f} tuned={tuned:.3f}"
print(f"PASS base={base:.3f} -> tuned={tuned:.3f}")
```

評価は**用途に合わせた採点関数**が肝です。構造化出力なら完全一致/スキーマ準拠率、要約なら LLM-judge、分類なら F1。**「本番で何が正解か」を数値化**できて初めて、FT は工学になります。

---

## デプロイとライセンス（命名規約に注意）

1. **アダプタをマージ**：LoRA アダプタをベースに統合し、単一の重みにする（推論を軽くするため）。
2. **サーブ**：[vLLM で自前サーブ](/blog/vllm-llama-self-hosting-production-inference-server)するか、Bedrock Custom Model Import で Converse から叩く。
3. **コストを見積もる**：自前運用は[推論コストの設計](/blog/llama-inference-cost-optimization-self-host-vs-api)で損益分岐を出してから。

> ⚖️ **ライセンス必読**：[Llama Community License](https://www.llama.com/llama4/license/) では、**Llama を使って作った派生モデルを配布・提供する場合、モデル名の先頭に「Llama」を付ける**ことが求められます（例：`Llama-YourCompany-Support-8B`）。さらに `Built with Llama` 表示が必要です。**社内利用のみなら配布には当たりませんが、外部提供する瞬間に命名・表示義務が発生**します。詳細は[ピラー記事のライセンス章](/blog/meta-llama-open-weight-llm-production-guide#ライセンスの落とし穴商用前に必読)を参照してください。

---

## よくある落とし穴

- **破滅的忘却（catastrophic forgetting）**：特化に振りすぎて、元の汎用能力が落ちる。**汎用タスクも評価セットに混ぜて**回帰を監視する。
- **過学習**：エポック過多・データ過少で、評価が悪化する。**早期に eval を見て**止める。
- **データリーク**：評価が学習に混入。**分割を最初に決定的に**。
- **小さすぎるデータ**：数十件で様式は変わらない。**数百〜数千の良質例**を狙う。
- **RAGで足りたのにFTした**：知識追加は RAG が基本。FTで事実を焼くと陳腐化する。

---

## よくある質問（FAQ）

**Q. RAG とファインチューニング、どちらを先にやるべき？**
A. **RAG が先**です。知識・鮮度は RAG、振る舞い・様式は FT。多くの案件は RAG＋プロンプトで要件を満たし、足りない様式固定だけ軽い LoRA を足す、が費用対効果の最適点です。

**Q. どれくらいのデータが必要？**
A. 様式・口調の固定なら**数百〜数千の良質サンプル**が目安。量より質。手で磨いた少数が、雑多な大量に勝ちます。

**Q. Llama 4（MoE）も微調整できる？**
A. 技術的には可能ですが、巨大MoEのFTは分散基盤が要り**最初の一歩には不向き**。まず **Llama 3.3 8B/70B** などの密モデル＋LoRA/QLoRA から始めるのが現実的です。

**Q. GPUが無くてもできる？**
A. できます。**Bedrock のマネージドFT**（対象モデルは要確認）なら GPU 運用ゼロ。検証だけなら小型モデルを Colab/単一GPUの QLoRA で回せます。

**Q. 成果物（アダプタ）はどれくらいの大きさ？**
A. LoRA アダプタは**数十MB級**。ベースとは別に配布・差し替えでき、複数ドメイン用のアダプタを使い分ける運用も可能です。

---

## まとめ

ファインチューニングは「賢くする魔法」ではなく、「**振る舞いを設計どおりに固定する工学**」です。だからこそ、**RAGで足りないかを先に疑い**、**データ品質に投資し**、**評価ゲートを通し**、**ライセンス命名を守る**——この規律が、デモと本番を分けます。

> ドメイン特化した Llama を、RAG との役割分担・評価ハーネス・コスト設計・ライセンス対応まで含めて本番に載せたいなら、[実績](/case-studies/ai-voice-chatbot)をご覧のうえご相談ください。**一人 × 生成AI**で、PoC から本番運用まで一気通貫で設計します。

### 出典・公式リソース

- [torchtune（meta-pytorch/torchtune）](https://github.com/meta-pytorch/torchtune) — PyTorchネイティブの LoRA/QLoRA recipe
- [Llama 公式 Fine-tuning ガイド](https://www.llama.com/docs/how-to-guides/fine-tuning/)
- [Hugging Face TRL](https://huggingface.co/docs/trl) / [PEFT](https://huggingface.co/docs/peft)
- [Amazon Bedrock モデルカスタマイズ対応表](https://docs.aws.amazon.com/bedrock/latest/userguide/custom-model-supported.html)
- [Llama Community License](https://www.llama.com/llama4/license/) — 派生モデルの命名規約・Built with Llama

※ 対応モデル・リージョン・ライセンスは更新されます。実装前に必ず一次情報を確認してください。
