技術 約14分で読めます

FLUX.2 Kleinの成人向けLoRAはM1 Max環境でそのまま試せるのか

いけさん目次

FLUX.2 Klein 4BをM1 Maxで動かした前回の実験では、成人向けプロンプトを入れても出力が途中で丸められることを確認した。
写実ではクロップや布っぽいものに逃げ、アニメ寄りでも完全には通らない。モデル側にセーフティ寄りの癖が残っている。

その後、FLUX.2 Klein 9B向けの成人向けLoRAがHugging Faceに出ているのを見つけた。
問題は「それを今のこちらの環境で実行できるのか」だ。モデル互換性としてはKlein 9B用なので、4B環境にはそのまま載せないほうがいい
mfluxで4Bしか回していない現状だと、同じFLUX.2 Kleinファミリでも安全な組み合わせではない。

出ているLoRAはKlein 9B前提

見つけたのは diroverflo/FLux_Klein_9B_NSFW
モデルカードでは、FLUX.2 Klein 9B向けに学習したLoRAとして説明されている。

ここで効いてくるのが、FLUX.2 Kleinのモデル分岐だ。
Black Forest Labsの公式リポジトリでは、Kleinに4B、9B、9B KV、Base系が並んでいる。
用途としては、リアルタイムや手元GPUならKlein 4B、LoRA学習や柔軟性重視ならKlein BaseまたはFLUX.2 devという整理になっている。

つまり「FLUX.2 Klein用LoRA」とだけ見ると同じに見えるが、実際には 4B / 9B / Base / distilled の差をまたいでよいとは限らない
RunComfyのFLUX.2 Dev LoRAワークフローでも、FLUX.1、FLUX.2 Dev、Klein variants のLoRAは互換ではない前提で扱われている。
違う土台に刺すと、lora key not loaded やshape mismatchで終わるか、見かけ上は動いても効果が出ない。

手元の実運用は4B mflux寄り

手元で実測済みなのは、M1 Max 64GBでの flux2-klein-4b
mflux 0.17.5なら1024×1024を30秒台、iris.cなら40秒台で生成できた。
最近作ったFastAPIラッパでも、FLUX.2側は ComfyUI に寄せず mflux-generate-flux2 をsubprocessで呼ぶ構成にしている。

この構成の長所は軽いこと。
Python + MLXで済み、ComfyUIのワークフロー差分やカスタムノードに巻き込まれにくい。
ただし今回のLoRAは9B向けなので、現在の4B mfluxルートに追加して検証する対象ではない。

mfluxで試すなら9Bが先

mfluxはLoRA外付けに対応している。
過去にQwen Image Editで Lightning LoRA を足したときも、--lora-paths 経由で品質が大きく変わった。
FLUX.2 Klein 4Bの記事でも、mflux側はLoRA対応あり、iris.c側はLoRAなしとして整理した。

ただし、今回必要なのは「LoRAを読めるか」ではなく「Klein 9B本体をmfluxで現実的に回せるか」だ。
Klein 4Bは手元で実測済みだが、9Bはメモリ・時間ともに重くなる。
M1 Max 64GBならメモリだけは足りる可能性が高いが、4Bの30秒台とは別物として見たほうがいい。

4Bに無理やり9B用LoRAを刺して「効かない」と判断するのは切り分けとして弱いため、まずは成人向けLoRAを入れずに flux2-klein-9b 単体がmfluxで最後まで走るか確認する。
その後、同じseedとプロンプトでLoRAの有無を比較し、--lora-scales を調整して意図した変化が出るかを見る流れになる。

ComfyUIで試す場合はLoRA経路が別の罠になる

ComfyUIならFLUX.2 DevやKlein系のテンプレートがある。
公式チュートリアルでも、FLUX.2 Devは最新ComfyUI前提でワークフローを読み込む流れになっている。

ただ、FLUX.2 LoRAは通常の Load LoRA -> KSampler だけで済むとは限らない。
RunComfyのワークフローは、Flux2Pipelineに合わせた経路でLoRAを適用する設計になっている。
汎用LoRAローダーをFP8重みに重ねると mul_cuda がFloat8で未実装、あるいはLoRAキー不一致で崩れる、というトラブルも挙げられている。

これはMac環境でも同じ種類の問題になる。
CUDAの mul_cuda そのものは出なくても、MPSではFP8が使えないし、BF16/FP16の扱いで別のエラーや黒画像を踏むことがある。
Qwen Image Editの記事で見たように、ComfyUI + MPSはモデル更新やdtype変更で急に遅くなったり壊れたりする。

手元で安定しているのはmflux。
ComfyUIで9B LoRAを試すなら、専用ワークフローか、Klein 9B LoRA対応を明示しているノードを使うほうが筋だ。

検証ルートの切り分け

