キャラ+画風+衣装+シチュの複数LoRA併用の強度設計・干渉回避・マージ手法
2026-05-30 CC1発行 / dr_gemini 2パス推敲 / R18画像実装DR
### 1. 概要: SDXL R18量産における複数LoRA併用の物理限界と数理モデル
SDXL(Stable Diffusion XL)およびその派生モデル(Pony Diffusion V6、Animagine XL 3.1等)を用いたR18アニメCGの24時間自動量産ラインにおいて、最も深刻なボトルネックは**「複数LoRA併用時の表現崩壊(テンソル飽和と次元崩壊)」**である。
キャラ、画風、衣装、シチュエーション(ポーズ・構図・R18特異表現)の4要素のLoRAを単純に同時適用(ウェイト一律 $1.0$ 積み)した場合、U-Net内部のクロスアテンション層における特徴量マップの活性化値が許容閾値を超え、以下の現象が不可避的に発生する。
#### ① コサイン類似度の極端な低下(画風汚染・ノイズ化)
特にPony系モデルにおいて、CLIP-L/Gのテキストエンコーダー(TE)空間がLoRAの過剰なトリガーワードによって汚染され、プロンプトの指示(例:体位、アングル)を完全に無視する。これは、LoRAの重み行列 $W_{LoRA} = A \times B$ がベースモデルの重み $W_0$ に加算される際、特異値分布が特定の高次元方向に偏り、潜在表現の直交性が失われるためである。
#### ② 解剖学的破綻(多腕・多脚・関節の融解)
シチュエーションLoRA(例:`fella`, `paizuri`, `double_penetration`)のポーズバイアスと、キャラLoRAの骨格バイアスが競合し、U-NetのResNetブロック(特にDown/Upブロックの解像度 $64\times64$ 以下の低解像度層)で干渉を起こす。
これを防ぐため、本レポートでは**「総ウェイト制限ルール($\sum W_{model} \le 1.6$)」「U-Net階層別重み制御(LoRA Block Weighting)」「SVD(特異値分解)による直交化マージ」**を組み合わせた、実務で即座に稼働可能な量産設計を提示する。
---
### 2. 具体手順: 干渉を極小化する階層適用(Block Weight)と適用順序
SDXLのU-Netは計19のブロック(Input: 9, Middle: 1, Output: 9)で構成される。各LoRAが影響を与えるべき階層は、その役割(キャラ/画風/衣装/シチュ)によって完全に分離可能である。
#### SDXL U-Net 19ブロック構造と物理的解像度・役割定義
| ブロック名 | インデックス | 解像度 | 主な物理的役割 | 干渉する要素 |
| :--- | :--- | :--- | :--- | :--- |
| **BASE** | `L00` | - | ネットワーク全体へのグローバルバイアス | 全て |
| **IN00 - IN02** | `L01 - L03` | $1024\times1024$ | 構図、大まかなポーズ、パース、ライティング | シチュエーション、画風 |
| **IN03 - IN05** | `L04 - L06` | $512\times512$ | 中間的な幾何構造、大まかな衣装の輪郭 | 衣装、シチュエーション |
| **IN06 - IN08** | `L07 - L09` | $256\times256$ | テクスチャ、局部の位置関係、骨格の微調整 | キャラ、衣装、シチュエーション |
| **MID** | `L10` | $128\times128$ | 構図とディテールの境界。画風とポーズの結合部 | 全て(最も干渉が激しい) |
| **OUT00 - OUT02**| `L11 - L13` | $256\times256$ | 性器ディテール、体液テクスチャ、衣装の質感 | シチュエーション、衣装 |
| **OUT03 - OUT05**| `L14 - L16` | $512\times512$ | 顔の造形、瞳の描き込み、髪のハイライト | キャラ、画風 |
| **OUT06 - OUT08**| `L17 - L19` | $1024\times1024$ | 色彩、最終的なシャープネス、微細なノイズ処理 | 画風、キャラ |
```
[Input Blocks (IN00-08)] --> [Middle Block (MID)] --> [Output Blocks (OUT00-08)]
- 構図 / 骨格 / ポーズ - 抽象画風 / 接続 - 顔 / 髪 / 衣服 / 性器ディテール
(シチュLoRAを重点配置) (キャラ・衣装LoRAを重点配置)
```
#### 具体的な適用手順
1. **LoRAのロード順序(パイプライン順)**:
ComfyUIまたはDiffusersにおいて、LoRAは**「画風 $\rightarrow$ キャラ $\rightarrow$ 衣装 $\rightarrow$ シチュエーション」**の順でモデルに適用する。
* **理由**: 下流に適用されるLoRAほど、U-Netの残差結合(Residual Connections)に対して強いバイアスをかける。最も構図を支配しやすい「シチュエーション」を最後に適用し、微細な顔の造形(キャラ)を「OUTブロックの保護」によって維持する。
2. **階層フィルター(Block Weight)の適用**:
* **キャラLoRA**: 顔と髪のアイデンティティを保持するため、`OUT03-OUT05`(顔の解像度領域)のみにウェイトを集中させ、`IN`ブロックは完全に `0` にする。
* **シチュエーションLoRA**: 構図と体位を決定するため、`IN`ブロックおよび`MID`にウェイトを集中させ、顔を破壊しないよう`OUT03-OUT05`を `0` にする。
---
### 3. 推奨パラメータ表: 4カテゴリLoRAの強度設計マトリクス
SDXL(特にPony V6ベースのR18特化モデル)で4枚のLoRAを同時適用する場合の、実務における限界突破パラメータ設定値である。
#### SDXL 19ブロック完全ベクトル指定(BASE, IN00-08, MID, OUT00-08)
| LoRAカテゴリ | 推奨 $W_{model}$ | 推奨 $W_{clip}$ | SDXL Block Weight 19次元ベクトル | 優先適用順 | 役割と干渉回避のロジック |
| :--- | :--- | :--- | :--- | :--- | :--- |
| **1. 画風**<br>(Artstyle) | `0.40` | `0.20` | `1.0, 0.2, 0.2, 0.2, 0.2, 0.2, 0.5, 0.8, 0.8, 0.5, 0.8, 0.8, 0.8, 0.5, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2` | 1 (最初) | 全体のトーンを決定。CLIPを極小化し、プロンプト汚染を防ぐ。 |
| **2. キャラ**<br>(Character) | `0.75` | `0.55` | `0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.5, 0.5, 0.2, 0.5, 0.5, 0.5, 0.8, 1.0, 1.0, 1.0, 0.8, 0.5, 0.2` | 2 | 顔・髪の再現。`OUT03-05`(高解像度ディテール)に特化させ、骨格干渉を防ぐ。 |
| **3. 衣装**<br>(Outfit) | `0.50` | `0.30` | `0.0, 0.2, 0.5, 0.5, 0.5, 0.2, 0.0, 0.0, 0.0, 0.0, 0.2, 0.5, 0.8, 0.8, 0.2, 0.2, 0.2, 0.0, 0.0, 0.0` | 3 | 特定の衣服の形状。キャラLoRAのOUT領域と干渉しないよう、`IN01-02`と`OUT00-02`に分散。 |
| **4. シチュ**<br>(Situation/R18) | `0.65` | `0.45` | `1.0, 1.0, 1.0, 1.0, 0.8, 0.5, 0.2, 0.0, 0.0, 0.0, 0.5, 0.2, 0.2, 0.2, 0.0, 0.0, 0.0, 0.2, 0.5, 0.8` | 4 (最後) | ポーズ・体位・断面図等の制御。`IN`ブロックを最大化し、顔(`OUT03-05`)を完全にバイパス。 |
#### 運用上の鉄則(トータルウェイト制御)
$$\sum W_{model} \le 1.6 \quad \left( \text{許容限界値: } 1.8 \right)$$
$$\sum W_{clip} \le 1.1 \quad \left( \text{許容限界値: } 1.3 \right)$$
これらを超えると、画像全体に「砂嵐状のノイズ(Tensor Saturation)」が発生するか、コントラストが異常に高くなる(いわゆる「お焦げ」状態)。
---
### 4. 落とし穴と対策: トリガーワード衝突・過学習・画風汚染の回避技術
#### ① トリガーワードの衝突(Namespace Collision)
* **現象**: キャラLoRAのトリガーワード(例:`nagatoro`)と、衣装LoRAのトリガーワード(例:`school_uniform`)が、ベースモデル(Pony等)の内部タグと競合し、意図しないポーズや古い画風が強制出力される。
* **対策**:
* **LoRAのテキストエンコーダー(TE)の無効化**: キャラLoRA以外のTEウェイト($W_{clip}$)を `0` または `0.1` に極限まで下げる。
* **プロンプトの「アイソレーション(隔離)」**:
`Character: (nagatoro:1.1), Outfit: (school_uniform:0.9), Situation: (lying_on_back, spread_legs:1.2)` のように、プロンプト内で要素ごとにカンマで区切り、LoRAのトリガーワードをプロンプトの先頭(キャラ)と末尾(シチュ)に分離配置する。
#### ② R18特有の「断面図(Cross-section)」および「体液(Cum)」の画風汚染
* **現象**: 断面図LoRAや体液LoRAを有効にすると、キャラクターの顔や背景までアニメ調から不気味なリアル調/3D調に引きずられる。
* **対策**:
* **U-Netの「MID」および「OUT00-02」のピンポイント適用**:
R18表現のテクスチャ(液体、断面)は、U-Netの中間解像度($128\times128$ 前後)で処理される。
したがって、シチュエーションLoRAのBlock Weightを `0, 0, 0, 0, 1.0, 1.0, 0.5, 0, 0, 0, 0, 0`(MIDおよびその周辺のみ)に制限することで、顔のクオリティを担保したまま、局部のディテールのみを変化させる。
---
### 5. 実装コード設計: ComfyUIカスタムノード構成 & Pythonによる自動マージスクリプト
#### A. ComfyUI API JSON 構成(完全版)
複数LoRAを階層適用するための、ComfyUIノード構成のベストプラクティス。`LoraLoaderBlockWeight` ノード(`sd-webui-lora-block-weight` 互換ノード)を使用。
```json
{
"3": {
"inputs": {
"ckpt_name": "ponyDiffusionV6_v6StartWithThis.safetensors"
},
"class_type": "CheckpointLoaderSimple"
},
"10": {
"inputs": {
"model": ["3", 0],
"clip": ["3", 1],
"lora_name": "artstyle_flat_anime.safetensors",
"strength_model": 0.40,
"strength_clip": 0.20,
"block_vector": "1.0, 0.2, 0.2, 0.2, 0.2, 0.2, 0.5, 0.8, 0.8, 0.5, 0.8, 0.8, 0.8, 0.5, 0.2, 0.2, 0.2, 0.2, 0.2, 0.2"
},
"class_type": "LoraLoaderBlockWeight"
},
"11": {
"inputs": {
"model": ["10", 0],
"clip": ["10", 1],
"lora_name": "character_hilde.safetensors",
"strength_model": 0.75,
"strength_clip": 0.55,
"block_vector": "0.0, 0.0, 0.0, 0.0, 0.0, 0.0, 0.2, 0.5, 0.5, 0.2, 0.5, 0.5, 0.5, 0.8, 1.0, 1.0, 1.0, 0.8, 0.5, 0.2"
},
"class_type": "LoraLoaderBlockWeight"
},
"12": {
"inputs": {
"model": ["11", 0],
"clip": ["11", 1],
"lora_name": "situation_nakadashi_creampie.safetensors",
"strength_model": 0.65,
"strength_clip": 0.45,
"block_vector": "1.0, 1.0, 1.0, 1.0, 0.8, 0.5, 0.2, 0.0, 0.0, 0.0, 0.5, 0.2, 0.2, 0.2, 0.0, 0.0, 0.0, 0.2, 0.5, 0.8"
},
"class_type": "LoraLoaderBlockWeight"
},
"13": {
"inputs": {
"text": "score_9, score_8_up, score_7_up, source_anime, 1girl, hilde, solo, latex_suit, lying_on_back, spread_legs, vaginal_creampie, cum_dripping",
"clip": ["12", 1]
},
"class_type": "CLIPTextEncode"
},
"14": {
"inputs": {
"text": "score_6, score_5, score_4, worst quality, low quality, 3d, monochrome, photo, realistic",
"clip": ["12", 1]
},
"class_type": "CLIPTextEncode"
},
"15": {
"inputs": {
"samples": ["16", 0],
"vae": ["3", 2]
},
"class_type": "VAEDecode"
},
"16": {
"inputs": {
"model": ["12", 0],
"seed": 42,
"steps": 25,
"cfg": 7.0,
"sampler_name": "euler_a",
"scheduler": "normal",
"denoise": 1.0,
"positive": ["13", 0],
"negative": ["14", 0],
"latent_image": ["17", 0]
},
"class_type": "KSampler"
},
"17": {
"inputs": {
"width": 1024,
"height": 1024,
"batch_size": 1
},
"class_type": "EmptyLatentImage"
},
"18": {
"inputs": {
"images": ["15", 0],
"filename_prefix": "R18_Prod_Output"
},
"class_type": "SaveImage"
}
}
```
#### B. Pythonによる自動マージスクリプト (SVDベース / Kohya-ss互換)
量産ラインにおいて、推論時の複数LoRAロードによるVRAMオーバーヘッドを削減するため、**「キャラLoRA + 衣装LoRA」を事前に1つのLoRAにマージ**する。
以下は、`safetensors` ライブラリを使用し、SVD(特異値分解)を用いて次元を維持したまま2つのLoRAをマージする実用コードである。
```python
import torch
from safetensors.torch import load_file, save_file
import os
def merge_loras_svd(lora_char_path, lora_outfit_path, output_path, r_char=0.75, r_outfit=0.35, target_rank=16):
"""
SVD(特異値分解)を用いて、キャラLoRAと衣装LoRAを低ランク近似を維持したまま直交化マージする。
"""
print(f"Loading LoRAs:\n 1. {lora_char_path}\n 2. {lora_outfit_path}")
char_tensors = load_file(lora_char_path)
outfit_tensors = load_file(lora_outfit_path)
merged_tensors = {}
all_keys = set(char_tensors.keys()).union(set(outfit_tensors.keys()))
for key in all_keys:
# 片方にしか存在しないキーはそのまま保持
if key not in outfit_tensors:
merged_tensors[key] = char_tensors[key]
continue
if key not in char_tensors:
merged_tensors[key] = outfit_tensors[key]
continue
# LoRAの重み行列の再構築とSVDマージ
if "lora_down.weight" in key:
up_key = key.replace("lora_down.weight", "lora_up.weight")
alpha_key = key.split(".")[0] + ".alpha"
if up_key in char_tensors and up_key in outfit_tensors:
# 重みテンソルの取得
down_c = char_tensors[key].to(torch.float32)
up_c = char_tensors[up_key].to(torch.float32)
down_o = outfit_tensors[key].to(torch.float32)
up_o = outfit_tensors[up_key].to(torch.float32)
# アルファによるスケーリング
alpha_c = char_tensors.get(alpha_key, torch.tensor(down_c.shape[0])).to(torch.float32)
alpha_o = outfit_tensors.get(alpha_key, torch.tensor(down_o.shape[0])).to(torch.float32)
scale_c = alpha_c / down_c.shape[0]
scale_o = alpha_o / down_o.shape[0]
# 2次元行列への復元
W_c = (up_c @ down_c) * scale_c
W_o = (up_o @ down_o) * scale_o
# 加重線形結合
W_merged = r_char * W_c + r_outfit * W_o
# SVDによる低ランク近似の実行
try:
U, S, Vh = torch.linalg.svd(W_merged, full_matrices=False)
# ターゲットランクでスライス
U = U[:, :target_rank]
S = S[:target_rank]
Vh = Vh[:target_rank, :]
# 新しいLoRAペアの作成
new_up = U @ torch.diag(torch.sqrt(S))
new_down = torch.diag(torch.sqrt(S)) @ Vh
merged_tensors[up_key] = new_up.to(torch.float16)
merged_tensors[key] = new_down.to(torch.float16)
# アルファ値の固定設定
merged_tensors[alpha_key] = torch.tensor(target_rank, dtype=torch.int32)
print(f"Merged & SVD-approximated: {key} to rank {target_rank}")
except Exception as e:
print(f"SVD failed for {key}, fallback to simple merge. Error: {e}")
merged_tensors[key] = (r_char * down_c + r_outfit * down_o).to(torch.float16)
merged_tensors[up_key] = (r_char * up_c + r_outfit * up_o).to(torch.float16)
else:
merged_tensors[key] = char_tensors[key]
elif "lora_up.weight" in key or ".alpha" in key:
# すでにdown_weightのループ内で処理されているためスキップ
continue
else:
# その他のメタデータやバイアス
merged_tensors[key] = char_tensors[key]
# 保存
os.makedirs(os.path.dirname(output_path)) if os.path.dirname(output_path) else None
save_file(merged_tensors, output_path)
print(f"[SUCCESS] SVD Merged LoRA saved to: {output_path}")
# 実行例
if __name__ == "__main__":
merge_loras_svd(
lora_char_path="character_hilde.safetensors",
lora_outfit_path="outfit_latex_suit.safetensors",
output_path="merged_hilde_latex.safetensors",
r_char=0.75,
r_outfit=0.35,
target_rank=16
)
```
---
### 6. チェックリスト: プロダクションリリース前の破綻検知フロー
量産バッチ処理を走らせる前に、テストレンダリング(Grid生成)において以下のチェック項目を自動/目視で確認せよ。
* [ ] **1. コントラスト異常(お焦げ)の自動検知**
* **検知基準**: 出力画像のRGBヒストグラムにおいて、輝度 $0$(純黒)および $255$(純白)のピクセル比率が全体の $3.5\%$ を超えているか。
* **対策**: $W_{model}$ の総和を `0.1` 下げる、または `Dynamic Thresholding` ノードを導入して Mimic Scale を `7.0` に固定する。
* [ ] **2. キャラクターの顔の「別人化」判定**
* **検知基準**: キャラクター特有の瞳の色、髪型が、衣装やシチュエーションのLoRAに上書きされていないか。
* **対策**: キャラLoRAの `OUT03-OUT05` のウェイトを `1.2` まで引き上げ、衣装LoRAの `OUT03-OUT05` を完全に `0` にする。
* [ ] **3. 骨格・四肢の異常(多腕・多脚・融合)の検知**
* **検知基準**: 特に密着した絡み(R18ポーズ)において、手足の数が正常か。
* **対策**: シチュエーションLoRAの $W_{model}$ を `0.5` まで下げ、ControlNet(Openpose / Depth)を併用して骨格を強制固定する。
* [ ] **4. 局部(性器・体液)の描画破綻の検知**
* **検知基準**: 断面図や挿入部がモザイク状のノイズ、または単なる肉塊になっていないか。
* **対策**: シチュエーションLoRAの `MID` ブロックのウェイトを `0.8` から `1.0` に上げ、プロンプトでのトリガーワードの強調度を `(trigger_word:1.2)` に設定する。
* [ ] **5. テキストエンコーダー(TE)飽和によるプロンプト無視の検知**
* **検知基準**: `red hair` と指定しているのに、キャラLoRAのデフォルトの `blonde hair` が出力されるなど、プロンプトの変更が効かない。
* **対策**: キャラLoRAの $W_{clip}$ を `0.4` 以下に下げ、プロンプト側での重み付けを `(red hair:1.3)` に引き上げる。