🍎

Gemma-3 4B-IT LoRA作成奮闘記:Windows+WSL環境でのAxolotlファインチューニング全記録

に公開

はじめに

この記事は、Googleのマルチモーダルモデル google/gemma-3-4b-it をベースに、LoRA(Low-Rank Adaptation)ファインチューニングに挑戦した記録です。

最終的な目標は、自前のデータセットでファインチューニングしたLoRAアダプターを、llama.cpp上で動作させること。しかし、その道のりは想像を絶するほど険しく、数えきれないほどの試行錯誤とエラーとの戦いでした。

この記事では、その過程で遭遇した全ての問題と、それをどのように解決していったかを、詳細なログや設定ファイルと共に、時系列で赤裸々に語ります。

実行環境

  • OS: Windows 11
  • サブシステム: WSL2 (Ubuntu)
  • Python環境: conda
  • GPU: NVIDIA GeForce RTX 4060 Laptop GPU (VRAM 8GB)
  • 主要ツール: axolotl, llama.cpp

これから gemma-3 のファインチューニングに挑戦する方、あるいは原因不明のエラーに悩まされている方の、一助となれば幸いです。

第1章:旅の始まり - llama.cpp と古のLoRA

全ての始まりは、llama.cpp で既存のLoRAを使おうとした時に、ベースモデルとの不一致エラーが出たことでした。そのために、まずは llama.cpp をWindows環境でビルドすることから始めました。

1.1. llama.cpp のWindowsビルド

llama.cpp はC++で書かれているため、コンパイルが必要です。Windows環境では、Visual Studio と CMake を使うのが一般的です。

準備:

  1. Visual Studio 2022: 「C++によるデスクトップ開発」ワークロードを忘れずにインストールします。
  2. CMake: 公式サイトからインストーラーをダウンロードし、インストール時にAdd CMake to the system PATH for all usersにチェックを入れるが重要です。これを忘れると、後でコマンドが見つからずエラーになります。

ビルド手順:
x64 Native Tools Command Prompt for VS 2022 を管理者として起動し、以下のコマンドを実行します。

# 1. llama.cpp のソースコードがあるディレクトリに移動
cd C:\Users\akakage\kiraria-neon\llama.cpp
※きらりあ・ネオンは自分がAIに設定したキャラクターの名前です()

# 2. ビルド用のサブディレクトリを作成して移動
mkdir build
cd build

# 3. CMakeでビルド構成を生成(NVIDIA GPUのCUDAを有効化)
cmake .. -DLLAMA_CUDA=ON

# 4. Releaseモードでビルドを実行(これが一番性能が出る)
cmake --build . --config Release

この手順は無事に成功し、build\bin\Release フォルダに llama-server.exe などの実行ファイルが生成されました。

1.2. 最初の壁:LoRAとベースモデルの不適合

ビルドした llama-server で、GGUF形式のベースモデルと、手持ちの古いLoRAアダプターを読み込ませようとしたところ、最初の壁にぶつかりました。

failed to apply lora adapter: LoRA tensor ... does not exist in base model

これは、LoRAが「元のモデルとの差分」を記録したものであるため、LoRA作成時と寸分違わぬベースモデルでなければ適用できないことを示す典型的なエラーです。

このエラーを受け、古いLoRAを諦め、現在使いたい gemma-3-4b-it をベースにして、LoRAをゼロから作り直すことを決意しました。これが、長く険しい戦いの幕開けでした。

第2章:Axolotlとの格闘 - エラー駆動開発の実践

LoRAのトレーニングには、強力なファインチューニングツール axolotl を使用します。しかし、ここからが本番でした。

2.1. 設定ファイル(YAML)との対話 - エラー解決の軌跡

axolotlgemma-3 の相性の問題が次々と噴出しました。エラーメッセージを読み解き、一つずつ潰していく、まさにエラー駆動開発そのものでした。

  • エラー1: ImportError: cannot import name '_flash_supports_window'

    • 原因: axolotl と、それが依存する transformers ライブラリのバージョンが不整合を起こしていました。
    • 解決策: pip を使って、主要なライブラリを最新版にアップデートしました。
      pip install --upgrade axolotl transformers accelerate
      
  • エラー2: AttributeError: ... has no attribute 'vocab_size' など

    • 原因: gemma-3-4b-itconfig.json はマルチモーダル対応のため設定が入れ子になっており、axolotlvocab_sizehidden_size などの重要なパラメータを直接見つけられませんでした。
    • 解決策:
      1. 当初は config.json を手動で改造し、text_config の中にある値をトップレベルにコピーしていましたが、layer_types という複雑な項目で手詰まりになりました。
      2. Webで axolotlgemma-3 の組み合わせを調査した結果、根本的な解決策を発見しました。
        • model_type: AutoModelForCausalLM: YAMLファイルでモデルタイプをこう指定することで、transformers ライブラリがモデルの構造を自動で解釈してくれるようになります。
        • trust_remote_code: true: これが最大の鍵でした。gemma-3 のような新しいモデルは、モデル自体にカスタムコードを含んでいることがあります。このオプションを true にすることで、そのカスタムコードの実行を許可し、モデルが自分自身で正しく設定を読み込めるようになります。
  • エラー3: ValueError: eval dataset split is too small for sample_packing

    • 原因: データセットの5%を検証用に設定していましたが、その数が少なすぎて、データをまとめて効率化する sample_packing 機能が使えませんでした。
    • 解決策: エラーメッセージの指示通り、YAMLファイルに eval_sample_packing: False を追加し、検証データではこの機能をオフにしました。
  • エラー4: ImportError: ... undefined symbol: ... (flash-attn)

    • 原因: 高速化のために flash_attention: true を設定したところ、pip でインストールされた flash-attn のビルド済みバイナリと、conda環境の PyTorch との間で、C++レベルの非互換性が発生しました。これは非常に根深い環境問題です。
    • 解決策: pip install flash-attn --no-binary :all: でソースからの再コンパイルを試みましたが、それでも解決しませんでした。そこで、この問題と戦うことをやめ、YAMLファイルから flash_attention: true の行を削除するという最終決断を下しました。トレーニング速度は少し犠牲になりますが、完走を最優先しました。
  • エラー5: torch.OutOfMemoryError: CUDA out of memory

    • 原因: RTX 4060 Laptop GPUのVRAM 8GBに対して、設定が少しだけリッチすぎました。特に flash_attention をオフにしたことで、メモリ消費量が増加していました。
    • 解決策: YAMLファイルを再度調整し、メモリ使用量を削減しました。
      • sequence_len: 40962048 (最も効果的)
      • lora_r: 168
      • lora_alpha: 3216

