Skip to content

omnidreams: Clean Forcing drift corrector (training recipe + gated deploy)#398

Open
wenqingw-nv wants to merge 1 commit into
NVIDIA:mainfrom
wenqingw-nv:clean-forcing-omnidreams
Open

omnidreams: Clean Forcing drift corrector (training recipe + gated deploy)#398
wenqingw-nv wants to merge 1 commit into
NVIDIA:mainfrom
wenqingw-nv:clean-forcing-omnidreams

Conversation

@wenqingw-nv

@wenqingw-nv wenqingw-nv commented Jul 24, 2026

Copy link
Copy Markdown
Collaborator

Long autoregressive Omnidreams rollouts drift: each chunk conditions on self-generated history and small errors compound into color/saturation runaway, texture mush, and content repetition. This PR adds a trained drift corrector (Clean Forcing), the recipe that produced it, and a gated deploy hook. drift_corrector=None (default) is byte-identical to current behavior.

Contents

  • omnidreams/_drift_corrector.py + two runner-config fields (drift_corrector, drift_corrector_gain) — a frozen-base LoRA (r16 on the self-attention q/k/v/o projections, ~7.3M params) applied at alpha*(t) × gain per denoise step, where alpha*(t) is the measured systematic fraction of the drift-induced error at each solver timestep (0.96 @ t=1000, 0.667 @ t=803).
  • integrations/omnidreams/drift_correction/ — the full recipe: mixed-lap tiled-HDMap loop pair construction (+ a gate-validated loop-free fork variant as an ablation), step-0 alpha*(t) systematicity gate (go/no-go + the deploy gain profile), v1 counterfactual-teacher training in velocity space, v2 DAgger + drift-contraction round with step-tagged and validation-peak snapshots, dial-grid eval, per-timestep reliability analysis. README documents the ~1 GPU-day reproduction.
  • tests/test_drift_corrector.py (ci_cpu, 6 tests) — the LoRA wrapper is a strict identity until a checkpoint is loaded and gated on; the wrap set matches the training-side module (same checkpoint order); the gate profile resolves by nearest-t lookup.

Docs

Measured (704×1280, 81-chunk ≈21.5 s rollouts, held-out scenes, GB300)

config MUSIQ ↑ late-window MUSIQ ↑ Δ-drift ↓ RAFT dynamics
base 49.1 46.7 +2.44 16.6
shipped alpha*(t) × 0.25 50.1 49.1 (+5%) −60% 13.8 (−17%)

Shipped config cuts the drift metric 60% with overall quality above base and visibly better tree/foliage detail and consistency across the rollout (human side-by-side review was held as the final acceptance bar throughout). The method/reproduction details: drift_correction/README.md (quick start)

Deploy notes

  • Per-step alpha*(t) gating cannot merge into one weight set → the LoRA runs unfused (~0.4% extra matmul); drift_corrector=None skips module surgery entirely.
  • Corrector LoRA checkpoint (~88 MB, md5 84beb8c014346833ce182310373b5d32) hosting: currently a fork release asset (https://github.com/wenqingw-nv/flashdreams-wq/releases/tag/clean-forcing-omnidreams-v3vp); release asset here vs HF — maintainer preference welcome.
  • Correctors encode a checkpoint-specific error map: re-instantiation on a new base checkpoint is ~1 GPU-day with the included recipe (the README quick-start is the refresh procedure).

Inference overhead (GB300, same rollout protocol as Measured; 3 runs, steady-state mean ± std)

config per-chunk (ms) end-to-end 81 chunks (s) effective fps peak VRAM
base 233.0 ± 1.5 18.93 ± 0.12 34.1 31.53 GB
shipped corrgate025 338.5 ± 5.9 27.53 ± 0.36 23.4 31.53 GB
  • No extra forward passes: correction is a single forward per denoise step — the LoRA delta is computed inside the wrapped projections (merged velocity path).
  • The +45% wall-clock overhead is the current unfused-LoRA hook: the per-timestep alpha*(t) gate prevents merging the LoRA into one weight set, and the hook runs the rank-16 delta path in fp32 with a per-step rescale. Added parameters: 7.34M (0.36% of the 2.06B base, ~56 MB of weights); peak activation VRAM unchanged. A bf16 delta path / compiled gate would reclaim most of this headroom.
  • drift_corrector=None (default) runs the untouched base graph at zero overhead.

Video Comparison

Left: Omnidreams (base), Right: w/ Clean Forcing (better color/detail consistency across time; 4× speed):

base_vs_cleanForcing1.mp4
base_vs_cleanForcing2.mp4

…d deploy)

