rapid-anima / docs /distillation.md
darask0's picture
Initial commit: rapid-anima distillation codebase
77cc641 verified
|
Raw
History Blame Contribute Delete
24.3 kB
# Anima 蒸留 — 実走で分かった知見
[← README に戻る](../README.md)
> **TL;DR**: 数万円予算で Anima を自前 SOTA 蒸留(DMD2+TSCD+R3GAN)するのは
> 現実的に困難。**5 回失敗で累計 $165**。実用解は **`merge_turbo_lora`**
> (公式 Anima Turbo LoRA との合成、$0.5、即動く)。自前で挑む人向けに、
> 何が起きるか・なぜ難しいかを以下に集約。
>
> 2026-05 以降の改善試行 (PCM / LADD / Reflow など既存実装の移植) は
> [migration_log.md](migration_log.md) を参照。
---
## 1. 概要(結論先出し)
このリポジトリでは Anima(CircleStone Labs, Cosmos-Predict2 派生 2B DiT)を
**Decoupled DMD2 + TSCD + R3GAN****step 削減蒸留** することを目指した。
B200 GPU(Modal)で 5000 枚 self-distillation データセットを使い、**計 5 回の失敗**:
| 試行 | 構成 | 結果 | コスト |
|---|---|---|---|
| Phase A | full DiT, R3GAN gamma=50 | 完全ノイズ(D 爆発) | $70 |
| Phase B v1 | Q,V LoRA, R3GAN OFF | 緑色化(AdaLN 不触) | $10 |
| Phase B v2 | wide LoRA, R3GAN OFF | mean collapse | $10 |
| Phase B v3a | wide LoRA, gamma=0.1 | step 30 で D 爆発 abort | $5 |
| **Phase B v3b** | wide LoRA, gamma=1.0 | **mean collapse + 後で D 爆発** | $20 |
**最終理解**: R3GAN は「D 爆発」と「無信号 → mean collapse」の間に **安定 zone が
極めて狭く**、Anima latent(16ch × 128²)で持続的な訓練は現状の構成では困難。
DMDR / Z-Image の公式実装が **DINOv2 reward に依存** していることからも、
adv loss 単独の R3GAN だけでは Anima 系の蒸留は厳しい可能性が高い。
### 1.1 結局どうすればいいか
| 用途 | 推奨パス | コスト | 結果 |
|---|---|---|---|
| **すぐ 8-12 step 推論したい** | `merge_turbo_lora`(公式 Civitai Turbo LoRA を Anima base に重ねる)| **$0.5** | ✅ 即動く、CFG=1 / 8-12 step |
| **どうしても自前で蒸留したい** | 本ドキュメントの試行錯誤を踏まえ、**DINOv2 reward 切替** 等を追加実装 | +$50-100 + 開発時間数日 | △ 未検証 |
| **学習目的・記録目的** | 本ドキュメントを読む | $0 | 知見だけ |
> **強い推奨**: 商用要件がなければ `merge_turbo_lora` で十分。CircleStone Labs
> 自身が遥かに多い計算資源と試行錯誤の上で作った Turbo LoRA が既に Civitai
> にある(https://civitai.com/models/2560840)、これに勝つのは数万円では困難。
---
## 2. 結局成功した実用パス: `merge_turbo_lora`
```bash
# 1. Anima Turbo LoRA を Civitai からダウンロード
modal run modal_app.py::download_civitai_lora # version_id=2877687 がデフォルト
# 2. (任意) 自前 Phase 1 LoRA とマージ(なくても可)
modal run modal_app.py::merge_turbo_lora
# 3. ComfyUI で base + LoRA(strength=1.0)を組み、8 step CFG=1 で生成
```
実装: `modal_app.py::download_civitai_lora`, `merge_turbo_lora`, `stage_lora_to_models`
ComfyUI workflow は `scripts/anima_workflow_turbo.json` 参照。
実測の Turbo LoRA 出力品質: **base 30 step CFG=4.5 と同等のキャラクター品質を
8 step CFG=1 で達成**(検証画像は `/dataset/compare/turbo_8step/` に保存)。
---
## 3. 自前 SOTA 蒸留(本リポジトリで実装、未完成)
`scripts/distill/` に Decoupled DMD2 + TSCD + R3GAN を実装。**コード自体は動く** が、
ハイパーパラメータの調整で 5 回失敗。以下「我々が試した推奨設定」だが、これでも
2000 step で失敗 — **これから挑む人は次節 §4 §5 を読んでから**
### 3.1 試した(が安定しなかった)設定
```text
=== モデル / データ ===
base model: Anima preview3-base (Cosmos-Predict2 派生)
text encoder: Qwen3-0.6B + LLM Adapter (T5 空間ブリッジ)
VAE: WanVAE (16-channel latent, scale=[mean, 1/std])
dataset: self-distillation 5000 枚(base が 30 step CFG 4.5 で生成)
解像度 1024px 周辺の 7 種 aspect ratio をランダム
=== 学習 (LoRA-only) ===
trainable: gen + guidance ともに LoRA、base は frozen
gen LoRA: **wide** (all-linear except llm_adapter), rank=32
→ AdaLN modulation を含む全 Linear に attach
guidance LoRA: Q,V LoRA, rank=32 (DMDR 互換、軽量)
optimizer: gen / guidance: AdamW lr=2e-5 β=(0.9, 0.999) wd=0.01
D: Adam lr=2e-4 β=(0, 0.99) (StyleGAN 流)
grad clip: 1.0
=== Loss 構成 ===
L_dmd: DMDR gradient trick (p_real - p_fake) / norm
L_consist: TSCD MSE vs EMA generator (decay=0.999)
L_adv (gen): R3GAN relativistic RpGAN, weight=0.05
L_disc: R3GAN softplus + γ/2 · (R1 + R2)
γ = 0.05〜0.5 (grid search 必須)
=== 更新比率 / スケジュール ===
guidance update: 5 回 / outer step
generator update: 1 回 / outer step
discriminator: 1 回 / outer step
CFG Augmentation: real_score を cfg=2.0 で評価 (cold_start 100 step は cfg=0)
dynamic LoRA r: real-side scale を cosine で 0 に decay (decay_steps=2000)
=== Phase 構成 ===
Phase B: base 直接 + wide LoRA, num_inference_steps=4, 2000 step
Phase C: Phase B から resume または base 直接, wide LoRA, num_steps=2, 2000 step
※ Full DiT 訓練 (Phase A) は不要 — LoRA-only で十分
=== Sanity / Guard ===
sanity test: 200 step + 中間生成 1 枚で degeneration 検出
abort guards: d_r1 > 1000 / d_r2 > 1000 / loss > 1e6
checkpoint: 500 step 毎 + 都度 volume.commit()
PYTHONUNBUFFERED=1 + cwd=/workspace/diffusion-pipe
=== Modal infra ===
GPU: B200 (192GB) — LoRA-only で余裕、batch=4
image: CUDA 12.4 + Python 3.11 + diffusion-pipe + torchvision
volumes: anima-models / anima-dataset / anima-outputs
secrets: hf_token, civitai_api_key
=== ComfyUI 出力形式 ===
LoRA key: diffusion_model.<module>.lora_A.weight / .lora_B.weight
(PEFT の base_model.model.<...>.lora_A.default.weight から変換)
基底 ckpt: anima ckpt 系は net. prefix 必須 (ComfyUI が arch 検出に使う)
```
実装は `scripts/distill/` + `modal_app.py::train_sota_distill` を参照。
> ⚠️ **重要**: 上の設定で 2000 step training を 5 回試したが、いずれも失敗。
> Anima latent では R3GAN の安定 zone が極めて狭く、長期持続が困難だった。
> **これから挑む人は §4(全失敗の詳細)+ §5(残された改善方向)を必ず読むこと**
### 3.2.1 なぜ LoRA-only(full DiT 訓練ではなく)
| | full DiT(Phase A) | LoRA-only(Phase B/C) |
|---|---|---|
| trainable params | 2B | 30-150M |
| step 時間 (B200) | 14 秒 | **2.8-3.0 秒**(5x 速) |
| 出力 | 4 GiB ckpt | **30-150 MiB LoRA** |
| 配布性 | 他 FT と排他 | **他 LoRA とスタック可** |
| optimizer state | 8 GB | ~200 MB |
| failure 局所化 | base 全体破壊リスク | LoRA だけ |
Anima のように既に高品質な base を蒸留する場合、LoRA-only がコスト・配布性・
安全性のすべてで優位。Phase A 流の full DiT 訓練は不要だった。
### 3.2.2 なぜ「wide LoRA」(Q,V LoRA ではなく)
Anima/Cosmos の DiT は **AdaLN modulation で timestep 情報を処理**する架構:
```
block(x, t):
t_embed = t_embedder(t)
# AdaLN modulation = block_input をどう変調するかを timestep から決める
scale, shift, gate = adaln_modulation(t_embed) ← ここが timestep の入り口
x = LayerNorm(x) * (1 + scale) + shift
x = x + gate * attention(x, ...)
```
蒸留とは「**timestep の解釈を変える**」こと(少ない step でも denoise しきる)。
それには adaln_modulation の Linear を学習対象に含める必要がある。Q,V LoRA だと
attention pattern だけ変えられて timestep 入り口を触れない → 学習が degenerate
な「色 / スタイル shift」に逃げる(Phase B v1 の緑色化はこれ)。
公式 Anima Turbo LoRA も同じ理由で adaln_modulation を target(1016 keys、
我々の wide LoRA 980 keys とほぼ同等)。
### 3.2.3 なぜ R3GAN が必要(adv loss なしではダメ)
DMD2 単体は **mean collapse** に陥る(Phase B v2 で実証)。理由:
- DMD gradient `(real_score - fake_score)` は「real distribution の方向」を示す
- LoRA capacity が大きいほど、「real の **mean** を出力するのが最も安全」という解に収束しやすい
- TSCD(EMA self との consistency)は collapse を加速こそすれ防げない
R3GAN(または同等の adv / reward 信号)があると:
- D が「real は sharp、fake は blurry」と学習
- gen は「blurry な mean を出すと D に見抜かれる」と分かり、sharp な多様性を保つ
- これが mean collapse の唯一の防御策
実際、DMD2 / Hyper-SD / DMDR 系の全公式実装で adv loss(or reward loss)が必須。
### 3.2.4 なぜ R3GAN gamma の grid search が必須
R3GAN の R1+R2 gradient penalty 重み `gamma`**入力空間のスケールに比例**:
| ドメイン | 入力 channel | 解像度 | gamma |
|---|---|---|---|
| CIFAR-10 | 3 | 32x32 | **0.05** |
| FFHQ-64 | 3 | 64x64 | 2 |
| FFHQ-256 | 3 | 256x256 | 150 |
| **Anima latent** | **16** | **128x128** | **~0.05-1**(grid search 必須) |
Anima latent は per-sample 262k 要素で、典型 pixel 空間より gradient norm が
大きい → gamma は数値が小さくないと penalty が爆発。Phase A は gamma=50 で **500x 過大**
にしたら discriminator が崩壊した(詳細は §4 Phase A 参照)。
### 3.2.5 なぜ guard rail(`d_r1/r2 > 1000` で abort)
Phase A は **loss は全部健全に推移**(`l_dmd` 0.005, `l_adv_g` 0.69 で一定)しつつ
裏で discriminator gradient が爆発し、completed ckpt が完全ノイズ出力という
**ステルス失敗**。後付けの分析で「step 1500 時点 d_r1 ~700 / step 2300 で 500k」と
わかったため、しきい値 1000 で abort すれば即停止できる。
しきい値 1000 の根拠: 正常上限 ~100 の 10x、致命傷 500k の 1/500 で、確実に
ステルス damage が始まる前に止まる。
### 3.2.6 なぜ Decoupled DMD2 + TSCD(LCM / Hyper-SD ではなく)
| 手法 | 性質 | 我々の選択理由 |
|---|---|---|
| LCM | consistency 単体、シンプル | low-step 品質に限界 |
| Hyper-SD | TSCD + adv、segment-wise progressive | gamma calibration 必要、参照実装 SDXL only |
| **DMD2 / DMDR** | **分布マッチング + adv、Anima のような flow matching DiT に最適** | Z-Image(Cosmos 派生)で実証済、適応容易 |
DMD2 系は **flow matching ベース(Anima の rectified flow と同じ)** で、math
変換不要なのが決定打。TSCD は Hyper-SD から借りた多 step 一貫性で、純粋 DMD2
の単 step 偏りを補正。
### 3.2.7 なぜ 5000 枚 self-distillation データセット
| データソース | コスト | 利点 | 欠点 |
|---|---|---|---|
| Anima 自己生成 | $44 (10 B200 並列 40min) | base の出力分布に最適化 / ライセンス問題なし | base の癖をそのまま継承 |
| Danbooru / Pixiv | $0 + キュレーション時間 | 多様性 | ライセンス・タグ品質ばらつき |
| 公開データセット(LAION 等) | $0 + DL コスト | 多様性最大 | アニメ系少ない |
蒸留の本質は「**base と同じ出力を低 step で**」なので、self-distillation で
分布を base に一致させるのが理論的に正しい(distillation の定義)。
### 3.2.8 なぜ B200(H100 / A100 ではなく)
| GPU | Modal $/h | LoRA-only step 時間 | 192GB VRAM の意味 |
|---|---|---|---|
| A100-80GB | $2.50 | 7-9 秒 | OOM 危険、batch=1 |
| H100-80GB | $3.95 | 4-6 秒 | OOM 危険、batch=1-2 |
| **B200** | **$6.25** | **2.8-3.0 秒** | **3 model 同時 + batch=4-8 余裕** |
B200 の per-hour 単価は高いが、**3 model(real/fake/gen)同時+batch 増**
**実質的な per-experiment コストは最安**。R3GAN を入れても OOM 不安なし。
---
## 4. 実際の失敗(全 5 件、詳細)
| # | 試行 | 構成 | 結果 | 累計 |
|---|---|---|---|---|
| 1 | **Phase A** | full DiT + Q,V LoRA, R3GAN γ=**50**, 3000 step | 完全ノイズ(D 爆発) | $70 |
| 2 | **Phase B v1** | Q,V LoRA, R3GAN **OFF**, 2000 step | 緑色化(AdaLN 不触) | $80 |
| 3 | **Phase B v2** | wide LoRA, R3GAN **OFF**, 2000 step | mean collapse(灰緑平面)| $90 |
| 4 | **Phase B v3a** | wide LoRA, R3GAN γ=**0.1**, 2000 step | step 30 で d_r1=1648 → abort | $95 |
| 5 | **Phase B v3b** | wide LoRA, R3GAN γ=**1.0**, 2000 step | 早期 mean collapse → 後半 D 爆発 | $115 |
各失敗が **異なる failure mode** で起きた点が重要。「1 つ直しても他で詰まる」
直交性が、SOTA 蒸留を Anima 系で安定させる難しさの本質。
### 4.1 Phase A の詳細 postmortem(5 層因果分析)
**ステルス失敗**: 訓練ログ上は全 loss 健全(`l_dmd` 0.003-0.008, `l_adv_g` 0.69 一定)
だったのに、完走 ckpt が完全ノイズ出力。原因は 5 層の連鎖:
**Layer 1 — gamma calibration ミス(根本)**
- Anima latent(16ch × 128x128)に対し gamma=50 は 50-500 倍過大
- Agent リサーチが「latent norm 用 grid search 必須」と明示警告していたが、
手抜きで推測値を採用したのが発端
**Layer 2 — R3GAN gradient が gen に流入(見えない damage)**
```
l_adv_g = softplus(-(d_fake - d_real)) ≈ log 2 = 0.6914 ← 中立点
```
`l_adv_g` が一定で「adv 信号効いてない」と誤読。**しかし gradient norm は別物**:
```
∂L_adv_g/∂gen_output = (sigmoid 微分 ~0.5) · ∂(D の全層 Jacobian)
```
D 重みが gamma penalty 最小化で異常拡大 → D の Jacobian 爆発 → gen に流れる
gradient が正常時の **数百〜数千倍**。adv_weight=0.05 で抑えても scale 負け。
> 教訓: **adv_loss の絶対値だけ見て安全と判断するな**。gradient norm を別途モニタ。
**Layer 3 — D 自体の破滅スパイラル**
```
最初: l_disc = 0.69 + 25·0 = 0.69
膨張後: l_disc = 0.69 + 25·600,000 = 15,000,000
```
gamma が大きすぎて R1+R2 penalty 項が adv 項を支配 → D は adv 学習する余地なく、
ひたすら penalty 最小化に動く → ある瞬間 scale 崩れて d_r1/r2 が指数的に膨張。
**Layer 4 — Sanity test の見落とし**
- 50 step では destabilization が緩やかすぎて d_r1/r2 が正常範囲
- 本番 step 500-1000 から急速悪化
- 後から見れば step 1000 で d_r1/r2 が 100 超え始めていた($50 救えた)
> 教訓: **Sanity は 200-500 step に延長**。緩慢な発散も検出可能に。
**Layer 5 — 監視/アラート不足**
- d_r1/r2 はログ出していたが **しきい値アラート未実装**
- NaN/Inf check はあったが「大きすぎる値」検出なし
- gen の weight L2 norm を時系列で見れば damage 早期検出可能だった
- 中間 ckpt 毎の自動生成テストも未実装
修正後の guard rail(実装済):
```python
# scripts/distill/dmd2_trainer.py
if d_r1 > 1000.0 or d_r2 > 1000.0:
raise RuntimeError(f"R3GAN penalty exploded (r1={d_r1:.1f}, r2={d_r2:.1f}). "
f"Reduce r3gan_gamma. Aborting.")
```
**Phase A から学んだ悪設計のチェックリスト**
| 問題 | 影響 |
|---|---|
| gamma を grid search せず推測 | 50-500 倍ズレ |
| adv_weight=0.05 で「安全装置」になると誤解 | gradient norm は別軸、効かなかった |
| sanity 50 step だけで本番投入 | 緩慢な発散見逃し |
| `d_r1/r2` にアラートしない | 早期 abort 機会逸失 |
| 中間 ckpt で部分検証しなかった | 完走してから初めて発覚($70 ロス) |
### 4.2 Phase B v1 — Q,V LoRA だと AdaLN を触れない
R3GAN を外して安全側に振った構成。loss は 0.02-0.03 で安定したが、生成すると
**緑色化した anime キャラ**。LoRA strength を 1.0 → 0.3 に下げると base に戻る
(= LoRA は「均一に緑方向に押している」)。
原因: Q,V LoRA は cross-attention / self-attention の query / value だけを変更。
**timestep modulation の入り口である AdaLN modulation Linear を触れない**
DiT が「少ない step でちゃんと denoise する」ために必要な timestep 解釈の変更が
できず、勾配が attention の color / style shift に逃げて degenerate に収束。
→ 解決: LoRA target を **「AdaLN + attention + MLP の全 Linear」(wide LoRA)** に拡張。
公式 Anima Turbo LoRA も同じ範囲を target にしている(検証で 1016 keys 確認)。
### 4.3 Phase B v2 — wide LoRA + R3GAN OFF で mean collapse
wide LoRA(980 keys)に拡張したら、今度は **完全に灰緑色の平面**を出力。loss は
v1 より遥かに小さく(`l_dmd` 0.001-0.007)、**訓練ログ上は最も健全に見える**
失敗。LoRA strength を変えても改善せず。
原因: DMD2 単体は「real distribution の方向」を示すだけ。LoRA capacity が大きい
ほど **「data distribution の mean を出力すれば DMD gradient はゼロになる」**
という degenerate な解に収束しやすい。R3GAN なしでは「sharp なものを出せ」と
gen に教える信号がないので、訓練が進むほど mean に潰れていく。
→ 解決: **R3GAN(または同等の reward / classifier loss)を必ず投入**。DMD2 系の
全公式実装(NVIDIA DMD2, Z-Image DMDR, Hyper-SD)で adv 系 loss が必須なのは
このため。
### 4.4 Phase B v3a — gamma=0.1 で D が即爆発
grid search の 100-200 step では gamma=0.1 が安定に見えていた:
```
[step 60/100] d_r1=0.020 d_r2=0.015 l_disc=0.017 ← 健全
```
しかし本番(2000 step)で開始 30 step 以内に爆発:
```
[step 10/2000] d_r1=67.5 d_r2=74.8 l_disc=7.1 ← 既に異常
[step 30/2000] d_r1=1648.0 d_r2=1821.6 ← guard 発火 abort
```
原因推測:
- 短時間 grid search は **ランダム初期化の運**で偶然安定 zone に居ただけ
- 本番の異なる初期化 + 長時間 → 不安定境界を越えた
- gamma=0.1 は Anima latent では **安定境界の崖際**
### 4.5 Phase B v3b — gamma=1.0 で長期 mean collapse + 最終 D 爆発
gamma=1.0 で training は確かに **1500 step まで全 metric 健全**:
```
[step 1000/2000] l_dmd=0.001 l_consist=0.005 d_r1=0.005 l_disc=0.004 ← 全部小さい
```
**しかし step 500/1000/1500 の中間 ckpt で生成テストすると全部 mean collapse**:
- step 500: 白い平面
- step 1000: 白い平面
- step 1500: 青+白の blob
しかも step 1380 から D が「目覚め」始め、step ~1600 で d_r1=2656 で abort:
```
[step 1380/2000] d_adv=3.73 d_r1=0.36 d_r2=0.22 ← D が動き始める
[step 1510/2000] d_adv=0.00 d_r1=0.67 d_r2=0.45 ← じわじわ拡大
[step ~1600] d_r1=2656.0 d_r2=3043.2 ← abort
```
**核心の理解**:
- gamma=1.0 は D が **静か → 突然動く → 爆発** のパターン
- 静かな間(d_r1 ~ 0.005)は **D が adv 信号を gen に渡せず**、gen は mean に collapse
- D が動き始めた頃には gen は既に degenerate state
- ckpt が「健全に見える」のは loss だけ。実態は破綻
これが **「R3GAN の安定 zone が存在しない」** という最終診断の根拠。
### 4.6 失敗パターンの直交性
| | Phase A | B v1 | B v2 | B v3a | B v3b |
|---|---|---|---|---|---|
| failure mode | gradient damage(ノイズ)| degenerate(緑) | mean collapse | D 爆発 abort | D 静止 → mean collapse → D 爆発 |
| 根本原因 | R3GAN γ 過大 | LoRA target 狭 | adv なし | γ 小すぎて D 暴走 | γ 大すぎて D が adv 信号出さず |
| 検出 | step 1000+ d_r1 | 生成テスト即 | 生成テスト即 | step 30 で guard | 中間 ckpt 生成テスト |
| 対策 | γ grid search + guard | wide LoRA | R3GAN を入れる | γ 上げる | γ 下げる(でも v3a に戻る)|
**5 つの failure mode は互いに直交かつ循環**(γ 下げると v3a、上げると v3b)。
Anima latent で R3GAN を安定させる γ は、**存在するとしても極めて狭く**
我々の grid 解像度では発見できなかった。
### 4.7 周辺で踏んだ Modal / diffusion-pipe / ComfyUI 罠
蒸留の本体とは別に、インフラ側で踏んだバグ:
**diffusion-pipe を programmatic に使う際の 8 件**
| 罠 | 症状 | 対処 |
|---|---|---|
| `models/`, `utils/` が namespace package(`__init__.py` なし)| site-packages 側の `utils` に shadow されて `from utils.common import` 失敗 | `importlib.util.spec_from_file_location` で強制ロード(`scripts/distill/anima_loader.py`)|
| `CosmosPredict2Pipeline(cfg)` の dtype が **torch.dtype 必須** | `tensor() argument 'dtype' must be torch.dtype, not str` | `cfg["model"]["dtype"] = torch.bfloat16`(文字列禁止) |
| `load_text_encoder()` / `load_vae()` メソッドが **存在しない** | AttributeError | `load_diffusion_model()` だけ呼ぶ、残りは `__init__` で完了 |
| `MiniTrainDIT.forward``padding_mask` 必須(None 不可) | `transforms.functional.resize(None, ...)` TypeError | `torch.zeros(B, 1, H, W)` を渡す |
| VAE input が bfloat16 必須 | `Input type (float) and bias type (BFloat16) should be the same` | `vae_encode` 内で auto-cast |
| DiT 入力 `(noisy, t, cond)` の dtype 統一必須 | `expected mat1 and mat2 same dtype` | dit_forward helper で全部 weight dtype に揃える |
| `torchvision` import するが requirements に無い | `ModuleNotFoundError` | image に明示インストール |
| save の filter で integer buffer を落とすと壊れる | ロード後ノイズ | float 限定 filter は廃止、全 tensor 保存 |
**ComfyUI LoRA 形式**
Anima/Cosmos 用 LoRA は `lora_A.weight` / `lora_B.weight`(NOT `lora_down/up`)、
`diffusion_model.` prefix 必須。PEFT 出力からの変換は `modal_app.py::_convert_peft_to_comfy_lora`
SDXL 流 `lora_down/up` に変換すると ComfyUI が key 認識できずランダム scaling で
適用 → 出力が緑化(これも Phase B v1 のもう 1 つの寄与因子だった可能性)。
**Modal 運用**
| 罠 | 対処 |
|---|---|
| `modal run --detach` の local CLI が早く戻る | 完了確認は `modal app list` / `modal app logs <id>` |
| `print()` の block buffering で logs 滞留 | `PYTHONUNBUFFERED=1` を env に追加 |
| Volume commit は subprocess 終了時のみ | 訓練ループ内で `modal.Volume.from_name(...).commit()` を定期発行 |
| `modal volume cp` は cross-volume 不可 | 両 volume mount した Modal function で `shutil.copy` |
| `run_in_background=true` + TaskStop で app が "detached_disconnected" に残る | 必ず `--detach` を一緒に付ける(明示 detach)|
| Anima ckpt は `net.` prefix 前提で ComfyUI が arch 検出 | 保存時に `{"net." + k: v for k, v in sd.items()}` |
### 4.8 コスト・時間の実測値
| タスク | 時間 | コスト |
|---|---|---|
| dataset 生成(B200 × 10 並列) | 40 分 | $44 |
| Path A baseline 確認(Turbo LoRA DL + 10 枚生成) | 5 分 | ~$1 |
| **Phase A: full DiT 訓練(失敗)** | **11.6 h** | **~$70**(全損)|
| Phase B v1: Q,V LoRA(失敗) | 1.6 h | ~$10 |
| Phase B v2: wide LoRA, R3GAN OFF(失敗) | 1.6 h | ~$10 |
| Phase B v3a: wide LoRA, γ=0.1(早期 abort) | ~5 分 | ~$5 |
| Phase B v3b: wide LoRA, γ=1.0(失敗) | 4 h | ~$20 |
| R3GAN gamma grid search | 30 分 | ~$3 |
| 比較生成 30 枚 + その他 | | ~$2 |
| Modal volume(50 GB × 月)| 常時 | ~$7.5/月 |
| **累計** | | **~$165 ≈ 25,000円** |
R3GAN ON で 1 step 約 +20-30%(D forward + R1/R2 backward 追加分)。
---
## 5. 続き
§4 の 5 回失敗を受け、「自前で書き下す」を諦めて **既存の動く実装を最小改造で
移植**した試行が 2026-05 から始まる。各手法 (Z-Image traj / LADD / Reflow / DMD2 /
SiD2 / Shortcut / PCM / DRaFT+) の移植ログ・コスト・結果は
[migration_log.md](migration_log.md) を参照。