技術 約10分で読めます

FLUX.2 Klein 9B + 9B NSFW LoRAをM1 Max 64GB / mflux 0.17.5で実機検証する

いけさん目次

前回の机上検討では「mfluxで9Bが回るか」「9B用LoRAがmfluxの想定するキー名と合うか」「そもそもNSFWプロンプトでNSFW出力が出るのか」が宙に浮いていた。
M1 Max 64GBの手元環境で mflux 0.17.5flux2-klein-9bdiroverflo/FLux_Klein_9B_NSFW を実際に走らせて、すべて答えを取った。

先に結果だけ。

  • 9B本体は走る。512は1分51秒、1024は5分37秒(4bit量子化、20ステップ)
  • LoRAは224キー全マッチで読み込まれ、推論オーバーヘッドは誤差レベル
  • NSFWプロンプト + LoRA scale 1.0でNSFW出力が出る。検閲・ぼかし・崩れなし
  • 「ローカルKlein 9B NSFW」は成立する。律速はディスク容量と1枚あたりの待ち時間だけ

環境

  • M1 Max / 64GB unified memory
  • macOS(Darwin 25.3.0)
  • Python 3.13(miniconda base)
  • mflux 0.17.5(mflux-generate-flux2--base-model flux2-klein-9b をネイティブサポート)
  • HuggingFace個人トークンでログイン済み
  • ディスク空き: 検証開始時で38GB、9Bフルキャッシュ後で6.8GB

9Bはgated repoだが利用同意のクリック1回で通る

black-forest-labs/FLUX.2-klein-9B は HuggingFace上で gated: "auto" 設定。
モデルページ上で FLUX Non-Commercial License Agreement と Acceptable Use Policy への同意ボタン(“Agree and access repository”)が出るので、それを押すと即時アクセス権が付く。手動レビュー待ちはなし。

CLI経由のトークン認証だけでは弾かれる(403)。ブラウザ側でログインしてフォームを通す手順が一度だけ必要。
トークンとブラウザセッションが別物なので、CLIで hf auth whoami が通っていてもこの同意は別途必要になる。
これだけ済ませれば以降は何もない。

9B本体のダウンロード

mfluxは初回呼び出しで snapshot_download 経由で全ファイルを取りに行く。
20ファイル、合計で約30GB。手元の回線で 10分23秒 かかった。

Fetching 20 files: 100%|██████████| 20/20 [10:23<00:00, 31.19s/it]

ダウンロード後の ~/.cache/huggingface/hub/models--black-forest-labs--FLUX.2-klein-9B/32GB
内訳は transformer(DiT本体BF16)、text_encoder(T5系)、tokenizervae
4Bが15GBだったので、ほぼ倍の重量。

ディスクが圧迫されるのが今回いちばん痛い。
38GB空きから一気に6.8GBまで落ちる。動かす前に40GB近い余地があるか確認しておく必要がある。

4bit量子化で512×512

まず小サイズで完走確認。--quantize 4 を付けることで、mflux側がBF16重みをロード時に4bit量子化して使う。
ディスク上のキャッシュはBF16のまま残るので、初回コストは小さくない。

mflux-generate-flux2 \
  --model flux2-klein-9b \
  --quantize 4 \
  --prompt "a portrait photo of a young woman, soft natural light, photorealistic" \
  --steps 20 --width 512 --height 512 --seed 42 \
  --output base_q4_512.png

20ステップで 1分51秒、per-step 5.30秒

Klein 9B base, 512x512, q4, 20 steps

LoRAなしのベースライン画像。写実調の若い女性ポートレート、屋外、自然光、Tシャツ。
プロンプトに忠実で、肌や髪のディテールは512にしてはしっかり出ている。4Bの512出力と比べると、細部の質感(目、髪のひと房、肌の毛穴感)が一段上。

NSFW LoRAの読み込み