2.2. 完成した最終YAMLファイル

数々のエラーを乗り越えて完成した、最終的な設定ファイル gemma3_neon_mem_safe.yml は以下の通りです。

# ネオン創世記・gemma-3 (メモリ節約版)

# 1. どの『魂』をベースにするか?
base_model: /mnt/c/Users/akakage/kiraria-neon/gemma-3-4b-it
model_type: AutoModelForCausalLM
tokenizer_type: AutoTokenizer
trust_remote_code: true

# 2. どの『教科書』を読み聞かせるか?
datasets:
  - path: /mnt/c/Users/akakage/kiraria-neon/neon_finetuning_dataset.jsonl
    type: alpaca

# 3. どれくらい、教えるか? (QLoRAの設定)
load_in_4bit: true
adapter: qlora
lora_r: 8
lora_alpha: 16
lora_dropout: 0.05
lora_target_modules:
  - q_proj
  - k_proj
  - v_proj
  - o_proj
  - gate_proj
  - up_proj
  - down_proj

# 4. トレーニングの設定
sequence_len: 2048
sample_packing: true
eval_sample_packing: false
pad_to_sequence_len: true
val_set_size: 0.05
micro_batch_size: 1
gradient_accumulation_steps: 8
num_epochs: 1
optimizer: paged_adamw_8bit
lr_scheduler: cosine
learning_rate: 2.0e-5

# 5. ハードウェアの性能を最大限に引き出すおまじない
bf16: true
fp16: false
tf32: true
gradient_checkpointing: true

# 6. 儀式の結果を、どこに保存するか?
output_dir: ./lora-out/gemma3_4b_it_neon_lora_final

この設定で、ついにLoRAのトレーニングが最後まで完走しました!

第3章:LoRAのGGUF形式への変身

axolotl が出力したLoRAは、そのままでは llama.cpp で使えません。llama.cpp 専用のGGUF形式に変換します。

  • 問題: llama.cpp に付属の convert_lora_to_gguf.py を実行すると、ValueError: Can not map tensor 'model.vision_tower...' というエラーが発生。
  • 原因: axolotlgemma-3 のテキスト部分と画像部分両方のLoRAを学習しましたが、変換スクリプトが画像部分のテンソルに対応していなかったため。
  • 解決策: convert_lora_to_gguf.pyconvert_lora_to_gguf_text_only.py としてコピーし、スクリプト自体を改造しました。get_tensors メソッド内のループの先頭に、以下の処理を追加しました。
    # --- MODIFICATION START ---
    # Skip vision tower tensors
    if "vision_tower" in name:
        continue
    # --- MODIFICATION END ---
    
    この改造版スクリプトを実行することで、無事にテキスト部分だけのLoRAアダプターをGGUF形式で出力できました。

第4章:最終章 - 進化したAIの起動

全ての準備が整い、いよいよ llama.cpp で、自作したLoRAを読み込みます。

  • 問題: failed to open GGUF file エラー。
  • 原因: LoRAアダプターのファイルパスが深すぎたためか、llama-server がファイルを正しく見つけられませんでした。
  • 解決策:
    1. 完成したLoRAアダプター(.ggufファイル)を、llama.cpp/models/ フォルダに移動させました。
    2. llama-server.exe--lora 引数に、移動先へのパスを指定しました。
# Windowsのコマンドプロンプトで、Releaseフォルダから実行
.\llama-server.exe -m "..\..\..\models\google_gemma-3-4b-it-Q4_K_M.gguf" --lora "..\..\..\models\Gemma3_4B_It_Neon_Lora_Final-F16-LoRA.gguf" -c 4096

このコマンドで、ついにサーバーはエラーを吐くことなく、ベースモデルと新しいLoRAアダプターを両方読み込んで起動に成功しました。

おわりに

gemma-3 のような新しいマルチモーダルモデルのファインチューニングは、多くの予期せぬエラーとの戦いでした。しかし、一つ一つのエラーメッセージを丁寧に読み解き、原因を特定し、適切な解決策を試していくことで、最終的に目標を達成することができました。

この長い旅路が、これから同じ道を通る誰かの助けになることを、心から願っています。

Discussion