# MiniMax H3 LoRA — anime loop i2v (projeto h3_loop) ## Objetivo LoRA de MiniMax H3 (FL2VA) no dataset `AdwolfCzar/anime_loop_2d_dataset_captioned_29-07`. Fase 1 ("quick"): 2 épocas nos ~3.9k clipes do `mega_curated` para validar o pipeline completo. Fase 2: treino com o dataset completo (`loopcut_000..005`, ~49 GB) ou o que couber. Foco de uso: image2video (primeiro frame → vídeo). Task de treino: `i2va`. ## RUNBOOK — caminho das pedras (para o próximo agente/treino) Sequência exata que funcionou, com os custos medidos. Scripts em `scripts/`, configs em `configs/`. 1. **Ambiente**: usar /venv/main (torch 2.10.0+cu130 — NÃO troque torch; nightly quebra CUBLAS). `uv pip install -e .` no fork Akane commit c4b84fa. `uv pip install tensorboard` (não vem). Se o venv der Permission denied: `sudo chown -R claude:claude /venv/main`. 2. **Downloads** (hf CLI com xet, muito rápido): pesos Comfy-Org/MiniMax-H3 (DiT bf16 66 GB + TE nvfp4 15 GB + 2 VAEs) e dataset. TE bf16 (48 GB) é desnecessário — nvfp4 funciona em Blackwell para cache E sampling do trainer. 3. **Análise quantitativa do dataset ANTES de tudo** (ffprobe em paralelo): fps real, SAR/DAR (DVDs 720×480 são anamórficos!), nb_frames, áudio. Sem isso o treino sai distorcido/errado. 4. **Normalização** (`normalize_videos.py`, ~30 jobs, ~15 min/4k clipes): 24 fps CFR + setsar=1 + scale por SAR. Nunca treinar fonte com fps misto (uncanny motion) ou pixel anamórfico. 5. **Extensão de loops** (`extend_loops.py`): tiling do ciclo até escada 124/90/73/56 (canvas do H3 começa em ~124f; treinar 22f não é validado por ninguém). Loops = tiling limpo. 6. **Buckets multi-res** (`assign_buckets.py`): 3 áreas (512×288 / 640×384 / 832×480, 55/28/17), symlinks, sem upscale. Multi-res é OBRIGATÓRIA no H3 (RoPE por área) + jitter 0.2 no treino. 7. **Smoke test** (`smoke_train.sh`): 10 steps + sampling + checkpoint + resume (leg 2 com --max_train_steps 15 valida autoresume de verdade). Incluir os itens MAIS PESADOS por bucket. 8. **Cache** (`cache_quick.sh`): batch 24 + workers 16, SERIAL (latents → TE). Medido na 5090: latents ~4,5 h + TE ~1,5 h para 3.885 itens ≈ 31,7 GB. NUNCA rodar TE em paralelo com latents na mesma GPU (medido: piora ambos). Batch >24 não ajuda (VAE é o gargalo, GPU 100%). 9. **Treino** (`train_quick.sh` via tmux + tee log): receita anti-destilação completa (ver seção Receita). ~13 s/it na 5090 no regime 124f multi-res. Sampling+checkpoint a cada 500 steps. 10. **Uploader HF** (`hf_uploader.py`) ativo DESDE o smoke; repo gated manual, nunca "minimax" no nome. 11. **Monitorar**: loss média SOBE com preservation ligada — é normal, olhar val/loss de vídeo; watchdog de stall (freeze silencioso near-OOM = step 10x mais lento, não crasha). **Armadilhas que já custaram tempo aqui:** - ffmpeg pesado em paralelo durante CARGA do DiT → 6× mais lento (carga é CPU-bound); durante steps de treino, ok. - Jupyter cria `.ipynb_checkpoints` root-owned dentro de output/ quando o usuário navega — `rm -rf` falha; usar `sudo rm` ou ignorar. - Nunca `pkill -f` com padrão que casa com o próprio comando composto; matar por PID exato. - Nunca apagar cache com processo de treino/cache vivo apontando para ele. - Cache TOML separado do train TOML (batch alto só no cache; treino EXIGE batch_size=1). - `--save_last_n_steps_state 1500` limita states em disco (cada state = 859 MB). ## Ambiente (medido em 2026-08-10) - Vast.ai, RTX 5090 32 GB (sm_120), 124 GB RAM, 32 vCPU, disco 600 GB (overlay, **não persistente**) - Python 3.12.13 em `/venv/main`, torch 2.10.0+cu130 (combinação validada pela comunidade) - Trainer: fork `AkaneTendo25/musubi-tuner`, branch `minimax-h3`, commit **c4b84fa** (pin do relatório 2026-08-10) - Instalação: `uv pip install -e .` (torch não é tocado — extras cu130 não instalados) - ffmpeg 6.1.1 do sistema ## Pesos `Comfy-Org/MiniMax-H3` → `/workspace/models/MiniMax-H3/` - `diffusion_models/minimax_h3_fl2va_bf16.safetensors` (~66 GB; int8 convrot na carga) - `text_encoders/qwen3vl_32b_minimax_h3_nvfp4_awq.safetensors` (~15 GB; Blackwell OK) - `vae/minimax_h3_video_vae_fp16.safetensors`, `vae/minimax_h3_audio_vae_fp32.safetensors` ## Dataset — fatos medidos (mega_curated) - 3.889 mp4 + 3.889 .txt (captions sidecar dentro do zip; jsonl tem 6.855 linhas, 2.966 com caption nula — os .txt do zip estão completos) - **Áudio: nenhum clipe tem trilha** → `h3_target_mode = "video"` (sem audio VAE no cache, sequência menor) - fps caótico: ~24 (2239), 25 (341), ~30 (182), centenas de variantes 22–29 (VFR/telecine) - Resoluções: 720×480 anamórfico (SAR 853:720 e 186:157, ambos DAR ~16:9) ×1852; 1920×1080 ×1093; 1280×720 ×354; 704×396 ×245; 1270×720 ×104; 960×720 ×35; 640×480 ×7 - Frames pós-conversão 24fps, snap 17n+5: 22×1706, 39×875, 56×480, 73×199, 90×173, 107×114, 124×139; 4 itens < 22 frames (descartados) ## Normalização (não-destrutiva) `scripts/normalize_videos.py`: fonte `/workspace/datasets/mega_curated` (intocada) → `/workspace/datasets/mega_24`: 24 fps CFR (`fps=24`), pixels quadrados (scale por SAR + `setsar=1`), libx264 crf 15 preset fast, sem áudio. Clipes com <22 frames pós-conversão são pulados. ## Buckets multi-resolução (obrigatório no H3 — RoPE normalizado por área) Diretiva do usuário (10/08): resolução pequena/média, 1024×576 é alto demais. `scripts/assign_buckets.py` (seed 42, sem upscale, estratificado por resolução de origem): - `v288` [512×288] ×2138 (~55%) — movimento barato - `v384` [640×384] ×1087 (~28%) — bucket principal - `v480` [832×480] ×660 (~17%) — âncora de densidade média (v576 descartado; caches v384/v480 de smoke reaproveitados, cache v576 removido) Views por symlink em `/workspace/datasets/train_quick/`; manifest em `configs/bucket_manifest_quick.csv`. ## Receita de treino (relatórios info/ de 09-10/08 + docs do fork) - guidance 4 + schedule sigma + form normalized; preservation 0.02; spatial density jitter 0.2 - SEM adapter do Ostris; SDPA (flash_attn quebra int8 convrot); int8 convrot fwd bf16; AdaLN rank 16 - rank/alpha 32 (estilo amplo), LR 1e-4 AdamW8bit (recomendação do trainer), warmup 50 - batch físico 1 (obrigatório no H3), **gradient_accumulation = 1** - 5090 32 GB: blocks_to_swap 24 h2d_only ring 2 pinned; PYTORCH_ALLOC_CONF=expandable_segments:True - Sampling i2v durante treino: 3 primeiros-frames do próprio dataset + captions como estão (`--i`), TE nvfp4 - max_frames 124 (regime de extensão de loops — ver seção Extensão de loops) ## Fase 2 — dimensionamento (medido) - loopcut_000..005: **19.747 mp4** (~50 GB descomprimido), zero overlap de nomes com mega_curated - Dataset completo fase 2 = loopcut (19.7k) + mega_curated (3.9k) ≈ 23,6k clipes - Normalização 24fps concluída 10/08 06:5x: 19.746 ok (1 duplicata), zero falhas, 43 GB em loopcut_24 - Extensão de loops (mesma escada 124/90/73/56) → loopcut_24_ext, iniciada em seguida - Extensão loopcut concluída: 4.592 tiled + 15.154 linked (clipes naturalmente longos), só 8,3 GB. Histograma: 124f×16490 (84%!), 107×1774, 90×1100, 73×269, 56×113 - Config fase 2 deve reintroduzir diversidade de duração: sub-datasets por bucket com max_frames 124/90/73 via symlinks (per-dataset max_frames no TOML, sem re-encode) - Disco p/ fase 2: caches ~100-140 GB estimados, 341 GB livres — cabe; recalcular antes do caching ## Log de execução - 2026-08-10 04:1x: downloads iniciados (modelos ~86 GB, dataset completo ~52 GB) — xet, muito rápido - 2026-08-10 04:2x: normalização 3.889 clipes lançada (30 jobs paralelos) ## Incidentes - 2026-08-10 04:47: smoke #1 (buckets antigos c/ v576) caiu no step 1 com FileNotFoundError — causa: remoção do cache v576 com o processo ainda vivo (erro operacional meu, não bug do trainer). Antes de cair validou: carga DiT bf16→int8 convrot (~2 min), sampling i2v 3/3 (pico 14,8 GiB), 1 step com loss 0.546 a 4,8 s/it. Smoke #2 relançado com buckets novos. ## Extensão de loops (decisão técnica minha após pergunta do usuário, 2026-08-10 ~05:30) Faixa publicada do H3 começa em ~124 frames (5,17s); mediana do dataset era 42f. Como os clipes são loops seamless, cada clipe é estendido por tiling do ciclo até um alvo sorteado (seed determinística): 124f 55% / 90f 20% / 73f 15% / 56f 10%. Justificativa: canvas publicado do H3 comeca em ~124f; 44% do dataset natural estava em 22f (nunca validado por ninguem no canal); caso worldx documenta acao esticada ao gerar mais longo que o treinado. Contra: custo ~2,4x/step; canal treina 39-73f sem quebra especifica de duracao. Decisao: casar treino com regime de geracao (~5s) vence o custo. Clipes ja maiores que o alvo ficam no comprimento natural (cap 124, symlink sem re-encode). Ensina movimento cíclico no regime real de geração (~5s). `scripts/extend_loops.py` → `/workspace/datasets/mega_24_ext`; max_frames=124; sampling --f 124. Custo: step médio ~2x vs regime 73f. Block swap mantido em 24 (teste com 16 não mostrou ganho; folga de VRAM protege contra freeze near-OOM, e o custo do swap no H3 é ~5% — compute-bound). ## Smoke test (2026-08-10 ~05:10) - Leg 1 (buckets novos 288/384/480): PASSOU exit 0 — 10 steps, loss 0.895→0.156, ~5-7 s/it (inclui overhead de saves), VRAM pico 23,5 GiB c/ swap 24, RAM 26 GB - Sampling i2v 3/3 nas 3 resoluções: denoising 13,5s (512×288), 21s (640×384), 33s (832×480) a 20 steps; pico sampling 14-15 GiB; mp4+json escritos - Checkpoint validado: 600 tensores LoRA, dim/alpha 32/32, 569 MB (fp32); state 859 MB - Carga DiT bf16→int8: ~2 min sem contenção de CPU (com 24 ffmpeg paralelos: 6× mais lento — lição: pausar normalização pesada durante fases de carga) - Leg 2 (resume + swap 16) em execução: valida autoresume e mede ganho de velocidade com menos swap - Uploader HF verificado durante smoke (NOTES/configs subiram para AdwolfCzar/minih33-loop-i2v, gated manual) ## Probe 124f + lançamento (2026-08-10 05:36-05:42) - Probe 5 steps no regime estendido: PASSOU exit 0, ~12-13 s/it, loss 0.167→0.101, VRAM ~18 GiB no início dos steps (dimensionado pelos itens mais pesados nos smoke buckets) - Sampling 124f: 35s (512×288) / 62s (640×384) / 108s (832×480) de denoising por vídeo; pico 16,7 GiB; ~3,5 min por evento de sampling (a cada 500 steps → overhead ~1%) - 05:42 lançado tmux `h3quick`: cache completo (3.885 itens; batch final 24) → treino 2 épocas encadeado - Log: /workspace/logs/quick_pipeline.log ## Medições de cache (10/08 06:00-06:40) - Latents batch 4: ~7 s/item; batch 12: ~5 s/item; batch 24: ~1,7-6,5 s/item conforme grupo de frames (124f domina). GPU 100% — VAE encode é o gargalo real; batch >24 não deve ajudar. - TE em paralelo com latents na mesma GPU: FRACASSO (TE 11-12 s/item vs ~1,6 solo; latents +14%). GPU time-slicing serializa com overhead. Regra: estágios de cache SEMPRE seriais em single-GPU. - Checkpoints do treino ajustados a pedido do usuário: a cada 500 steps (sampling idem); states retidos: últimos 1500 steps. ## Fase 2 — config pronta (10/08 ~12:20) - `assign_buckets_phase2.py`: loopcut 19.746 → v288 10.862 / v384 5.528 / v480 3.356; views de duração (≥124f: 70% f124 / 20% f90 / 10% f73): 9 sub-datasets em train_full/ - `dataset_full_train.toml`: 12 datasets (3 mega reutilizando cache/quick + 9 loopcut → cache/full) - `train_full.sh` (minih33_full, 1 época default — revisar com resultados do quick), `cache_full.sh` ## Treino quick — lançado (2026-08-10 12:12 UTC) - Cache final: v288 13 GB + v384 9,7 GB + v480 9,0 GB = 31,7 GB (3.885 itens, latents+TE i2va+guidance) - Duração total do caching (batch 24, serial): ~6h (latents ~4,5h + TE ~1,5h) - Treino: 2 épocas = 7.770 steps; checkpoint+sampling a cada 500; disco a 49% no início - Monitores: watchdog stall 15min, checkpoints/samples/erros, marcos do pipeline ## Treino quick — ENCERRADO pelo usuário no step 2.500 (10/08 ~21:20 UTC) - Rodou 2.564 steps a 12,8 s/it; loss 0.129→0.088; 5 checkpoints (500..2500) + samples no HF - Usuário avaliou samples: "está aprendendo bem"; decidiu parar e ir para fase 2 maior - Checkpoint 2500 convertido p/ ComfyUI (convert_lora.py --target other, chaves diffusion_model.*) → checkpoints/comfy/minih33_quick-step00002500_comfy.safetensors no repo ## BUG da base preservation em c4b84fa (descoberto pelo canal em 10/08) - Ada: preservation comparava contra o modelo COM a LoRA ativa → efeito nulo/parcial - Fix: 5d06e34 "disable training LoRA for base preservation" (verificado no diff: toggle enabled no LoRAModule + teste); head atualizado para f175417 - Implicação: o quick run rodou na prática como "guidance 4 + jitter" — checkpoint VÁLIDO (guidance segura runs curtos), mas diz pouco sobre preservation em run longo - Caches NÃO afetados (fix é na loss; chancelor retomou sem recache; diff não toca fingerprint) - Validação externa: mamad8 24h+ em 8x4090 com a receita guidance4+prsv0.02+jitter SEM degradação ## Fase 2 — revisão pós-relatório do outro agente (10/08 ~21:30) - Fork: f175417 (fix preservation); re-smoke em execução - Durações rebalanceadas 35/30/35 (f124/f90/f73) — corta step médio de ~12 p/ ~8-9 s/it - Duas etapas: STAGE A main (v288+v384, mega+loopcut, 1 época ≈ 20,2k steps) → STAGE B polish (v480, 3.000 steps, --network_weights do LoRA final da A) - `--h3_fused_qk_norm_rope` habilitado (recomendação Ada; validando no smoke) - **LR 1e-4 nas duas etapas — decisão explícita do usuário** (recusou 5e-5) - Block swap 24 mantido (benchmark Ada: 0 vs 48 blocos = 0,03 s/it — swap é grátis) - Pipeline: launch_full_pipeline.sh (cache 12 datasets → stage A → stage B, tmux) ## FASE 3 (composição nova definida pelo usuário, 10/08 ~21:30) Substitui o plano "fase 2 completa". Dataset: mega_curated 3.889 (caches quick reutilizados) + loopcut 2.000 (seleção seeded estratificada por bucket/duração; não-selecionados deletados p/ liberar ~38 GB) + looper_v4 10.000 → ~15,9k itens, ~2 épocas (~31,8k steps), LR 1e-4 (usuário). - looper_v4 (AdwolfCzar/looper_v4_dataset_captioned): 3 zips ~50 GB, 26,5k mp4, TUDO ~30fps SAR 1:1, resoluções web modernas (1080p/720p/4K + retrato/quadrado), 79% vira 107f em 24fps; 25.724 com captions; 1.222 curtos demais excluídos - Prep looper: seleção seeded 10k → normalize 24fps + DOWNSCALE cap 0,61 MP (fonte 4K não serve p/ buckets ≤0,4MP) → SEM extensão de loop (107f natural já no regime; diversidade via views) - Views compartilhadas train_p3/{bucket}_{dur} (loopcut2k + looper10k juntos), caches cache/p3/* - Smoke f175417 completo: leg1 10 steps + resume leg (loss 0.185→0.066) com fused_qk_norm_rope - Limpezas autorizadas executadas: extract_tmp, zips (todos), mega_curated raw, loopcut não-selecionado, states antigos — pico de 443 GB livres - Entregas ao repo: guia_comfyui.md (resoluções/durações/uso), exemplo_de_captions.txt (100 aleatórias), checkpoint 2500 convertido p/ ComfyUI (convert_lora.py --target other) - Armadilha nova p/ o runbook: NUNCA usar `until pgrep -f ` em background quando o próprio comando contém o padrão — o wrapper se auto-detecta e trava para sempre - Armadilha (11/08, CAUSA REAL corrigida): vídeos com metadata não-UTF8 (tags latin-1) quebram o PyAV do musubi (av.open → avdict_to_dict → UnicodeDecodeError) — o ffprobe tolera, o PyAV não, e o ffmpeg PRESERVA metadata da fonte por default. Fix: remux lossless `-c copy -map_metadata -1` nos 10k do looper (validação PyAV 10000/10000 ok) + `-map_metadata -1` adicionado aos scripts de normalização. (Primeira hipótese "race de symlinks" estava errada.) ## FASE 3 — treino LANÇADO e saudável (2026-08-11 16:48 UTC) - Cache p3 completo: 100 GB (12k itens novos: loopcut-2k + looper-10k; mega reutilizou cache/quick) Latents ~14h + TE ~5h no total (incluindo os reruns dos incidentes de metadata) - Treino minih33_v2: 2 épocas × 15.885 itens = 31.770 steps Medido no step 500: **10,15 s/it** (rebalance de durações 35/30/35 + fused_qk_norm_rope entregaram: era 12,8 no quick), loss 0.116 caindo, VRAM 17,4 GB - Config: f175417 (preservation FUNCIONAL), guidance 4/sigma/normalized, prsv 0.02, jitter 0.2, LR 1e-4 (decisão do usuário), rank 32, swap 24, checkpoint+sampling a cada 500 + upload HF - Samples step 0 e step 500 (3/3 cada) no repo; primeiro checkpoint 500 salvo - Projeção aritmética (não promessa): ~3,7 dias para as 2 épocas; checkpoints utilizáveis desde já (quick mostrou sinal bom em ~2,5k steps) - Disco: 333 GB livres após tudo | Watchdog stall + monitores de erro ativos ## RUN v3 (12/08 ~04:1x UTC) — decisões do usuário após avaliação dos samples v2 Contexto: samples do v2 a 3,5k steps considerados fracos pelo usuário. Diagnóstico: (1) f175417 NÃO tem o fix 365d980 (amostragem da preservation + propagação do jitter + alinhamento sigma-guidance) — receita parcialmente torta pela 3ª vez; (2) doutrina do canal virou em 11/08: LR 1e-4 é alto para H3 destilado (rank 32 → 2-5e-5); (3) preservation freia ~2x por design. Decisões (usuário): fork f8561f6c; LR 5e-5; SEM preservation (vai parar cedo por checkpoint- picking, prioriza aprendizado rápido; guidance 4 + jitter 0.2 ficam); max 2 épocas (~31,8k steps) mas parada manual a qualquer momento; restart DO ZERO (10h de GPU descartadas por baseline limpo); output_v3/ + repo novo AdwolfCzar/minih33-loop-i2v-v3 (gated manual) com uploader próprio. v2 encerrado no step ~3.5k (checkpoints 500-3500 + samples preservados no repo original). Novidade do f8561f6c: checkpoint sob demanda por arquivo-gatilho (parada graciosa a qualquer momento — casa exatamente com o plano de parada manual do usuário). ## Incidente v3: OOM no step ~4.502 (12/08 15:1x UTC) — RECUPERADO - CUDA OOM (alocação de 1,12 GiB com 30,08 GiB já alocados) após 11h de run — batch pesado no pior bucket; expandable_segments estava ativo (fragmentação não era a causa; VRAM cheia real) - Fix: blocks_to_swap 24→32 (~3 GiB a mais de folga; custo ~zero, H3 é compute-bound) + autoresume do state 4500. Checkpoints 500-4500 + samples intactos no repo v3 - Lição p/ runbook: em runs >10h na 5090, swap 32 desde o início; monitores com `tail -n 0` (log em append guarda Tracebacks antigos → falso alarme no relançamento) ## FASE 4 — dois treinos separados (decisão do usuário, 14/08) v3 encerrado no step 17.500 (usuário: "já tá bom o suficiente"). Caches antigos apagados (449 GB livres). Agora: **um treino por dataset, um de cada vez** — smoothloop primeiro, jiggle depois; 10 épocas cada (parada manual quando estiver bom). ### SMOOTHLOOP (966 clipes) - Fontes: 2 pastas mega.nz + 1 arquivo mega (zip 5 GB) + zip do HF smoothloop_dataset_base_v2 - **Dedup real (o palpite do usuário estava certo)**: 1.863 mp4 → 966 únicos 444 duplicatas exatas (md5 do conteúdo) + 444 mesmo-ID re-encodado em outra fonte - Captions casadas por **stem entre fontes** (o zip do mega3 tinha 1.580 captions p/ 505 vídeos — as fontes se complementam; matching por sibling .txt teria perdido a maioria) - Normalização p4: 24fps CFR + SAR + cap 0,61 MP + `-map_metadata -1`; 966/966 ok, mediana 236f (78% já ≥124f → sem tiling de loop) - Views: v288 532 / v384 270 / v480 164 × f124/f90/f73; cache 11 GB (966 latents + 966 TE) - Config: **idêntica ao minih33_v3** (verificado por diff), só muda dataset/output/samples/épocas - Repo: AdwolfCzar/minih33-smoothloop (gated) + uploader + conversor ComfyUI automático ### JIGGLE (1.232 clipes) — preparado, aguardando o fim do smoothloop - bouncing_body_jiggle_captioned_30-07; cap **800 por nível** (medium 866→800, low 225, intense 272) - **Triggers por nível aplicados na caption** conforme README do dataset: "bouncing body, " → "bouncing body with {low,medium,intense} jiggle and bouncing, " - Normalizado: 1.232 ok, 65 curtos demais descartados ### Armadilha nova (14/08) `--max_train_steps N` NÃO limita quando o script já passa `--max_train_epochs M` — o smoke de 8 steps virou treino real de 230 steps antes de eu notar. Para smoke com esses scripts, sobrescreva TAMBÉM as épocas (ou use um script dedicado). ## Publicação HF (resolvido 10/08) Repo `AdwolfCzar/minih33-loop-i2v` — público (sem custo de cota privada) + gated manual (arquivos só baixáveis com aprovação do dono). Regra do usuário: nunca mencionar "minimax" no nome/card público; identificador: minih33. Uploader: `scripts/hf_uploader.py` (watch em output/).