今回のLoRAは、現在のFLUX.2 Klein 4B運用にはそのまま足せない。
使うならまずKlein 9B本体を用意し、mfluxまたはComfyUIで9B単体生成を通す必要がある。

経路判定理由
mflux + Klein 4B不向きLoRAが9B前提。モデルサイズ違いで効果なし・不一致の可能性が高い
mflux + Klein 9B要検証LoRA経路はあるが、9B本体の速度とメモリをまだ測っていない
iris.c + Klein 4B不可iris.c側にLoRA適用経路がない
ComfyUI + Klein 9B要検証専用ワークフロー次第。汎用LoRAローダーだけではズレやすい
RunPod + NVIDIA GPU一番切り分けしやすい9BとLoRAを素直に載せやすく、Mac固有のMPS問題を避けられる

自分の手元で次にやるなら、M1 Maxでいきなり粘るより、まずRunPodかローカルNVIDIA機でKlein 9B + LoRAの効果を確認する。
効果が確認できたあとに、mfluxで9BをApple Siliconへ持ち込む。
逆順にすると、モデル互換性・LoRA形式・MPS性能の3つが同時に絡んで、失敗理由が分からなくなる。

LoRA適用で速度はどれくらい落ちるのか

前回のKlein 4B記事で、mfluxは1024×1024を30秒切りで出せることを確認した。
かなり速い部類で、1分以内に1枚仕上がるなら試行錯誤のストレスが小さい。

LoRAを載せるとどうなるか。
mfluxの --lora-paths は推論ループの中でLoRA重みをオンザフライで適用する方式で、LoRAのランクが小さければ(rank 4〜16程度)加算量は限定的。
前回のQwen Image Edit記事でLightning LoRAを足したときも、体感で5〜10%程度遅くなる程度だった。
ただしそれは4Bの話で、9Bだとベースの推論時間自体が伸びる。
仮にベース推論が60〜90秒になるなら、そこから5〜10%増えて65〜100秒という見積もりになる。
4Bの30秒と比べると明確に待つが、1枚2分以内なら十分にローカル検証の範囲内ではある。

注意点は、LoRAランクが大きい(rank 64以上)場合や複数LoRAを同時に適用する場合。
乗算のコストが目に見えて増えるので、試行回数で稼ぐスタイルには向かなくなる。
今回のNSFW LoRAがどのランクで学習されているかはモデルカードに明示されていないため、実際に読み込んでみないと分からない。

Flux系でアニメ調は本当に出るのか

もうひとつ気になっているのが画風の問題。
FLUX.2 Kleinはリアル系メインのアーキテクチャで、前回の実験でもアニメプロンプトを入れれば一応「アニメっぽいもの」は出てきた。
ただし線画の質感は「AI生成のアニメ画像」そのもので、WAI-AnimaやIllustrious系で出てくる線の切れ味とは比較にならなかった。

今回のNSFW LoRAがリアル写実系の学習データで作られているなら、アニメ調との相性は期待しにくい。
LoRAの学習画像が写実nude中心であれば、プロンプトでanime styleを指定しても出力は写実の肌テクスチャが混ざったキメラ的な絵になりやすい。
逆にアニメ系のデータで学習されたNSFW LoRAが別に出てくれば、Klein 9Bをベースにしてアニメ系の表現が通るかどうか実験できる。

実質的な選択肢は2つに分かれる。
写実NSFW → Klein 9B + 今回のLoRAでそのまま試す。
アニメNSFW → Kleinではなく、ComfyUI + WAI-Anima系チェックポイント + アニメ特化NSFW LoRAの組み合わせに切り替える。
後者はすでにWAI-Anima + ComfyUIの構成で動いている経路なので、新たに検証が必要なのは前者の写実側だけということになる。

手元でやらずRunPodに逃がす判断

手元のM1 Max 64GBはKlein 9Bのメモリ要件を満たせるはず。
ただ、9Bの推論が60〜90秒/枚になると、LoRAのスケール調整やプロンプト試行がきつい。
50枚出すだけで80分以上。知識や環境構築の問題ではなく、単純にイテレーション速度が足りない。

それに加えてMac + MPSのdtype問題がある。
BF16のFLUX系モデルをComfyUIで扱うとMPSでは黒画像やFP8非対応エラーが出やすく、LoRAが効いていないのかMPS側の問題なのかが切り分けられない。
RunPodならCUDA環境で素直に動くので、先にLoRAの効果自体を確認して、Mac移植の問題と分離できる。

RTX 4090(24GB VRAM)がKlein 9Bをギリギリ載せられる範囲で最も安い。
RunPodのCommunity Cloudで$0.4/hr前後。1セッション2〜3時間で$1前後の計算。
手元で半日MPS問題に嵌まるコストと比べれば十分に安い。
24GBで足りなければA100 40GB($0.8/hr前後)に上げればいい。

