本記事は、当社が社内PoCとして実施したLLMファインチューニング環境の構築と検証の記録です。題材はパイプライン検証用に作った合成タスクであり、実業務データでの精度を示すものではありません。数値はすべて当社の評価機での実測です。
ファインチューニングは「やれた」で終わらせない
LLMのファインチューニングは、手順を真似ればそれらしく動くところまでは意外と簡単です。難しいのはその先で、「前回と同じ条件でもう一度やったら同じ結果になるのか」「本当に良くなったのか、どれだけ良くなったのか」を説明できる状態にすることです。
お客様の業務データで社内専用モデルを作る相談が増えるなか、当社では本番データに触る前に、再現でき、定量的に評価できる学習手順を自分たちの手元で確立することにしました。この記事はその社内PoCの記録です。
検証環境と、手順の全体像
評価機は、AMD Ryzen AI MAX+ 395とRadeon 8060S(gfx1151)を載せた統合メモリ64GBのWindows 11 PCです。NVIDIAではないため、CUDA前提の手順はそのまま使えません。ROCm 7.2.1とAMD配布のPyTorch 2.9.1をWindowsネイティブのvenvに入れ、PyTorchから約99.7GBのGPUメモリが見えることを確認しました(CPUと共有する統合メモリのため、搭載量より大きく見えます)。
| 項目 | 内容 |
|---|---|
| PC | Ryzen AI MAX+ 395 / Radeon 8060S / 統合メモリ64GB / Windows 11 |
| 実行環境 | Windowsネイティブ(venv)、ROCm 7.2.1、PyTorch 2.9.1 |
| 学習方式 | bf16のLoRA(r=16、alpha=32、全線形層が対象) |
| モデル | Qwen3-1.7B、Qwen3-4B |
手順は6ステップに絞りました。①GPU疎通テスト(認識・bf16の行列積・逆伝播でNaNが出ないか)、②データ準備、③学習前のベース評価、④LoRA学習、⑤学習後の評価、⑥結果の比較です。設定は1つのYAMLファイルに集約し、実行のたびにそのコピーを保存します。QLoRA(4bit量子化)は未検証のため、1.7B〜8Bが64GBに収まるbf16のLoRAを標準としました。
「正確に」を担保する仕組み
「正確に」を精度の数字だけで語らず、あとから第三者が同じ結果を出せることと良し悪しを同じ物差しで比べられることの2点に分けて仕組み化しました。
- 乱数シードの固定——学習の乱数を固定し、実行ごとに設定・ライブラリのバージョン(pip freeze)・学習データのSHA256ハッシュ・GPU名を
run_meta.jsonへ保存します。同一シードで1ステップ目の損失がまったく同じ値(0.3218747…)になることを確認しました。 - 評価の固定——学習に使わない評価セットを用意し、貪欲デコード(毎回もっとも確率の高い語を選ぶ方式)で答えさせます。Qwen3は既定で思考モードが有効ですが、評価では無効にしました。学習時と評価時でプロンプトの組み立てを同一にしています。
- 前後比較——学習前(ベース)と学習後を同じスクリプト・同じデータで測り、誤答の中身も評価結果のファイルに残します。
- NaN・発散の検知——損失が有限でない値になったら即停止します。
結果:学習前後を同じ物差しで測る
題材は、「2030年9月24日」のような西暦の日付を「令和12年9月24日」の和暦表記に直すという、答えが一意に決まる合成タスクです。学習データ400件、評価データ50件(日付が重複しないように分割)、3エポック(75ステップ)で学習しました。
| モデル | 学習前 | 学習後(LoRA) | 学習時間 |
|---|---|---|---|
| Qwen3-1.7B | 68% | 100% | 約2分16秒 (約1.8秒/ステップ) |
| Qwen3-4B | 86% | 100% | 約3分38秒 (約2.9秒/ステップ) |
1.7Bのベースモデルの誤答は、年の引き算のミスが中心でした(たとえば2030年を「令和2年」と答える)。学習後は評価50問すべてに正解しています。4Bまで64GBの統合メモリで問題なく学習できました(ピークメモリは未計測です)。
ただし、これはあくまでパイプラインが正しく動くことの確認です。易しい合成タスクで評価も50問のため、統計的な性能保証にはなりません。なお、1.7Bのベース評価の「68%」は、評価結果の保存名がモデルに依存しておらず4Bの評価で上書きされたため、ファイルとしては残っておらず、当時の作業ログの値です。その反省から、以降はモデル名を付けて保存する運用に直しました。
ハマった落とし穴と、まだ言えないこと
Windows+ROCmでの構築では、公式の手順どおりに進まない場面がいくつもありました。再現する方の参考として、ハマった順に残しておきます。
- trl / Trainerがimportできない——Windows版ROCm PyTorchには
torch.distributedが含まれておらず、読み込みで失敗します。素のPyTorchの学習ループで実装し直しました。 - transformers 5.xでの引数の廃止——
warmup_ratioが廃止され、warmup_stepsに小数で比率を渡す形になりました。 - 例外後にHIPプロセスが残る——落ちたあとにCPUを回し続けることがあります。スクリプト末尾で明示的にプロセスを終了させ、残った場合は親プロセスごと止めます(子だけでは止まりません)。
- Qwen3の思考モードで評価が極端に遅い——評価時は思考モードを無効にして解決しました。
まだ言えないことも明確にしておきます。実データ・実タスク・本番想定のベースモデル(4〜8B)での検証、評価指標の設計、QLoRAの可否、長い系列や大きなバッチでのメモリ上限は、いずれもこれからです。また、サーバーへの展開はDockerで行う社内方針ですが、今回の検証環境はWindowsネイティブです。WSL2+Docker経由はROCmの導入に管理者権限が要ること、専用VRAMしか見えないこと、bf16でNaNが出る報告があることから採用を見送っています。
よくある質問(FAQ)
- Q. LoRAファインチューニングとは何ですか?
- A. 元のLLMの重みは固定したまま、各層に小さな追加パラメータ(アダプタ)だけを学習させる手法です。全パラメータを更新するより必要なメモリと時間が少なく、学習結果は元のモデルとは別の小さなファイルとして保存できます。
- Q. 学習結果が良くなったかどうかは、どうやって確かめましたか?
- A. 学習に使わない評価セット(50問)を固定し、学習前のモデルと学習後のモデルに同じスクリプト・同じ設定(貪欲デコード)で答えさせて正答率を比べました。誤答の中身も保存しているので、数字だけでなく何を間違えたかまで確認できます。
- Q. この結果は、実際の業務データでも同じ精度が出ると考えてよいですか?
- A. いいえ。今回は日付の和暦変換という易しい合成タスクと50問の評価でパイプラインを検証したもので、実業務での性能を保証する数字ではありません。実データでは評価セットを十分に増やし、評価指標も設計し直す必要があります。
まとめ
CUDAのないWindows+ROCm環境でも、Qwen3の1.7Bと4BをLoRAで学習でき、合成タスクの正答率は1.7Bで68%から100%、4Bで86%から100%になりました。学習時間は75ステップで2〜4分ほどです。
この記事で持ち帰っていただきたいのは数字より仕組みです。シードの固定、設定・ライブラリ・データハッシュの記録、固定した評価セットでの前後比較があれば、ファインチューニングは「やれた」から「説明できる」に変わります。当社では、お客様の業務データを使ったLLMのチューニングや、社内で完結するローカルLLMの導入を、こうした評価の設計から支援しています。「何をどう測れば良いか決まっていない」という段階のご相談も歓迎です。