実装 / 運用 Tips + トラブルシューティング
distillation.md §4.7 の diffusion-pipe / ComfyUI 罠に加え、 2026-05 試行 (5 手法移植) で踏んだ追加の罠と対処、よくあるトラブルの解決策。
Modal CLI on Windows + JP (cp932) locale
毎回必須:
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 参照) |
| 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 定義の最後に追加:
.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 の Hugging Face シークレット節を参照して作成、または
modal_app.py の from_name("hf_token", ...) を既存 secret 名に書き換え。
HF upload で 401 Unauthorized
hf_token secret が read-only token の場合。write 権限が必要 (repo create / upload):
modal secret create HF_TOKEN_WRITE HF_TOKEN_WRITE=hf_xxxxx
modal_app.py::upload_lora_to_hf が HF_TOKEN_WRITE secret を参照する。
詳細は setup.md。
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 並列起動時)。
対処:
- https://modal.com/settings/billing で Spend limits タブを確認
- current cycle の limit を引き上げる、または cycle reset 待ち
- 引き上げ後は新規 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)。