Adds a trained drift corrector for the Omnidreams single-view
integration: a frozen-base LoRA (r16 on the self-attn q/k/v/o
projections, ~7.3M params) trained from the model's own tiled-HDMap
loop rollouts with a counterfactual clean-history teacher in velocity
space.

Deploy: OmnidreamsRunnerConfig.drift_corrector (checkpoint path) +
drift_corrector_gain (default 0.25). The hook rescales the LoRA to
alpha*(t) x gain per denoise step, where alpha*(t) is the measured
systematic fraction of the drift-induced error at each solver timestep.
drift_corrector=None (default) is byte-identical to current behavior.

Recipe under integrations/omnidreams/drift_correction/: pair
construction (mixed-lap tiled-HDMap loops; loop-free fork variant as an
ablation), the step-0 alpha*(t) systematicity gate, v1 counterfactual-
teacher training, v2 DAgger + drift-contraction round with step-tagged
and validation-peak snapshots, dial-grid eval, and a per-timestep
reliability analysis utility.

Measured on held-out 21.5 s rollouts: drift Delta-MUSIQ +0.99 vs base
+2.44 (60% cut), late-window MUSIQ +5% over base, dynamics -17%.

tests/test_drift_corrector.py (ci_cpu, 6 tests): the LoRA wrapper is a
strict identity until a checkpoint is loaded and gated on; the wrap set
matches the training-side module; the gate profile resolves by
nearest-t lookup.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Signed-off-by: wenqingw <wenqingw@nvidia.com>
@copy-pr-bot

copy-pr-bot Bot commented Jul 24, 2026

Copy link
Copy Markdown

This pull request requires additional validation before any workflows can run on NVIDIA's runners.

Pull request vetters can view their responsibilities here.

Contributors can view more details about this message here.

@greptile-apps

greptile-apps Bot commented Jul 24, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds a Clean Forcing drift corrector for Omnidreams single-view rollouts: a frozen-base LoRA (r16, ~7.3M params on self-attention q/k/v/o projections) trained via counterfactual teacher + DAgger + drift-contraction and gated per denoise step at alpha*(t) × gain. The drift_corrector=None default is byte-identical to current behavior.

  • omnidreams/_drift_corrector.py and two new OmnidreamsRunnerConfig fields deploy the corrector with a nearest-t alpha*(t) gate; runner.py applies it unconditionally before the mode-dispatch block, so both the save-embeddings and AR-loop paths deploy the corrector even though the former never calls predict_flow.
  • drift_correction/ ships the full recipe (pair-build, gate, v1/v2 trainers, eval sweep, gain analysis) plus _train_attn.py, which patches SelfAttention.forward with a grad-friendly functional variant controlled by process-wide toggle globals that are not thread-safe.
  • Six CPU-only tests cover the deploy contract (identity at zero scale/B, wrap-set matching, gate profile resolution), but there is no test for the save_embeddings_path interaction or the corrector-on-then-save sequence.

Confidence Score: 4/5

Safe to merge with the caveat that the production checkpoint-loading path uses weights_only=False, and the corrector is deployed on the save-embeddings code path unnecessarily.

The core mechanism is well-designed: drift_corrector=None preserves byte-identical behavior, the LoRA wrapping skips already-wrapped modules, the alpha*(t) nearest-t gate is tested, and the training recipe is clearly documented and resumable. The two points worth addressing before wide deployment are the weights_only=False pickle surface in the production load path and the corrector being deployed before the save-embeddings early return. Neither breaks current behavior.