RunPod上でのKlein 9B + LoRA検証

RunPodにはComfyUIテンプレートが複数あり、FLUX系モデル対応のものを選べばテキストエンコーダ(T5-XXLとCLIP-L)もプリインストールされている。
Pod起動後すぐWebUIが使えるので、セットアップはモデルとLoRAのダウンロードだけで済む。

# Klein 9B本体
huggingface-cli download black-forest-labs/FLUX.2-Klein-9B \
  --local-dir /workspace/ComfyUI/models/unet/

# NSFW LoRA
huggingface-cli download diroverflo/FLux_Klein_9B_NSFW \
  --local-dir /workspace/ComfyUI/models/loras/

テンプレートにエンコーダが入っていなければ追加で20GB超のダウンロードになる。
起動前にテンプレートの説明欄でFLUX対応を確認しておく。

ダウンロードが終わったらComfyUIでKlein 9B用ワークフローを読み込み、まずLoRAなしで1枚出す。
同じseedとプロンプトでLoRAありも出して差を見る。
lora_scaleを0.5→0.7→1.0と振って段階的にNSFW表現が強まるか、色崩れや黒画像が出ないかを確認する。

CUDAで正常に出力されれば、LoRA自体は機能しているという確認が取れる。
そのあとmflux + 9BでLoRAを載せて同じ結果にならなければ、原因はMLXのLoRA経路かMPSのdtype処理に絞れる。
逆にRunPodでも効果が薄ければ、LoRA自体のランクや学習データの問題なので、Mac移植を試す前に別のLoRAを探すか自前で学習する方向になる。

生成画像は /workspace/ComfyUI/output/ に溜まる。
RunPodのファイルブラウザか runpodctl send で手元に回収する。

既存LoRAのランクを調べる

RunPodで検証する前に、LoRAファイルのメタデータからランクとアルファ値を確認しておくと、lora_scaleの初期値を決めやすい。
safetensorsのヘッダにはネットワーク構成が埋まっていることが多い。

from safetensors import safe_open

with safe_open("flux_klein_9b_nsfw.safetensors", framework="pt") as f:
    print(f.metadata())
    for key in list(f.keys())[:5]:
        print(key, f.get_tensor(key).shape)

キー名に lora_downlora_up が含まれ、lora_down 側のshapeが (rank, in_features) になっている。
rank 16前後ならscale 0.7〜1.0で素直に効く。
rank 64以上だと1.0で色崩れしやすく、0.3〜0.5から始めたほうが安全。

モデルカードにランクが明記されていないので、これを先に見ておかないとscale調整が手探りになる。

自前でKlein 9B LoRAを学習する場合

既存LoRAの効果が薄い、あるいは画風が合わないとなったら、自前で学習する選択肢がある。
FLUX.2のLoRA学習ツールは、ostrisのai-toolkitとkohya_ssのsd-scriptsが二大勢力。
FLUX.2 Dev時代からどちらもFLUXアーキテクチャに対応済みで、Klein 9BもTransformerの構造が共通なのでそのまま使える。

学習にはCUDA環境が必須。
Apple SiliconのMLXにはLLM向けLoRA学習機能があるが、FLUX.2のようなDiTアーキテクチャ向けの画像生成LoRA学習パイプラインは存在しない。
学習はRunPodかローカルNVIDIA機で回し、できたsafetensorsファイルをmfluxに持ち込む流れになる。

VRAM

LoRA学習は推論よりVRAMを食う。
FLUX.2 Dev(12B相当)のLoRA学習で、gradient checkpointing + adafactor使用時にRTX 4090の24GBでギリギリという報告がある。
Klein 9Bは少し軽いが、full fine-tuningは無理で、LoRAが前提になる。

構成推定VRAM備考
rank 16 + gradient checkpointing + adafactor20〜22GBRTX 4090で動く見込み
rank 64 + gradient checkpointing24〜28GBA100 40GBが安全
QLoRA(4bit量子化ベースモデル + LoRA)16〜20GBai-toolkitで対応。品質は要検証

rank 16でRTX 4090に収まるなら、RunPodの推論検証と同じPodで学習も回せる。
A100に上げても$0.8/hr前後なので、2〜3時間の学習セッションで$2前後。

ai-toolkitの設定

ai-toolkitはYAMLで学習設定を書いてワンコマンドで回す方式。
FLUX.2向けの設定をKlein 9Bに読み替えるとこうなる。