diroverflo/FLux_Klein_9B_NSFWFlux Klein - NSFW v2.safetensors 1ファイル、158MB。
metadata から見ると、ai-toolkit 0.7.20 で 8000ステップ・77エポック、ss_base_model_version=flux2_klein_9b、ランク32、bfloat16。
キー命名は diffusion_model.double_blocks.X.<sublayer>.lora_A/B.weight のai-toolkit流。

mfluxの --lora-paths に直接渡すと、起動ログでこう表示される。

📦 Loading 1 LoRA file(s)...
🔧 Applying LoRA: Flux Klein - NSFW v2.safetensors (scale=0.7)
   ✅ Applied to 144 layers (224/224 keys matched)
✅ All LoRA weights applied successfully

224キーすべてが9B側の対応レイヤーにマッチ、144レイヤーへLoRAが噛んだ。
9B用に学習されたLoRAなので当然と言えば当然だが、mflux側のキー名期待とai-toolkit側のキー出力が完全に一致していることが実機で確認できた。

9B + LoRAでの推論オーバーヘッド

ベースと同じ条件(512×512、20ステップ、scale=0.7)。

mflux-generate-flux2 \
  --model flux2-klein-9b \
  --quantize 4 \
  --lora-paths "Flux Klein - NSFW v2.safetensors" \
  --lora-scales 0.7 \
  --prompt "a portrait photo of a young woman, soft natural light, photorealistic" \
  --steps 20 --width 512 --height 512 --seed 42 \
  --output 9b_lora07_512.png

1分49秒、per-step 5.35秒
LoRAなしの1分51秒、5.30秒/stepと比べて誤差レベル。rank 32なら推論コストはほぼ無視できる範囲だった。

Klein 9B + LoRA scale 0.7, 512x512

プロンプトはSFWのままなのでLoRAの本来の用途(成人向け)には踏み込んでいないが、出力自体には変化がある。
顔の輪郭、髪のテクスチャ、構図がベースと別物に振れ、室内・窓辺の光に変わった。LoRAのデータ分布側に引っ張られて、構図やライティングの傾向が動いていることが分かる。

scaleを上げる、LoRA本来の用途寄りのプロンプトにする、のいずれかで本来意図した方向の効きが出るはず。
ただし「LoRAが正しく適用されて、生成挙動が確かに変わっている」ことは、SFWプロンプトの段階でも確認できた。

NSFWプロンプトで実際にNSFWは出るか

前回記事の本筋にあった疑問。
4Bでは「成人向けプロンプトを入れても出力が途中で丸められる」状態だった。9B + 9B NSFW LoRAでこれが解けるかを直接確認する。

scaleを1.0に、プロンプトもLoRAの学習分布寄りに振る。

mflux-generate-flux2 \
  --model flux2-klein-9b \
  --quantize 4 \
  --lora-paths "Flux Klein - NSFW v2.safetensors" \
  --lora-scales 1.0 \
  --prompt "a topless portrait photo of a young woman, soft natural light, photorealistic, bare chest, nude" \
  --steps 20 --width 512 --height 512 --seed 42 \
  --output 9b_nsfw_lora10_512.png

1分49秒、per-step 5.36秒。SFWプロンプトのときと変わらない速度。

Klein 9B + NSFW LoRA scale 1.0, NSFWプロンプト

出力はプロンプト通りトップレスのポートレート写真で、検閲・ぼかし・破綻はない。
解剖学的にも素直で、4Bで踏んだ「胸元が布や影に逃げる」「ノイジーな塗りに丸められる」現象は出ていない。LoRAの学習データに沿った構図と質感まで通っている。

つまり「Klein 9B本体 + 9B NSFW LoRA + NSFW寄りプロンプト + scale 1.0」のセットで、ローカル生成は成立する。
モデル単体のセーフティ寄りの癖はLoRAで上書きされ、512解像度でも実用に耐える。

日本人を指定して出せるか

これまでの出力はどれも欧米人寄りの顔だった。
FLUX系は学習データの偏りで、人物プロンプトを素のまま投げるとデフォルトで欧米顔になりやすい。日本人読者からすると「指定して出せないと使い物にならない」話。

