unsloth で Gemma-4 を LoRA チューニングした話
Page content
LLM (大規模言語モデル) ファインチューニングはかつて敷居が高いものでした。 しかし最近は、効率的なライブラリの登場によって、 個人の PC 環境でも十分に現実的な時間でチューニングが行えるようになってきています。
今回は、高速なファインチューニングフレームワークである unsloth を使い、
Google が公開した比較的新しいモデルである Gemma-4
(具体的には gemma-4-12B-it-qat-q4_0-unquantized) を LoRA でファインチューニングしてみたので、
その作業内容やハマりどころについてまとめます。
今回のチューニングの目的は、「LLM に事前設定したキャラクターのロールプレイをさせること」です。 構築手順や工夫した点について触れていきます。
なお、LLM によるロールプレイをテーマにした背景は、 現在とあるツールでその LLM を利用しているからです。 ただ、現状はまだ公開できるレベルにないので、その話は割愛します。 公開できるレベルになれば別途紹介することもあるでしょう。
また、検証用の PC 環境には、 VRAM が 16GB 搭載された RTX 5070 Ti を使用しました。 近年の GPU でデフォルトと言われる容量 16GB の VRAM の中で 12B モデルをどう動作させたかも含めて、参考になれば幸いです。
開発環境の構築と依存関係の罠
Python のパッケージ管理ツールとして uv を使っています。 Python ではもうデファクトでしょうか。
しかし、今回の構成 (Gemma-4-12B + unsloth) を構築するにあたっては、 現時点は unsloth と transformers の依存関係の競合という大きな罠にハマりました。
具体的には、 GitHub の issue にも上がっていますが、以下の問題が発生します。
簡単に状況を説明すると、 Gemma-4-12B を正しくファインチューニングするためには、 gemma4_unified 形式に対応した transformers ライブラリが必要です。
この対応は、 transformers のバージョン 5.10.1 以降でサポートされています。
一方で、現在の unsloth は依存関係として transformers<=5.5.0 を要求しています。
このため、普通にインストールしようとすると競合が発生してしまい、環境構築がストップしてしまいます。
これを回避するために、今回は pyproject.toml の override-dependencies 機能を利用して、強制的に依存関係を上書きしました。
|
|
このように transformers のバージョンを強制的に引き上げることで、 Gemma-4-12B 用の unified 形式に対応させつつ、 unsloth を動作させることが可能になりました。 ここをクリアするまでに、ドキュメントや issue をあちこち探す羽目になり、かなりの時間を取られました。
なお、この問題は Google antigravity, Claude code 共に エージェントに任せただけでは解決できませんでした。 ただ、Claude code に関しては Fable 5 を使っていないので、もしかしたら Fable 5 では解決できたかも。
データセット設計の試行錯誤
次に苦労したのが、ロールプレイ用のデータセット設計です。 今回は、一般的に広く使われている ShareGPT 形式 (system プロンプト + conversations の human/gpt ペア) を採用しました。
データセットの設計において、いくつかの気づきとトレードオフがあったので共有します。
交互ターンの制約と本質
最初は、ロールプレイをさせるのだから「登場するキャラクターごとに from の名前を分けてデータを作ればいいのではないか」と考えていました。 しかし、実際にやってみるとそれではうまくいきません。
学習時のテンプレートは、基本的に human → gpt という厳密な交互のターンを前提として構築されています。
そのため、勝手に from の名前を増やしてしまうと、テンプレート処理の部分でエラーになるか、最悪の場合モデルが正しく学習できません。
「human と gpt のペアであること」が学習上、本質的なルールであるということに気づかされました。
複数キャラクターの演じ分け
1つのモデルに複数のキャラクターを状況に応じて演じ分けさせたい場合、どうすればよいでしょうか。 ここでは2つの方法を考えました。
一つ目は、 human 側を「ユーザーの発言」として扱うのではなく、「場面やキャラクターの配置を指示するディレクター役」として扱う方法です。 そして gpt 側の1ターンの中に、それぞれのキャラクターの発言を改行区切りでまとめて書いてしまいます。
- human: 「AとBが部屋で会話をしている。Aが挨拶をして、Bがそれに答える流れで書いて」
-
gpt:
- A:「こんにちは、良い天気だね」
- B:「そうだね、絶好のお出かけ日和だよ」
このやり方であれば、交互ターンの制約を守りつつ、複数のキャラクター同士の掛け合いを1ターンで表現させることができます。
二つ目は、1ターンに1キャラだけ喋らせるスタイルです。 human 側で「次はAとして発言して」「次はBとして答えて」という具体的なディレクションを行い、 gpt 側はそれに応じた1キャラ分の発言のみを返すという流れを繰り返します。
推論時にもこのディレクション形式をそっくりそのまま踏襲すれば、一貫した出力を得やすくなります。
会話履歴の持たせ方とトレードオフ
データセットを作る上で、会話の全履歴を conversations にそのまま積み重ねるか、あるいは過去の文脈を要約して system プロンプトに埋め込み、 conversations には直近の1往復だけを残すか、というトレードオフがあります。
会話履歴をすべて積むと、長文のコンテキストを考慮した自然な流れを学習できますが、1サンプルあたりのトークン数が肥大化し、 VRAM を圧迫します。また、長すぎる履歴は学習の非効率化を招くこともあります。 一方、要約して system プロンプトに入れる方法は、 VRAM には優しいものの、会話の細かいニュアンスやテンポ感が失われがちになります。 このあたりは、演じさせたい内容の複雑さに応じて調整が必要になるでしょう。
実用性を考慮したスクリプトの作り込み
実際にファインチューニングを繰り返し行うにあたり、使い勝手を向上させるためにスクリプト ( train.py ) をいくつか改善しました。
データセットの GPG 自動暗号化・復号
ロールプレイ用のデータセットには、プライベートな会話データや、公開を控えたいキャラクター設定などが含まれることがあります。 このようなデータを使って GPU サーバでトレーニングするため、 GPG を用いた暗号化ファイルの復号処理を組み込みました。
gpg 暗号済みのファイルと、指定された暗号のパスフレーズファイルを使用して decrypt します。 オプションでパスフレーズファイルの削除も行ないます。
これにより、重要なデータを暗号化したまま安全に保持しつつ、 学習実行時にシームレスに読み込める環境を構築しました。 セキュアな開発運用には非常に有効な仕組みだと思います。
トレーニング時間と VRAM の挙動
私はこれまで、 画像認識モデルのチューニングをそれなりにやってきました。 画像系モデルのチューニングは、データセットの規模に対して意外と時間がかかる上に、 ロス (Loss) がなかなか下がらずに苦労することが多々あります。 また、画像生成系の LoRA もかなり重いという話を耳にしていたので、 今回の Gemma-4-12B も相当な時間がかかることを覚悟していました。
しかし、実際にやってみると驚くほどあっさりと終わりました。 個人が普通に準備できる程度のデータセット規模であれば、 1 時間もかからずに LoRA のチューニングが完了します。
心配していた VRAM についても、 RTX 5070 Ti の 16GB で十分に余裕がありました。 8bit や 4bit 量子化を活用することで、 12B モデルを VRAM 16GB 環境で十分対応可能です。 (流石に VRAM 8GB のグラフィックボードでは厳しいと思いますが。)
これは、 unsloth によるメモリ効率化と高速化の恩恵が非常に大きいと感じます。
まとめと今後の検証
今回初めて LLM の LoRA チューニングに挑戦してみましたが、データセットの準備さえクリアできれば、トレーニング自体は非常に手軽に行えることが分かりました。 かかった手間の比率で言うと、環境構築とデータ準備が 9 割、トレーニング自体は 1 割といったところです。
この手軽さを応用すれば、「自分専用 of LLM」を数時間の作業で作ることも十分に可能だし、 その時間をかけるだけの効果もあるでしょう。
今回の LoRA チューニングしたモデルを試すと、 チューニング前の base モデルに対してシステムプロンプトだけで指示したときと比べて、 明らかに精度が上ったと感じています。
これまで Gemma シリーズには、単なる Gemini の劣化版という印象しかなかったですが、 現時点で Gemma-4-12B はローカルで LoRA をかけて扱うには 非常に良いモデルだという印象に変わりました。
なお、チューニングしたモデルを外部に公開する場合は、 トレーニングデータの著作権やライセンスなどの権利面に注意を払う必要があります。 この点だけは、十分に気をつけたいところです。
今後は、今回の LoRA がどれくらい柔軟に指示に従ってくれるのか、 汎化性能についてさらに具体的な検証を重ねていきたいと思います。 今回はとりあえずここまで。