# 実装 / 運用 Tips + トラブルシューティング [← README に戻る](../README.md) [distillation.md §4.7](distillation.md#47-周辺で踏んだ-modal--diffusion-pipe--comfyui-罠) の diffusion-pipe / ComfyUI 罠に加え、 2026-05 試行 (5 手法移植) で踏んだ追加の罠と対処、よくあるトラブルの解決策。 ## Modal CLI on Windows + JP (cp932) locale 毎回必須: ```bash MSYS_NO_PATHCONV=1 PYTHONIOENCODING=utf-8 PYTHONUTF8=1 modal run ... ``` | 罠 | 症状 | 対処 | |---|---|---| | cp932 codec error | Modal CLI 出力の Unicode ✓ や ● を encode できず即死 | `PYTHONIOENCODING=utf-8 PYTHONUTF8=1` | | Git Bash MSYS path mangling | `--dataset-path /dataset/raw` が `C:/Program Files/Git/dataset/raw` に化ける | `MSYS_NO_PATHCONV=1` | | Modal token が silent revoke | `.modal.toml` は valid、`modal config show` で見える、しかし API 呼び出しで "Token not found" | `MODAL_LOGLEVEL=DEBUG modal app list` でサーバー側のエラーを確認 → ユーザーが `modal token new` で再発行 | | `modal app stop` が対話確認で停止 | `Are you sure...? [y/N]:` で固まる | `--yes` を必ず付ける | ## diffusion-pipe + Anima 移植時の罠 | 罠 | 症状 | 対処 | |---|---|---| | `_find_blocks` ヘルパーが `llm_adapter.blocks` を選ぶ | Anima DiT は本体 28 blocks + llm_adapter 6 blocks。名前 heuristic で adapter 側を拾うと **5 head すべて学習されない** | `transformer.blocks` を direct 参照、または `"llm_adapter" not in name` で filter (`anima_ladd_disc.py::_find_blocks` 参照) | | LPIPS reg が OOM | `compute_regularization` 内で **student rollout を grad-through で 2 回目** 走らせると 16 forward が graph に乗り 178 GiB 食い尽くす | (1) `--lpips-weight 0` で disable (2) `--lpips-every N` で間欠的に有効化 (Reflow パターン) (3) 解像度 1024 → 768 に下げる | | PEFT LoRA 2 adapter 切替 | DMD2 で同じ base に student + fake_score を attach する場合、**毎 forward で `set_adapter(name)` を呼ばないと 片方の重みが leak する** | `make_velocity_fn(peft_model, adapter_name)` のような callable wrapper で隠蔽 (`train_dmd2_official.py` 参照) | | Civitai Turbo LoRA → PEFT 形式変換 | warm-start 時、ComfyUI 形式 `diffusion_model.<…>.lora_A.weight` を PEFT 形式 `base_model.model.<…>.lora_A.default.weight` に変換必要 | `train_traj.py::convert_comfy_to_peft_lora` (`_convert_peft_to_comfy_lora` の逆向き) | | warm-start key match | wide LoRA target が `x_embedder / t_embedder` を含まないため、Anima Turbo の 1016 keys のうち **968 keys のみ match** (12 keys skip、48 keys missing) | 致命的ではない、warm-start として十分機能 | | subprocess 内 `import modal` 失敗 | 訓練 script が `modal.Volume.from_name(...).commit()` を呼ぶと `ModuleNotFoundError: No module named 'modal'` (subprocess の Python path に Modal がない) | `try/except` で囲んで warning 出力のみ。Modal は subprocess 終了時に自動で volume commit する | | **LADD discriminator の `torch.no_grad()` + `feat.detach()` で adv 勾配が student に届かない** | G phase で adv loss を `.backward()` しても勾配が disc の heads までで止まる。実質「Smooth L1 recon distill only」になり LADD のメリット消失 | `AnimaLADDDiscriminator.forward(...)` に `gradient_to_input` フラグを足す。G phase は `True` (teacher は frozen なので weight 更新されないが activations が残り x_input → student に勾配が流れる)、D phase は `False` (heads だけ訓練、メモリ節約) | | **LADD bs=4 × accum=4 × misaligned_pairs_d で 170 s/step (smoke の 200x 遅い)** | bs=4 で D forward の cost が線形増、accum=4 で 4 倍 micro-step、misaligned で D batch を 2 倍に → 20+ full-DiT forward/step、B200 上で 170 s | **bs=2 accum=1 + misaligned 無効** に下げる → ~2 s/step (85x 高速化)。LADD は GAN なので大 batch 不要 (small noise の方が D の安定性に貢献)。`train_ladd.py` 引数: `--batch-size 2 --grad-accum 1` (`--misaligned-pairs-d` フラグを付けない) | | **異なる rank の LoRA を warm-start に使えない** | `--lora-rank 128` で `--warm-lora /models/loras/anima_turbo.safetensors` (rank 32) を指定 → `load_warm_lora` 内で shape mismatch、`No LoRA keys matched` で abort | rank 変更時は **cold-start** で訓練 (warm-start を省略)。または rank padding helper を書く | | **modal CLI の uppercase arg は失敗** | function param `K: int` を CLI から `--K 1` で渡しても Modal が `k=1` に lowercase 変換、`unexpected keyword argument 'k'` で abort | function param は **必ず lowercase** にする (例 `k_grad: int`)、CLI 側 `--k-grad 1` を渡す | | **ComfyUI workflow JSON の `_comment` キーが node 扱いされて crash** | workflow JSON を iterate して `node.get("class_type")` する処理で、`_comment` (string value) が来ると `'str' object has no attribute 'get'` | `for node_id, node in wf.items(): if not isinstance(node, dict): continue` で skip | | **workflow JSON の node ID が `patch_workflow` の hardcode と一致しないと全 generation が HTTP 400 で fail** | `scripts/generate_dataset.py::patch_workflow` は positive prompt を node "5"、latent を "7"、KSampler を "8" に hardcode。ユーザー由来 workflow の node ID が違うと `$PROMPT/$SEED` プレースホルダが置換されず ComfyUI が prompt graph reject | 新 workflow を作る時は **必ず node ID を 5=positive, 7=latent, 8=KSampler** で構築。`anima_workflow.json` 系を雛形にする | | **reward fine-tuning (DRaFT+) で reward score 上昇 ≠ 視覚品質向上** | HPSv2 score +13.21 上昇したが視覚的には reward hacking が起き、anime → 西洋イラスト ドリフト + キャラ固有要素曖昧化 + prompt adherence 落下 | 必ず視覚検証する、score だけで「勝った」と判断しない。kl_coeff 上げる (0.2 → 0.5) や reward 種類変更 (HPSv2 → ImageReward) で軽減可能性、ただし根本解決ではない ([migration_log §5.10](migration_log.md#510-品質評価の方法論-reward-hacking-実証あり) 参照) | | **base model 切替で既存 LoRA の出力 style が drift する** | preview3 で訓練した LoRA を v1.0 base で使うと chibi/SD 寄りに style が変わる (LoRA は動作する、出力分布が違う) | LoRA は train 時の base と一致させて使うのが筋。base 切替時は LoRA を再訓練するか style drift を許容する | | **sageattention は .so 不要 (Triton JIT)** | `cache_sageattention_to_volume` で `/usr/local/lib/python3.11/site-packages/sageattention/` を volume copy しても `.so` が 0 個。一見「コンパイル失敗」に見える | 仕様。sageattention は Triton で runtime JIT、import 時に kernel が compile される。動作確認は CUDA kernel smoke test (`sageattn(q,k,v)` を呼ぶ) で行う | | **ComfyUI で sageattention を効かせる** | デフォルトは torch SDPA、sageattention 入っていても自動使用されない | ComfyUI 起動引数に `--use-sage-attention` を追加。`generate_dataset.py` は `import sageattention` 成功時に自動で付与するよう改修済 | | **Modal volume get で Japanese 文字パスに download すると後から Bash で見つけられない** | `modal volume get anima-dataset path/ "/c/Users/micro/Downloads/新しいフォルダー/dest/"` で download は成功するが、後続の `cd` / `find` / Python `os.walk` で日本語パスが broken or 空に見える | 一次 download 先は ASCII path に (例: `./verify_v1_local/`)、必要なら後で copy で日本語フォルダに移す | | **Modal volume `rm` は subprocess 内では使えない** | training script が訓練中に preview3 を消そうとしてもファイルハンドルが mmap で開いている (PyTorch safetensors loader) | volume cleanup は別の Modal function で実行、training 中は触らない (実害は少ないが orchestration 注意) | ## コスト / 時間の実測値 (本リポジトリ 5 手法移植時) すべて B200 ($6.25/h Modal)、batch=1-4、768-1024 解像度: | 操作 | 時間 | コスト | |---|---|---| | Image rebuild (lpips 追加分) | ~5 分 (キャッシュ後 0 分) | ~$0.5 | | B200 cold start | ~3 分 | ~$0.30 | | Smoke test (1 step) | ~5-10 分 | ~$0.5-2 | | Z-Image train 2000 step | ~3 h (5.2s/step) | **~$18** (見積もり $42 の半額) | | DMD2 train 3000 outer (5 critic + 1 gen) | ~5.8 h (3.6s/step) | **~$19** (見積もり $36 の半額) | | precompute_teacher_x0 5000 サンプル | ~1.5 h (0.69/s) | ~$11 | | LADD train 5000 step bs=4 accum=4 | ~4.8 h (0.86s/step) | ~$30 | | Reflow train 8000 step | ~3-4 h | ~$25 | | PCM train 5000 step (1 grad + 3 no_grad) | ~6.5 h (4.7s/step) | ~$41 | | **5 手法 + precompute × 2 トータル** | wall-clock ~8 h (並列) | **~$170** | 実測 step 時間は **predicted の概ね 半分** — Modal の B200 は H100 比 2-3x 速いことを織り込まないと過大見積もりになる。 **推論側 (verify 段階) の実測 (v1.0 base + sageattention 有効、B200、1024×1024)**: | 条件 | per-image | 内訳 | |---|---|---| | LoRA 4-step CFG=1 | **3.5-4.0s** | sage 効果あり、ほぼ全 LoRA 同等 (Z-Image / DMD2 / Turbo / Reflow / DRaFT+ 系) | | LoRA 8-step CFG=1 | **4.0-4.7s** | sage 効果あり、step 数比例 | | base 30-step CFG=4.5 (LoRA strength=0) | **10.7s** | 品質基準ライン | → 蒸留 LoRA 4-step は base 30-step の **3 倍速 = $0.006/枚 vs $0.018/枚** (B200 $6.25/h 換算)。 ## トラブルシューティング ### OOM (CUDA out of memory) - `phase1_anima.toml` の `activation_checkpointing = true` を確認 - `[adapter].rank` を 32 まで下げる - 解像度を 768 に落とす(`phase1_dataset.toml` の `resolutions`) - それでもダメなら H100 80GB に上げる ### 学習が進まない - データセット数枚で `epochs = 1` 試走 → エラーログを見る - `modal app logs rapid-anima` でリアルタイム監視 - `llm_adapter_lr = 0` が効いているか(消えていると壊れる) ### 出力の絵が地味 - これは正常。Anima base は素なので、データセットを `score≥200` 等で 絞って色彩豊かな絵を集めると改善 - artist タグを残して `@favorite_artist1, @favorite_artist2` を caption に入れたままにすると、それらの平均が出やすくなる ### diffusion-pipe が image build で失敗 - `requirements.txt` 内のバージョン競合が原因のことが多い - `modal_app.py` の `.pip_install` 部分でバージョン固定済み - それでも詰まる時は GitHub Issues 参照: https://github.com/tdrussell/diffusion-pipe/issues ### attention をもっと速くしたい デフォルトは torch SDPA(flash-attention 2 が built-in で使われる)。 さらに 1.2-1.5x 速くしたい場合は、`modal_app.py` の image 定義の最後に追加: ```python .run_commands( # diffusion-pipe install 後にビルドして torch ABI を合わせる "pip install --no-build-isolation sageattention", # または flash-attn (source build, 30-40分): # "pip install --no-build-isolation flash-attn", ) ``` 依存解決の都合で順序が重要(diffusion-pipe より後ろ)。 ### `modal volume put` で大きいデータが上がらない - `--force` を付けて差分上書き - 5GB 超えるなら一旦 tar.gz → put → 中で展開する関数を作る方が安定 ### `Secret 'hf_token' not found` Modal 上に `hf_token` という名前で secret が無い場合に出る。 [setup.md](setup.md) の Hugging Face シークレット節を参照して作成、または `modal_app.py` の `from_name("hf_token", ...)` を既存 secret 名に書き換え。 ### HF upload で `401 Unauthorized` `hf_token` secret が read-only token の場合。write 権限が必要 (repo create / upload): ```bash modal secret create HF_TOKEN_WRITE HF_TOKEN_WRITE=hf_xxxxx ``` `modal_app.py::upload_lora_to_hf` が `HF_TOKEN_WRITE` secret を参照する。 詳細は [setup.md](setup.md#hf-hub-への-upload-lora-配布など)。 ### Modal `workspace billing cycle spend limit reached` 新規 app 起動時: ``` App creation failed: workspace billing cycle spend limit reached ``` **現在進行中の cloud function にも SIGINT が送られて停止する**(KeyboardInterrupt として cloud log に出る、ローカル CLI の Ctrl-C と区別不可)。先に並列起動した training が **両方** とも preempt される事例あり (2026-05-18 DMD2 v1 と DMDX v1 並列起動時)。 対処: 1. https://modal.com/settings/billing で **Spend limits** タブを確認 2. current cycle の limit を引き上げる、または cycle reset 待ち 3. 引き上げ後は新規 app 起動も既存 detach app も復旧 予防: - 並列で高負荷 training を起動する前に Modal dashboard で remaining budget 確認 - DMD2 + DMDX 並列は B200 1 つあたり ~$30 × 2 = $60+ 高速消費するので、limit に余裕が必要 ### 並列訓練時の rate 低下 本日実測 (2026-05-19、DMD2 resume + DMDX 並列): - DMD2 単独: 3.66s/outer - DMD2 並列 (with DMDX): **6.2s/outer** (1.7x 遅い) - DMDX 単独 (smoke): 3.45s/outer - DMDX 並列: 3.6s/outer (slowdown 小さい) 原因推定: Modal が異なる B200 instance を割当てるが、混雑度や近隣 workload で per-step rate に差。DMDX は forward 数が少ない (2 disc + 1 gen = 3 stage vs DMD2 の 5+1=6 stage) ので絶対的に slowdown 耐性が高い。 並列予算は単独の 1.5-2x で見積もる (e.g., DMD2 単独 $30 → 並列時 $50-60)。