Japanese woman を入れた上で black hairasian features を補助で足し、地理コンテキスト(taken in Tokyo)も入れて回した。

mflux-generate-flux2 \
  --model flux2-klein-9b \
  --quantize 4 \
  --lora-paths "Flux Klein - NSFW v2.safetensors" \
  --lora-scales 0.7 \
  --prompt "a portrait photo of a young Japanese woman, black hair, asian features, soft natural light, photorealistic, taken in Tokyo" \
  --steps 20 --width 512 --height 512 --seed 42 \
  --output 9b_japanese_lora07_512.png

Klein 9B + LoRA、日本人指定プロンプト

明確にアジア系・日本人寄りの顔、黒髪、背景は日本語看板の街並み。
Japanese woman 単体だと欧米寄りバイアスに負けることがあるが、black hairasian features を補助に置くと安定して通る。taken in Tokyo を足すと背景も日本の都市風景に切り替わる。

LoRA越しでも顔の人種特徴は普通に通る。
このLoRAは姿勢・露出系の補正で、顔のレースや骨格までは上書きしないため、ベース側のプロンプトで人種を指定すればその通りに出てくる。9B本体の表現力は欧米顔と同等で、肌・髪のディテールに目立つ劣化はない。

ChatGPT画像生成との雑誌レイアウト比較

X上で見かけた、ChatGPT(GPT-Image-2)のプロンプトをそのまま投げると次のような出力が出るらしい。

水着のファッション雑誌(広告ページ)の見開きページを作って。
4:3 ナノビキニ+Tフロント+Tバック特集。日本人モデル。
日常感ある雑誌のデザイン。
あまり飾らず、普段の表情、メイク。
モルディブで撮影。
明るい光。
モデルをいろんな視点や姿勢で撮影
寄りや引きの構図
上半身や下半身のみのズーム
文章は最低限で、モデルの画像を大きく多く表示
スタイルを変えて4パターン

ChatGPT側の出力(参考):

ChatGPT画像生成での雑誌見開き出力

見開き4パネル、複数視点、雑誌タイポグラフィ(“Beachwear” タイトル、“モルディブ撮影” サブ、“special” タグ)、同一モデルの一貫性まで通っている。
これはGPT-Image-2の得意領域で、LLMが内部で構図・タイポ・複数パネルを設計してから生成するため、レイアウト指示が効く。

FLUX系は純粋な拡散モデルで、1枚絵に複数の独立シーンを組ませる、複雑な日本語や英語のタイポを正しく描く、というのは苦手。
プロンプトを単体ポートレートに落とし込めばどこまで近づけるかを見るのが、ローカル運用での妥当な比較になる。

同じプロンプトの単体ポートレート版でテストする。
「ナノビキニ+Tバック+モルディブ+カジュアル+雑誌風+日本人」をFLUXで通せるか。

LoRAなし vs LoRA(0.7)

同じseed、同じプロンプト、768×1024、20ステップ、4bit量子化、片方だけLoRAあり。

LoRAなし:

Klein 9B素、日本人水着プロンプト

雑誌表紙風レイアウト(“UHER HAPLB” 風タイトル、“NANO” サブテキスト)を再現しようと試みている。文字は崩れているが、editorial styleの解釈は素のKlein 9Bの方がむしろ強く出ている。
水着自体は花柄パターンの普通のビキニで、「ナノビキニ」「Tバック」の具体指定は通らず、無難な布量に丸められた。Klein 9B素のままでも水着出力を拒否はしないが、露出度の指定は安全側に引き戻される。

LoRA scale 0.7:

Klein 9B + LoRA(0.7)、日本人水着プロンプト

ストレートなポートレート構図に切り替わり、雑誌表紙風レイアウトの試みは消えた。LoRAの学習分布が「単体ポートレート」寄りなため、editorial styleの傾向を抑制する方向に効いている。
水着の露出度はLoRAなし版とほぼ同等で、scale 0.7では「ナノビキニ」「Tバック」までは押し切れていない。

