はじめに
きっかけは単純で、「自分専用のAIを作ることに興味があった」 からだ。
特に最近はローカルLLM界隈で明るいニュースが目立っていると思う。
私が最初にローカルLLMに触れたのはPixel 9に入れた「Google AI Edge Gallery」というアプリで導入できるモデルだ。
当時はGeminiも使い始めたころで、Geminiと比べると応答品質はよろしくなく、「どうやって活用するんだ?」その程度の感想だった。
作り始める前から、ネット記事でこんなキーワードを何度も見かけていた。
- 「使えば使うほど賢くなるAI」
- 「個人情報や機密情報はAIに入力してはいけない」
- 「入力した値が学習に使われるかもしれない」
なんとなくわかっているつもりでも、仕組みが腑に落ちていなかった。「じゃあ自分で作ってみればいい」 と思ったのが出発点だ。
もう一つの動機は、コストへの疑問だった。ChatGPTやClaudeを使うたびに月額や従量課金が発生する。無料で作れるなら、そっちの方がいい。 同時に、最新のオープンモデルを実際に動かして、その使い勝手を自分の手で確かめてみたかった。
そして、この体験を誰かと共有したいという想いも最初からあった。自分が感じた「仕組みが分かった瞬間の面白さ」を、他の人にも体験してほしかった。作っていく中で「社内研修」というゴールを定め、そこから先は受講者のためにわかりやすく、簡単で、確実に動くコードに仕上げることを一貫して意識した。Google Colab(T4 GPU / RAM 12.7GB)で、3種類のオープンモデルにLoRAファインチューニング→GGUF変換→Ollamaローカル実行まで完走できる構成だ。完全無料で動く。
このノートブックでできること
このノートブックは2つに分けている。その代わり「自分専用のAI」を、お金をかけずに、ローカルPCで動く形で作れる。
ベースモデル(Qwen2.5-3B / Gemma3 4B / Gemma2 2B から選択)
↓ LoRAファインチューニング(Google Colab T4 GPU、無料)
↓ 自分で用意した質問と回答のペアを学習させる
LoRAアダプタ(約200MB)をGoogle Driveに保存
↓ ランタイムを切断してメモリリセット
ベースモデル + アダプタをマージ → GGUF変換(f16 → q4_k_m)
↓
Ollamaでローカルで動く自分だけのAI(Wi-Fi不要、完全プライベート)
ノートブックを2つに分けている理由 — T4のRAM 12.7GBは、ファインチューニングとGGUF変換を同一セッションで行うには足りない。間にランタイムを切断してメモリをリセットすることで、両工程を無料枠の中に収めている。
選べる3つのモデル
研修では自分のPCスペックや好みに応じてモデルを選べる。
| モデル | パラメータ | GGUF(q4) | こんな人に | |
|---|---|---|---|---|
| A | Qwen/Qwen2.5-3B-Instruct | 3B | ~1.8GB | まず試したい、どんなPCでも動かしたい |
| B | google/gemma-3-4b-it | 4B | ~2.5GB | 品質重視、GPU搭載PCを持っている |
| C | google/gemma-2-2b-it | 2B | ~1.5GB | 最軽量で動かしたい、古いPCしかない |
3モデルとも、ファインチューニングからGGUF変換まで完走を確認済みだ。
「自分専用」の中身
学習データは自分で用意する。形式はシンプルで、「質問と回答のペア」を書くだけ。
training_data = [
{
"instruction": "あなたは誰ですか?",
"output": "私は〇〇が作ったオリジナルAIです。"
},
{
"instruction": "社内の休暇申請方法は?",
"output": "休暇申請は社内ポータルから..."
},
# ← ここに自分の知識を追加していく
]
このデータがそのままAIの「性格」と「知識」になる。社内FAQでも、架空キャラクターの人格でも、趣味の専門知識でも何でもいい。件数は最低3件から、多いほど精度が上がる。
学習後は、Colab上でそのまま会話テストができる。確認できたらGoogle Driveに保存し、Part 2でGGUF変換してローカルPCに持ち帰る。
完全ローカルで動く、の意味
Part 2でGGUF形式に変換したファイルを、OllamaというローカルLLMランナーに読み込ませると、自分のPCだけで動くAIになる。
ollama create my-ai -f Modelfile
ollama run my-ai
一度登録すれば、Wi-Fiを切っても動く。 データは一切外部に送信されない。「個人情報や機密情報をAIに入力してはいけない」という制約から、自由になれる。
技術的な背景(実はこんなことをやっている)
ノートブックを上から順に実行するだけで動くように設計してあるが、内部では結構複雑なことをやっている。同じことを自分で実装しようとする人の参考に、技術的なポイントをまとめておく。
LoRAとは
モデル全体(数GB)を書き換えるのではなく、小さな「差分」だけを学習する手法。差分は約200MBで済むため、Colab無料枠のRAMに収まる。
学習後、差分(LoRAアダプタ)をベースモデルに合体(マージ)させると、フルサイズのオリジナルモデルが完成する。
4bit量子化でGPUに収める
3B〜4Bのモデルをそのままロードするとfloat16で6〜8GB必要になり、T4の15GB VRAMでは学習時のgradientも含めると厳しい。BitsAndBytesによる4bit量子化で消費VRAMを半分以下に抑えている。
Gemma系に必要なラベルマスク
QwenはSFTTrainerが自動でラベルマスク(「質問部分は学習しない」設定)をやってくれるが、Gemma 2/3はchat templateの構造上これが機能しない。内部で明示的にラベルを生成している。
# assistant部分のみ学習するラベルを生成
labels = [-100] * prefix_len + full_ids[prefix_len:]
EarlyStopで過学習を防ぐ
小規模データ(数十件)では過学習が急速に進む。Loss 0.5を下回ったら自動停止する仕組みを入れている。
class EarlyStopOnLowLoss(TrainerCallback):
def __init__(self, threshold=0.5):
self.threshold = threshold
def on_log(self, args, state, control, logs=None, **kwargs):
if logs and "loss" in logs and logs["loss"] < self.threshold:
control.should_training_stop = True
閾値0.5は「具体的な知識を学習しつつ、汎用的な日本語能力も保つ」バランスポイント。
開発中にはまったポイント(参考情報)
ここからは「完成したノートブックがなぜこういう実装になっているか」の裏話だ。同じ実装をしようとした際の参考になれば。
Gemma 3のGGUF変換が3段階で詰まった
llama.cppのconvert_hf_to_gguf.pyでValueErrorが出続けた。
原因はtransformers 5.xがGemma 3をGemma3ForConditionalGeneration(マルチモーダル)として保存することだった。llama.cppはテキスト専用のGemma3ForCausalLMを期待しているため不整合が起きる。
| 段階 | 問題 | 対処 |
|---|---|---|
| 1回目 | config.jsonがマルチモーダル形式 | text_configを抽出してCausalLM形式で上書き |
| 2回目 | テンソル名にmodel.language_model.プレフィックス |
リネーム処理を追加(プレフィックス誤推定で0件マッチ) |
| 3回目 | マルチモーダルテンソルが残存 | プレフィックス付きのみ抽出、残りを除去 |
最終的な対処:
if model_family == 'gemma3':
# config.json → テキスト専用に変換
_text_cfg = _saved_cfg['text_config']
_text_cfg['architectures'] = ['Gemma3ForCausalLM']
# テンソル名リネーム + マルチモーダル除去
_PREFIX = 'model.language_model.'
for _sf_path in sorted(glob(os.path.join(local_dir, 'model*.safetensors'))):
_tensors = load_file(_sf_path)
_fixed = {_k[len(_PREFIX):]: _v for _k, _v in _tensors.items() if _k.startswith(_PREFIX)}
save_file(_fixed, _sf_path)
GPU上からsave_pretrainedする
Gemma 3(4B)のマージ+保存でRAM 12.7GBを超えてクラッシュした。モデルをCPUに移動してから保存しようとするとfloat16で8GB+シリアライズ分の追加消費が重なるためだ。
save_pretrainedはGPU上から直接実行できる。シャード単位(2GB)でdiskに書き出されるため、モデル全体をRAMに载せる必要がない。
base_model = AutoModelForCausalLM.from_pretrained(
base_model_id, torch_dtype=torch.float16, device_map="auto"
)
merged_model = lora_model.merge_and_unload()
merged_model.save_pretrained(local_dir, safe_serialization=True, max_shard_size='2GB')
transformersのバージョンを固定しない
特定バージョンに固定したところ、5段階の連鎖で迷走した(固定→競合→ダウングレード→ImportError→キャッシュクリア→固定撤廃)。pip install -Uで最新版に追従させる方式が最終的に最も安定した。
SFTTrainerはサイレントにフォールバックする
processing_class=tokenizerのままにしておくと、明示的に作ったラベルマスクが内部で再トークナイズされて上書きされる。Lossは正常に下がるが推論に反映されない、という診断しにくい状態になる。Gemma系ではprocessing_class=Noneが必須。
Gemma 4との格闘(6回のテスト、断念)
当初はGemma 4 E4B-it(2026年4月リリース)も選択肢に加えようとしたが断念した記録も残しておく。
Gemma 4のGemma4ClippableLinearはPEFTと互換性がなく、モンキーパッチで対処してLossは0.006まで収束させた。しかし推論で学習内容がほとんど反映されない状態が6回のテストを経ても改善しなかった。
原因はteacher-forcingとautoregressive generationの乖離だ。
学習時(teacher-forcing): 正解トークンを入力 → Loss低下 ✅
推論時(autoregressive): 自分の出力を入力 → 1トークン目のズレが連鎖的に増幅 ❌
Qwen同一データで試したところ1回で成功し、モンキーパッチが唯一の原因と確定した。
Unslothが内部解決済みなので、ノートブックをUnslothベースに書き換えれば将来対応できる可能性はある。
ノートブック作成のためにかかったコスト
一点、正直に書いておく。
このノートブック自体は完全無料で動く。Google Colabの無料T4 GPUを使い、ローカルPCにはOllamaをインストールするだけだ。
ただ、私がノートブックを開発・検証するためにはコストがかかった。Google Colab Proプランを契約したが、1週間で計算リソースを使い切り、さらにPay As You Goで追加課金した。Gemma 4対応で6回テストを回し、3モデル×複数回のE2Eテストを繰り返した結果だ。
これは後悔していない。完璧に動くものを作るには、試行錯誤できる環境への投資が先に来る。 受講者が無料で確実に動くノートブックを使えるなら、開発コストは十分に元が取れる。
まとめ
「使えば使うほど賢くなるAI」「入力した値が学習に使われるかもしれない」——これらのキーワードが腑に落ちていなかった。作ってみて分かった。
ファインチューニングとは、既存のモデルに自分の書いたデータを追加学習させることだ。「使えば賢くなる」サービスも、ユーザーの入力データが学習に使われている可能性があることも、仕組みとして理解できた。そしてローカルで動かす限り、そのデータは誰にも渡らない。
このノートブックは、その体験を誰でも無料でできるように整えたものだ。興味があればぜひ動かしてみてほしい。
最後に——このコードの価値はいくらか?
このノートブックを作るにあたって、16個のバグを潰し、3モデルすべてでPart 1からPart 2まで完走を確認した。Gemma 4との格闘に6回のテストを費やし、Colab Proプランを1週間で使い切り、追加課金まで行った。検討開始から数えると1か月はかかった。
同じことを自分でゼロからやろうとすると——同じだけの時間と、同じだけの課金が必要になる。
「動くコード」にはその試行錯誤の分の価値がある。 情報やコードは無料で手に入るものという感覚が広まっているが、「確実に動く」状態に仕上げるコストは、作った人間にしか見えない。
あなたはこのノートブックに、いくらの価値を見出すだろうか。