jungwoo-ahn/DronePhotographerV11

Goal-conditioned drone-photography policy — v11: frozen Cosmos world-model + dedicated action head

0

stars

1

commits

Python

primary language

Jul 29, 2026

updated

README

DronePhotographer v11

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.

Why v11

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.

Architecture

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.

The ablation ladder (also the build/validation order)

ConfigWhatSuccess gate
A0 v11_worldoff.yamldedicated head only, no DiTcheck_goal_dependence ratio → ~0.6; val/action_mse ≤ ~0.10
A1 v11_frozen.yaml+ frozen DiT imaginationA1 ≤ A0 on val/action_mse; closed-loop mean_improvement_over_noop > 0
A2 v11_lora.yaml (Phase 3, TODO)+ LoRA on DiT + world lossA2 beats A0/A1
A3 v11_dinov2_xcheck.yaml (TODO)DINOv2 obs cross-checkisolates Cosmos-latent vs DINOv2

If A1 never beats A0, that's a clean negative ablation — and A0 still ships as a ceiling policy.

Setup

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)

Test (CPU, no GPU / no weights)

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.

Train

# 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.

Layout

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)

Not yet built (later phases)

  • Phase 4 eval: load_policy.py, rollout_eval.py (closed-loop Blender), diag_goal_gap.py, check_goal_dependence.py.
  • Phase 3: v11_lora.yaml + LoRA wiring on the DiT.
  • Multi-GPU DDP (frozen v11.0 fits one B200, so deferred).
  • A dedicated v11 venv (sbatch currently falls back to the shared DronePhotographer venv).

Contributors

jungwoo721

1 commits

jungwoo-ahn/DronePhotographerV11

Goal-conditioned drone-photography policy — v11: frozen Cosmos world-model + dedicated action head

0

stars

1

commits

Python

primary language

Jul 29, 2026

updated

README

DronePhotographer v11

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.

Why v11

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.

Architecture

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.

The ablation ladder (also the build/validation order)

ConfigWhatSuccess gate
A0 v11_worldoff.yamldedicated head only, no DiTcheck_goal_dependence ratio → ~0.6; val/action_mse ≤ ~0.10
A1 v11_frozen.yaml+ frozen DiT imaginationA1 ≤ A0 on val/action_mse; closed-loop mean_improvement_over_noop > 0
A2 v11_lora.yaml (Phase 3, TODO)+ LoRA on DiT + world lossA2 beats A0/A1
A3 v11_dinov2_xcheck.yaml (TODO)DINOv2 obs cross-checkisolates Cosmos-latent vs DINOv2

If A1 never beats A0, that's a clean negative ablation — and A0 still ships as a ceiling policy.

Setup

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)

Test (CPU, no GPU / no weights)

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.

Train

# 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.

Layout

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)

Not yet built (later phases)

  • Phase 4 eval: load_policy.py, rollout_eval.py (closed-loop Blender), diag_goal_gap.py, check_goal_dependence.py.
  • Phase 3: v11_lora.yaml + LoRA wiring on the DiT.
  • Multi-GPU DDP (frozen v11.0 fits one B200, so deferred).
  • A dedicated v11 venv (sbatch currently falls back to the shared DronePhotographer venv).

Contributors

jungwoo721

1 commits

Languages

Python

97.7%

Shell

2.3%