Files Needing Attention: omnidreams/_drift_corrector.py (checkpoint loading and gated_pf robustness) and omnidreams/runner.py (corrector placement relative to mode dispatch).

Security Review

  • Arbitrary code execution via weights_only=False (omnidreams/_drift_corrector.py line 132, drift_correction/_host.py line 425, drift_correction/_lora.py line 128): torch.load(..., weights_only=False) deserializes arbitrary Python via pickle. The checkpoint path in the production deploy path (drift_corrector) is a user-supplied Path in OmnidreamsRunnerConfig; any file an attacker can write to (or substitute for the expected checkpoint) will execute arbitrary code at load time. The stored format ({"lora": {int: Tensor}}) does not require this flag — weights_only=True would suffice.

Important Files Changed

Filename Overview
integrations/omnidreams/omnidreams/_drift_corrector.py Production deploy hook: wraps self-attn linears with LoRA, loads checkpoint, patches predict_flow with an alpha*(t) gate. Two issues: weights_only=False in torch.load allows arbitrary code execution from a user-supplied path; and gated_pf silently converts a missing timestep arg into a None-dereference AttributeError.
integrations/omnidreams/omnidreams/runner.py Adds drift_corrector and drift_corrector_gain fields to OmnidreamsRunnerConfig and calls apply_drift_corrector in run(). The corrector is deployed unconditionally before the save-embeddings early return, wasting ~88 MB of load and permanently patching the transformer even when the AR loop is never entered.
integrations/omnidreams/drift_correction/_train_attn.py Functional, grad-friendly self-attention toggle for training. The _INJECT.pop(0) pattern runs O(n) per block during contraction training steps, but otherwise the context-manager design is clean and idempotent.
integrations/omnidreams/drift_correction/_lora.py Training-side LoRA helper: apply_lora, save_lora, load_lora. Uses weights_only=False in load_lora (training-only). Initialization sets scale=1.0 vs deploy-side 0.0; intentional difference since deploy always sets scale before inference.
integrations/omnidreams/drift_correction/_host.py Omnidreams host adapter for rollout capture, KV-cache replay, counterfactual probes, clip I/O. load_clip uses weights_only=False. The os.environ.setdefault at module import time is a side-effect that runs on any import.
integrations/omnidreams/drift_correction/train_v1.py v1 counterfactual-teacher LoRA trainer. Includes resume, exclusion logging, and startup replay-equivalence check. Well-structured.
integrations/omnidreams/drift_correction/train_v2.py v2 DAgger + drift-contraction trainer. Accesses private scheduler internals (_full_timesteps, _full_sigmas). Val-peak snapshot logic is correct.
integrations/omnidreams/drift_correction/gate_faithful.py Step-0 systematicity gate: computes alpha*(t) per timestep and depth, writes JSON summary, prints GO/NO-GO. Clean and read-only.
integrations/omnidreams/tests/test_drift_corrector.py Six CPU-only unit tests covering identity at zero scale/B, linearity of delta, target matching, scale propagation, and gate profile lookup. Good deploy-contract coverage.

Sequence Diagram

sequenceDiagram
    participant Runner as OmnidreamsRunner.run()
    participant DC as apply_drift_corrector()
    participant Net as CosmosTransformer.network
    participant PF as transformer.predict_flow (gated_pf)
    participant Orig as original predict_flow

    Runner->>DC: checkpoint, gain
    DC->>Net: _apply_lora(network) wrap Linear to _LoRALinear
    DC->>Net: load checkpoint tensors into A/B params
    DC->>PF: "monkey-patch transformer.predict_flow = gated_pf"
    DC-->>Runner: log line

    Note over Runner: AR rollout loop or save-embeddings early return

    Runner->>PF: predict_flow(noisy_latent, timestep, cache, input)
    PF->>PF: "nearest-t lookup alpha*(t)"
    PF->>Net: "_set_scale(network, alpha * gain)"
    PF->>Orig: "orig_pf(*args, **kwargs)"
    Orig-->>PF: flow
    PF-->>Runner: corrected flow