比較から分かること

  • Klein 9B素のままでも水着出力は出る(モデルレベルで拒否しない)
  • 「ナノビキニ」「Tバック」の具体指定はscale 0.7では通らない、普通のビキニに丸まる
  • LoRAは露出度ブーストよりも構図・スタイルの傾向を変える効果が目立つ。学習データが単体ポートレート中心だと、editorial / 雑誌レイアウトの方向は逆に弱まる
  • 露出度を確実に上げたければscale 1.0付近まで上げるかトリガーワードを足す必要がある(前述のNSFW検証ではscale 1.0で踏み込めた)
  • ChatGPT画像生成のような「見開き4パネル+雑誌タイポグラフィ」は構造的にFLUXでは無理。狙うならComfyUIで複数生成して後処理で合成する方向

1024×1024での実用速度

512は完走確認用のサイズなので、実用解像度の1024でもう一度同じ条件を回す。

mflux-generate-flux2 \
  --model flux2-klein-9b \
  --quantize 4 \
  --lora-paths "Flux Klein - NSFW v2.safetensors" \
  --lora-scales 0.7 \
  --prompt "a portrait photo of a young woman, soft natural light, photorealistic" \
  --steps 20 --width 1024 --height 1024 --seed 42 \
  --output 9b_lora07_1024.png

5分37秒、per-step 16.86秒
512の3.2倍、4Bの1024(mflux 0.17.5で30秒前後)と比べると約11倍。

Klein 9B + LoRA scale 0.7, 1024x1024

肌の質感、髪のディテール、部屋の背景の落ち込みなど、1024にしたメリットがそのまま出た画像。
ニットの編み目、ハイライトの粒立ちまで描ける解像度になっている。
4Bの1024と比べても明らかに別解像度の絵で、写実方向に振るならこっちが基準になる。

メモリ・ディスク・実用性の感覚

メモリ

ピーク時のRAM使用は推論中にWiredで30GB前後。残り30GBは他作業用に確保できるが、Chromeを2〜3ウィンドウ開きながら、というほどの余裕ではない。生成中は他の重い処理を避けるのが安全。

ディスク

9Bフルキャッシュで32GB持っていく。同時に4Bを残しておくと47GB、Qwen系のキャッシュも残してると100GB近く食う。9Bを継続的に使うなら他のFLUX/Qwenキャッシュは整理しておく。

速度

1024で5分半は試行錯誤の単位としては重い。50枚出すと4時間半。
プロンプト微調整・LoRAスケール振りでは「先に小サイズで当たりを付けて、本命だけ1024」という運用に切り替えるのが現実的。512なら20ステップで2分、scaleを0.3、0.5、0.7、1.0と振っても10分かからない。

LoRAスケールの調整余地

scale 0.7では水着の露出度指定(ナノビキニ、Tバック)まで押し切れず無難な布量に丸まる。scale 1.0でNSFWプロンプト寄りに振ると意図した方向に効く。実用ではプロンプトとscaleの組み合わせで0.5〜1.0を振りながら当たりを探すスタイルになる。

4Bと9Bの使い分け

前回の4B記事で1024が30秒前後だった。
9Bの1024が5分半なので、生成1枚に投じる時間は11倍。質感のリッチさを取るか、回数で稼ぐかのトレードオフがそのまま速度差に乗っている。

ローカルで「とにかく数を出して当たりを引く」運用なら4B。
「本命の1〜2枚を高解像度・高ディテールで仕上げたい」なら9B。
LoRAの効き自体は4Bと9Bでベースが違うので、9B用LoRAは9B側でしか使えない。アニメ寄りの絵を作りたいなら別系統(WAI-Anima系のSDXLチェックポイント+ComfyUI)を併用したほうが早い。

前回記事で残っていた「Klein 9B + 9B NSFW LoRAをM1 Max 64GBでローカルだけで成立させられるか」という問いの答えはイエス。
RunPod等の外部GPUに逃がさなくても、mfluxの --lora-paths で素直に走る。
動かない側の懸念は、ディスク容量と1枚あたりの待ち時間だけ。