job: extension
config:
  name: "flux2_klein_9b_nsfw_lora"
  process:
    - type: sd_trainer
      training_folder: "output"
      device: cuda:0
      network:
        type: lora
        linear: 16
        linear_alpha: 16
      model:
        name_or_path: "black-forest-labs/FLUX.2-Klein-9B"
        is_flux: true
      train:
        batch_size: 1
        steps: 2000
        gradient_accumulation_steps: 1
        gradient_checkpointing: true
        optimizer: adafactor
        lr: 4e-4
      save:
        save_every_n_steps: 500
      sample:
        sample_every_n_steps: 250
        prompts:
          - "a portrait photo of a woman"
      datasets:
        - folder_path: "/data/nsfw_dataset"
          caption_ext: ".txt"
          resolution: 1024

save_every_n_steps で500ステップごとにチェックポイントを保存し、sample_every_n_steps で途中の生成画像を確認する。
2000ステップは目安で、20枚程度のデータセットなら1000前後で収束することもある。
過学習すると生成画像が学習データのコピーに近づくので、サンプル出力が崩れ始めたら打ち切る。

sd-scriptsはネットワーク構成の選択肢が広く、LoHAやLoKrも使える。
ただしコマンドラインのフラグが多く、初見のセットアップに手間がかかる。
Klein 9B固有の調整(distilled構造への対応など)については、ai-toolkitのほうがissueの反応が速い。
特にこだわりがなければai-toolkitから始めるのが手っ取り早い。

学習データの質

NSFW LoRAの学習データは、量より画風の一貫性が効く。
100枚の雑多な画像より、20〜30枚のポーズ・ライティング・画風が揃ったセットのほうが安定した結果になりやすい。

キャプションはWD TaggerやFlorenceで自動タグをつけたあと、トリガーワードを手動で先頭に追加する。
たとえばトリガーワードを nsfw_v1 にしておくと、推論時にプロンプトへ nsfw_v1, を足すだけでLoRAが発動する。
トリガーワードなしで学習した場合、LoRAの効果がプロンプト全体に薄く乗る形になり、ON/OFFの切り替えが効かない。

写実とアニメでデータを混ぜると中途半端になる。
前のセクションで触れた画風の問題と同じで、写実NSFWなら写実だけ、アニメNSFWならアニメだけで統一したほうがいい。

学習の途中経過を確認する

ai-toolkitはsample_every_n_stepsで途中画像を自動出力する。
見るのは「崩れていないか」と「LoRAの効果が出ているか」の2つ。

最初の250ステップでは、ベースモデルの出力とほぼ変わらない。
500〜1000ステップで学習データの特徴が乗り始める。
プロンプトに含まれないポーズや構図が混じってきたら過学習の兆候で、そのあたりのチェックポイントで止めるのが安全。

loss値はFLUX系のLoRA学習ではあまり当てにならない。
DiTアーキテクチャのlossはUNet系と比べて変動が大きく、数値だけ見て収束判断すると過学習を見逃す。
結局、サンプル画像の目視がいちばん信頼できる。

overfitにも典型的な兆候がある。
サンプルが学習データの特定画像そのものに近づく、顔が崩れる、背景が溶ける、特定の色に偏る。
こうなったらステップを戻してそのチェックポイントを使う。
ai-toolkitはsave_every_n_stepsで複数チェックポイントを残せるので、ベストなものを選んで後続の検証に回す。

学習済みLoRAの比較テスト

RunPod上のComfyUIにsafetensorsを読ませて、推論で評価する。

同じseed・同じプロンプトで、LoRAなしとLoRAありの画像を並べる。
差がほぼなければ学習ステップが足りないか、データ品質が悪いか、scaleが低すぎる。

lora_scaleを0.3、0.5、0.7、1.0で振って、どの値でNSFW表現が変わるか見る。
scale 1.0で色崩れや顔の破綻が出るなら、rank対比でscaleが高い。
0.5〜0.7常用にするか、rankを下げて再学習する。

学習データと異なるプロンプトも試す。
学習データにないポーズやシーンで効果が出るかが汎化性能の指標になる。
特定のポーズでしか効かないLoRAは、プロンプト1枚ごとに「効くかどうか」を試す運用になって実用的でない。

mfluxへの持ち込み

RunPodでLoRAの効果が確認できたら、safetensorsを手元に持ってくる。

runpodctl receive <file-id>

mfluxでは--lora-pathsにsafetensorsを指定するだけで済む。

mflux-generate-flux2 \
  --model flux2-klein-9b \
  --lora-paths ./flux2_klein_9b_nsfw_lora.safetensors \
  --lora-scales 0.7 \
  --prompt "..." \
  --seed 42 \
  --steps 30 \
  --width 1024 --height 1024

RunPodと同じseed・プロンプトで出力を比べる。
結果が同じならM1 Maxでそのまま運用する。
結果が変わったら、原因はMLXのLoRA適用経路かMPSのdtype処理に限定される。
4BのときLightning LoRAの適用で5〜10%の速度低下だったので、9Bでも同程度のオーバーヘッドに収まるか実測する。