Loading

Reviews (1): Last reviewed commit: "omnidreams: add Clean Forcing drift corr..." | Re-trigger Greptile

Comment on lines +245 to 254
if cfg.drift_corrector is not None:
from omnidreams._drift_corrector import apply_drift_corrector

mode = apply_drift_corrector(
self, cfg.drift_corrector, cfg.drift_corrector_gain
)
logger.info(f"[{cfg.runner_name}] drift corrector: {mode}")
if cfg.save_embeddings_path is not None:
self._run_save_embeddings(cfg.save_embeddings_path)
return

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Corrector deployed even on the save-embeddings path

apply_drift_corrector runs unconditionally before the save_embeddings_path early-return check. When a user sets both drift_corrector and save_embeddings_path, the full corrector deployment happens (loading ~88 MB, wrapping every target linear, monkey-patching predict_flow), then _run_save_embeddings returns immediately without ever calling predict_flow. The patch persists on the transformer instance, leaving it in a permanently modified state even after that run exits — which matters if the runner or pipeline object is later inspected or reused. Moving the corrector setup after the mode-dispatch block (or gating it on embeddings_path is None and save_embeddings_path is None) would avoid the wasted work and the permanently-patched transformer.

network = network._orig_mod
params = _apply_lora(network)

sd = torch.load(checkpoint, map_location="cpu", weights_only=False)["lora"]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 security weights_only=False allows arbitrary code execution from the checkpoint file

torch.load(..., weights_only=False) deserializes arbitrary Python objects via pickle, so any file pointed at by drift_corrector (a user-supplied Path in the runner config) can execute arbitrary code at load time. The checkpoint format used here is a plain {"lora": {int: Tensor}} dict — no custom classes or non-tensor objects — so weights_only=True would work after registering the built-in types with torch.serialization.add_safe_globals if needed. The same pattern appears in _host.py (load_clip) and _lora.py (load_lora), which are used in the training scripts; at minimum the production deploy path in _drift_corrector.py should use weights_only=True.

Comment on lines +145 to +148
def gated_pf(*args, **kwargs):
ts = kwargs.get("timestep", args[1] if len(args) > 1 else None)
t = float(ts.reshape(-1).max())
alpha = min(GATE_ALPHA.items(), key=lambda kv: abs(kv[0] - t))[1]

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 Silent None path in gated_pf gives an unhelpful AttributeError

If predict_flow is ever called without a timestep argument and with fewer than two positional args, ts resolves to None and ts.reshape(-1) raises AttributeError: 'NoneType' object has no attribute 'reshape' with no context about the actual problem. In all current call sites timestep is passed as a keyword argument, so the defensive fallback args[1] if len(args) > 1 else None works, but the None case should raise an explicit ValueError rather than deferring to an opaque AttributeError deep inside the corrector hook. A one-line guard (if ts is None: raise ValueError(...)) would make the failure immediately diagnosable.

Comment on lines +125 to +129
if _INJECT is not None:
ik, iv = _INJECT.pop(0)
span = ik.shape[-3]
pk = torch.cat([pk[..., :-span, :, :], ik], dim=-3)
pv = torch.cat([pv[..., :-span, :, :], iv], dim=-3)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 _INJECT.pop(0) is O(n) per block, making injection O(n²) over all blocks

inject_kv sets _INJECT = list(kvs) and _functional_forward calls _INJECT.pop(0) once per transformer block. list.pop(0) shifts every remaining element left, so across all B blocks the total cost is O(B²). Using an iterator (e.g. iter(kvs)) and next() instead would give O(1) per block. This only affects training (the deploy path has no _INJECT active), but it runs in the inner training loop over every sample_losses call with a contraction term.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

@wenqingw-nv
wenqingw-nv enabled auto-merge July 24, 2026 23:49
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant