技術 約7分で読めます

Mac M1 MaxでLoRA学習に13回全敗してからRunPodで成功するまで

Mac Studio M1 Max 64GBがあるんだから、ローカルでSDXL系のLoRA学習くらいできるだろう。そう思って始めたら13回試行して全部失敗した。複数のAIエージェントに相談しながら設定を変え続けたが、Macでは最後まで「崩れた手」と「ERROR」の文字しか出なかった。最終的にRunPod RTX 4090に移行して成功するまでの全記録。

環境

  • Mac Studio (M1 Max, 64GB RAM)
  • Python 3.10 / PyTorch 2.x (MPS)
  • ベースモデル: Illustrious-XL v1.0 (waiIllustriousSDXL_v160.safetensors)
  • 学習スクリプト: kohya-ss/sd-scripts

学習データはこんな感じのキャラクター画像を59枚用意した。

学習データの例

症状

学習を開始するとLossは0.06付近で安定して下がっていく。数値上は学習が進んでいるように見える。しかしサンプル画像を生成すると、Epoch 1からEpoch 10まで一貫して崩れた手や**「ERROR」という謎の文字**が出力される。キャラクターが出る気配がまったくない。

13回の試行錯誤

v1〜v5: 基本設定の調整

普通のSDXL学習設定から開始。mixed_precisionをfp16とbf16で切り替え、OptimizerもAdamWとAdamW8bitを試した。ただしbitsandbytesがMacで動かない問題にぶつかり、AdamW8bitは使えなかった。

結果: 謎の手しか出ない。

v6〜v9: Mac特化設定

MPSバックエンドが怪しいと睨んで、Mac向けに最適化を試みた。

  • mixed_precision="no"でfp32を強制(メモリは食うが精度優先)
  • PyTorch 2.0のsdpa (Scaled Dot Product Attention) を使用
  • xformersはMacで使えないため無効化

結果: 変わらず「手」。生成速度が遅くなっただけ。

v10〜v11: UNetのみ学習とキャッシュ全削除

テキストエンコーダーの学習がMPSでおかしくなっている仮説を立てて、network_train_unet_only = trueでUNetのみに絞った。既存の.npzキャッシュが壊れている可能性も考えて全削除・再生成。

結果: それでも「手」。むしろ「ERROR」の文字が鮮明になってきた。

v12: 1枚画像テスト

データセットに問題がある可能性を排除するため、クリーンな画像1枚だけで学習を実行。

結果: 1枚だろうが何だろうが「手」が出る。ここで心が折れかける。

v13: ステップ数の見直し(Claudeの指摘)

ここでClaude(Claude Code)に相談したところ、「1エポックあたりのステップ数が少なすぎる(12ステップしかない)」という指摘を受けた。num_repeatsを1→5に増やし、learning_rateを1e-4→5e-5に下げて再挑戦。

推論スクリプトで確認すると、ベースモデル自体は正常にキャラクターを生成できていた。モデルと環境は死んでいない。

結果: Epoch 1で「顔」らしきものが出た。が、Epoch 2に向かう途中で力尽きた。

関わったAIエージェントたち

この一連の作業には複数のAIエージェントが関わっている。

エージェント役割
Antigravity失敗ログ全体の作成・記録。v1〜v13の試行を通じて設定変更とログ出力を担当
Claude (Claude Code)v13でステップ数の不足を指摘。その後RunPod向けのv3設定・クイックスタート手順・確定チートシートの作成も担当
Gemini成功事例の調査レポートを作成。6件の成功事例を収集し、失敗設定との比較表を生成

それぞれ別のタイミング・文脈で協力を求めた結果、ファイルが複数箇所に散らばっている。Mac全敗のフェーズではAntigravityとClaudeが中心で、原因調査でGeminiが入り、RunPod移行後のセットアップはClaudeと詰めた。

成功事例との設定差分

Geminiが収集した成功事例6件と、失敗した設定を比較すると明確な差分が浮かび上がる。

確実に問題だった設定

text_encoder_lr = 0(無効)

全成功事例でテキストエンコーダー学習が有効になっていた。これが0だとキャラクター特徴をプロンプトに紐づけできない。DCAI事例では5e-5、Prodigy使用事例では1.0(自動調整)に設定されていた。

clip_skip = 1

Illustrious系はclip_skip = 2が鉄板。成功事例すべてで2が使われている。

sdxl_no_half_vae 未設定

SDXL VAEはfp16で壊れるという既知問題がある。成功事例ではsdxl_no_half_vae = trueが明示的に設定されていた。

おそらく問題だった設定

network_dim=32, alpha=32(比率1.0)

成功事例ではalpha/dim比が0.125〜0.5。比率1.0はLoRA効果が強すぎてサンプル崩壊を引き起こす可能性がある。

repeats=1

59枚×1repeat=59ステップ/エポック。成功事例ではrepeats=5〜10で、総ステップ数1,400〜3,000が目安。

設定比較表

パラメータ失敗設定DCAI(成功)カズヤ弟氏(成功)
OptimizerAdafactorAdamW8bitProdigy
text_encoder_lr05e-51.0(自動)
network_dim3288
network_alpha3214
alpha/dim比1.00.1250.5
clip_skip12不明
no_half_vae未設定不明不明
repeats1510

RunPodで再挑戦→成功

Macを諦め、RunPod RTX 4090に移行した。Geminiの調査レポートとClaudeとの設定詰めを経て、v3設定を確定。

環境

RunPod Template:  RunPod Pytorch 2.1
GPU:              RTX 4090
PyTorch:          2.1.2 + CUDA 11.8
sd-scripts:       v0.8.7
xformers:         0.0.23.post1

致命的だった3パラメータ

Macでの13回の試行で一度も正しく設定できていなかった致命的なパラメータが3つある。

パラメータ正しい値間違えた値なぜ壊れるか
no_half_vaetrue未設定(false)VAEがfp16でオーバーフロー→サンプル画像が破綻
text_encoder_lr5e-50トリガーワードとキャラ特徴が紐付かない
clip_skip21Illustrious系はclip_skip=2で学習されている

確定設定(抜粋)

[additional_network_arguments]
unet_lr = 1e-4
text_encoder_lr = 5e-5          # 0にしない
network_dim = 8
network_alpha = 1               # ratio = 0.125
network_train_unet_only = false # trueにしない

[optimizer_arguments]
optimizer_type = "AdamW8bit"
learning_rate = 1e-4
lr_scheduler = "cosine"

[training_arguments]
max_train_epochs = 10
clip_skip = 2                   # 1にしない
no_half_vae = true              # 必須
mixed_precision = "fp16"
xformers = true

[dataset]
num_repeats = 10

自動化スクリプト

Claudeと一緒に、RunPodでの学習を1コマンドで回せるスクリプトを2本作った。

runpod_train_final.sh — 環境セットアップから学習完了まで全自動。PyTorch 2.1.2+CUDA 11.8のインストール、sd-scripts v0.8.7のクローン、xformers/bitsandbytesの導入、設定ファイル生成、事前チェック(ベースモデル・学習データの存在確認、ステップ数計算、warmup自動算出)、そして学習実行まで一気に走る。

setup_comfyui_final.sh — 学習完了後にComfyUIをセットアップして生成テストするためのスクリプト。ベースモデルと出力LoRAをシンボリックリンクで配置し、RunPodのPort 8188でブラウザアクセスできるようにする。

手順はこうなる:

  1. RunPodでRTX 4090 / RunPod Pytorch 2.1 / Disk 50GBを起動
  2. scpでスクリプト2本、ベースモデル、学習データ(png+txt)をアップロード
  3. bash runpod_train_final.sh で学習開始
  4. 完了後 bash setup_comfyui_final.sh でComfyUI起動
  5. epoch 3,5,7,10あたりを比較してベストを選ぶ

結果

59枚×10repeats×10epochs = 5,900ステップ。RTX 4090で1.3 it/s、約75分で完了。Lossは0.06台で安定。

RunPod上でテスト生成した画像がこちら。

RunPodテスト生成1

RunPodテスト生成2

RunPodテスト生成3

RunPodテスト生成4

RunPodテスト生成5

学習済みLoRAをダウンロードして、手元のComfyUIでも生成してみた。

手元のComfyUIで生成

RunPodでもローカルでも問題なく動く。Macで13回「崩れた手」しか出なかったのが嘘のように、ちゃんとキャラクターが出ている。

なぜsd-scripts直叩きだとハマるのか

kohya_ss GUIを使っている人はこの問題に遭遇しにくい。理由は単純で、GUIがデフォルトで正しい値を設定してくれるから。

  • no_half_vae: GUIでは「No half VAE」チェックボックスがデフォルトON
  • text_encoder_lr: GUIでは入力欄が見えるので自然と設定する
  • clip_skip: GUIではモデル種別ごとのプリセットがある

sd-scriptsを直接叩くとこれらが全部デフォルト値(false/0/1)になる。設定ファイルに書かなければ存在しないのと同じ。Mac環境の問題だと思い込んで13回もMPS周りを弄っていたが、実際には設定値の問題がほとんどだった。

振り返り

Macでの13回全敗は「MPSの限界」ではなく「設定の問題」が大半だった可能性が高い。ただし、MacではAdamW8bit(bitsandbytes)やxformersが使えないという制約があり、NVIDIA環境と同じ土俵に立てない部分は確かにある。

RunPod RTX 4090は1時間あたり数十円程度。Mac環境でデバッグに費やした数時間と精神的コストを考えると、最初からクラウドGPUを借りるべきだった。