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/.
- 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. - 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.
- 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.
- 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.
- 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. - 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. - 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. - 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%). - Treino (
train_quick.shvia 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. - Uploader HF (
hf_uploader.py) ativo DESDE o smoke; repo gated manual, nunca "minimax" no nome. - 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_checkpointsroot-owned dentro de output/ quando o usuário navega —rm -rffalha; usarsudo rmou ignorar. - Nunca
pkill -fcom 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 1500limita 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, branchminimax-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 baratov384[640×384] ×1087 (~28%) — bucket principalv480[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 emconfigs/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_ropehabilitado (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 <padrão>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 -1nos 10k do looper (validação PyAV 10000/10000 ok) +-map_metadata -1adicionado 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/).