RTX 2070ノートだけで個人RAGをファインチューニングした話
LLM Fine-tuning QLoRA GPU AI RAG
RTX 2070ノートだけで個人RAGをファインチューニングした話
なぜローカルで完結させたかったか
my-rag-brainには個人の思考ログ、仕事の判断記録、気づいたことなどが全部入っている。これをクラウドのAPIに流すことに抵抗があった。
ローカルで動かしたかった。
Tiny-Criticとは何か
RAGシステムがChromaDBから取得したチャンクは、必ずしもクエリと関連しているとは限らない。距離が近くても、内容がズレていることがある。
Tiny-Criticは、取得済みのチャンクをもう一度「クエリと関係があるか/ないか」で二値判定する小さなモデルだ。無関係なチャンクを除去して、LLMに渡すコンテキストを絞り込む。
訓練データの作り方
手元のRAGログ(クエリとヒットチャンクのペア)から、ラベル付きデータを生成した。
# 2114件のペアを均衡化して 1036件(yes/no = 50:50)に
positives = [(q, chunk, 1) for q, chunk in relevant_pairs]
negatives = [(q, chunk, 0) for q, chunk in irrelevant_pairs]
balanced = positives[:518] + negatives[:518]
訓練データが偏っていると片側に収束する。均衡化は必須だった。
QLoRAで4bit量子化訓練
Qwen2.5-3B-Instructを4bit量子化してLoRAアダプタをかける構成にした。RTX 2070 Max-Qのメモリ(8GB)でも動く。
bnb_config = BitsAndBytesConfig(
load_in_4bit=True,
bnb_4bit_quant_type="nf4",
bnb_4bit_compute_dtype=torch.float16,
)
model = AutoModelForSequenceClassification.from_pretrained(
"Qwen/Qwen2.5-3B-Instruct",
quantization_config=bnb_config,
num_labels=2,
)
model = get_peft_model(model, LoraConfig(
r=8, lora_alpha=32,
target_modules=["q_proj", "v_proj"],
lora_dropout=0.05,
bias="none",
))
結果
| 項目 | 値 |
|---|---|
| 訓練データ | 2114件 → 均衡化 1036件 |
| 訓練時間 | 約1時間53分(RTX 2070 Max-Q) |
| 最終loss | 1.454 |
| token accuracy | 約76%(ピーク 約81%) |
76%というのは完璧ではない。しかし「無関係なチャンクが混入しやすい」という問題に対して、コスト0で動くフィルタとして十分に機能している。
クラウドを使わなかった理由
訓練も推論も手元のGPUで完結している。個人ログはどこにも出ていかない。
これはセキュリティの話というより、自分のデータが自分の管理下にあるという感覚の問題だ。RAGシステムに何を入れるかの判断が、外部サービスの利用規約に縛られない。
関連する記事
- Geminiの挙動から自分のRAGと同じ設計思想を確認した話は AIが自分と同じ設計思想に辿り着いていた話 に書いた。
- 出典に戻れる設計 (source_origin) を実装した話は 使って気づいた、Geminiにあって自分になかったもの にまとめた。
- 検索精度そのものを測定駆動で上げていった記録は ベクトル検索だけのRAGは「肝心なときに思い出さない」— ハイブリッド検索+測定で recall を 0.2→1.0 にした話 に書いた。
- この個人RAG全体の設計思想は AI に5回訂正された夜 — 個人 RAG を5年付き合える相棒にする設計思想 にまとめた。