A goal-conditioned world-action policy for autonomous drone photography — the clean-room rewrite that fixes the v8–v10 root cause while keeping the imagine-before-act thesis.
v8–v10 underperformed for one reason: the 5D×8 action chunk (40 DOF) was tiled ~2160× into an 86,400-element video latent frame and read back by mean-pooling, with no learned action decoder. A project-internal controlled experiment proved a DP-style dedicated 1D-U-Net head reaches the goal-signal ceiling (between≈0.09, ratio≈0.65) while the tiled head caps at 21%. Everything else (5D action rep, camera conventions, HER data, goalnorm conditioner) was verified correct and is reused verbatim.
frozen Wan VAE latents ─┐
├─ frozen Cosmos DiT ─ imagine ẑ_next (goal-conditioned)
z_cur ──────────────────┘
z_cur, ẑ_next ─[trainable LatentConvEncoder]→ f_cur, f_imag
goal ─[MLP]→ g_e ; global_cond = [f_cur ⊕ f_imag ⊕ g_e]
├─[trainable ConditionalUnet1D] DDPM/DDIM (v-pred + min-SNR) → action (B,8,5)
└─[trainable ValueHead] → value (B,8)
The frozen Cosmos DiT imagines the goal-conditioned next-frame latent; a dedicated
ConditionalUnet1D denoises the action chunk from [enc(z_cur) ⊕ enc(ẑ_next) ⊕ goal]
— no tiling. Because the imagined "achieved-goal shot" is on the action head's
forward path, "does the world model help the action?" is the built-in ablation.
Key choices: v-prediction + min-SNR-γ action loss (the small goal-dependent action
signal survives the ε-dominated target); CFG removed (guidance 1 is optimal);
deterministic value head; and full training hygiene v8–v10 lacked — gradient
clipping, weight EMA for sampling, cosine LR with warmup, periodic keep-last-k
checkpoints, and a val/train action-normalization that can't drift
(build_datasets is the choke point).
lambda_world > 0 even with a frozen DiT: gradient still flows through the frozen
backbone to the trainable goal tokens, so the conditioner learns to steer the
imagination.
| Config | What | Success gate |
|---|---|---|
A0 v11_worldoff.yaml | dedicated head only, no DiT | check_goal_dependence ratio → ~0.6; val/action_mse ≤ ~0.10 |
A1 v11_frozen.yaml | + frozen DiT imagination | A1 ≤ A0 on val/action_mse; closed-loop mean_improvement_over_noop > 0 |
A2 v11_lora.yaml (Phase 3, TODO) | + LoRA on DiT + world loss | A2 beats A0/A1 |
A3 v11_dinov2_xcheck.yaml (TODO) | DINOv2 obs cross-check | isolates Cosmos-latent vs DINOv2 |
If A1 never beats A0, that's a clean negative ablation — and A0 still ships as a ceiling policy.
python -m venv .venv && source .venv/bin/activate # or reuse a shared venv on NAS
pip install -r requirements.txt
hf auth login # gated Cosmos-Predict2.5-2B weights
# assets/text_anchor.pt ships in-repo (goal conditioner anchor; Qwen is never downloaded)
PYTHONPATH=. pytest tests/ # 115 tests: model, data, val-norm regression, trainer hygiene
The whole obs→imagine→action→value path runs on a mock DiT + identity VAE.
# login smoke (≤50 steps, 1 GPU) — verifies the real data→encode→train loop
PYTHONPATH=. python scripts/train_v11.py --config configs/policy/v11_smoke.yaml
# A0 then A1 on one B200 (frozen fits a large batch; no DDP)
sbatch scripts/sbatch_v11.sh configs/policy/v11_worldoff.yaml
sbatch scripts/sbatch_v11.sh configs/policy/v11_frozen.yaml
Runs land in runs/<name>_<timestamp>/ with config.yaml, tb/, and
ckpt_iter*/last/final.pt.
src/common/ action_repr (5D), goal_space, reward, annotations, dataset_base, flow [reused verbatim]
src/scoring/ V5 shot-profile = the goal space [reused verbatim]
src/model/ vae, conditioner (goalnorm, CFG stripped), world_backbone (frozen DiT),
latent_encoder, action_head (ConditionalUnet1D), value_head, policy
src/data/ dataset + build_datasets (identical train/val normalization)
src/train/ trainer (grad-clip, EMA, cosine, periodic ckpt, min-SNR)
scripts/ train_v11.py, sbatch_v11*.sh (eval/diag: rollout_eval, diag_goal_gap,
check_goal_dependence, load_policy — Phase 4, TODO)
load_policy.py, rollout_eval.py (closed-loop Blender), diag_goal_gap.py, check_goal_dependence.py.v11_lora.yaml + LoRA wiring on the DiT.1 commits
Python
97.7%
Shell
2.3%
A goal-conditioned world-action policy for autonomous drone photography — the clean-room rewrite that fixes the v8–v10 root cause while keeping the imagine-before-act thesis.
v8–v10 underperformed for one reason: the 5D×8 action chunk (40 DOF) was tiled ~2160× into an 86,400-element video latent frame and read back by mean-pooling, with no learned action decoder. A project-internal controlled experiment proved a DP-style dedicated 1D-U-Net head reaches the goal-signal ceiling (between≈0.09, ratio≈0.65) while the tiled head caps at 21%. Everything else (5D action rep, camera conventions, HER data, goalnorm conditioner) was verified correct and is reused verbatim.
frozen Wan VAE latents ─┐
├─ frozen Cosmos DiT ─ imagine ẑ_next (goal-conditioned)
z_cur ──────────────────┘
z_cur, ẑ_next ─[trainable LatentConvEncoder]→ f_cur, f_imag
goal ─[MLP]→ g_e ; global_cond = [f_cur ⊕ f_imag ⊕ g_e]
├─[trainable ConditionalUnet1D] DDPM/DDIM (v-pred + min-SNR) → action (B,8,5)
└─[trainable ValueHead] → value (B,8)
The frozen Cosmos DiT imagines the goal-conditioned next-frame latent; a dedicated
ConditionalUnet1D denoises the action chunk from [enc(z_cur) ⊕ enc(ẑ_next) ⊕ goal]
— no tiling. Because the imagined "achieved-goal shot" is on the action head's
forward path, "does the world model help the action?" is the built-in ablation.
Key choices: v-prediction + min-SNR-γ action loss (the small goal-dependent action
signal survives the ε-dominated target); CFG removed (guidance 1 is optimal);
deterministic value head; and full training hygiene v8–v10 lacked — gradient
clipping, weight EMA for sampling, cosine LR with warmup, periodic keep-last-k
checkpoints, and a val/train action-normalization that can't drift
(build_datasets is the choke point).
lambda_world > 0 even with a frozen DiT: gradient still flows through the frozen
backbone to the trainable goal tokens, so the conditioner learns to steer the
imagination.
| Config | What | Success gate |
|---|---|---|
A0 v11_worldoff.yaml | dedicated head only, no DiT | check_goal_dependence ratio → ~0.6; val/action_mse ≤ ~0.10 |
A1 v11_frozen.yaml | + frozen DiT imagination | A1 ≤ A0 on val/action_mse; closed-loop mean_improvement_over_noop > 0 |
A2 v11_lora.yaml (Phase 3, TODO) | + LoRA on DiT + world loss | A2 beats A0/A1 |
A3 v11_dinov2_xcheck.yaml (TODO) | DINOv2 obs cross-check | isolates Cosmos-latent vs DINOv2 |
If A1 never beats A0, that's a clean negative ablation — and A0 still ships as a ceiling policy.
python -m venv .venv && source .venv/bin/activate # or reuse a shared venv on NAS
pip install -r requirements.txt
hf auth login # gated Cosmos-Predict2.5-2B weights
# assets/text_anchor.pt ships in-repo (goal conditioner anchor; Qwen is never downloaded)
PYTHONPATH=. pytest tests/ # 115 tests: model, data, val-norm regression, trainer hygiene
The whole obs→imagine→action→value path runs on a mock DiT + identity VAE.
# login smoke (≤50 steps, 1 GPU) — verifies the real data→encode→train loop
PYTHONPATH=. python scripts/train_v11.py --config configs/policy/v11_smoke.yaml
# A0 then A1 on one B200 (frozen fits a large batch; no DDP)
sbatch scripts/sbatch_v11.sh configs/policy/v11_worldoff.yaml
sbatch scripts/sbatch_v11.sh configs/policy/v11_frozen.yaml
Runs land in runs/<name>_<timestamp>/ with config.yaml, tb/, and
ckpt_iter*/last/final.pt.
src/common/ action_repr (5D), goal_space, reward, annotations, dataset_base, flow [reused verbatim]
src/scoring/ V5 shot-profile = the goal space [reused verbatim]
src/model/ vae, conditioner (goalnorm, CFG stripped), world_backbone (frozen DiT),
latent_encoder, action_head (ConditionalUnet1D), value_head, policy
src/data/ dataset + build_datasets (identical train/val normalization)
src/train/ trainer (grad-clip, EMA, cosine, periodic ckpt, min-SNR)
scripts/ train_v11.py, sbatch_v11*.sh (eval/diag: rollout_eval, diag_goal_gap,
check_goal_dependence, load_policy — Phase 4, TODO)
load_policy.py, rollout_eval.py (closed-loop Blender), diag_goal_gap.py, check_goal_dependence.py.v11_lora.yaml + LoRA wiring on the DiT.1 commits
Python
97.7%
Shell
2.3%