Spaces:
Paused
Add Orbis 2 hierarchical world model demo (app2.py)
Browse filesVendors the Orbis 2 inference code into orbis2/ and adds app2.py, a Gradio
demo for the hierarchical L1-L2 model. Orbis 1 (app.py, orbis/) is untouched;
the Space still boots app.py until app_file is switched.
Unlike Orbis 1, the Orbis 2 rollout takes an mp4 directly and samples its own
L1 (high-rate) and L2 (low-rate, further back) context windows, so app2.py
re-encodes the upload to a constant 10 fps instead of extracting frames and
writing a val config.
- orbis2/evaluate/rollout_demo_v2.py: add --num_videos to roll out N futures
from one context as a single minibatch (tiling context/steering before
building condition_kwargs), and save each to fake_images/sequence_XXXX/,
matching the layout app2.py reads.
- orbis2/models/second_stage/fm_model_v2.py: label the rollout tqdm bars so
the app can scrape progress from the child process.
- app2.py: downloads the three models (L1 detail predictor, L2 abstract
predictor, tokenizer) from sud0301/orbis2_test into the HF cache and points
$ORBIS2_MODELS_DIR at the snapshot, which the L1/L2 configs expand to locate
each other. Uses the cache path directly rather than local_dir= to avoid
duplicating ~20 GB on disk.
- .gitignore: exclude the root models/ staging dir (weights + configs live on
the Hub). Anchored so it does not match orbis2/models/ source.
- requirements.txt: decord, h5py, requests for the video-in rollout path.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- .gitignore +8 -0
- app2.py +460 -0
- orbis2/data/datamodule.py +306 -0
- orbis2/data/helper_types.py +49 -0
- orbis2/data/l2_context.py +83 -0
- orbis2/data/steering_loaders.py +948 -0
- orbis2/data/utils.py +281 -0
- orbis2/data/video_loaders.py +638 -0
- orbis2/data/vista_style.py +183 -0
- orbis2/evaluate/rollout_demo_v2.py +706 -0
- orbis2/models/first_stage/vqgan.py +1190 -0
- orbis2/models/second_stage/cd_model_v2.py +279 -0
- orbis2/models/second_stage/fm_conditions_v2.py +1414 -0
- orbis2/models/second_stage/fm_model_v2.py +1319 -0
- orbis2/modules/dit.py +483 -0
- orbis2/modules/quantize.py +174 -0
- orbis2/modules/steering.py +52 -0
- orbis2/networks/DiT/dit.py +1026 -0
- orbis2/networks/DiT/dit_v2.py +740 -0
- orbis2/networks/swin/swin_free_aspect_ratio.py +740 -0
- orbis2/networks/tokenizer/ae.py +852 -0
- orbis2/networks/tokenizer/pretrained_models.py +158 -0
- orbis2/util.py +214 -0
- requirements.txt +5 -0
|
@@ -1,3 +1,11 @@
|
|
| 1 |
__pycache__/
|
| 2 |
*.pyc
|
| 3 |
orbis/imgs/Rollout.png
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
__pycache__/
|
| 2 |
*.pyc
|
| 3 |
orbis/imgs/Rollout.png
|
| 4 |
+
|
| 5 |
+
# Checkpoints are never committed (orbis1's logs_*/checkpoints, and anything else).
|
| 6 |
+
*.ckpt
|
| 7 |
+
|
| 8 |
+
# Orbis 2 models (L1/L2/tok — ~20 GB of weights plus their configs) live on the Hub
|
| 9 |
+
# at sud0301/orbis2_test; app2.py downloads them at startup. Leading slash anchors
|
| 10 |
+
# this to the repo root so it does NOT match orbis2/models/, which is source code.
|
| 11 |
+
/models/
|
|
@@ -0,0 +1,460 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Orbis 2 World Model — interactive rollout demo for Hugging Face Spaces.
|
| 3 |
+
|
| 4 |
+
This is the Orbis 2 counterpart to app.py (which drives the single-level Orbis 1
|
| 5 |
+
model). Orbis 2 is a hierarchical L1-L2 world model whose rollout script takes an
|
| 6 |
+
mp4 directly, so the flow differs from app.py:
|
| 7 |
+
|
| 8 |
+
1. User uploads a short driving clip.
|
| 9 |
+
2. We re-encode it to a constant CONTEXT_FPS (= the L1 frame rate) so the
|
| 10 |
+
rollout script's exact integer-multiple frame-rate check passes.
|
| 11 |
+
3. We call evaluate/rollout_demo_v2.py, which samples the L1 (high-rate) and L2
|
| 12 |
+
(low-rate, further back) context windows from the tail of that video.
|
| 13 |
+
4. The context is tiled NUM_VIDEOS times so one minibatch rolls out that many
|
| 14 |
+
independently sampled futures under a single seed.
|
| 15 |
+
5. Generated frames (fake_images/sequence_XXXX/*.jpg) are encoded back into mp4s.
|
| 16 |
+
|
| 17 |
+
Checkpoint / config resolution (see resolve_exp_dir / download_checkpoints):
|
| 18 |
+
* If ORBIS2_EXP_DIR is set (and contains the config + checkpoint), it is used
|
| 19 |
+
as-is — this is the path that works today against a local training run.
|
| 20 |
+
* Otherwise, if ORBIS2_HF_REPO is set, the config + checkpoint are pulled from
|
| 21 |
+
that Hub repo into a local exp dir. Orbis 2 is not on the Hub yet; set these
|
| 22 |
+
env vars once the checkpoint is uploaded.
|
| 23 |
+
"""
|
| 24 |
+
|
| 25 |
+
import os
|
| 26 |
+
import random
|
| 27 |
+
import re
|
| 28 |
+
import subprocess
|
| 29 |
+
import tempfile
|
| 30 |
+
import traceback
|
| 31 |
+
import uuid
|
| 32 |
+
from pathlib import Path
|
| 33 |
+
|
| 34 |
+
import cv2
|
| 35 |
+
import gradio as gr
|
| 36 |
+
import spaces
|
| 37 |
+
from huggingface_hub import snapshot_download
|
| 38 |
+
|
| 39 |
+
# ----------------------------------------------------------------------------
|
| 40 |
+
# Workaround for gradio 5.9.1 / gradio_client bug (gradio-app/gradio#11722):
|
| 41 |
+
# get_api_info() walks the app's JSON schema and crashes with
|
| 42 |
+
# "TypeError: argument of type 'bool' is not iterable"
|
| 43 |
+
# when a schema node is a boolean (e.g. "additionalProperties": true), because
|
| 44 |
+
# get_type()/_json_schema_to_python_type() assume every schema is a dict. The
|
| 45 |
+
# main page route calls api_info() on every load, so without this the endpoint
|
| 46 |
+
# 500s continuously. Make the walker tolerate boolean schemas.
|
| 47 |
+
# ----------------------------------------------------------------------------
|
| 48 |
+
import gradio_client.utils as _gc_utils
|
| 49 |
+
|
| 50 |
+
_orig_get_type = _gc_utils.get_type
|
| 51 |
+
_orig_json_to_py = _gc_utils._json_schema_to_python_type
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def _safe_get_type(schema):
|
| 55 |
+
if isinstance(schema, bool):
|
| 56 |
+
return "bool"
|
| 57 |
+
return _orig_get_type(schema)
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def _safe_json_to_py(schema, defs=None):
|
| 61 |
+
if isinstance(schema, bool):
|
| 62 |
+
return "Any"
|
| 63 |
+
return _orig_json_to_py(schema, defs)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
_gc_utils.get_type = _safe_get_type
|
| 67 |
+
_gc_utils._json_schema_to_python_type = _safe_json_to_py
|
| 68 |
+
|
| 69 |
+
# ----------------------------------------------------------------------------
|
| 70 |
+
# Configuration — adjust these to your setup
|
| 71 |
+
# ----------------------------------------------------------------------------
|
| 72 |
+
REPO_DIR = Path(__file__).parent / "orbis2" # copied silviogalesso/orbis2_release
|
| 73 |
+
|
| 74 |
+
# HF cache location. Prefer the Space's persistent /data volume so large
|
| 75 |
+
# checkpoints download once and survive restarts; fall back to the default
|
| 76 |
+
# ephemeral cache if /data isn't writable (e.g. persistent storage not enabled).
|
| 77 |
+
def _pick_cache_dir() -> str:
|
| 78 |
+
for candidate in ("/data/.cache/huggingface",
|
| 79 |
+
os.path.expanduser("~/.cache/huggingface")):
|
| 80 |
+
try:
|
| 81 |
+
probe = Path(candidate)
|
| 82 |
+
probe.mkdir(parents=True, exist_ok=True)
|
| 83 |
+
test = probe / ".write_probe"
|
| 84 |
+
test.touch()
|
| 85 |
+
test.unlink()
|
| 86 |
+
return candidate
|
| 87 |
+
except OSError:
|
| 88 |
+
continue
|
| 89 |
+
return os.path.expanduser("~/.cache/huggingface")
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
HF_CACHE_DIR = os.environ.get("HF_HOME") or _pick_cache_dir()
|
| 93 |
+
os.environ["HF_HOME"] = HF_CACHE_DIR
|
| 94 |
+
HF_HUB_CACHE_DIR = str(Path(HF_CACHE_DIR) / "hub")
|
| 95 |
+
print(f"[startup] HF cache dir: {HF_CACHE_DIR}"
|
| 96 |
+
f"{'' if HF_CACHE_DIR.startswith('/data') else ' (ephemeral — /data not writable)'}")
|
| 97 |
+
|
| 98 |
+
# --- Orbis 2 models (configs + checkpoints) ----------------------------------
|
| 99 |
+
# Three models are needed, laid out as <models>/{L1,L2,tok}/{config,checkpoints/last.ckpt}:
|
| 100 |
+
# L1 — detail predictor (the world model rollout_demo_v2.py loads directly)
|
| 101 |
+
# L2 — abstract predictor (frozen; referenced by L1's condition_preprocessor)
|
| 102 |
+
# tok — tokenizer (referenced by BOTH L1's and L2's tokenizer_config)
|
| 103 |
+
#
|
| 104 |
+
# L1's and L2's configs locate their dependencies via $ORBIS2_MODELS_DIR, which we
|
| 105 |
+
# export below so OmegaConf's expandvars resolves them wherever the tree lands.
|
| 106 |
+
CONFIG_NAME = "config_distill.yaml" # L1's config, relative to EXP_DIR
|
| 107 |
+
CKPT_NAME = "checkpoints/last.ckpt" # every model stores its weights here
|
| 108 |
+
|
| 109 |
+
#: (sub-dir, config filename) for each of the three required models.
|
| 110 |
+
MODEL_SPECS = [
|
| 111 |
+
("L1", CONFIG_NAME),
|
| 112 |
+
("L2", "config.yaml"),
|
| 113 |
+
("tok", "config.yaml"),
|
| 114 |
+
]
|
| 115 |
+
|
| 116 |
+
# Hub repo holding the three models (root contains L1/, L2/, tok/).
|
| 117 |
+
HF_CKPT_REPO = os.environ.get("ORBIS2_HF_REPO", "sud0301/orbis2_test")
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
def _missing_models(root: Path) -> list[str]:
|
| 121 |
+
"""Return the models whose config or checkpoint is absent under `root`."""
|
| 122 |
+
return [sub for sub, cfg in MODEL_SPECS
|
| 123 |
+
if not (root / sub / cfg).exists() or not (root / sub / CKPT_NAME).exists()]
|
| 124 |
+
|
| 125 |
+
|
| 126 |
+
def resolve_models_dir() -> Path:
|
| 127 |
+
"""Return a directory containing L1/, L2/ and tok/, downloading it if needed.
|
| 128 |
+
|
| 129 |
+
A local tree wins if it's complete (a dev checkout, or ORBIS2_MODELS_DIR pointing
|
| 130 |
+
at one). Otherwise we snapshot the Hub repo and use the *cache* path directly
|
| 131 |
+
rather than copying into the Space — `local_dir=` would duplicate ~20 GB on disk.
|
| 132 |
+
The cache lives on /data when persistent storage is enabled, so the download
|
| 133 |
+
survives restarts.
|
| 134 |
+
"""
|
| 135 |
+
local = Path(os.environ.get("ORBIS2_MODELS_DIR") or (Path(__file__).parent / "models"))
|
| 136 |
+
if not _missing_models(local):
|
| 137 |
+
print(f"[startup] Using local Orbis 2 models: {local}")
|
| 138 |
+
return local
|
| 139 |
+
|
| 140 |
+
print(f"[startup] Fetching Orbis 2 models from {HF_CKPT_REPO} (~20 GB, first run only)…")
|
| 141 |
+
snapshot = Path(snapshot_download(
|
| 142 |
+
repo_id=HF_CKPT_REPO,
|
| 143 |
+
allow_patterns=[f"{sub}/**" for sub, _ in MODEL_SPECS],
|
| 144 |
+
cache_dir=HF_HUB_CACHE_DIR,
|
| 145 |
+
))
|
| 146 |
+
|
| 147 |
+
still_missing = _missing_models(snapshot)
|
| 148 |
+
if still_missing:
|
| 149 |
+
raise RuntimeError(
|
| 150 |
+
f"Downloaded {HF_CKPT_REPO} but these models are incomplete: "
|
| 151 |
+
f"{', '.join(still_missing)}. Each of L1/, L2/, tok/ must contain its "
|
| 152 |
+
f"config and '{CKPT_NAME}'."
|
| 153 |
+
)
|
| 154 |
+
print(f"[startup] Orbis 2 models ready: {snapshot}")
|
| 155 |
+
return snapshot
|
| 156 |
+
|
| 157 |
+
|
| 158 |
+
MODELS_DIR = resolve_models_dir()
|
| 159 |
+
os.environ["ORBIS2_MODELS_DIR"] = str(MODELS_DIR) # L1/L2 configs expandvars this
|
| 160 |
+
EXP_DIR = MODELS_DIR / "L1" # rollout_demo_v2.py --exp_dir
|
| 161 |
+
|
| 162 |
+
FRAME_H, FRAME_W = 288, 512
|
| 163 |
+
L1_FRAME_RATE = 10 # Hz to sample L1 context/rollout frames (and output fps)
|
| 164 |
+
CONTEXT_FPS = L1_FRAME_RATE # we re-encode uploads to this exact fps for the rollout script
|
| 165 |
+
DEFAULT_GEN_STEPS = 10 # rollout steps; each yields model.num_pred_frames frames
|
| 166 |
+
DEFAULT_STEPS = 30 # flow-matching solver steps (matches the reference command)
|
| 167 |
+
OUTPUT_FPS = L1_FRAME_RATE
|
| 168 |
+
NUM_VIDEOS = 3 # futures rolled out per run, batched into one minibatch
|
| 169 |
+
DEFAULT_ETA = 0.0 # 0 = deterministic ODE; >0 injects noise each solver step
|
| 170 |
+
|
| 171 |
+
DESCRIPTION = f"""
|
| 172 |
+
# Orbis 2: A Hierarchical World Model for Driving
|
| 173 |
+
|
| 174 |
+
Sudhanshu Mittal\\*, Arian Mousakhan\\*, Silvio Galesso\\*, Karim Farid,
|
| 175 |
+
Johannes Dienert, Rajat Sahay, Thomas Brox
|
| 176 |
+
— University of Freiburg, Germany (\\*main contributors)
|
| 177 |
+
|
| 178 |
+
[Project page](https://lmb-freiburg.github.io/orbis2.github.io/) ·
|
| 179 |
+
[Code](https://github.com/lmb-freiburg/orbis) ·
|
| 180 |
+
[Orbis 1](https://lmb-freiburg.github.io/orbis.github.io/)
|
| 181 |
+
|
| 182 |
+
Upload a short driving clip (a few seconds is enough). The hierarchical model takes
|
| 183 |
+
the tail of the clip as L1 (high-rate) and L2 (low-rate, further back) context and
|
| 184 |
+
autoregressively predicts future frames.
|
| 185 |
+
|
| 186 |
+
Each run samples **{NUM_VIDEOS} rollouts** from the same context in a single
|
| 187 |
+
minibatch, so you see several plausible futures diverge from identical starting
|
| 188 |
+
conditions.
|
| 189 |
+
"""
|
| 190 |
+
|
| 191 |
+
PAPER_MD = """
|
| 192 |
+
### Abstract
|
| 193 |
+
|
| 194 |
+
> Current world models typically operate at a single abstraction level, favoring
|
| 195 |
+
> perceptual fidelity but lacking the spatial and semantic reasoning needed for
|
| 196 |
+
> downstream driving tasks. We propose a hierarchical driving world model that
|
| 197 |
+
> separates prediction into a high-level long-horizon scene forecaster and a
|
| 198 |
+
> low-level detail generator conditioned on it. This design improves both visual
|
| 199 |
+
> fidelity and spatial-semantic representation quality. We also introduce a
|
| 200 |
+
> two-stage training strategy: diffusion-forcing pretraining for richer
|
| 201 |
+
> representations, followed by teacher-forcing fine-tuning for stable
|
| 202 |
+
> autoregressive rollouts. Our method achieves state-of-the-art results on
|
| 203 |
+
> standard driving world model benchmarks, including long-horizon fidelity,
|
| 204 |
+
> counterfactual steering responsiveness, and internal representation quality.
|
| 205 |
+
|
| 206 |
+
### Method
|
| 207 |
+
|
| 208 |
+
Orbis 2 splits future prediction across two levels of abstraction:
|
| 209 |
+
|
| 210 |
+
- **Abstract predictor (L2)** — operates over a long temporal context to forecast a
|
| 211 |
+
future state in latent space, capturing abstract scene dynamics over long
|
| 212 |
+
horizons and enabling steering control.
|
| 213 |
+
- **Detail predictor (L1)** — conditioned on that abstract prediction, generates
|
| 214 |
+
fine-grained short-horizon frames, so high-fidelity local prediction stays
|
| 215 |
+
grounded in long-range temporal context.
|
| 216 |
+
|
| 217 |
+
Training runs in two stages: **diffusion-forcing pretraining** for richer
|
| 218 |
+
representations, followed by **teacher-forcing fine-tuning** for stable
|
| 219 |
+
autoregressive rollouts.
|
| 220 |
+
|
| 221 |
+
### Links & citation
|
| 222 |
+
|
| 223 |
+
```bibtex
|
| 224 |
+
@article{orbis2_2026,
|
| 225 |
+
author = {Mittal, Sudhanshu and Mousakhan, Arian and Galesso, Silvio and
|
| 226 |
+
Farid, Karim and Dienert, Johannes and Sahay, Rajat and Brox, Thomas},
|
| 227 |
+
title = {Orbis 2: A Hierarchical World Model for Driving},
|
| 228 |
+
journal = {arXiv preprint arXiv:XXXX.XXXXX},
|
| 229 |
+
year = {2026},
|
| 230 |
+
}
|
| 231 |
+
```
|
| 232 |
+
"""
|
| 233 |
+
|
| 234 |
+
DEMO_MD = f"""
|
| 235 |
+
**Pipeline.** Your clip is re-encoded to a constant {CONTEXT_FPS} fps. The rollout
|
| 236 |
+
script samples two context windows from the tail of that video: an **L1** window at
|
| 237 |
+
{L1_FRAME_RATE} Hz (fine, recent frames) and an **L2** window sampled further back at
|
| 238 |
+
the frozen L2 predictor's own trained rate (coarse, long-horizon context). Each frame
|
| 239 |
+
is resized to {FRAME_H}×{FRAME_W}. The **L2 abstract predictor** forecasts a latent
|
| 240 |
+
future, and the **L1 detail predictor**, conditioned on it, generates the next frames
|
| 241 |
+
autoregressively.
|
| 242 |
+
|
| 243 |
+
**Rollout length.** *Rollout steps* sets how many autoregressive steps to run; each
|
| 244 |
+
step emits several image frames, so the final clip is longer than the step count.
|
| 245 |
+
|
| 246 |
+
**Sampling.** Each step integrates the flow-matching ODE with *Diffusion steps*
|
| 247 |
+
solver steps. *Eta* (under Advanced) injects noise per step: 0 is the deterministic
|
| 248 |
+
ODE, higher values turn it into an SDE.
|
| 249 |
+
|
| 250 |
+
**Randomness.** The {NUM_VIDEOS} rollouts share one context and one seed, but the
|
| 251 |
+
solver draws independent initial noise per batch element, so the futures diverge.
|
| 252 |
+
Leave the seed at -1 under Advanced for a fresh draw each run, or set it explicitly
|
| 253 |
+
to reproduce a run exactly.
|
| 254 |
+
|
| 255 |
+
**Note.** The clip must be long enough for the L2 look-back window; very short clips
|
| 256 |
+
are rejected with an explicit error. Frame and step counts are kept modest to fit the
|
| 257 |
+
ZeroGPU time budget.
|
| 258 |
+
"""
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
# ----------------------------------------------------------------------------
|
| 262 |
+
# Video helpers (CPU)
|
| 263 |
+
# ----------------------------------------------------------------------------
|
| 264 |
+
def reencode_to_fps(src_path: str, out_path: Path, fps: int = CONTEXT_FPS):
|
| 265 |
+
"""Re-encode a clip to a constant `fps` H.264 mp4.
|
| 266 |
+
|
| 267 |
+
The rollout script requires the video's native frame rate to be an exact integer
|
| 268 |
+
multiple of the L1/L2 sampling rates. Forcing a constant fps here (via -vsync cfr)
|
| 269 |
+
makes an arbitrary upload satisfy that check.
|
| 270 |
+
"""
|
| 271 |
+
out_path.parent.mkdir(parents=True, exist_ok=True)
|
| 272 |
+
subprocess.run(
|
| 273 |
+
["ffmpeg", "-y", "-i", str(src_path),
|
| 274 |
+
"-vsync", "cfr", "-r", str(fps),
|
| 275 |
+
"-an", "-c:v", "libx264", "-pix_fmt", "yuv420p",
|
| 276 |
+
"-movflags", "+faststart", str(out_path)],
|
| 277 |
+
check=True, capture_output=True,
|
| 278 |
+
)
|
| 279 |
+
|
| 280 |
+
|
| 281 |
+
def frames_to_mp4(frame_paths: list[Path], out_path: Path, fps: int = OUTPUT_FPS):
|
| 282 |
+
first = cv2.imread(str(frame_paths[0]))
|
| 283 |
+
h, w = first.shape[:2]
|
| 284 |
+
# mp4v then re-encode with ffmpeg for browser-compatible H.264
|
| 285 |
+
tmp = out_path.with_suffix(".raw.mp4")
|
| 286 |
+
writer = cv2.VideoWriter(str(tmp), cv2.VideoWriter_fourcc(*"mp4v"), fps, (w, h))
|
| 287 |
+
for p in frame_paths:
|
| 288 |
+
writer.write(cv2.imread(str(p)))
|
| 289 |
+
writer.release()
|
| 290 |
+
subprocess.run(
|
| 291 |
+
["ffmpeg", "-y", "-i", str(tmp), "-c:v", "libx264",
|
| 292 |
+
"-pix_fmt", "yuv420p", "-movflags", "+faststart", str(out_path)],
|
| 293 |
+
check=True, capture_output=True,
|
| 294 |
+
)
|
| 295 |
+
tmp.unlink(missing_ok=True)
|
| 296 |
+
|
| 297 |
+
|
| 298 |
+
# ----------------------------------------------------------------------------
|
| 299 |
+
# GPU inference
|
| 300 |
+
# ----------------------------------------------------------------------------
|
| 301 |
+
#: tqdm renders the rollout bar as "Rolling out frames: 30%|### | 3/10 [00:05<...]"
|
| 302 |
+
_ROLLOUT_RE = re.compile(r"Rolling out frames.*?(\d+)/(\d+)")
|
| 303 |
+
|
| 304 |
+
|
| 305 |
+
def _stream_rollout(cmd: list[str], say) -> str:
|
| 306 |
+
"""Run rollout_demo_v2.py, forwarding its output to the log and driving progress.
|
| 307 |
+
|
| 308 |
+
The heavy lifting happens in a child process, so gr.Progress(track_tqdm=True)
|
| 309 |
+
cannot see its tqdm bars. We scrape the child's own bar instead. tqdm redraws in
|
| 310 |
+
place with a carriage return rather than a newline, so read whatever bytes are
|
| 311 |
+
available and split on both.
|
| 312 |
+
"""
|
| 313 |
+
proc = subprocess.Popen(
|
| 314 |
+
cmd, cwd=REPO_DIR, stdout=subprocess.PIPE, stderr=subprocess.STDOUT,
|
| 315 |
+
)
|
| 316 |
+
tail: list[str] = []
|
| 317 |
+
buf = ""
|
| 318 |
+
while True:
|
| 319 |
+
chunk = proc.stdout.read1(4096) # returns as soon as bytes are ready
|
| 320 |
+
if not chunk:
|
| 321 |
+
break
|
| 322 |
+
buf += chunk.decode("utf-8", errors="replace")
|
| 323 |
+
*lines, buf = re.split(r"[\r\n]", buf)
|
| 324 |
+
for line in lines:
|
| 325 |
+
line = line.rstrip()
|
| 326 |
+
if not line:
|
| 327 |
+
continue
|
| 328 |
+
print(line) # full trace stays in the Space logs
|
| 329 |
+
tail.append(line)
|
| 330 |
+
del tail[:-200]
|
| 331 |
+
m = _ROLLOUT_RE.search(line)
|
| 332 |
+
if m:
|
| 333 |
+
done, total = int(m.group(1)), max(int(m.group(2)), 1)
|
| 334 |
+
say(0.15 + 0.70 * min(done / total, 1.0),
|
| 335 |
+
f"Rolling out step {done}/{total}…")
|
| 336 |
+
|
| 337 |
+
proc.wait()
|
| 338 |
+
joined = "\n".join(tail)
|
| 339 |
+
if proc.returncode != 0:
|
| 340 |
+
raise gr.Error(f"Rollout failed:\n{joined[-800:]}")
|
| 341 |
+
return joined
|
| 342 |
+
|
| 343 |
+
|
| 344 |
+
@spaces.GPU(duration=300)
|
| 345 |
+
def run_rollout(video_path: str, num_gen_steps: int, num_steps: int,
|
| 346 |
+
seed: int, eta: float, progress=gr.Progress()):
|
| 347 |
+
if video_path is None:
|
| 348 |
+
raise gr.Error("Please upload a short mp4 clip first.")
|
| 349 |
+
|
| 350 |
+
def say(frac, msg):
|
| 351 |
+
progress(frac, desc=msg)
|
| 352 |
+
|
| 353 |
+
try:
|
| 354 |
+
# The rollout script only seeds when seed > 0; within the batch each sample
|
| 355 |
+
# still draws its own noise, so one seed yields NUM_VIDEOS distinct rollouts.
|
| 356 |
+
# seed < 0 means "surprise me" — draw a fresh positive seed each run.
|
| 357 |
+
seed = random.randrange(1, 2**31 - 1) if int(seed) < 0 else max(1, int(seed))
|
| 358 |
+
|
| 359 |
+
job = Path(tempfile.gettempdir()) / f"orbis2_{uuid.uuid4().hex[:8]}"
|
| 360 |
+
|
| 361 |
+
say(0.02, "Preparing context video…")
|
| 362 |
+
ctx_video = job / "context.mp4"
|
| 363 |
+
reencode_to_fps(video_path, ctx_video, CONTEXT_FPS)
|
| 364 |
+
print(f"context @ {CONTEXT_FPS} fps | seed {seed} | "
|
| 365 |
+
f"{int(num_gen_steps)} rollout steps | {int(num_steps)} solver steps | eta {eta}")
|
| 366 |
+
|
| 367 |
+
output_dir = job / "rollout"
|
| 368 |
+
|
| 369 |
+
say(0.10, "Loading model and rolling out…")
|
| 370 |
+
cmd = [
|
| 371 |
+
"python", "evaluate/rollout_demo_v2.py",
|
| 372 |
+
"--exp_dir", str(EXP_DIR),
|
| 373 |
+
"--config", CONFIG_NAME,
|
| 374 |
+
"--ckpt", CKPT_NAME,
|
| 375 |
+
"--video", str(ctx_video),
|
| 376 |
+
"--l1_frame_rate", str(L1_FRAME_RATE),
|
| 377 |
+
# Given explicitly so resolve_context_image_size never falls back to the
|
| 378 |
+
# config's `data:` block (which points at training-only dataset paths).
|
| 379 |
+
"--height", str(FRAME_H),
|
| 380 |
+
"--width", str(FRAME_W),
|
| 381 |
+
"--num_gen_frames", str(int(num_gen_steps)),
|
| 382 |
+
"--num_steps", str(int(num_steps)),
|
| 383 |
+
"--eta", str(float(eta)),
|
| 384 |
+
"--seed", str(seed),
|
| 385 |
+
"--num_videos", str(NUM_VIDEOS),
|
| 386 |
+
"--output_dir", str(output_dir),
|
| 387 |
+
"--device", "cuda",
|
| 388 |
+
]
|
| 389 |
+
_stream_rollout(cmd, say)
|
| 390 |
+
|
| 391 |
+
say(0.88, "Encoding videos…")
|
| 392 |
+
out_mp4s = []
|
| 393 |
+
for i in range(NUM_VIDEOS):
|
| 394 |
+
seq_dir = output_dir / "fake_images" / f"sequence_{i:04d}"
|
| 395 |
+
gen_frames = sorted(seq_dir.glob("*.jpg"))
|
| 396 |
+
if not gen_frames:
|
| 397 |
+
raise gr.Error(f"No generated frames found in {seq_dir} — "
|
| 398 |
+
"check rollout output path.")
|
| 399 |
+
out_mp4 = job / f"rollout_{i}.mp4"
|
| 400 |
+
frames_to_mp4(gen_frames, out_mp4)
|
| 401 |
+
out_mp4s.append(str(out_mp4))
|
| 402 |
+
|
| 403 |
+
say(1.0, f"Done — {NUM_VIDEOS} rollouts of {int(num_gen_steps)} steps.")
|
| 404 |
+
return tuple(out_mp4s)
|
| 405 |
+
|
| 406 |
+
except gr.Error:
|
| 407 |
+
raise
|
| 408 |
+
except Exception as e:
|
| 409 |
+
tb = traceback.format_exc()
|
| 410 |
+
print(tb)
|
| 411 |
+
raise gr.Error(f"Rollout failed: {e}\n\n{tb[-1500:]}")
|
| 412 |
+
|
| 413 |
+
|
| 414 |
+
# ----------------------------------------------------------------------------
|
| 415 |
+
# UI
|
| 416 |
+
# ----------------------------------------------------------------------------
|
| 417 |
+
def build_demo():
|
| 418 |
+
with gr.Blocks(title="Orbis 2: A Hierarchical World Model for Driving") as demo:
|
| 419 |
+
gr.Markdown(DESCRIPTION)
|
| 420 |
+
|
| 421 |
+
with gr.Accordion("About the paper", open=False):
|
| 422 |
+
gr.Markdown(PAPER_MD)
|
| 423 |
+
with gr.Accordion("About this demo", open=False):
|
| 424 |
+
gr.Markdown(DEMO_MD)
|
| 425 |
+
|
| 426 |
+
with gr.Row():
|
| 427 |
+
with gr.Column(scale=1):
|
| 428 |
+
inp = gr.Video(label="Upload a short mp4 clip", sources=["upload"])
|
| 429 |
+
n_steps_gen = gr.Slider(4, 30, value=DEFAULT_GEN_STEPS, step=2,
|
| 430 |
+
label="Rollout steps",
|
| 431 |
+
info="Each step emits several frames")
|
| 432 |
+
n_steps = gr.Slider(10, 40, value=DEFAULT_STEPS, step=5,
|
| 433 |
+
label="Diffusion steps",
|
| 434 |
+
info="Flow-matching solver steps per frame")
|
| 435 |
+
|
| 436 |
+
with gr.Accordion("Advanced", open=False):
|
| 437 |
+
seed_in = gr.Number(value=-1, precision=0, label="Seed",
|
| 438 |
+
info="-1 draws a fresh seed each run")
|
| 439 |
+
eta = gr.Slider(0.0, 1.0, value=DEFAULT_ETA, step=0.05,
|
| 440 |
+
label="Eta (stochasticity)",
|
| 441 |
+
info="0 = deterministic ODE; >0 adds noise per step")
|
| 442 |
+
|
| 443 |
+
btn = gr.Button("Generate rollouts", variant="primary")
|
| 444 |
+
|
| 445 |
+
with gr.Column(scale=1):
|
| 446 |
+
outs = [gr.Video(label=f"Rollout {i + 1}", autoplay=True, loop=True)
|
| 447 |
+
for i in range(NUM_VIDEOS)]
|
| 448 |
+
|
| 449 |
+
btn.click(
|
| 450 |
+
run_rollout,
|
| 451 |
+
inputs=[inp, n_steps_gen, n_steps, seed_in, eta],
|
| 452 |
+
outputs=outs,
|
| 453 |
+
concurrency_limit=1,
|
| 454 |
+
)
|
| 455 |
+
|
| 456 |
+
return demo
|
| 457 |
+
|
| 458 |
+
|
| 459 |
+
if __name__ == "__main__":
|
| 460 |
+
build_demo().queue(max_size=8).launch()
|
|
@@ -0,0 +1,306 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
import torch.distributed as dist
|
| 6 |
+
from torch.utils.data import DataLoader, ConcatDataset
|
| 7 |
+
from torch.utils.data.distributed import DistributedSampler
|
| 8 |
+
from torch.utils.data.dataloader import default_collate
|
| 9 |
+
|
| 10 |
+
import pytorch_lightning as pl
|
| 11 |
+
|
| 12 |
+
from omegaconf import ListConfig, DictConfig
|
| 13 |
+
|
| 14 |
+
import logging
|
| 15 |
+
|
| 16 |
+
from util import instantiate_from_config
|
| 17 |
+
|
| 18 |
+
logger = logging.getLogger(__name__)
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _collate_pad_missing(batch):
|
| 22 |
+
"""Collate dicts that may have different keys across datasets.
|
| 23 |
+
|
| 24 |
+
Missing keys are filled with zero tensors matching the shape of the first
|
| 25 |
+
sample in the batch that has that key. Non-tensor values are filled with
|
| 26 |
+
None. Allows heterogeneous datasets (e.g. with/without steering) to be
|
| 27 |
+
mixed in the same batch.
|
| 28 |
+
"""
|
| 29 |
+
if not isinstance(batch[0], dict):
|
| 30 |
+
return default_collate(batch)
|
| 31 |
+
|
| 32 |
+
all_keys = set().union(*[item.keys() for item in batch])
|
| 33 |
+
filled = []
|
| 34 |
+
for item in batch:
|
| 35 |
+
item = dict(item)
|
| 36 |
+
for key in all_keys:
|
| 37 |
+
if key not in item:
|
| 38 |
+
ref = next((b[key] for b in batch if key in b), None)
|
| 39 |
+
if isinstance(ref, torch.Tensor):
|
| 40 |
+
item[key] = torch.full_like(ref, float("nan"))
|
| 41 |
+
else:
|
| 42 |
+
item[key] = ref
|
| 43 |
+
filled.append(item)
|
| 44 |
+
return default_collate(filled)
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def _env_bool(name, default):
|
| 48 |
+
raw = os.environ.get(name)
|
| 49 |
+
if raw is None:
|
| 50 |
+
return default
|
| 51 |
+
value = raw.strip().lower()
|
| 52 |
+
if value in {"1", "true", "yes", "y", "on"}:
|
| 53 |
+
return True
|
| 54 |
+
if value in {"0", "false", "no", "n", "off"}:
|
| 55 |
+
return False
|
| 56 |
+
logger.warning("Invalid boolean %s=%r; using default %s", name, raw, default)
|
| 57 |
+
return default
|
| 58 |
+
|
| 59 |
+
|
| 60 |
+
def _env_int(name, default):
|
| 61 |
+
raw = os.environ.get(name)
|
| 62 |
+
if raw is None:
|
| 63 |
+
return default
|
| 64 |
+
try:
|
| 65 |
+
return int(raw)
|
| 66 |
+
except ValueError:
|
| 67 |
+
logger.warning("Invalid integer %s=%r; using default %s", name, raw, default)
|
| 68 |
+
return default
|
| 69 |
+
|
| 70 |
+
|
| 71 |
+
class DataModuleFromConfig(pl.LightningDataModule):
|
| 72 |
+
def __init__(self, batch_size, val_batch_size=None, train=None, validation=None, test=None,
|
| 73 |
+
wrap=False, num_workers=None, dbg=False, train_weights=None):
|
| 74 |
+
super().__init__()
|
| 75 |
+
self.batch_size = batch_size
|
| 76 |
+
self.val_batch_size = val_batch_size if val_batch_size is not None else batch_size
|
| 77 |
+
self.dataset_configs = dict()
|
| 78 |
+
self.num_workers = num_workers if num_workers is not None else batch_size*2
|
| 79 |
+
if train is not None:
|
| 80 |
+
self.dataset_configs["train"] = train
|
| 81 |
+
self.train_dataloader = self._train_dataloader
|
| 82 |
+
if validation is not None:
|
| 83 |
+
self.dataset_configs["validation"] = validation
|
| 84 |
+
self.val_dataloader = self._val_dataloader
|
| 85 |
+
if test is not None:
|
| 86 |
+
self.dataset_configs["test"] = test
|
| 87 |
+
self.test_dataloader = self._test_dataloader
|
| 88 |
+
self.wrap = wrap
|
| 89 |
+
self.dbg = dbg
|
| 90 |
+
|
| 91 |
+
if train_weights is not None:
|
| 92 |
+
if not isinstance(train, (list, ListConfig)):
|
| 93 |
+
raise ValueError("train_weights requires train to be a list of dataset configs")
|
| 94 |
+
if len(train_weights) != len(train):
|
| 95 |
+
raise ValueError(
|
| 96 |
+
f"train_weights has {len(train_weights)} entries but train has {len(train)} datasets"
|
| 97 |
+
)
|
| 98 |
+
self.train_weights = train_weights
|
| 99 |
+
|
| 100 |
+
if self.wrap:
|
| 101 |
+
raise NotImplementedError("Wrapped datasets not implemented")
|
| 102 |
+
|
| 103 |
+
self._resume_epoch = None
|
| 104 |
+
self._resume_batches_completed = 0
|
| 105 |
+
|
| 106 |
+
def state_dict(self):
|
| 107 |
+
trainer = getattr(self, "trainer", None)
|
| 108 |
+
if trainer is None:
|
| 109 |
+
return {}
|
| 110 |
+
|
| 111 |
+
epoch = int(getattr(trainer, "current_epoch", 0))
|
| 112 |
+
batches_completed = 0
|
| 113 |
+
try:
|
| 114 |
+
# Lightning tracks completed batches for the current epoch in this counter.
|
| 115 |
+
batches_completed = int(trainer.fit_loop.epoch_loop.batch_progress.current.completed)
|
| 116 |
+
except Exception:
|
| 117 |
+
batches_completed = 0
|
| 118 |
+
|
| 119 |
+
return {
|
| 120 |
+
"resume_epoch": epoch,
|
| 121 |
+
"resume_batches_completed": max(batches_completed, 0),
|
| 122 |
+
}
|
| 123 |
+
|
| 124 |
+
def load_state_dict(self, state_dict):
|
| 125 |
+
if not isinstance(state_dict, dict):
|
| 126 |
+
return
|
| 127 |
+
|
| 128 |
+
self._resume_epoch = state_dict.get("resume_epoch")
|
| 129 |
+
self._resume_batches_completed = int(state_dict.get("resume_batches_completed", 0) or 0)
|
| 130 |
+
if self._resume_batches_completed < 0:
|
| 131 |
+
self._resume_batches_completed = 0
|
| 132 |
+
|
| 133 |
+
def setup(self, stage=None):
|
| 134 |
+
self.datasets = dict()
|
| 135 |
+
for k, cfg in self.dataset_configs.items():
|
| 136 |
+
logger.info("Loading dataset: %s", k)
|
| 137 |
+
if isinstance(cfg, (list, ListConfig)):
|
| 138 |
+
datasets = [instantiate_from_config(c) for c in cfg]
|
| 139 |
+
self.datasets[k] = ConcatDataset(datasets)
|
| 140 |
+
[logger.info(d) for d in datasets]
|
| 141 |
+
elif isinstance(cfg, DictConfig):
|
| 142 |
+
ds = instantiate_from_config(cfg)
|
| 143 |
+
self.datasets[k] = ds
|
| 144 |
+
logger.info(ds)
|
| 145 |
+
else:
|
| 146 |
+
raise ValueError(f"Invalid dataset config: {cfg}")
|
| 147 |
+
|
| 148 |
+
def _train_dataloader(self):
|
| 149 |
+
is_distributed = dist.is_available() and dist.is_initialized()
|
| 150 |
+
use_distributed_sampler = _env_bool("ORBIS_USE_DISTRIBUTED_SAMPLER", True)
|
| 151 |
+
sampler = None
|
| 152 |
+
if self.train_weights is not None:
|
| 153 |
+
train_ds = self.datasets["train"]
|
| 154 |
+
sub_datasets = getattr(train_ds, "datasets", [train_ds])
|
| 155 |
+
dataset_sizes = [len(ds) for ds in sub_datasets]
|
| 156 |
+
# Anchor epoch length to min(size/weight) across datasets.
|
| 157 |
+
# This fully covers the most "weight-adjusted-constrained" dataset (typically
|
| 158 |
+
# the highest-weight one) with exactly one pass, while letting smaller/lower-weight
|
| 159 |
+
# datasets cycle. Avoids the 2x repetition that len(ConcatDataset) causes when
|
| 160 |
+
# one dataset is large and dominates with high weight.
|
| 161 |
+
num_samples = math.ceil(min(s / w for s, w in zip(dataset_sizes, self.train_weights)))
|
| 162 |
+
sampler = _WeightedDistributedSampler(
|
| 163 |
+
self.train_weights, dataset_sizes, num_samples,
|
| 164 |
+
num_replicas=dist.get_world_size() if (is_distributed and use_distributed_sampler) else 1,
|
| 165 |
+
rank=dist.get_rank() if (is_distributed and use_distributed_sampler) else 0,
|
| 166 |
+
)
|
| 167 |
+
logger.info(
|
| 168 |
+
"Train DataLoader: weighted sampling fractions=%s dataset sizes=%s num_samples/epoch=%s",
|
| 169 |
+
list(self.train_weights),
|
| 170 |
+
dataset_sizes,
|
| 171 |
+
num_samples,
|
| 172 |
+
)
|
| 173 |
+
elif is_distributed and use_distributed_sampler:
|
| 174 |
+
sampler = _ResumableDistributedSampler(
|
| 175 |
+
self.datasets["train"],
|
| 176 |
+
shuffle=True,
|
| 177 |
+
drop_last=True,
|
| 178 |
+
resume_epoch=self._resume_epoch,
|
| 179 |
+
resume_batches_completed=self._resume_batches_completed,
|
| 180 |
+
batch_size=self.batch_size,
|
| 181 |
+
)
|
| 182 |
+
|
| 183 |
+
timeout_s = _env_int("ORBIS_DATALOADER_TIMEOUT_S", 0)
|
| 184 |
+
prefetch_factor = _env_int("ORBIS_DATALOADER_PREFETCH_FACTOR", 1)
|
| 185 |
+
persistent_workers = _env_bool(
|
| 186 |
+
"ORBIS_DATALOADER_PERSISTENT_WORKERS",
|
| 187 |
+
self.num_workers > 0,
|
| 188 |
+
)
|
| 189 |
+
mp_context = os.environ.get("ORBIS_DATALOADER_MP_CONTEXT", "").strip()
|
| 190 |
+
|
| 191 |
+
loader_kwargs = dict(
|
| 192 |
+
batch_size=self.batch_size,
|
| 193 |
+
num_workers=self.num_workers,
|
| 194 |
+
shuffle=sampler is None,
|
| 195 |
+
pin_memory=True,
|
| 196 |
+
drop_last=True,
|
| 197 |
+
sampler=sampler,
|
| 198 |
+
timeout=max(timeout_s, 0),
|
| 199 |
+
collate_fn=_collate_pad_missing if self.train_weights is not None else None,
|
| 200 |
+
)
|
| 201 |
+
if self.num_workers > 0:
|
| 202 |
+
loader_kwargs["persistent_workers"] = persistent_workers
|
| 203 |
+
if prefetch_factor > 0:
|
| 204 |
+
loader_kwargs["prefetch_factor"] = prefetch_factor
|
| 205 |
+
if mp_context:
|
| 206 |
+
loader_kwargs["multiprocessing_context"] = mp_context
|
| 207 |
+
|
| 208 |
+
logger.info(
|
| 209 |
+
"Train DataLoader: distributed=%s sampler=%s workers=%s timeout_s=%s mp_context=%s",
|
| 210 |
+
is_distributed,
|
| 211 |
+
sampler.__class__.__name__ if sampler is not None else "None",
|
| 212 |
+
self.num_workers,
|
| 213 |
+
loader_kwargs["timeout"],
|
| 214 |
+
mp_context or "<default>",
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
if self.dbg:
|
| 218 |
+
dbg_sampler = DistributedSampler(self.datasets["train"], shuffle=True)
|
| 219 |
+
loader_kwargs["sampler"] = dbg_sampler
|
| 220 |
+
loader_kwargs["shuffle"] = False
|
| 221 |
+
|
| 222 |
+
return DataLoader(self.datasets["train"], **loader_kwargs)
|
| 223 |
+
|
| 224 |
+
def _val_dataloader(self):
|
| 225 |
+
return DataLoader(self.datasets["validation"],
|
| 226 |
+
batch_size=self.val_batch_size,
|
| 227 |
+
num_workers=self.num_workers, pin_memory=True)
|
| 228 |
+
|
| 229 |
+
def _test_dataloader(self):
|
| 230 |
+
return DataLoader(self.datasets["test"], batch_size=self.val_batch_size,
|
| 231 |
+
num_workers=self.num_workers)
|
| 232 |
+
|
| 233 |
+
|
| 234 |
+
class _ResumableDistributedSampler(DistributedSampler):
|
| 235 |
+
def __init__(
|
| 236 |
+
self,
|
| 237 |
+
dataset,
|
| 238 |
+
*,
|
| 239 |
+
resume_epoch=None,
|
| 240 |
+
resume_batches_completed=0,
|
| 241 |
+
batch_size=1,
|
| 242 |
+
**kwargs,
|
| 243 |
+
):
|
| 244 |
+
super().__init__(dataset, **kwargs)
|
| 245 |
+
self.resume_epoch = None if resume_epoch is None else int(resume_epoch)
|
| 246 |
+
self.resume_batches_completed = int(resume_batches_completed or 0)
|
| 247 |
+
self.batch_size = max(int(batch_size), 1)
|
| 248 |
+
|
| 249 |
+
def __iter__(self):
|
| 250 |
+
indices = list(super().__iter__())
|
| 251 |
+
if (
|
| 252 |
+
self.resume_epoch is not None
|
| 253 |
+
and int(self.epoch) == self.resume_epoch
|
| 254 |
+
and self.resume_batches_completed > 0
|
| 255 |
+
):
|
| 256 |
+
skip = self.resume_batches_completed * self.batch_size
|
| 257 |
+
if skip > 0:
|
| 258 |
+
logger.info(
|
| 259 |
+
"Resuming dataloader at epoch=%s after %s batches (%s samples/rank).",
|
| 260 |
+
self.resume_epoch,
|
| 261 |
+
self.resume_batches_completed,
|
| 262 |
+
skip,
|
| 263 |
+
)
|
| 264 |
+
indices = indices[skip:]
|
| 265 |
+
return iter(indices)
|
| 266 |
+
|
| 267 |
+
|
| 268 |
+
class _WeightedDistributedSampler(torch.utils.data.Sampler):
|
| 269 |
+
"""Weighted sampler for dataset balancing, with optional distributed sharding.
|
| 270 |
+
|
| 271 |
+
Two-stage sampling: (1) pick a dataset by weight, (2) pick a uniform index
|
| 272 |
+
within that dataset. Avoids torch.multinomial's 2^24 category limit since
|
| 273 |
+
only num_datasets categories are ever sampled, not num_total_samples.
|
| 274 |
+
"""
|
| 275 |
+
|
| 276 |
+
def __init__(self, dataset_weights, dataset_sizes, num_samples, num_replicas=1, rank=0):
|
| 277 |
+
self.dataset_weights = torch.as_tensor(dataset_weights, dtype=torch.float64)
|
| 278 |
+
self.dataset_sizes = torch.tensor(dataset_sizes, dtype=torch.long)
|
| 279 |
+
offsets = [0]
|
| 280 |
+
for s in dataset_sizes[:-1]:
|
| 281 |
+
offsets.append(offsets[-1] + s)
|
| 282 |
+
self.dataset_offsets = torch.tensor(offsets, dtype=torch.long)
|
| 283 |
+
self.num_replicas = num_replicas
|
| 284 |
+
self.rank = rank
|
| 285 |
+
self.num_samples_per_replica = math.ceil(num_samples / num_replicas)
|
| 286 |
+
self.epoch = 0
|
| 287 |
+
|
| 288 |
+
def set_epoch(self, epoch):
|
| 289 |
+
self.epoch = epoch
|
| 290 |
+
|
| 291 |
+
def __iter__(self):
|
| 292 |
+
g = torch.Generator()
|
| 293 |
+
g.manual_seed(self.epoch)
|
| 294 |
+
total = self.num_samples_per_replica * self.num_replicas
|
| 295 |
+
|
| 296 |
+
# Stage 1: pick dataset for each draw (num_datasets categories, well within 2^24)
|
| 297 |
+
ds_idx = torch.multinomial(self.dataset_weights, total, replacement=True, generator=g)
|
| 298 |
+
|
| 299 |
+
# Stage 2: pick a uniform index within each chosen dataset
|
| 300 |
+
local = (torch.rand(total, generator=g) * self.dataset_sizes[ds_idx].float()).long()
|
| 301 |
+
indices = (self.dataset_offsets[ds_idx] + local).tolist()
|
| 302 |
+
|
| 303 |
+
return iter(indices[self.rank::self.num_replicas])
|
| 304 |
+
|
| 305 |
+
def __len__(self):
|
| 306 |
+
return self.num_samples_per_replica
|
|
@@ -0,0 +1,49 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from typing import Dict, Tuple, Optional, NamedTuple, Union
|
| 2 |
+
from PIL.Image import Image as pil_image
|
| 3 |
+
from torch import Tensor
|
| 4 |
+
|
| 5 |
+
try:
|
| 6 |
+
from typing import Literal
|
| 7 |
+
except ImportError:
|
| 8 |
+
from typing_extensions import Literal
|
| 9 |
+
|
| 10 |
+
Image = Union[Tensor, pil_image]
|
| 11 |
+
BoundingBox = Tuple[float, float, float, float] # x0, y0, w, h
|
| 12 |
+
CropMethodType = Literal['none', 'random', 'center', 'random-2d']
|
| 13 |
+
SplitType = Literal['train', 'validation', 'test']
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class ImageDescription(NamedTuple):
|
| 17 |
+
id: int
|
| 18 |
+
file_name: str
|
| 19 |
+
original_size: Tuple[int, int] # w, h
|
| 20 |
+
url: Optional[str] = None
|
| 21 |
+
license: Optional[int] = None
|
| 22 |
+
coco_url: Optional[str] = None
|
| 23 |
+
date_captured: Optional[str] = None
|
| 24 |
+
flickr_url: Optional[str] = None
|
| 25 |
+
flickr_id: Optional[str] = None
|
| 26 |
+
coco_id: Optional[str] = None
|
| 27 |
+
|
| 28 |
+
|
| 29 |
+
class Category(NamedTuple):
|
| 30 |
+
id: str
|
| 31 |
+
super_category: Optional[str]
|
| 32 |
+
name: str
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
class Annotation(NamedTuple):
|
| 36 |
+
area: float
|
| 37 |
+
image_id: str
|
| 38 |
+
bbox: BoundingBox
|
| 39 |
+
category_no: int
|
| 40 |
+
category_id: str
|
| 41 |
+
id: Optional[int] = None
|
| 42 |
+
source: Optional[str] = None
|
| 43 |
+
confidence: Optional[float] = None
|
| 44 |
+
is_group_of: Optional[bool] = None
|
| 45 |
+
is_truncated: Optional[bool] = None
|
| 46 |
+
is_occluded: Optional[bool] = None
|
| 47 |
+
is_depiction: Optional[bool] = None
|
| 48 |
+
is_inside: Optional[bool] = None
|
| 49 |
+
segmentation: Optional[Dict] = None
|
|
@@ -0,0 +1,83 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
class L2ContextMixin:
|
| 2 |
+
"""Reusable helper for hierarchical L1/L2 frame alignment."""
|
| 3 |
+
|
| 4 |
+
def _init_l2_context(
|
| 5 |
+
self,
|
| 6 |
+
*,
|
| 7 |
+
num_l2_context=0,
|
| 8 |
+
l2_frame_rate=1.0,
|
| 9 |
+
l1_context_frames=1,
|
| 10 |
+
require_l2_context=False,
|
| 11 |
+
):
|
| 12 |
+
self.num_l2_context = int(num_l2_context)
|
| 13 |
+
self.l1_context_frames = int(l1_context_frames)
|
| 14 |
+
|
| 15 |
+
if self.num_l2_context < 0:
|
| 16 |
+
raise ValueError(f"num_l2_context must be non-negative, got {self.num_l2_context}.")
|
| 17 |
+
if self.l1_context_frames <= 0:
|
| 18 |
+
raise ValueError(f"l1_context_frames must be positive, got {self.l1_context_frames}.")
|
| 19 |
+
if require_l2_context and self.num_l2_context <= 0:
|
| 20 |
+
raise ValueError("num_l2_context must be positive for hierarchical L1/L2 loaders.")
|
| 21 |
+
|
| 22 |
+
self.l2_frame_interval = None
|
| 23 |
+
self._l2_anchor_offset = (self.l1_context_frames - 1) * self.frame_interval
|
| 24 |
+
if self.num_l2_context == 0:
|
| 25 |
+
return
|
| 26 |
+
|
| 27 |
+
ratio = float(self.stored_data_frame_rate) / float(l2_frame_rate)
|
| 28 |
+
rounded_ratio = round(ratio)
|
| 29 |
+
if abs(ratio - rounded_ratio) > 1e-8:
|
| 30 |
+
raise ValueError(
|
| 31 |
+
"stored_data_frame_rate must be an integer multiple of l2_frame_rate, "
|
| 32 |
+
f"got stored_data_frame_rate={self.stored_data_frame_rate}, "
|
| 33 |
+
f"l2_frame_rate={l2_frame_rate}."
|
| 34 |
+
)
|
| 35 |
+
self.l2_frame_interval = int(rounded_ratio)
|
| 36 |
+
|
| 37 |
+
@property
|
| 38 |
+
def l2_context_enabled(self):
|
| 39 |
+
return self.num_l2_context > 0
|
| 40 |
+
|
| 41 |
+
def get_required_l1_start_offset(self):
|
| 42 |
+
if not self.l2_context_enabled:
|
| 43 |
+
return 0
|
| 44 |
+
return max(
|
| 45 |
+
0,
|
| 46 |
+
(self.num_l2_context - 1) * self.l2_frame_interval - self._l2_anchor_offset,
|
| 47 |
+
)
|
| 48 |
+
|
| 49 |
+
def has_l2_context_for_start(self, start_frame):
|
| 50 |
+
if not self.l2_context_enabled:
|
| 51 |
+
return True
|
| 52 |
+
l2_anchor = start_frame + self._l2_anchor_offset
|
| 53 |
+
oldest_l2 = l2_anchor - self.l2_frame_interval * (self.num_l2_context - 1)
|
| 54 |
+
return oldest_l2 >= 0
|
| 55 |
+
|
| 56 |
+
def filter_index_map_with_l2_headroom(self, index_map, start_frame_idx=-1):
|
| 57 |
+
if not self.l2_context_enabled:
|
| 58 |
+
return list(index_map)
|
| 59 |
+
|
| 60 |
+
filtered = []
|
| 61 |
+
for item in index_map:
|
| 62 |
+
start_frame = item[start_frame_idx]
|
| 63 |
+
if self.has_l2_context_for_start(start_frame):
|
| 64 |
+
filtered.append(item)
|
| 65 |
+
return filtered
|
| 66 |
+
|
| 67 |
+
def get_l1_indices(self, start_frame, num_frames):
|
| 68 |
+
return list(range(
|
| 69 |
+
start_frame,
|
| 70 |
+
start_frame + num_frames * self.frame_interval,
|
| 71 |
+
self.frame_interval,
|
| 72 |
+
))
|
| 73 |
+
|
| 74 |
+
def get_l2_indices(self, start_frame):
|
| 75 |
+
if not self.l2_context_enabled:
|
| 76 |
+
return []
|
| 77 |
+
|
| 78 |
+
l2_end = start_frame + self._l2_anchor_offset
|
| 79 |
+
l2_start = l2_end - (self.num_l2_context - 1) * self.l2_frame_interval
|
| 80 |
+
return list(range(l2_start, l2_end + 1, self.l2_frame_interval))
|
| 81 |
+
|
| 82 |
+
def get_l1_and_l2_indices(self, start_frame, num_frames):
|
| 83 |
+
return self.get_l1_indices(start_frame, num_frames), self.get_l2_indices(start_frame)
|
|
@@ -0,0 +1,948 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
|
| 3 |
+
import h5py
|
| 4 |
+
import numpy as np
|
| 5 |
+
import torch
|
| 6 |
+
|
| 7 |
+
from data.utils import get_trajectory_from_speeds_and_yaw_rates
|
| 8 |
+
from data.l2_context import L2ContextMixin
|
| 9 |
+
from data.video_loaders import (
|
| 10 |
+
MultiHDF5DatasetMultiFrameIdxMapping,
|
| 11 |
+
MultiMP4DatasetMultiFrameIdxMapping,
|
| 12 |
+
MultiMP4DatasetMultiFrameIdxMappingWithL2Context,
|
| 13 |
+
)
|
| 14 |
+
from data.vista_style import (
|
| 15 |
+
VistaStyleNuScenesLoader,
|
| 16 |
+
VistaStyleNuScenesLoaderWithL2Context,
|
| 17 |
+
extract_pose_table,
|
| 18 |
+
get_pose,
|
| 19 |
+
)
|
| 20 |
+
from util import instantiate_from_config
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
class OdometryHorizonMixin:
|
| 24 |
+
@staticmethod
|
| 25 |
+
def _validate_frame_rate_ratio(stored_rate, sampled_rate, rate_name):
|
| 26 |
+
ratio = stored_rate / sampled_rate
|
| 27 |
+
if not np.isclose(ratio, round(ratio), atol=1e-8):
|
| 28 |
+
raise ValueError(
|
| 29 |
+
f"stored_data_frame_rate={stored_rate} must be an integer multiple of "
|
| 30 |
+
f"{rate_name}={sampled_rate}"
|
| 31 |
+
)
|
| 32 |
+
|
| 33 |
+
@staticmethod
|
| 34 |
+
def _infer_num_frames_odo_for_matched_horizon(num_frames, frame_rate, odometry_frame_rate):
|
| 35 |
+
inferred = (num_frames - 1) * odometry_frame_rate / frame_rate + 1
|
| 36 |
+
if not np.isclose(inferred, round(inferred), atol=1e-8):
|
| 37 |
+
raise ValueError(
|
| 38 |
+
"Matched video/odometry horizons require an integer odometry length, got "
|
| 39 |
+
f"num_frames={num_frames}, frame_rate={frame_rate}, "
|
| 40 |
+
f"odometry_frame_rate={odometry_frame_rate}"
|
| 41 |
+
)
|
| 42 |
+
return int(round(inferred))
|
| 43 |
+
|
| 44 |
+
@classmethod
|
| 45 |
+
def _resolve_odometry_horizon(
|
| 46 |
+
cls,
|
| 47 |
+
num_frames,
|
| 48 |
+
frame_rate,
|
| 49 |
+
num_frames_odo,
|
| 50 |
+
odometry_frame_rate,
|
| 51 |
+
odometry_horizon,
|
| 52 |
+
inferred_num_frames_odo,
|
| 53 |
+
):
|
| 54 |
+
valid_horizons = {"auto", "match_video", "explicit"}
|
| 55 |
+
if odometry_horizon not in valid_horizons:
|
| 56 |
+
raise ValueError(
|
| 57 |
+
f"Unknown odometry_horizon={odometry_horizon!r}. Expected one of {sorted(valid_horizons)}"
|
| 58 |
+
)
|
| 59 |
+
|
| 60 |
+
if odometry_horizon == "auto":
|
| 61 |
+
if num_frames_odo is None or num_frames_odo == inferred_num_frames_odo:
|
| 62 |
+
odometry_horizon = "match_video"
|
| 63 |
+
else:
|
| 64 |
+
odometry_horizon = "explicit"
|
| 65 |
+
|
| 66 |
+
if odometry_horizon == "match_video":
|
| 67 |
+
resolved_num_frames_odo = inferred_num_frames_odo
|
| 68 |
+
if num_frames_odo is not None and num_frames_odo != resolved_num_frames_odo:
|
| 69 |
+
raise ValueError(
|
| 70 |
+
f"num_frames_odo={num_frames_odo} does not match the video horizon; "
|
| 71 |
+
f"expected {resolved_num_frames_odo} for num_frames={num_frames}, "
|
| 72 |
+
f"frame_rate={frame_rate}, odometry_frame_rate={odometry_frame_rate}"
|
| 73 |
+
)
|
| 74 |
+
return resolved_num_frames_odo, odometry_horizon
|
| 75 |
+
|
| 76 |
+
if num_frames_odo is None:
|
| 77 |
+
raise ValueError("odometry_horizon='explicit' requires num_frames_odo to be provided")
|
| 78 |
+
return int(num_frames_odo), odometry_horizon
|
| 79 |
+
|
| 80 |
+
@classmethod
|
| 81 |
+
def reconfigure_params_for_required_odometry_horizon(cls, params, required_odo_steps):
|
| 82 |
+
if required_odo_steps is None:
|
| 83 |
+
raise ValueError("`required_odo_steps` must be provided")
|
| 84 |
+
required_odo_steps = int(required_odo_steps)
|
| 85 |
+
if required_odo_steps <= 0:
|
| 86 |
+
raise ValueError(f"`required_odo_steps` must be positive, got {required_odo_steps}.")
|
| 87 |
+
|
| 88 |
+
if "odometry_frame_rate" not in params or params.odometry_frame_rate is None:
|
| 89 |
+
params.odometry_frame_rate = params.frame_rate
|
| 90 |
+
|
| 91 |
+
frame_rate = float(params.frame_rate)
|
| 92 |
+
odometry_frame_rate = float(params.odometry_frame_rate)
|
| 93 |
+
|
| 94 |
+
if odometry_frame_rate != frame_rate:
|
| 95 |
+
required_num_frames = int(np.ceil((required_odo_steps - 1) * frame_rate / odometry_frame_rate)) + 1
|
| 96 |
+
params.num_frames = max(int(params.num_frames), required_num_frames)
|
| 97 |
+
params.odometry_horizon = "match_video"
|
| 98 |
+
if "num_frames_odo" in params:
|
| 99 |
+
del params["num_frames_odo"]
|
| 100 |
+
return params
|
| 101 |
+
|
| 102 |
+
if "num_frames_odo" in params and params.num_frames_odo is not None:
|
| 103 |
+
params.num_frames_odo = max(int(params.num_frames_odo), required_odo_steps)
|
| 104 |
+
else:
|
| 105 |
+
params.num_frames_odo = required_odo_steps
|
| 106 |
+
params.odometry_horizon = "explicit"
|
| 107 |
+
return params
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
class PlaceholderSteeringMixin:
|
| 111 |
+
"""Mixin for datasets that have no real steering data.
|
| 112 |
+
|
| 113 |
+
Injects a NaN steering tensor of the correct shape so that rollout_steering_v2
|
| 114 |
+
can work with --steering_file or --no_steering without any changes to downstream
|
| 115 |
+
code. Using such a dataset without either of those flags raises an explicit error
|
| 116 |
+
at rollout validation time (see validate_steering_source in rollout_steering_v2.py).
|
| 117 |
+
"""
|
| 118 |
+
|
| 119 |
+
def _init_placeholder_steering(self, num_frames_odo, steering_dim=2, steering_format="speed_yawrate"):
|
| 120 |
+
self._placeholder_num_frames_odo = int(num_frames_odo)
|
| 121 |
+
self._placeholder_steering_dim = int(steering_dim)
|
| 122 |
+
self._placeholder_steering_format = steering_format
|
| 123 |
+
|
| 124 |
+
def _add_placeholder_steering(self, item: dict) -> dict:
|
| 125 |
+
item["steering"] = torch.full(
|
| 126 |
+
(self._placeholder_num_frames_odo, self._placeholder_steering_dim), float("nan")
|
| 127 |
+
)
|
| 128 |
+
item["steering_format"] = self._placeholder_steering_format
|
| 129 |
+
item["_steering_placeholder"] = True
|
| 130 |
+
return item
|
| 131 |
+
|
| 132 |
+
@classmethod
|
| 133 |
+
def reconfigure_params_for_required_odometry_horizon(cls, params, required_odo_steps):
|
| 134 |
+
if required_odo_steps is None:
|
| 135 |
+
raise ValueError("`required_odo_steps` must be provided")
|
| 136 |
+
required_odo_steps = int(required_odo_steps)
|
| 137 |
+
if required_odo_steps <= 0:
|
| 138 |
+
raise ValueError(f"`required_odo_steps` must be positive, got {required_odo_steps}.")
|
| 139 |
+
current = int(getattr(params, "num_frames_odo", 0) or 0)
|
| 140 |
+
params.num_frames_odo = max(current, required_odo_steps)
|
| 141 |
+
return params
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
class OdometryLoaderConti:
|
| 145 |
+
steering_format = "speed_yawrate"
|
| 146 |
+
|
| 147 |
+
def __call__(self, odo_data):
|
| 148 |
+
speeds = np.array([odo_frame[0] for odo_frame in odo_data])
|
| 149 |
+
yaw_rates = np.array([odo_frame[6] for odo_frame in odo_data])
|
| 150 |
+
ret_odo = np.stack([speeds, yaw_rates], axis=1)
|
| 151 |
+
assert ret_odo.shape[1] == 2 and ret_odo.shape[0] == len(odo_data), f"Unexpected odometry shape {ret_odo.shape}, expected ({len(odo_data)}, 2)"
|
| 152 |
+
return ret_odo
|
| 153 |
+
|
| 154 |
+
|
| 155 |
+
class OdometryLoaderNuPlan:
|
| 156 |
+
steering_format = "speed_yawrate"
|
| 157 |
+
|
| 158 |
+
def __call__(self, odo_data):
|
| 159 |
+
"""
|
| 160 |
+
odo_data: list of dicts, each dict contains IMU data for a frame
|
| 161 |
+
"""
|
| 162 |
+
ret = np.stack([np.array([odo_frame["vx"], odo_frame["angular_rate_z"]]) for odo_frame in odo_data], axis=0)
|
| 163 |
+
assert ret.shape[1] == 2 and ret.shape[0] == len(odo_data), f"Unexpected odometry shape {ret.shape}, expected ({len(odo_data)}, 2)"
|
| 164 |
+
return ret
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
class OdometryLoaderNVIDIAPhysAI:
|
| 168 |
+
steering_format = "speed_yawrate"
|
| 169 |
+
|
| 170 |
+
def __init__(self, speed_key="vx", curvature_key="curvature"):
|
| 171 |
+
self.speed_key = speed_key
|
| 172 |
+
self.curvature_key = curvature_key
|
| 173 |
+
|
| 174 |
+
def __call__(self, odo_data):
|
| 175 |
+
"""
|
| 176 |
+
odo_data: list of dicts, each dict contains vx (speed), and curvature (which can be used to compute yaw rate as curvature * speed)
|
| 177 |
+
|
| 178 |
+
We return the speed and yaw rate as the odometry.
|
| 179 |
+
|
| 180 |
+
"""
|
| 181 |
+
speeds = np.array([odo_frame[self.speed_key] for odo_frame in odo_data])
|
| 182 |
+
yaw_rates = np.array([odo_frame[self.curvature_key] * odo_frame[self.speed_key] for odo_frame in odo_data])
|
| 183 |
+
ret_odo = np.stack([speeds, yaw_rates], axis=1)
|
| 184 |
+
assert ret_odo.shape[1] == 2 and ret_odo.shape[0] == len(odo_data), f"Unexpected odometry shape {ret_odo.shape}, expected ({len(odo_data)}, 2)"
|
| 185 |
+
return ret_odo
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
class TrajectoryLoaderNuPlanFromSpeedYawRate:
|
| 189 |
+
steering_format = "trajectory_with_heading"
|
| 190 |
+
|
| 191 |
+
def __init__(self, frame_rate, speed_key="vx", yaw_rate_key="angular_rate_z"):
|
| 192 |
+
self.frame_rate = frame_rate
|
| 193 |
+
self.speed_key = speed_key
|
| 194 |
+
self.yaw_rate_key = yaw_rate_key
|
| 195 |
+
|
| 196 |
+
def __call__(self, odo_data):
|
| 197 |
+
"""
|
| 198 |
+
odo_data: list of dicts, each dict contains IMU data for a frame
|
| 199 |
+
"""
|
| 200 |
+
traj, headings = get_trajectory_from_speeds_and_yaw_rates(
|
| 201 |
+
speeds=np.array([odo_frame[self.speed_key] for odo_frame in odo_data]),
|
| 202 |
+
yaw_rates=np.array([odo_frame[self.yaw_rate_key] for odo_frame in odo_data]),
|
| 203 |
+
dt=1.0 / self.frame_rate,
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
assert traj.shape[1] == 2 and traj.shape[0] == len(odo_data), f"Unexpected odometry shape {traj.shape}, expected ({len(odo_data)}, 2)"
|
| 207 |
+
steering = np.concatenate([traj, headings[:, None]], axis=-1) # (num_frames, 3)
|
| 208 |
+
return steering
|
| 209 |
+
|
| 210 |
+
|
| 211 |
+
class TrajectoryLoaderNVIDIAPhysAIFromSpeedCurvature:
|
| 212 |
+
def __init__(self, frame_rate, speed_key="speed", curvature_key="curvature", return_headings=False):
|
| 213 |
+
self.frame_rate = frame_rate
|
| 214 |
+
self.speed_key = speed_key
|
| 215 |
+
self.curvature_key = curvature_key
|
| 216 |
+
self.return_headings = return_headings
|
| 217 |
+
self.steering_format = "trajectory_with_heading" if return_headings else "trajectory"
|
| 218 |
+
|
| 219 |
+
def __call__(self, odo_data):
|
| 220 |
+
"""
|
| 221 |
+
odo_data: list of dicts, each dict contains vx (speed), and curvature (which can be used to compute yaw rate as curvature * speed)
|
| 222 |
+
|
| 223 |
+
We then compute the trajectory by integrating the speeds and yaw rates over time.
|
| 224 |
+
|
| 225 |
+
"""
|
| 226 |
+
speeds = np.array([odo_frame[self.speed_key] for odo_frame in odo_data])
|
| 227 |
+
yaw_rates = np.array([odo_frame[self.curvature_key] * odo_frame[self.speed_key] for odo_frame in odo_data])
|
| 228 |
+
traj, headings = get_trajectory_from_speeds_and_yaw_rates(
|
| 229 |
+
speeds=speeds,
|
| 230 |
+
yaw_rates=yaw_rates,
|
| 231 |
+
dt=1.0 / self.frame_rate,
|
| 232 |
+
)
|
| 233 |
+
assert traj.shape[1] == 2 and traj.shape[0] == len(odo_data), f"Unexpected odometry shape {traj.shape}, expected ({len(odo_data)}, 2)"
|
| 234 |
+
if self.return_headings:
|
| 235 |
+
steering = np.concatenate([traj, headings[:, None]], axis=-1) # (num_frames, 3)
|
| 236 |
+
else:
|
| 237 |
+
steering = traj # (num_frames, 2)
|
| 238 |
+
return steering
|
| 239 |
+
|
| 240 |
+
|
| 241 |
+
|
| 242 |
+
class MultiMP4DatasetMultiFrameIdxMappingNVIDIAPhysAI(OdometryHorizonMixin, MultiMP4DatasetMultiFrameIdxMapping):
|
| 243 |
+
"""
|
| 244 |
+
Loads the odometry from the NVIDIAPhysAI dataset. The odometry is stored in a single HDF5 file. The HDF5 file contains a dataset for each video (named after the video id).
|
| 245 |
+
|
| 246 |
+
"""
|
| 247 |
+
def __init__(
|
| 248 |
+
self,
|
| 249 |
+
size,
|
| 250 |
+
mp4_paths_file,
|
| 251 |
+
odometry_h5_path,
|
| 252 |
+
num_frames,
|
| 253 |
+
num_frames_odo=None,
|
| 254 |
+
stored_data_frame_rate=5,
|
| 255 |
+
frame_rate=5,
|
| 256 |
+
odometry_frame_rate=None,
|
| 257 |
+
odometry_horizon="auto",
|
| 258 |
+
aug="resize_center",
|
| 259 |
+
backend=None,
|
| 260 |
+
odo_transform_config=None,
|
| 261 |
+
return_video_id=False,
|
| 262 |
+
subsample_interval=None,
|
| 263 |
+
intrinsics_h5_path=None,
|
| 264 |
+
spatial_transform_config=None,
|
| 265 |
+
validate_frame_rate_sample=True,
|
| 266 |
+
):
|
| 267 |
+
self.odometry_frame_rate = frame_rate if odometry_frame_rate is None else odometry_frame_rate
|
| 268 |
+
self.odometry_horizon = odometry_horizon
|
| 269 |
+
|
| 270 |
+
self._validate_frame_rate_ratio(
|
| 271 |
+
stored_rate=stored_data_frame_rate,
|
| 272 |
+
sampled_rate=frame_rate,
|
| 273 |
+
rate_name="frame_rate",
|
| 274 |
+
)
|
| 275 |
+
self._validate_frame_rate_ratio(
|
| 276 |
+
stored_rate=stored_data_frame_rate,
|
| 277 |
+
sampled_rate=self.odometry_frame_rate,
|
| 278 |
+
rate_name="odometry_frame_rate",
|
| 279 |
+
)
|
| 280 |
+
self.odo_frame_interval = int(round(stored_data_frame_rate / self.odometry_frame_rate))
|
| 281 |
+
inferred_num_frames_odo = self._infer_num_frames_odo_for_matched_horizon(
|
| 282 |
+
num_frames=num_frames,
|
| 283 |
+
frame_rate=frame_rate,
|
| 284 |
+
odometry_frame_rate=self.odometry_frame_rate,
|
| 285 |
+
)
|
| 286 |
+
self.num_frames_odo, self.odometry_horizon = self._resolve_odometry_horizon(
|
| 287 |
+
num_frames=num_frames,
|
| 288 |
+
frame_rate=frame_rate,
|
| 289 |
+
num_frames_odo=num_frames_odo,
|
| 290 |
+
odometry_frame_rate=self.odometry_frame_rate,
|
| 291 |
+
odometry_horizon=odometry_horizon,
|
| 292 |
+
inferred_num_frames_odo=inferred_num_frames_odo,
|
| 293 |
+
)
|
| 294 |
+
|
| 295 |
+
super().__init__(
|
| 296 |
+
size=size,
|
| 297 |
+
mp4_paths_file=mp4_paths_file,
|
| 298 |
+
num_frames=num_frames,
|
| 299 |
+
stored_data_frame_rate=stored_data_frame_rate,
|
| 300 |
+
frame_rate=frame_rate,
|
| 301 |
+
aug=aug,
|
| 302 |
+
backend=backend,
|
| 303 |
+
subsample_interval=subsample_interval,
|
| 304 |
+
intrinsics_h5_path=intrinsics_h5_path,
|
| 305 |
+
spatial_transform_config=spatial_transform_config,
|
| 306 |
+
validate_frame_rate_sample=validate_frame_rate_sample,
|
| 307 |
+
)
|
| 308 |
+
|
| 309 |
+
self.odo_file = h5py.File(odometry_h5_path, "r")
|
| 310 |
+
self.return_video_id = return_video_id
|
| 311 |
+
|
| 312 |
+
if odo_transform_config is None:
|
| 313 |
+
raise ValueError(
|
| 314 |
+
"MultiMP4DatasetMultiFrameIdxMappingNVIDIAPhysAI requires explicit "
|
| 315 |
+
"`odo_transform_config`. Use `data.steering_loaders.OdometryLoaderNVIDIAPhysAI` "
|
| 316 |
+
"for raw [speed, yaw_rate], or `data.steering_loaders."
|
| 317 |
+
"TrajectoryLoaderNVIDIAPhysAIFromSpeedCurvature` for trajectory targets."
|
| 318 |
+
)
|
| 319 |
+
self.odo_transform = instantiate_from_config(odo_transform_config)
|
| 320 |
+
self.steering_format = getattr(self.odo_transform, "steering_format", "unknown")
|
| 321 |
+
|
| 322 |
+
# check that all videos have corresponding odometry data, and that the odometry data has the same number of frames as the video
|
| 323 |
+
for mp4_path in self.mp4_paths:
|
| 324 |
+
video_id = self._get_video_id(mp4_path)
|
| 325 |
+
if video_id not in self.odo_file:
|
| 326 |
+
raise KeyError(f"Video id {video_id} not found in odometry file {odometry_h5_path}")
|
| 327 |
+
odo_length = len(self.odo_file[video_id]['odometry'])
|
| 328 |
+
video_length = self.mp4_lengths_in_frames[mp4_path] if self.mp4_lengths_in_frames is not None else self.get_video_length(mp4_path)
|
| 329 |
+
# max difference of 1 frame is allowed to account for rounding issues when the frame rates are different
|
| 330 |
+
if abs(odo_length - video_length) > 1:
|
| 331 |
+
raise ValueError(f"Odometry length {odo_length} does not match video length {video_length} for video id {video_id}")
|
| 332 |
+
|
| 333 |
+
def scan_mp4_files(self):
|
| 334 |
+
self.index_to_starting_frame_map = []
|
| 335 |
+
required_span = max(
|
| 336 |
+
self.num_frames * self.frame_interval,
|
| 337 |
+
self.num_frames_odo * self.odo_frame_interval,
|
| 338 |
+
)
|
| 339 |
+
for path in self.mp4_paths:
|
| 340 |
+
if self.mp4_lengths_in_frames is not None and path in self.mp4_lengths_in_frames:
|
| 341 |
+
video_length = self.mp4_lengths_in_frames[path]
|
| 342 |
+
else:
|
| 343 |
+
video_length = self.get_video_length(path)
|
| 344 |
+
|
| 345 |
+
frame_interval = self.frame_interval if self.subsample_interval is None else self.subsample_interval * self.frame_interval
|
| 346 |
+
max_frame_index = video_length - required_span - 1
|
| 347 |
+
for i in range(0, max_frame_index + 1, frame_interval):
|
| 348 |
+
self.index_to_starting_frame_map.append((path, i))
|
| 349 |
+
|
| 350 |
+
def _get_odometry_indices(self, start_frame):
|
| 351 |
+
return list(range(
|
| 352 |
+
start_frame,
|
| 353 |
+
start_frame + self.num_frames_odo * self.odo_frame_interval,
|
| 354 |
+
self.odo_frame_interval,
|
| 355 |
+
))
|
| 356 |
+
|
| 357 |
+
def _load_steering_for_video(self, video_id, start_frame):
|
| 358 |
+
indices = self._get_odometry_indices(start_frame)
|
| 359 |
+
odo_data = [self.odo_file[video_id]['odometry'][i] for i in indices]
|
| 360 |
+
return torch.from_numpy(self.odo_transform(odo_data)).float()
|
| 361 |
+
|
| 362 |
+
def __getitem__(self, idx):
|
| 363 |
+
images, (_, path, start_frame) = self.get_images_and_indices(idx)
|
| 364 |
+
images = self.apply_transforms(images, context=self.build_context((None, path, start_frame)))
|
| 365 |
+
video_id = self._get_video_id(path)
|
| 366 |
+
result = {
|
| 367 |
+
"images": images,
|
| 368 |
+
"frame_rate": self.frame_rate,
|
| 369 |
+
"steering": self._load_steering_for_video(video_id, start_frame),
|
| 370 |
+
"steering_format": self.steering_format,
|
| 371 |
+
}
|
| 372 |
+
if self.return_video_id:
|
| 373 |
+
result["video_id"] = video_id
|
| 374 |
+
return result
|
| 375 |
+
|
| 376 |
+
|
| 377 |
+
class MultiHDF5DatasetMultiFrameIdxMappingOdometry(
|
| 378 |
+
OdometryHorizonMixin,
|
| 379 |
+
MultiHDF5DatasetMultiFrameIdxMapping,
|
| 380 |
+
):
|
| 381 |
+
def __init__(
|
| 382 |
+
self,
|
| 383 |
+
size,
|
| 384 |
+
hdf5_paths_file,
|
| 385 |
+
num_frames,
|
| 386 |
+
num_frames_odo=None,
|
| 387 |
+
frames_file_suffix="frames.h5",
|
| 388 |
+
odo_files_suffix="odometry.h5",
|
| 389 |
+
stored_data_frame_rate=5,
|
| 390 |
+
frame_rate=5,
|
| 391 |
+
odometry_frame_rate=None,
|
| 392 |
+
odometry_horizon="auto",
|
| 393 |
+
aug="resize_center",
|
| 394 |
+
scale_min=0.15,
|
| 395 |
+
scale_max=0.5,
|
| 396 |
+
odo_transform_config=None,
|
| 397 |
+
):
|
| 398 |
+
self.frames_file_suffix = frames_file_suffix
|
| 399 |
+
self.odo_files_suffix = odo_files_suffix if odo_files_suffix is None or odo_files_suffix.lower() != "none" else None
|
| 400 |
+
self.odometry_frame_rate = frame_rate if odometry_frame_rate is None else odometry_frame_rate
|
| 401 |
+
self.odometry_horizon = odometry_horizon
|
| 402 |
+
|
| 403 |
+
self._validate_frame_rate_ratio(
|
| 404 |
+
stored_rate=stored_data_frame_rate,
|
| 405 |
+
sampled_rate=frame_rate,
|
| 406 |
+
rate_name="frame_rate",
|
| 407 |
+
)
|
| 408 |
+
self._validate_frame_rate_ratio(
|
| 409 |
+
stored_rate=stored_data_frame_rate,
|
| 410 |
+
sampled_rate=self.odometry_frame_rate,
|
| 411 |
+
rate_name="odometry_frame_rate",
|
| 412 |
+
)
|
| 413 |
+
self.odo_frame_interval = int(round(stored_data_frame_rate / self.odometry_frame_rate))
|
| 414 |
+
inferred_num_frames_odo = self._infer_num_frames_odo_for_matched_horizon(
|
| 415 |
+
num_frames=num_frames,
|
| 416 |
+
frame_rate=frame_rate,
|
| 417 |
+
odometry_frame_rate=self.odometry_frame_rate,
|
| 418 |
+
)
|
| 419 |
+
self.num_frames_odo, self.odometry_horizon = self._resolve_odometry_horizon(
|
| 420 |
+
num_frames=num_frames,
|
| 421 |
+
frame_rate=frame_rate,
|
| 422 |
+
num_frames_odo=num_frames_odo,
|
| 423 |
+
odometry_frame_rate=self.odometry_frame_rate,
|
| 424 |
+
odometry_horizon=odometry_horizon,
|
| 425 |
+
inferred_num_frames_odo=inferred_num_frames_odo,
|
| 426 |
+
)
|
| 427 |
+
|
| 428 |
+
super().__init__(
|
| 429 |
+
size=size,
|
| 430 |
+
hdf5_paths_file=hdf5_paths_file,
|
| 431 |
+
num_frames=num_frames,
|
| 432 |
+
stored_data_frame_rate=stored_data_frame_rate,
|
| 433 |
+
frame_rate=frame_rate,
|
| 434 |
+
aug=aug,
|
| 435 |
+
scale_min=scale_min,
|
| 436 |
+
scale_max=scale_max,
|
| 437 |
+
)
|
| 438 |
+
|
| 439 |
+
if odo_transform_config is not None:
|
| 440 |
+
self.odo_transform = instantiate_from_config(odo_transform_config)
|
| 441 |
+
else:
|
| 442 |
+
self.odo_transform = OdometryLoaderNuPlan()
|
| 443 |
+
self.steering_format = getattr(self.odo_transform, "steering_format", "unknown")
|
| 444 |
+
|
| 445 |
+
@staticmethod
|
| 446 |
+
def frames_odo_matching_check(frames_h5, odo_h5):
|
| 447 |
+
for key in frames_h5.keys():
|
| 448 |
+
if "meta_data" in key:
|
| 449 |
+
continue
|
| 450 |
+
if key not in odo_h5:
|
| 451 |
+
raise KeyError(f"Odometry key {key} not found in frames data")
|
| 452 |
+
if len(odo_h5[key]) != len(frames_h5[key]):
|
| 453 |
+
raise ValueError(
|
| 454 |
+
f"Odometry key {key} has different length than frames data: "
|
| 455 |
+
f"{len(odo_h5[key])} != {len(frames_h5[key])}"
|
| 456 |
+
)
|
| 457 |
+
|
| 458 |
+
def scan_h5_files_odo(self):
|
| 459 |
+
self.files_odo = {}
|
| 460 |
+
if self.odo_files_suffix is None:
|
| 461 |
+
return
|
| 462 |
+
|
| 463 |
+
for h5_file_frames in self.hdf5_files:
|
| 464 |
+
odo_h5_path = h5_file_frames.filename.replace(self.frames_file_suffix, self.odo_files_suffix)
|
| 465 |
+
self.files_odo[odo_h5_path] = h5py.File(odo_h5_path, "r")
|
| 466 |
+
self.frames_odo_matching_check(h5_file_frames, self.files_odo[odo_h5_path])
|
| 467 |
+
|
| 468 |
+
def scan_h5_files(self):
|
| 469 |
+
self.index_to_starting_frame_map = []
|
| 470 |
+
required_span = max(
|
| 471 |
+
self.num_frames * self.frame_interval,
|
| 472 |
+
self.num_frames_odo * self.odo_frame_interval,
|
| 473 |
+
)
|
| 474 |
+
for file in self.hdf5_files:
|
| 475 |
+
for key in file.keys():
|
| 476 |
+
if "meta_data" in key:
|
| 477 |
+
continue
|
| 478 |
+
video_length = len(file[key])
|
| 479 |
+
max_frame_index = video_length - required_span - 1
|
| 480 |
+
for i in range(0, max_frame_index + 1):
|
| 481 |
+
self.index_to_starting_frame_map.append((file, key, i))
|
| 482 |
+
self.scan_h5_files_odo()
|
| 483 |
+
|
| 484 |
+
def _get_odometry_indices(self, start_frame):
|
| 485 |
+
return list(range(
|
| 486 |
+
start_frame,
|
| 487 |
+
start_frame + self.num_frames_odo * self.odo_frame_interval,
|
| 488 |
+
self.odo_frame_interval,
|
| 489 |
+
))
|
| 490 |
+
|
| 491 |
+
def get_odometry(self, filename, key, start_idx):
|
| 492 |
+
odo_filename = filename.replace(self.frames_file_suffix, self.odo_files_suffix)
|
| 493 |
+
indices = self._get_odometry_indices(start_idx)
|
| 494 |
+
odo = [self.files_odo[odo_filename][key][i] for i in indices]
|
| 495 |
+
return torch.as_tensor(self.odo_transform(odo)).float()
|
| 496 |
+
|
| 497 |
+
def __getitem__(self, idx):
|
| 498 |
+
images, (filename, key, start_frame) = self.get_images_and_indices(idx)
|
| 499 |
+
images = self.apply_transforms(images)
|
| 500 |
+
if self.odo_files_suffix is not None:
|
| 501 |
+
odo = self.get_odometry(filename, key, start_frame)
|
| 502 |
+
else:
|
| 503 |
+
odo = torch.full((self.num_frames_odo, 3), float("nan"))
|
| 504 |
+
return {
|
| 505 |
+
"images": images,
|
| 506 |
+
"steering": odo,
|
| 507 |
+
"frame_rate": self.frame_rate,
|
| 508 |
+
"steering_format": self.steering_format,
|
| 509 |
+
}
|
| 510 |
+
|
| 511 |
+
|
| 512 |
+
class MultiMP4DatasetMultiFrameIdxMappingNVIDIAPhysAIWithL2Context(
|
| 513 |
+
L2ContextMixin,
|
| 514 |
+
MultiMP4DatasetMultiFrameIdxMappingNVIDIAPhysAI,
|
| 515 |
+
):
|
| 516 |
+
def __init__(self, *, num_l2_context, l2_frame_rate=1.0, l1_context_frames=1, **kwargs):
|
| 517 |
+
super().__init__(**kwargs)
|
| 518 |
+
self._init_l2_context(
|
| 519 |
+
num_l2_context=num_l2_context,
|
| 520 |
+
l2_frame_rate=l2_frame_rate,
|
| 521 |
+
l1_context_frames=l1_context_frames,
|
| 522 |
+
require_l2_context=True,
|
| 523 |
+
)
|
| 524 |
+
self.index_to_starting_frame_map = self.filter_index_map_with_l2_headroom(
|
| 525 |
+
self.index_to_starting_frame_map,
|
| 526 |
+
)
|
| 527 |
+
|
| 528 |
+
def _decode_indices(self, path, indices):
|
| 529 |
+
if self.backend == "decord":
|
| 530 |
+
from decord import VideoReader, cpu as decord_cpu
|
| 531 |
+
|
| 532 |
+
file = VideoReader(path, ctx=decord_cpu(0))
|
| 533 |
+
frames = file.get_batch(indices).asnumpy()
|
| 534 |
+
elif self.backend == "torchcodec":
|
| 535 |
+
from torchcodec.decoders import VideoDecoder
|
| 536 |
+
|
| 537 |
+
file = VideoDecoder(path)
|
| 538 |
+
frames = torch.stack([file[i] for i in indices])
|
| 539 |
+
else:
|
| 540 |
+
raise RuntimeError(f"Unknown backend {self.backend}")
|
| 541 |
+
return frames
|
| 542 |
+
|
| 543 |
+
def __getitem__(self, idx):
|
| 544 |
+
if idx >= len(self.index_to_starting_frame_map):
|
| 545 |
+
raise IndexError(f"Index {idx} out of range for dataset of length {len(self.index_to_starting_frame_map)}")
|
| 546 |
+
|
| 547 |
+
path, start_frame = self.index_to_starting_frame_map[idx]
|
| 548 |
+
l1_indices, l2_indices = self.get_l1_and_l2_indices(start_frame, self.num_frames)
|
| 549 |
+
all_frames = self._decode_indices(path, l1_indices + l2_indices)
|
| 550 |
+
all_transformed = self.augmenter(
|
| 551 |
+
all_frames,
|
| 552 |
+
context=self.build_context((None, path, start_frame)),
|
| 553 |
+
)
|
| 554 |
+
|
| 555 |
+
video_id = self._get_video_id(path)
|
| 556 |
+
return {
|
| 557 |
+
"images": all_transformed[:len(l1_indices)],
|
| 558 |
+
"l2_context": all_transformed[len(l1_indices):],
|
| 559 |
+
"frame_rate": self.frame_rate,
|
| 560 |
+
"steering": self._load_steering_for_video(video_id, start_frame),
|
| 561 |
+
"steering_format": self.steering_format,
|
| 562 |
+
}
|
| 563 |
+
|
| 564 |
+
|
| 565 |
+
class VistaStyleNuScenesSteeringMixin(OdometryHorizonMixin):
|
| 566 |
+
def _init_vista_style_steering(
|
| 567 |
+
self,
|
| 568 |
+
*,
|
| 569 |
+
dbs_root,
|
| 570 |
+
annotation_key,
|
| 571 |
+
num_frames,
|
| 572 |
+
stored_data_frame_rate,
|
| 573 |
+
frame_rate,
|
| 574 |
+
num_frames_odo=None,
|
| 575 |
+
odometry_frame_rate=None,
|
| 576 |
+
odometry_horizon="auto",
|
| 577 |
+
):
|
| 578 |
+
self.dbs_root = dbs_root
|
| 579 |
+
self.annotation_key = annotation_key
|
| 580 |
+
self.steering_format = annotation_key
|
| 581 |
+
self.odometry_frame_rate = frame_rate if odometry_frame_rate is None else odometry_frame_rate
|
| 582 |
+
self.odometry_horizon = odometry_horizon
|
| 583 |
+
self._validate_frame_rate_ratio(
|
| 584 |
+
stored_rate=stored_data_frame_rate,
|
| 585 |
+
sampled_rate=frame_rate,
|
| 586 |
+
rate_name="frame_rate",
|
| 587 |
+
)
|
| 588 |
+
self._validate_frame_rate_ratio(
|
| 589 |
+
stored_rate=stored_data_frame_rate,
|
| 590 |
+
sampled_rate=self.odometry_frame_rate,
|
| 591 |
+
rate_name="odometry_frame_rate",
|
| 592 |
+
)
|
| 593 |
+
self.odo_frame_interval = int(round(stored_data_frame_rate / self.odometry_frame_rate))
|
| 594 |
+
inferred_num_frames_odo = self._infer_num_frames_odo_for_matched_horizon(
|
| 595 |
+
num_frames=num_frames,
|
| 596 |
+
frame_rate=frame_rate,
|
| 597 |
+
odometry_frame_rate=self.odometry_frame_rate,
|
| 598 |
+
)
|
| 599 |
+
self.num_frames_odo, self.odometry_horizon = self._resolve_odometry_horizon(
|
| 600 |
+
num_frames=num_frames,
|
| 601 |
+
frame_rate=frame_rate,
|
| 602 |
+
num_frames_odo=num_frames_odo,
|
| 603 |
+
odometry_frame_rate=self.odometry_frame_rate,
|
| 604 |
+
odometry_horizon=odometry_horizon,
|
| 605 |
+
inferred_num_frames_odo=inferred_num_frames_odo,
|
| 606 |
+
)
|
| 607 |
+
assert self.annotation_key in ["speed_yawrate", "trajectory", "trajectory_with_heading"], (
|
| 608 |
+
f"Unsupported annotation key {self.annotation_key}"
|
| 609 |
+
)
|
| 610 |
+
|
| 611 |
+
@staticmethod
|
| 612 |
+
def _infer_num_frames_odo_for_matched_horizon(num_frames, frame_rate, odometry_frame_rate):
|
| 613 |
+
if num_frames is None:
|
| 614 |
+
raise ValueError("VistaStyleNuScenesLoaderSteering requires num_frames to resolve odometry horizon")
|
| 615 |
+
return OdometryHorizonMixin._infer_num_frames_odo_for_matched_horizon(
|
| 616 |
+
num_frames=num_frames,
|
| 617 |
+
frame_rate=frame_rate,
|
| 618 |
+
odometry_frame_rate=odometry_frame_rate,
|
| 619 |
+
)
|
| 620 |
+
|
| 621 |
+
def _get_vista_style_steering(self, sample):
|
| 622 |
+
pose_table = extract_pose_table(os.path.join(self.dbs_root, sample["db_name"]))
|
| 623 |
+
pose_tokens = sample["pose_tokens"]
|
| 624 |
+
poses = [get_pose(pose_table, pose_token) for pose_token in pose_tokens]
|
| 625 |
+
speeds = np.array([pose["vx"].values[0] for pose in poses])
|
| 626 |
+
yaw_rates = np.array([pose["angular_rate_z"].values[0] for pose in poses])
|
| 627 |
+
|
| 628 |
+
if self.annotation_key == "speed_yawrate":
|
| 629 |
+
speed_yawrate = np.stack([speeds, yaw_rates], axis=-1)
|
| 630 |
+
steering = speed_yawrate[::self.odo_frame_interval]
|
| 631 |
+
assert len(steering) >= self.num_frames_odo, (
|
| 632 |
+
f"Speed/YawRate length {len(steering)} is less than the required {self.num_frames_odo}"
|
| 633 |
+
)
|
| 634 |
+
steering = steering[:self.num_frames_odo]
|
| 635 |
+
elif self.annotation_key == "trajectory_with_heading":
|
| 636 |
+
traj, headings = get_trajectory_from_speeds_and_yaw_rates(
|
| 637 |
+
speeds,
|
| 638 |
+
yaw_rates,
|
| 639 |
+
dt=1 / self.stored_data_frame_rate,
|
| 640 |
+
)
|
| 641 |
+
traj, headings = traj[::self.odo_frame_interval], headings[::self.odo_frame_interval]
|
| 642 |
+
assert len(traj) >= self.num_frames_odo, (
|
| 643 |
+
f"Trajectory length {len(traj)} is less than the required {self.num_frames_odo}"
|
| 644 |
+
)
|
| 645 |
+
steering = np.concatenate([traj[:self.num_frames_odo], headings[:self.num_frames_odo, None]], axis=-1)
|
| 646 |
+
elif self.annotation_key == "trajectory":
|
| 647 |
+
traj, _ = get_trajectory_from_speeds_and_yaw_rates(
|
| 648 |
+
speeds,
|
| 649 |
+
yaw_rates,
|
| 650 |
+
dt=1 / self.stored_data_frame_rate,
|
| 651 |
+
)
|
| 652 |
+
traj = traj[::self.odo_frame_interval]
|
| 653 |
+
assert len(traj) >= self.num_frames_odo, (
|
| 654 |
+
f"Trajectory length {len(traj)} is less than the required {self.num_frames_odo}"
|
| 655 |
+
)
|
| 656 |
+
steering = traj[:self.num_frames_odo]
|
| 657 |
+
else:
|
| 658 |
+
raise ValueError(f"Unsupported annotation key {self.annotation_key}")
|
| 659 |
+
|
| 660 |
+
return torch.from_numpy(steering).float()
|
| 661 |
+
|
| 662 |
+
|
| 663 |
+
class VistaStyleNuScenesLoaderSteering(VistaStyleNuScenesSteeringMixin, VistaStyleNuScenesLoader):
|
| 664 |
+
def __init__(
|
| 665 |
+
self,
|
| 666 |
+
*,
|
| 667 |
+
size,
|
| 668 |
+
json_path,
|
| 669 |
+
images_root,
|
| 670 |
+
dbs_root,
|
| 671 |
+
annotation_key,
|
| 672 |
+
num_frames=None,
|
| 673 |
+
num_frames_odo=None,
|
| 674 |
+
stored_data_frame_rate=10,
|
| 675 |
+
frame_rate=5,
|
| 676 |
+
odometry_frame_rate=None,
|
| 677 |
+
odometry_horizon="auto",
|
| 678 |
+
aug="resize_center",
|
| 679 |
+
sample_indices=None,
|
| 680 |
+
):
|
| 681 |
+
self.frame_rate = frame_rate
|
| 682 |
+
self.stored_data_frame_rate = stored_data_frame_rate
|
| 683 |
+
self.odometry_frame_rate = frame_rate if odometry_frame_rate is None else odometry_frame_rate
|
| 684 |
+
self._validate_frame_rate_ratio(
|
| 685 |
+
stored_rate=stored_data_frame_rate,
|
| 686 |
+
sampled_rate=frame_rate,
|
| 687 |
+
rate_name="frame_rate",
|
| 688 |
+
)
|
| 689 |
+
self._validate_frame_rate_ratio(
|
| 690 |
+
stored_rate=stored_data_frame_rate,
|
| 691 |
+
sampled_rate=self.odometry_frame_rate,
|
| 692 |
+
rate_name="odometry_frame_rate",
|
| 693 |
+
)
|
| 694 |
+
frame_rate_multiplier = frame_rate / stored_data_frame_rate
|
| 695 |
+
super().__init__(
|
| 696 |
+
size=size,
|
| 697 |
+
json_path=json_path,
|
| 698 |
+
images_root=images_root,
|
| 699 |
+
num_frames=num_frames,
|
| 700 |
+
frame_rate_multiplier=frame_rate_multiplier,
|
| 701 |
+
aug=aug,
|
| 702 |
+
sample_indices=sample_indices,
|
| 703 |
+
)
|
| 704 |
+
self._init_vista_style_steering(
|
| 705 |
+
dbs_root=dbs_root,
|
| 706 |
+
annotation_key=annotation_key,
|
| 707 |
+
num_frames=self.num_frames,
|
| 708 |
+
stored_data_frame_rate=stored_data_frame_rate,
|
| 709 |
+
frame_rate=frame_rate,
|
| 710 |
+
num_frames_odo=num_frames_odo,
|
| 711 |
+
odometry_frame_rate=odometry_frame_rate,
|
| 712 |
+
odometry_horizon=odometry_horizon,
|
| 713 |
+
)
|
| 714 |
+
|
| 715 |
+
def __getitem__(self, index):
|
| 716 |
+
images = super().__getitem__(index)
|
| 717 |
+
sample = self.data[index]
|
| 718 |
+
return {
|
| 719 |
+
"images": images,
|
| 720 |
+
"steering": self._get_vista_style_steering(sample),
|
| 721 |
+
"frame_rate": torch.tensor(self.frame_rate).float(),
|
| 722 |
+
"steering_format": self.steering_format,
|
| 723 |
+
}
|
| 724 |
+
|
| 725 |
+
|
| 726 |
+
class VistaStyleNuScenesLoaderSteeringWithL2Context(
|
| 727 |
+
VistaStyleNuScenesSteeringMixin,
|
| 728 |
+
VistaStyleNuScenesLoaderWithL2Context,
|
| 729 |
+
):
|
| 730 |
+
def __init__(
|
| 731 |
+
self,
|
| 732 |
+
*,
|
| 733 |
+
size,
|
| 734 |
+
json_path,
|
| 735 |
+
images_root,
|
| 736 |
+
dbs_root,
|
| 737 |
+
annotation_key,
|
| 738 |
+
num_frames=None,
|
| 739 |
+
num_frames_odo=None,
|
| 740 |
+
stored_data_frame_rate=10,
|
| 741 |
+
frame_rate=5,
|
| 742 |
+
odometry_frame_rate=None,
|
| 743 |
+
odometry_horizon="auto",
|
| 744 |
+
num_l2_context,
|
| 745 |
+
l2_frame_rate=1.0,
|
| 746 |
+
l1_context_frames=1,
|
| 747 |
+
aug="resize_center",
|
| 748 |
+
sample_indices=None,
|
| 749 |
+
):
|
| 750 |
+
super().__init__(
|
| 751 |
+
size=size,
|
| 752 |
+
json_path=json_path,
|
| 753 |
+
images_root=images_root,
|
| 754 |
+
num_frames=num_frames,
|
| 755 |
+
stored_data_frame_rate=stored_data_frame_rate,
|
| 756 |
+
frame_rate=frame_rate,
|
| 757 |
+
num_l2_context=num_l2_context,
|
| 758 |
+
l2_frame_rate=l2_frame_rate,
|
| 759 |
+
l1_context_frames=l1_context_frames,
|
| 760 |
+
aug=aug,
|
| 761 |
+
sample_indices=sample_indices,
|
| 762 |
+
)
|
| 763 |
+
self._init_vista_style_steering(
|
| 764 |
+
dbs_root=dbs_root,
|
| 765 |
+
annotation_key=annotation_key,
|
| 766 |
+
num_frames=self.num_frames,
|
| 767 |
+
stored_data_frame_rate=stored_data_frame_rate,
|
| 768 |
+
frame_rate=frame_rate,
|
| 769 |
+
num_frames_odo=num_frames_odo,
|
| 770 |
+
odometry_frame_rate=odometry_frame_rate,
|
| 771 |
+
odometry_horizon=odometry_horizon,
|
| 772 |
+
)
|
| 773 |
+
|
| 774 |
+
def __getitem__(self, index):
|
| 775 |
+
batch = super().__getitem__(index)
|
| 776 |
+
sample = self.data[index]
|
| 777 |
+
batch["steering"] = self._get_vista_style_steering(sample)
|
| 778 |
+
batch["steering_format"] = self.steering_format
|
| 779 |
+
return batch
|
| 780 |
+
|
| 781 |
+
|
| 782 |
+
class MultiMP4DatasetMultiFrameIdxMappingNoSteering(PlaceholderSteeringMixin, MultiMP4DatasetMultiFrameIdxMapping):
|
| 783 |
+
"""MP4 dataset without real steering data.
|
| 784 |
+
|
| 785 |
+
Returns NaN steering placeholders so that rollout_steering_v2 can work with
|
| 786 |
+
--steering_file or --no_steering. Using it without either option raises an
|
| 787 |
+
explicit error at rollout time.
|
| 788 |
+
|
| 789 |
+
Args:
|
| 790 |
+
num_frames_odo: Number of steering timesteps to expose per sample. Set
|
| 791 |
+
this to at least the rollout odometry horizon, or rely on
|
| 792 |
+
reconfigure_params_for_required_odometry_horizon to expand it.
|
| 793 |
+
steering_dim: Feature dimension of the placeholder (default 2 for
|
| 794 |
+
speed/yaw-rate).
|
| 795 |
+
steering_format: The steering_format tag returned in the batch
|
| 796 |
+
(default "speed_yawrate").
|
| 797 |
+
All remaining args are forwarded to MultiMP4DatasetMultiFrameIdxMapping.
|
| 798 |
+
"""
|
| 799 |
+
|
| 800 |
+
def __init__(
|
| 801 |
+
self,
|
| 802 |
+
size,
|
| 803 |
+
mp4_paths_file,
|
| 804 |
+
num_frames,
|
| 805 |
+
num_frames_odo,
|
| 806 |
+
steering_dim=2,
|
| 807 |
+
steering_format="speed_yawrate",
|
| 808 |
+
stored_data_frame_rate=5,
|
| 809 |
+
frame_rate=5,
|
| 810 |
+
aug="resize_center",
|
| 811 |
+
backend=None,
|
| 812 |
+
subsample_interval=None,
|
| 813 |
+
intrinsics_h5_path=None,
|
| 814 |
+
spatial_transform_config=None,
|
| 815 |
+
):
|
| 816 |
+
self._init_placeholder_steering(num_frames_odo, steering_dim, steering_format)
|
| 817 |
+
MultiMP4DatasetMultiFrameIdxMapping.__init__(
|
| 818 |
+
self,
|
| 819 |
+
size=size,
|
| 820 |
+
mp4_paths_file=mp4_paths_file,
|
| 821 |
+
num_frames=num_frames,
|
| 822 |
+
stored_data_frame_rate=stored_data_frame_rate,
|
| 823 |
+
frame_rate=frame_rate,
|
| 824 |
+
aug=aug,
|
| 825 |
+
backend=backend,
|
| 826 |
+
subsample_interval=subsample_interval,
|
| 827 |
+
intrinsics_h5_path=intrinsics_h5_path,
|
| 828 |
+
spatial_transform_config=spatial_transform_config,
|
| 829 |
+
)
|
| 830 |
+
|
| 831 |
+
def __getitem__(self, idx):
|
| 832 |
+
item = MultiMP4DatasetMultiFrameIdxMapping.__getitem__(self, idx)
|
| 833 |
+
return self._add_placeholder_steering(item)
|
| 834 |
+
|
| 835 |
+
|
| 836 |
+
class MultiHDF5DatasetMultiFrameIdxMappingNoSteering(PlaceholderSteeringMixin, MultiHDF5DatasetMultiFrameIdxMapping):
|
| 837 |
+
"""HDF5 dataset without real steering data.
|
| 838 |
+
|
| 839 |
+
Returns NaN steering placeholders so that rollout_steering_v2 can work with
|
| 840 |
+
--steering_file or --no_steering. Using it without either option raises an
|
| 841 |
+
explicit error at rollout time.
|
| 842 |
+
|
| 843 |
+
Args:
|
| 844 |
+
num_frames_odo: Number of steering timesteps to expose per sample.
|
| 845 |
+
steering_dim: Feature dimension of the placeholder (default 2).
|
| 846 |
+
steering_format: steering_format tag returned in the batch (default "speed_yawrate").
|
| 847 |
+
All remaining args are forwarded to MultiHDF5DatasetMultiFrameIdxMapping.
|
| 848 |
+
"""
|
| 849 |
+
|
| 850 |
+
def __init__(
|
| 851 |
+
self,
|
| 852 |
+
size,
|
| 853 |
+
hdf5_paths_file,
|
| 854 |
+
num_frames,
|
| 855 |
+
num_frames_odo,
|
| 856 |
+
steering_dim=2,
|
| 857 |
+
steering_format="speed_yawrate",
|
| 858 |
+
stored_data_frame_rate=5,
|
| 859 |
+
frame_rate=5,
|
| 860 |
+
aug="resize_center",
|
| 861 |
+
scale_min=0.15,
|
| 862 |
+
scale_max=0.5,
|
| 863 |
+
):
|
| 864 |
+
self._init_placeholder_steering(num_frames_odo, steering_dim, steering_format)
|
| 865 |
+
MultiHDF5DatasetMultiFrameIdxMapping.__init__(
|
| 866 |
+
self,
|
| 867 |
+
size=size,
|
| 868 |
+
hdf5_paths_file=hdf5_paths_file,
|
| 869 |
+
num_frames=num_frames,
|
| 870 |
+
stored_data_frame_rate=stored_data_frame_rate,
|
| 871 |
+
frame_rate=frame_rate,
|
| 872 |
+
aug=aug,
|
| 873 |
+
scale_min=scale_min,
|
| 874 |
+
scale_max=scale_max,
|
| 875 |
+
)
|
| 876 |
+
|
| 877 |
+
def __getitem__(self, idx):
|
| 878 |
+
item = MultiHDF5DatasetMultiFrameIdxMapping.__getitem__(self, idx)
|
| 879 |
+
return self._add_placeholder_steering(item)
|
| 880 |
+
|
| 881 |
+
|
| 882 |
+
class VistaStyleNuScenesLoaderWithL2ContextNoSteering(
|
| 883 |
+
PlaceholderSteeringMixin,
|
| 884 |
+
VistaStyleNuScenesLoaderWithL2Context,
|
| 885 |
+
):
|
| 886 |
+
"""VistaStyle NuScenes + L2-context dataset without real steering data.
|
| 887 |
+
|
| 888 |
+
Returns NaN steering placeholders alongside the l2_context frames so that
|
| 889 |
+
rollout_steering_v2 can work with --steering_file or --no_steering.
|
| 890 |
+
Using it without either option raises an explicit error at rollout time.
|
| 891 |
+
|
| 892 |
+
Args:
|
| 893 |
+
num_frames_odo: Number of steering timesteps to expose per sample.
|
| 894 |
+
steering_dim: Feature dimension of the placeholder (default 2 for
|
| 895 |
+
speed/yaw-rate).
|
| 896 |
+
steering_format: steering_format tag returned in the batch
|
| 897 |
+
(default "speed_yawrate").
|
| 898 |
+
num_l2_context, l2_frame_rate, l1_context_frames: forwarded to
|
| 899 |
+
VistaStyleNuScenesLoaderWithL2Context / L2ContextMixin.
|
| 900 |
+
All remaining args forwarded to VistaStyleNuScenesLoaderWithL2Context.
|
| 901 |
+
"""
|
| 902 |
+
|
| 903 |
+
def __init__(
|
| 904 |
+
self,
|
| 905 |
+
num_frames_odo,
|
| 906 |
+
steering_dim=2,
|
| 907 |
+
steering_format="speed_yawrate",
|
| 908 |
+
**kwargs,
|
| 909 |
+
):
|
| 910 |
+
self._init_placeholder_steering(num_frames_odo, steering_dim, steering_format)
|
| 911 |
+
VistaStyleNuScenesLoaderWithL2Context.__init__(self, **kwargs)
|
| 912 |
+
|
| 913 |
+
def __getitem__(self, idx):
|
| 914 |
+
item = VistaStyleNuScenesLoaderWithL2Context.__getitem__(self, idx)
|
| 915 |
+
return self._add_placeholder_steering(item)
|
| 916 |
+
|
| 917 |
+
|
| 918 |
+
class MultiMP4DatasetMultiFrameIdxMappingWithL2ContextNoSteering(
|
| 919 |
+
PlaceholderSteeringMixin,
|
| 920 |
+
MultiMP4DatasetMultiFrameIdxMappingWithL2Context,
|
| 921 |
+
):
|
| 922 |
+
"""MP4 + L2-context dataset without real steering data.
|
| 923 |
+
|
| 924 |
+
Returns NaN steering placeholders alongside the l2_context frames so that
|
| 925 |
+
rollout_steering_v2 can work with --steering_file or --no_steering.
|
| 926 |
+
Using it without either option raises an explicit error at rollout time.
|
| 927 |
+
|
| 928 |
+
Args:
|
| 929 |
+
num_frames_odo: Number of steering timesteps to expose per sample.
|
| 930 |
+
steering_dim: Feature dimension of the placeholder (default 2).
|
| 931 |
+
steering_format: steering_format tag returned in the batch (default "speed_yawrate").
|
| 932 |
+
num_l2_context, l2_frame_rate, l1_context_frames: forwarded to L2ContextMixin.
|
| 933 |
+
All remaining args forwarded to MultiMP4DatasetMultiFrameIdxMapping.
|
| 934 |
+
"""
|
| 935 |
+
|
| 936 |
+
def __init__(
|
| 937 |
+
self,
|
| 938 |
+
num_frames_odo,
|
| 939 |
+
steering_dim=2,
|
| 940 |
+
steering_format="speed_yawrate",
|
| 941 |
+
**kwargs,
|
| 942 |
+
):
|
| 943 |
+
self._init_placeholder_steering(num_frames_odo, steering_dim, steering_format)
|
| 944 |
+
MultiMP4DatasetMultiFrameIdxMappingWithL2Context.__init__(self, **kwargs)
|
| 945 |
+
|
| 946 |
+
def __getitem__(self, idx):
|
| 947 |
+
item = MultiMP4DatasetMultiFrameIdxMappingWithL2Context.__getitem__(self, idx)
|
| 948 |
+
return self._add_placeholder_steering(item)
|
|
@@ -0,0 +1,281 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import collections
|
| 2 |
+
import os
|
| 3 |
+
import tarfile
|
| 4 |
+
import urllib
|
| 5 |
+
import zipfile
|
| 6 |
+
from pathlib import Path
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import torch
|
| 10 |
+
import torch.nn as nn
|
| 11 |
+
from data.helper_types import Annotation
|
| 12 |
+
from torch.utils.data._utils.collate import np_str_obj_array_pattern, default_collate_err_msg_format
|
| 13 |
+
from tqdm import tqdm
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def unpack(path):
|
| 17 |
+
if path.endswith("tar.gz"):
|
| 18 |
+
with tarfile.open(path, "r:gz") as tar:
|
| 19 |
+
tar.extractall(path=os.path.split(path)[0])
|
| 20 |
+
elif path.endswith("tar"):
|
| 21 |
+
with tarfile.open(path, "r:") as tar:
|
| 22 |
+
tar.extractall(path=os.path.split(path)[0])
|
| 23 |
+
elif path.endswith("zip"):
|
| 24 |
+
with zipfile.ZipFile(path, "r") as f:
|
| 25 |
+
f.extractall(path=os.path.split(path)[0])
|
| 26 |
+
else:
|
| 27 |
+
raise NotImplementedError(
|
| 28 |
+
"Unknown file extension: {}".format(os.path.splitext(path)[1])
|
| 29 |
+
)
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
def reporthook(bar):
|
| 33 |
+
"""tqdm progress bar for downloads."""
|
| 34 |
+
|
| 35 |
+
def hook(b=1, bsize=1, tsize=None):
|
| 36 |
+
if tsize is not None:
|
| 37 |
+
bar.total = tsize
|
| 38 |
+
bar.update(b * bsize - bar.n)
|
| 39 |
+
|
| 40 |
+
return hook
|
| 41 |
+
|
| 42 |
+
|
| 43 |
+
def get_root(name):
|
| 44 |
+
base = "data/"
|
| 45 |
+
root = os.path.join(base, name)
|
| 46 |
+
os.makedirs(root, exist_ok=True)
|
| 47 |
+
return root
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def is_prepared(root):
|
| 51 |
+
return Path(root).joinpath(".ready").exists()
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def mark_prepared(root):
|
| 55 |
+
Path(root).joinpath(".ready").touch()
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def prompt_download(file_, source, target_dir, content_dir=None):
|
| 59 |
+
targetpath = os.path.join(target_dir, file_)
|
| 60 |
+
while not os.path.exists(targetpath):
|
| 61 |
+
if content_dir is not None and os.path.exists(
|
| 62 |
+
os.path.join(target_dir, content_dir)
|
| 63 |
+
):
|
| 64 |
+
break
|
| 65 |
+
print(
|
| 66 |
+
"Please download '{}' from '{}' to '{}'.".format(file_, source, targetpath)
|
| 67 |
+
)
|
| 68 |
+
if content_dir is not None:
|
| 69 |
+
print(
|
| 70 |
+
"Or place its content into '{}'.".format(
|
| 71 |
+
os.path.join(target_dir, content_dir)
|
| 72 |
+
)
|
| 73 |
+
)
|
| 74 |
+
input("Press Enter when done...")
|
| 75 |
+
return targetpath
|
| 76 |
+
|
| 77 |
+
|
| 78 |
+
def download_url(file_, url, target_dir):
|
| 79 |
+
targetpath = os.path.join(target_dir, file_)
|
| 80 |
+
os.makedirs(target_dir, exist_ok=True)
|
| 81 |
+
with tqdm(
|
| 82 |
+
unit="B", unit_scale=True, unit_divisor=1024, miniters=1, desc=file_
|
| 83 |
+
) as bar:
|
| 84 |
+
urllib.request.urlretrieve(url, targetpath, reporthook=reporthook(bar))
|
| 85 |
+
return targetpath
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
def download_urls(urls, target_dir):
|
| 89 |
+
paths = dict()
|
| 90 |
+
for fname, url in urls.items():
|
| 91 |
+
outpath = download_url(fname, url, target_dir)
|
| 92 |
+
paths[fname] = outpath
|
| 93 |
+
return paths
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def quadratic_crop(x, bbox, alpha=1.0):
|
| 97 |
+
"""bbox is xmin, ymin, xmax, ymax"""
|
| 98 |
+
im_h, im_w = x.shape[:2]
|
| 99 |
+
bbox = np.array(bbox, dtype=np.float32)
|
| 100 |
+
bbox = np.clip(bbox, 0, max(im_h, im_w))
|
| 101 |
+
center = 0.5 * (bbox[0] + bbox[2]), 0.5 * (bbox[1] + bbox[3])
|
| 102 |
+
w = bbox[2] - bbox[0]
|
| 103 |
+
h = bbox[3] - bbox[1]
|
| 104 |
+
l = int(alpha * max(w, h))
|
| 105 |
+
l = max(l, 2)
|
| 106 |
+
|
| 107 |
+
required_padding = -1 * min(
|
| 108 |
+
center[0] - l, center[1] - l, im_w - (center[0] + l), im_h - (center[1] + l)
|
| 109 |
+
)
|
| 110 |
+
required_padding = int(np.ceil(required_padding))
|
| 111 |
+
if required_padding > 0:
|
| 112 |
+
padding = [
|
| 113 |
+
[required_padding, required_padding],
|
| 114 |
+
[required_padding, required_padding],
|
| 115 |
+
]
|
| 116 |
+
padding += [[0, 0]] * (len(x.shape) - 2)
|
| 117 |
+
x = np.pad(x, padding, "reflect")
|
| 118 |
+
center = center[0] + required_padding, center[1] + required_padding
|
| 119 |
+
xmin = int(center[0] - l / 2)
|
| 120 |
+
ymin = int(center[1] - l / 2)
|
| 121 |
+
return np.array(x[ymin : ymin + l, xmin : xmin + l, ...])
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def custom_collate(batch):
|
| 125 |
+
r"""source: pytorch 1.9.0, only one modification to original code """
|
| 126 |
+
|
| 127 |
+
elem = batch[0]
|
| 128 |
+
elem_type = type(elem)
|
| 129 |
+
if isinstance(elem, torch.Tensor):
|
| 130 |
+
out = None
|
| 131 |
+
if torch.utils.data.get_worker_info() is not None:
|
| 132 |
+
# If we're in a background process, concatenate directly into a
|
| 133 |
+
# shared memory tensor to avoid an extra copy
|
| 134 |
+
numel = sum([x.numel() for x in batch])
|
| 135 |
+
storage = elem.storage()._new_shared(numel)
|
| 136 |
+
out = elem.new(storage)
|
| 137 |
+
return torch.stack(batch, 0, out=out)
|
| 138 |
+
elif elem_type.__module__ == 'numpy' and elem_type.__name__ != 'str_' \
|
| 139 |
+
and elem_type.__name__ != 'string_':
|
| 140 |
+
if elem_type.__name__ == 'ndarray' or elem_type.__name__ == 'memmap':
|
| 141 |
+
# array of string classes and object
|
| 142 |
+
if np_str_obj_array_pattern.search(elem.dtype.str) is not None:
|
| 143 |
+
raise TypeError(default_collate_err_msg_format.format(elem.dtype))
|
| 144 |
+
|
| 145 |
+
return custom_collate([torch.as_tensor(b) for b in batch])
|
| 146 |
+
elif elem.shape == (): # scalars
|
| 147 |
+
return torch.as_tensor(batch)
|
| 148 |
+
elif isinstance(elem, float):
|
| 149 |
+
return torch.tensor(batch, dtype=torch.float64)
|
| 150 |
+
elif isinstance(elem, int):
|
| 151 |
+
return torch.tensor(batch)
|
| 152 |
+
elif isinstance(elem, str):
|
| 153 |
+
return batch
|
| 154 |
+
elif isinstance(elem, collections.abc.Mapping):
|
| 155 |
+
return {key: custom_collate([d[key] for d in batch]) for key in elem}
|
| 156 |
+
elif isinstance(elem, tuple) and hasattr(elem, '_fields'): # namedtuple
|
| 157 |
+
return elem_type(*(custom_collate(samples) for samples in zip(*batch)))
|
| 158 |
+
if isinstance(elem, collections.abc.Sequence) and isinstance(elem[0], Annotation): # added
|
| 159 |
+
return batch # added
|
| 160 |
+
elif isinstance(elem, collections.abc.Sequence):
|
| 161 |
+
# check to make sure that the elements in batch have consistent size
|
| 162 |
+
it = iter(batch)
|
| 163 |
+
elem_size = len(next(it))
|
| 164 |
+
if not all(len(elem) == elem_size for elem in it):
|
| 165 |
+
raise RuntimeError('each element in list of batch should be of equal size')
|
| 166 |
+
transposed = zip(*batch)
|
| 167 |
+
return [custom_collate(samples) for samples in transposed]
|
| 168 |
+
|
| 169 |
+
raise TypeError(default_collate_err_msg_format.format(elem_type))
|
| 170 |
+
|
| 171 |
+
|
| 172 |
+
def get_trajectory_from_speeds_and_yaw_rates(speeds, yaw_rates, dt):
|
| 173 |
+
heading_deltas = yaw_rates * dt
|
| 174 |
+
headings = np.cumsum(heading_deltas)
|
| 175 |
+
headings_for_translation = headings - heading_deltas
|
| 176 |
+
dx = speeds * np.cos(headings_for_translation) * dt
|
| 177 |
+
dy = speeds * np.sin(headings_for_translation) * dt
|
| 178 |
+
|
| 179 |
+
x = np.cumsum(dx)
|
| 180 |
+
y = np.cumsum(dy)
|
| 181 |
+
traj = np.stack([x, y], axis=1)
|
| 182 |
+
|
| 183 |
+
# Transform to local coordinates (first position is origin, first heading is along x-axis)
|
| 184 |
+
traj -= traj[0] # translate to origin
|
| 185 |
+
initial_heading = headings_for_translation[0]
|
| 186 |
+
rotation_matrix = np.array([[np.cos(-initial_heading), -np.sin(-initial_heading)],
|
| 187 |
+
[np.sin(-initial_heading), np.cos(-initial_heading)]])
|
| 188 |
+
local_traj = traj @ rotation_matrix.T # rotate to align with initial heading
|
| 189 |
+
|
| 190 |
+
return local_traj.astype(np.float32), headings.astype(np.float32)
|
| 191 |
+
|
| 192 |
+
def get_trajectory_from_speeds_and_yaw_rates_batch(speeds, yaw_rates, dt):
|
| 193 |
+
"""
|
| 194 |
+
Args:
|
| 195 |
+
speeds: Tensor of shape (B, N)
|
| 196 |
+
yaw_rates: Tensor of shape (B, N)
|
| 197 |
+
dt: Time step (scalar)
|
| 198 |
+
Returns:
|
| 199 |
+
local_traj: Tensor of shape (B, N, 2)
|
| 200 |
+
headings: Tensor of shape (B, N)
|
| 201 |
+
"""
|
| 202 |
+
assert speeds.shape == yaw_rates.shape, f"Speeds shape {speeds.shape} and yaw rates shape {yaw_rates.shape} do not match"
|
| 203 |
+
B, N = speeds.shape
|
| 204 |
+
|
| 205 |
+
# if dt is a scalar, ok, if dt is a tensor, make sure it has shape (B) and expand to (B, 1)
|
| 206 |
+
if isinstance(dt, torch.Tensor):
|
| 207 |
+
assert dt.shape == (B,), f"dt shape {dt.shape} does not match batch size {B}"
|
| 208 |
+
dt = dt.view(B, 1) # Shape: (B, 1)
|
| 209 |
+
|
| 210 |
+
heading_deltas = yaw_rates * dt # Shape: (B, N)
|
| 211 |
+
headings = torch.cumsum(heading_deltas, dim=1) # Shape: (B, N)
|
| 212 |
+
headings_for_translation = headings - heading_deltas
|
| 213 |
+
|
| 214 |
+
# Calculate dx and dy for each batch
|
| 215 |
+
dx = speeds * torch.cos(headings_for_translation) * dt # Shape: (B, N)
|
| 216 |
+
dy = speeds * torch.sin(headings_for_translation) * dt # Shape: (B, N)
|
| 217 |
+
|
| 218 |
+
# Calculate x and y for each batch
|
| 219 |
+
x = torch.cumsum(dx, dim=1) # Shape: (B, N)
|
| 220 |
+
y = torch.cumsum(dy, dim=1) # Shape: (B, N)
|
| 221 |
+
|
| 222 |
+
# Stack x and y to form the trajectory for each batch
|
| 223 |
+
traj = torch.stack([x, y], dim=2) # Shape: (B, N, 2)
|
| 224 |
+
|
| 225 |
+
# Transform to local coordinates for each batch
|
| 226 |
+
traj = traj- traj[:, 0:1, :] # Translate to origin for each batch
|
| 227 |
+
initial_heading = headings_for_translation[:, 0] # Shape: (B,)
|
| 228 |
+
|
| 229 |
+
# Create rotation matrices for each batch
|
| 230 |
+
cos_theta = torch.cos(-initial_heading) # Shape: (B,)
|
| 231 |
+
sin_theta = torch.sin(-initial_heading) # Shape: (B,)
|
| 232 |
+
|
| 233 |
+
# Rotation matrix for each batch
|
| 234 |
+
rotation_matrix = torch.stack([
|
| 235 |
+
torch.stack([cos_theta, -sin_theta], dim=1),
|
| 236 |
+
torch.stack([sin_theta, cos_theta], dim=1)
|
| 237 |
+
], dim=1) # Shape: (B, 2, 2)
|
| 238 |
+
|
| 239 |
+
# Rotate to align with initial heading for each batch
|
| 240 |
+
local_traj = torch.einsum('bni,bij->bnj', traj, rotation_matrix) # Shape: (B, N, 2)
|
| 241 |
+
|
| 242 |
+
return torch.cat([local_traj, headings.unsqueeze(-1)], dim=-1).float() # Return (B, N, 3)
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
class RunningNorm(nn.Module):
|
| 246 |
+
def __init__(self, num_features, momentum=0.1, eps=1e-5):
|
| 247 |
+
super().__init__()
|
| 248 |
+
self.momentum = momentum
|
| 249 |
+
self.eps = eps
|
| 250 |
+
|
| 251 |
+
self.register_buffer('running_mean', torch.zeros(num_features))
|
| 252 |
+
self.register_buffer('running_std', torch.ones(num_features))
|
| 253 |
+
|
| 254 |
+
def _reduce_dims(self, x):
|
| 255 |
+
# Dimensions to reduce: batch and spatial (leave channel/features alone)
|
| 256 |
+
return [0] + list(range(2, x.dim()))
|
| 257 |
+
|
| 258 |
+
def update_stats(self, x):
|
| 259 |
+
dims = self._reduce_dims(x)
|
| 260 |
+
batch_mean = x.mean(dim=dims)
|
| 261 |
+
batch_std = x.std(dim=dims, unbiased=False)
|
| 262 |
+
|
| 263 |
+
# Update running stats
|
| 264 |
+
with torch.no_grad():
|
| 265 |
+
self.running_mean = (1 - self.momentum) * self.running_mean + self.momentum * batch_mean
|
| 266 |
+
self.running_std = (1 - self.momentum) * self.running_std + self.momentum * batch_std
|
| 267 |
+
|
| 268 |
+
def normalize(self, x):
|
| 269 |
+
mean = self.running_mean.view(1, -1, *[1] * (x.dim() - 2))
|
| 270 |
+
std = self.running_std.view(1, -1, *[1] * (x.dim() - 2))
|
| 271 |
+
return (x - mean) / (std + self.eps)
|
| 272 |
+
|
| 273 |
+
def denormalize(self, x):
|
| 274 |
+
mean = self.running_mean.view(1, -1, *[1] * (x.dim() - 2))
|
| 275 |
+
std = self.running_std.view(1, -1, *[1] * (x.dim() - 2))
|
| 276 |
+
return x * (std + self.eps) + mean
|
| 277 |
+
|
| 278 |
+
def forward(self, x):
|
| 279 |
+
if self.training:
|
| 280 |
+
self.update_stats(x)
|
| 281 |
+
return self.normalize(x)
|
|
@@ -0,0 +1,638 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import json
|
| 2 |
+
import os
|
| 3 |
+
import random
|
| 4 |
+
import signal
|
| 5 |
+
import warnings
|
| 6 |
+
|
| 7 |
+
from tqdm import tqdm
|
| 8 |
+
import h5py
|
| 9 |
+
import numpy as np
|
| 10 |
+
import torch
|
| 11 |
+
from PIL import Image
|
| 12 |
+
from torch.utils.data import Dataset, get_worker_info
|
| 13 |
+
from torchvision import transforms
|
| 14 |
+
import torch.distributed as dist
|
| 15 |
+
|
| 16 |
+
from .l2_context import L2ContextMixin
|
| 17 |
+
from util import instantiate_from_config
|
| 18 |
+
|
| 19 |
+
try:
|
| 20 |
+
from torchcodec.decoders import VideoDecoder
|
| 21 |
+
TORCHCODEC_AVAILABLE = True
|
| 22 |
+
except ImportError:
|
| 23 |
+
TORCHCODEC_AVAILABLE = False
|
| 24 |
+
|
| 25 |
+
try:
|
| 26 |
+
from decord import VideoReader, cpu as decord_cpu
|
| 27 |
+
DECORD_AVAILABLE = True
|
| 28 |
+
except ImportError:
|
| 29 |
+
DECORD_AVAILABLE = False
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
|
| 33 |
+
def _normalize_size(size):
|
| 34 |
+
return (size, size) if isinstance(size, int) else tuple(size)
|
| 35 |
+
|
| 36 |
+
|
| 37 |
+
class FrameAdapter:
|
| 38 |
+
def __call__(self, frames):
|
| 39 |
+
raise NotImplementedError
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class PILFrameAdapter(FrameAdapter):
|
| 43 |
+
def __call__(self, frames):
|
| 44 |
+
arrays = np.stack([np.asarray(frame) for frame in frames], axis=0)
|
| 45 |
+
return torch.from_numpy(arrays).permute(0, 3, 1, 2).float() / 255.0
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
class DecordFrameAdapter(FrameAdapter):
|
| 49 |
+
def __call__(self, frames):
|
| 50 |
+
return torch.from_numpy(frames).permute(0, 3, 1, 2).float() / 255.0
|
| 51 |
+
|
| 52 |
+
|
| 53 |
+
class TensorFrameAdapter(FrameAdapter):
|
| 54 |
+
def __call__(self, frames):
|
| 55 |
+
if not torch.is_tensor(frames):
|
| 56 |
+
raise TypeError(f"Expected torch.Tensor frames, got {type(frames).__name__}")
|
| 57 |
+
frames = frames.float()
|
| 58 |
+
if frames.max().item() > 1.0:
|
| 59 |
+
frames = frames / 255.0
|
| 60 |
+
return frames
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
class SpatialTransformPolicy:
|
| 64 |
+
def __init__(self, size):
|
| 65 |
+
self.size = _normalize_size(size)
|
| 66 |
+
|
| 67 |
+
def sample_params(self, frames, context=None):
|
| 68 |
+
return None
|
| 69 |
+
|
| 70 |
+
def apply(self, frames, params, context=None):
|
| 71 |
+
raise NotImplementedError
|
| 72 |
+
|
| 73 |
+
|
| 74 |
+
class ResizeCenterPolicy(SpatialTransformPolicy):
|
| 75 |
+
def apply(self, frames, params, context=None):
|
| 76 |
+
frames = transforms.functional.resize(frames, [min(self.size)], antialias=True)
|
| 77 |
+
return transforms.functional.center_crop(frames, list(self.size))
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
class RandomShiftPolicy(SpatialTransformPolicy):
|
| 81 |
+
def __init__(self, size, max_shift_horizontal=60, max_shift_vertical=60):
|
| 82 |
+
super().__init__(size)
|
| 83 |
+
self.max_shift_horizontal = max_shift_horizontal
|
| 84 |
+
self.max_shift_vertical = max_shift_vertical
|
| 85 |
+
|
| 86 |
+
def sample_params(self, frames, context=None):
|
| 87 |
+
frames = transforms.functional.resize(frames, [min(self.size)], antialias=True)
|
| 88 |
+
crop_height, crop_width = self.size
|
| 89 |
+
width, height = frames.shape[-1], frames.shape[-2]
|
| 90 |
+
center_left = (width - crop_width) // 2
|
| 91 |
+
center_top = (height - crop_height) // 2
|
| 92 |
+
|
| 93 |
+
shift_horizontal = random.randint(-self.max_shift_horizontal, self.max_shift_horizontal)
|
| 94 |
+
shift_vertical = random.randint(-self.max_shift_vertical, self.max_shift_vertical)
|
| 95 |
+
|
| 96 |
+
left = max(0, min(center_left + shift_horizontal, width - crop_width))
|
| 97 |
+
top = max(0, min(center_top + shift_vertical, height - crop_height))
|
| 98 |
+
return left, top
|
| 99 |
+
|
| 100 |
+
def apply(self, frames, params, context=None):
|
| 101 |
+
frames = transforms.functional.resize(frames, [min(self.size)], antialias=True)
|
| 102 |
+
left, top = self.sample_params(frames) if params is None else params
|
| 103 |
+
crop_height, crop_width = self.size
|
| 104 |
+
return frames[..., top:top + crop_height, left:left + crop_width]
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
class RandomResizedCenterPolicy(SpatialTransformPolicy):
|
| 108 |
+
def __init__(self, size, scale=(0.5, 1.0)):
|
| 109 |
+
super().__init__(size)
|
| 110 |
+
self.scale = scale
|
| 111 |
+
|
| 112 |
+
def sample_params(self, frames, context=None):
|
| 113 |
+
height, width = frames.shape[-2], frames.shape[-1]
|
| 114 |
+
area = height * width
|
| 115 |
+
aspect_ratio = width / height
|
| 116 |
+
target_area = random.uniform(*self.scale) * area
|
| 117 |
+
|
| 118 |
+
new_width = int(round((target_area * aspect_ratio) ** 0.5))
|
| 119 |
+
new_height = int(round((target_area / aspect_ratio) ** 0.5))
|
| 120 |
+
return new_height, new_width
|
| 121 |
+
|
| 122 |
+
def apply(self, frames, params, context=None):
|
| 123 |
+
new_height, new_width = self.sample_params(frames) if params is None else params
|
| 124 |
+
frames = transforms.functional.resize(frames, [new_height, new_width], antialias=True)
|
| 125 |
+
return transforms.functional.center_crop(frames, list(self.size))
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
class CameraCalibrationPolicy(SpatialTransformPolicy):
|
| 129 |
+
def __init__(
|
| 130 |
+
self,
|
| 131 |
+
size,
|
| 132 |
+
transform=None,
|
| 133 |
+
transform_config=None,
|
| 134 |
+
calibration_key="intrinsics",
|
| 135 |
+
fallback_policy=None,
|
| 136 |
+
):
|
| 137 |
+
super().__init__(size)
|
| 138 |
+
self.transform = transform if transform is not None else instantiate_from_config(transform_config)
|
| 139 |
+
self.calibration_key = calibration_key
|
| 140 |
+
self.fallback_policy = fallback_policy or ResizeCenterPolicy(size)
|
| 141 |
+
|
| 142 |
+
def apply(self, frames, params, context=None):
|
| 143 |
+
if context is None or self.calibration_key not in context:
|
| 144 |
+
# print(f"[CamCalib] FALLBACK — key={self.calibration_key!r} context_keys={list(context.keys()) if context else None}", flush=True)
|
| 145 |
+
return self.fallback_policy.apply(frames, params, context=context)
|
| 146 |
+
return self.transform(frames, context[self.calibration_key])
|
| 147 |
+
|
| 148 |
+
|
| 149 |
+
class ClipAugmenter:
|
| 150 |
+
def __init__(self, adapter, spatial_policy):
|
| 151 |
+
self.adapter = adapter
|
| 152 |
+
self.spatial_policy = spatial_policy
|
| 153 |
+
|
| 154 |
+
def __call__(self, frames, context=None):
|
| 155 |
+
frames = self.adapter(frames)
|
| 156 |
+
params = self.spatial_policy.sample_params(frames, context=context)
|
| 157 |
+
frames = self.spatial_policy.apply(frames, params, context=context)
|
| 158 |
+
return frames * 2 - 1
|
| 159 |
+
|
| 160 |
+
|
| 161 |
+
def _build_frame_adapter(input_format):
|
| 162 |
+
if input_format == "pil":
|
| 163 |
+
return PILFrameAdapter()
|
| 164 |
+
if input_format == "decord":
|
| 165 |
+
return DecordFrameAdapter()
|
| 166 |
+
if input_format in {"tensor", "torchcodec"}:
|
| 167 |
+
return TensorFrameAdapter()
|
| 168 |
+
raise ValueError(f"Unknown input format: {input_format}")
|
| 169 |
+
|
| 170 |
+
|
| 171 |
+
def _build_spatial_policy(aug, size, scale_min=0.15, scale_max=0.5):
|
| 172 |
+
if aug == "resize_center":
|
| 173 |
+
return ResizeCenterPolicy(size)
|
| 174 |
+
if aug == "random_resize_center":
|
| 175 |
+
return RandomResizedCenterPolicy(size, scale=(scale_min, scale_max))
|
| 176 |
+
if aug == "random_shift":
|
| 177 |
+
return RandomShiftPolicy(size, max_shift_horizontal=60, max_shift_vertical=30)
|
| 178 |
+
raise ValueError(f"Unknown augmentation type: {aug}")
|
| 179 |
+
|
| 180 |
+
|
| 181 |
+
def build_clip_augmenter(*, aug, size, input_format, scale_min=0.15, scale_max=0.5, spatial_transform_config=None):
|
| 182 |
+
spatial_policy = (
|
| 183 |
+
instantiate_from_config(spatial_transform_config)
|
| 184 |
+
if spatial_transform_config is not None
|
| 185 |
+
else _build_spatial_policy(aug, size, scale_min=scale_min, scale_max=scale_max)
|
| 186 |
+
)
|
| 187 |
+
return ClipAugmenter(
|
| 188 |
+
adapter=_build_frame_adapter(input_format),
|
| 189 |
+
spatial_policy=spatial_policy,
|
| 190 |
+
)
|
| 191 |
+
|
| 192 |
+
|
| 193 |
+
def concat_frame_batches(*frame_batches):
|
| 194 |
+
first_batch = frame_batches[0]
|
| 195 |
+
if isinstance(first_batch, np.ndarray):
|
| 196 |
+
return np.concatenate(frame_batches, axis=0)
|
| 197 |
+
if torch.is_tensor(first_batch):
|
| 198 |
+
return torch.cat(frame_batches, dim=0)
|
| 199 |
+
raise TypeError(f"Unsupported frame batch type: {type(first_batch).__name__}")
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def _read_h5_value(node):
|
| 203 |
+
if isinstance(node, h5py.Dataset):
|
| 204 |
+
value = node[()]
|
| 205 |
+
if isinstance(value, bytes):
|
| 206 |
+
value = value.decode()
|
| 207 |
+
if isinstance(value, str) and value[:1] in "{[":
|
| 208 |
+
try:
|
| 209 |
+
return json.loads(value)
|
| 210 |
+
except json.JSONDecodeError:
|
| 211 |
+
pass
|
| 212 |
+
if isinstance(value, np.ndarray):
|
| 213 |
+
return value.tolist()
|
| 214 |
+
return value.item() if hasattr(value, "item") else value
|
| 215 |
+
return {key: _read_h5_value(node[key]) for key in node.keys()}
|
| 216 |
+
|
| 217 |
+
|
| 218 |
+
class DatasetMultiFrameIdxMapping(Dataset):
|
| 219 |
+
"""
|
| 220 |
+
Template dataset that maps each index to a specific frame in a specific video.
|
| 221 |
+
Subclasses must implement get_images_and_indices() and apply_transforms().
|
| 222 |
+
"""
|
| 223 |
+
def __init__(self):
|
| 224 |
+
super().__init__()
|
| 225 |
+
self.index_to_starting_frame_map = []
|
| 226 |
+
|
| 227 |
+
def __len__(self):
|
| 228 |
+
return len(self.index_to_starting_frame_map)
|
| 229 |
+
|
| 230 |
+
def get_images_and_indices(self, idx):
|
| 231 |
+
raise NotImplementedError
|
| 232 |
+
|
| 233 |
+
def apply_transforms(self, images, context=None):
|
| 234 |
+
raise NotImplementedError
|
| 235 |
+
|
| 236 |
+
def build_context(self, metadata):
|
| 237 |
+
return None
|
| 238 |
+
|
| 239 |
+
def __getitem__(self, idx):
|
| 240 |
+
images, metadata = self.get_images_and_indices(idx)
|
| 241 |
+
images = self.apply_transforms(images, context=self.build_context(metadata))
|
| 242 |
+
return {"images": images, "frame_rate": self.frame_rate}
|
| 243 |
+
|
| 244 |
+
|
| 245 |
+
class MultiHDF5DatasetMultiFrameIdxMapping(DatasetMultiFrameIdxMapping):
|
| 246 |
+
"""
|
| 247 |
+
This dataset maps each index to a specific frame in a specific video.
|
| 248 |
+
Useful for validation and selecting subsets of frames.
|
| 249 |
+
"""
|
| 250 |
+
def __init__(self, size, hdf5_paths_file, num_frames, stored_data_frame_rate=5, frame_rate=5, aug="resize_center", scale_min=0.15, scale_max=0.5):
|
| 251 |
+
super().__init__()
|
| 252 |
+
self.frame_interval = int(stored_data_frame_rate / frame_rate)
|
| 253 |
+
self.frame_rate = frame_rate
|
| 254 |
+
self.stored_data_frame_rate = stored_data_frame_rate
|
| 255 |
+
|
| 256 |
+
self.size = (size, size) if isinstance(size, int) else size
|
| 257 |
+
self.num_frames = num_frames
|
| 258 |
+
self.hdf5_paths_file = hdf5_paths_file
|
| 259 |
+
with open(os.path.expandvars(hdf5_paths_file), "r") as f:
|
| 260 |
+
self.hdf5_paths = f.read().splitlines()
|
| 261 |
+
|
| 262 |
+
self.hdf5_files = [h5py.File(path, "r") for path in self.hdf5_paths]
|
| 263 |
+
|
| 264 |
+
self.scan_h5_files()
|
| 265 |
+
|
| 266 |
+
self.aug = aug
|
| 267 |
+
self.augmenter = build_clip_augmenter(
|
| 268 |
+
aug=self.aug,
|
| 269 |
+
size=self.size,
|
| 270 |
+
input_format="pil",
|
| 271 |
+
scale_min=scale_min,
|
| 272 |
+
scale_max=scale_max,
|
| 273 |
+
)
|
| 274 |
+
|
| 275 |
+
def scan_h5_files(self):
|
| 276 |
+
self.index_to_starting_frame_map = []
|
| 277 |
+
for file in self.hdf5_files:
|
| 278 |
+
keys = list(file.keys())
|
| 279 |
+
for key in keys:
|
| 280 |
+
video_length = len(file[key])
|
| 281 |
+
max_frame_index = video_length - self.num_frames * self.frame_interval - 1
|
| 282 |
+
for i in range(0, max_frame_index + 1):
|
| 283 |
+
self.index_to_starting_frame_map.append((file, key, i))
|
| 284 |
+
|
| 285 |
+
def __str__(self):
|
| 286 |
+
return f"MultiHDF5DatasetMultiFrameIdxMapping({self.hdf5_paths_file}, num_samples={len(self)}, size={self.size}, num_frames={self.num_frames}, frame_interval={self.frame_interval})"
|
| 287 |
+
|
| 288 |
+
def get_images_and_indices(self, idx):
|
| 289 |
+
if idx >= len(self.index_to_starting_frame_map):
|
| 290 |
+
raise IndexError(f"Index {idx} out of range for dataset of length {len(self.index_to_starting_frame_map)}")
|
| 291 |
+
file, key, start_frame = self.index_to_starting_frame_map[idx]
|
| 292 |
+
images = [Image.fromarray(file[key][start_frame + i * self.frame_interval]) for i in range(self.num_frames)]
|
| 293 |
+
return images, (file.filename, key, start_frame)
|
| 294 |
+
|
| 295 |
+
def apply_transforms(self, images, context=None):
|
| 296 |
+
return self.augmenter(images, context=context)
|
| 297 |
+
|
| 298 |
+
def close(self):
|
| 299 |
+
for file in self.hdf5_files:
|
| 300 |
+
file.close()
|
| 301 |
+
|
| 302 |
+
|
| 303 |
+
class MultiMP4DatasetMultiFrameIdxMapping(DatasetMultiFrameIdxMapping):
|
| 304 |
+
def __init__(self, size, mp4_paths_file, num_frames, stored_data_frame_rate=5, frame_rate=5, aug="resize_center", backend=None, subsample_interval=None, intrinsics_h5_path=None, spatial_transform_config=None, validate_frame_rate_sample=True):
|
| 305 |
+
super().__init__()
|
| 306 |
+
self.frame_interval = int(stored_data_frame_rate / frame_rate)
|
| 307 |
+
self.frame_rate = frame_rate
|
| 308 |
+
self.stored_data_frame_rate = stored_data_frame_rate
|
| 309 |
+
self.size = (size, size) if isinstance(size, int) else size
|
| 310 |
+
self.num_frames = num_frames
|
| 311 |
+
self.mp4_paths_file = mp4_paths_file
|
| 312 |
+
self.subsample_interval = subsample_interval
|
| 313 |
+
self.intrinsics_h5_path = os.path.expandvars(intrinsics_h5_path) if intrinsics_h5_path is not None else None
|
| 314 |
+
self.intrinsics_file = None
|
| 315 |
+
|
| 316 |
+
with open(os.path.expandvars(mp4_paths_file), "r") as f:
|
| 317 |
+
# the list is either a list of mp4 file paths, or a csv with two columns: file_path,length_in_frames, with a header row.
|
| 318 |
+
lines = f.read().splitlines()
|
| 319 |
+
if len(lines) > 1 and len(lines[0].split(",")) == 2:
|
| 320 |
+
# CSV format
|
| 321 |
+
self.mp4_paths = [self._normalize_path(p.split(",")[0]) for p in lines[1:]]
|
| 322 |
+
mp4_lengths = [int(p.split(",")[1]) for p in lines[1:]]
|
| 323 |
+
# CSV format: create a {'path': length_in_frames} dict
|
| 324 |
+
self.mp4_lengths_in_frames = dict(zip(self.mp4_paths, mp4_lengths))
|
| 325 |
+
else:
|
| 326 |
+
# Plain list of file paths
|
| 327 |
+
self.mp4_paths = [self._normalize_path(p) for p in lines]
|
| 328 |
+
self.mp4_lengths_in_frames = None
|
| 329 |
+
|
| 330 |
+
|
| 331 |
+
if backend is not None:
|
| 332 |
+
self.backend = backend
|
| 333 |
+
assert self.backend in ["decord", "torchcodec"], f"Unknown backend {self.backend}"
|
| 334 |
+
assert (self.backend == "decord" and DECORD_AVAILABLE) or (self.backend == "torchcodec" and TORCHCODEC_AVAILABLE), f"Specified backend {self.backend} is not available"
|
| 335 |
+
else:
|
| 336 |
+
if DECORD_AVAILABLE:
|
| 337 |
+
self.backend = "decord"
|
| 338 |
+
elif TORCHCODEC_AVAILABLE:
|
| 339 |
+
self.backend = "torchcodec"
|
| 340 |
+
else:
|
| 341 |
+
raise ImportError("Either Decord or TorchCodec must be installed to use MultiMP4DatasetMultiFrameIdxMapping")
|
| 342 |
+
|
| 343 |
+
self.validate_frame_rate_sample = validate_frame_rate_sample
|
| 344 |
+
if self.validate_frame_rate_sample:
|
| 345 |
+
self._validate_frame_rate_sample()
|
| 346 |
+
self.scan_mp4_files_distributed()
|
| 347 |
+
|
| 348 |
+
if self.backend == "decord":
|
| 349 |
+
self.read_frames = self.read_frames_decord
|
| 350 |
+
elif self.backend == "torchcodec":
|
| 351 |
+
self.read_frames = self.read_frames_torchcodec
|
| 352 |
+
else:
|
| 353 |
+
raise ImportError("Either Decord or TorchCodec must be installed to use MultiMP4DatasetMultiFrameIdxMapping")
|
| 354 |
+
|
| 355 |
+
self.aug = aug
|
| 356 |
+
self.augmenter = build_clip_augmenter(
|
| 357 |
+
aug=self.aug,
|
| 358 |
+
size=self.size,
|
| 359 |
+
input_format=self.backend,
|
| 360 |
+
spatial_transform_config=spatial_transform_config,
|
| 361 |
+
)
|
| 362 |
+
|
| 363 |
+
# Decode robustness knobs (override via environment if needed).
|
| 364 |
+
self.decode_max_retries = max(int(os.environ.get("ORBIS_DECODE_MAX_RETRIES", "4")), 0)
|
| 365 |
+
self.decode_timeout_s = max(int(os.environ.get("ORBIS_DECODE_TIMEOUT_S", "20")), 0)
|
| 366 |
+
self.decode_warn_limit = max(int(os.environ.get("ORBIS_DECODE_WARN_LIMIT", "50")), 0)
|
| 367 |
+
self._decode_warn_count = 0
|
| 368 |
+
|
| 369 |
+
def _validate_frame_rate_sample(self, sample_size=100):
|
| 370 |
+
paths = random.sample(self.mp4_paths, min(sample_size, len(self.mp4_paths)))
|
| 371 |
+
for path in paths:
|
| 372 |
+
if self.backend == "decord":
|
| 373 |
+
fps = VideoReader(path, ctx=decord_cpu(0), num_threads=-1).get_avg_fps()
|
| 374 |
+
else:
|
| 375 |
+
fps = VideoDecoder(path).metadata.average_fps
|
| 376 |
+
assert self.stored_data_frame_rate == fps, (
|
| 377 |
+
f"Stored data frame rate {self.stored_data_frame_rate} does not match "
|
| 378 |
+
f"actual frame rate {fps} for file {path}"
|
| 379 |
+
)
|
| 380 |
+
|
| 381 |
+
def get_video_length(self, path):
|
| 382 |
+
if self.backend == "decord":
|
| 383 |
+
file = VideoReader(path, ctx=decord_cpu(0), num_threads=-1)
|
| 384 |
+
assert self.stored_data_frame_rate == file.get_avg_fps(), f"Stored data frame rate {self.stored_data_frame_rate} does not match actual frame rate {file.get_avg_fps()} for file {file}"
|
| 385 |
+
video_length = len(file)
|
| 386 |
+
elif self.backend == "torchcodec":
|
| 387 |
+
file = VideoDecoder(path)
|
| 388 |
+
assert self.stored_data_frame_rate == file.metadata.average_fps, f"Stored data frame rate {self.stored_data_frame_rate} does not match actual frame rate {file.metadata.average_fps} for file {file}"
|
| 389 |
+
video_length = file.metadata.num_frames
|
| 390 |
+
else:
|
| 391 |
+
raise ImportError("Either Decord or TorchCodec must be installed to use MultiMP4DatasetMultiFrameIdxMapping")
|
| 392 |
+
return video_length
|
| 393 |
+
|
| 394 |
+
def _normalize_path(self, path):
|
| 395 |
+
return os.path.normpath(os.path.expandvars(os.path.expanduser(path)))
|
| 396 |
+
|
| 397 |
+
def _get_video_id(self, path):
|
| 398 |
+
return os.path.basename(path).split(".")[0]
|
| 399 |
+
|
| 400 |
+
def _get_intrinsics_file(self):
|
| 401 |
+
if self.intrinsics_h5_path is None:
|
| 402 |
+
return None
|
| 403 |
+
if self.intrinsics_file is None:
|
| 404 |
+
self.intrinsics_file = h5py.File(self.intrinsics_h5_path, "r")
|
| 405 |
+
return self.intrinsics_file
|
| 406 |
+
|
| 407 |
+
def _load_intrinsics(self, path):
|
| 408 |
+
intrinsics_file = self._get_intrinsics_file()
|
| 409 |
+
if intrinsics_file is None:
|
| 410 |
+
return None
|
| 411 |
+
video_id = self._get_video_id(path)
|
| 412 |
+
if video_id not in intrinsics_file or "intrinsics" not in intrinsics_file[video_id]:
|
| 413 |
+
return None
|
| 414 |
+
return _read_h5_value(intrinsics_file[video_id]["intrinsics"])
|
| 415 |
+
|
| 416 |
+
def build_context(self, metadata):
|
| 417 |
+
_, path, start_frame = metadata
|
| 418 |
+
context = {"path": path, "video_id": self._get_video_id(path), "start_frame": start_frame}
|
| 419 |
+
intrinsics = self._load_intrinsics(path)
|
| 420 |
+
if intrinsics is not None:
|
| 421 |
+
context["intrinsics"] = intrinsics
|
| 422 |
+
return context
|
| 423 |
+
|
| 424 |
+
def scan_mp4_files(self):
|
| 425 |
+
is_rank0 = not (dist.is_available() and dist.is_initialized()) or dist.get_rank() == 0
|
| 426 |
+
self.index_to_starting_frame_map = []
|
| 427 |
+
for path in tqdm(
|
| 428 |
+
self.mp4_paths,
|
| 429 |
+
desc=f"Scanning MP4 files in {self.__class__.__name__}",
|
| 430 |
+
disable=not is_rank0,
|
| 431 |
+
):
|
| 432 |
+
if self.mp4_lengths_in_frames is not None and path in self.mp4_lengths_in_frames:
|
| 433 |
+
video_length = self.mp4_lengths_in_frames[path]
|
| 434 |
+
else:
|
| 435 |
+
video_length = self.get_video_length(path)
|
| 436 |
+
|
| 437 |
+
frame_interval = self.frame_interval if self.subsample_interval is None else self.subsample_interval*self.frame_interval
|
| 438 |
+
|
| 439 |
+
max_frame_index = video_length - self.num_frames * self.frame_interval - 1
|
| 440 |
+
# assert max_frame_index > 0
|
| 441 |
+
if not max_frame_index > 0: continue
|
| 442 |
+
for i in range(0, max_frame_index + 1, frame_interval):
|
| 443 |
+
self.index_to_starting_frame_map.append((path, i))
|
| 444 |
+
|
| 445 |
+
def scan_mp4_files_distributed(self):
|
| 446 |
+
if not (dist.is_available() and dist.is_initialized()):
|
| 447 |
+
self.scan_mp4_files()
|
| 448 |
+
return
|
| 449 |
+
|
| 450 |
+
# Scan only on rank 0 and broadcast index mapping to all other ranks.
|
| 451 |
+
if dist.get_rank() == 0:
|
| 452 |
+
self.scan_mp4_files()
|
| 453 |
+
index_map = self.index_to_starting_frame_map
|
| 454 |
+
else:
|
| 455 |
+
index_map = None
|
| 456 |
+
|
| 457 |
+
obj_list = [index_map]
|
| 458 |
+
dist.broadcast_object_list(obj_list, src=0)
|
| 459 |
+
self.index_to_starting_frame_map = obj_list[0]
|
| 460 |
+
|
| 461 |
+
def __str__(self):
|
| 462 |
+
return f"MultiMP4DatasetMultiFrameIdxMapping({self.mp4_paths_file}, num_samples={len(self)}, size={self.size}, num_frames={self.num_frames}, frame_interval={self.frame_interval})"
|
| 463 |
+
|
| 464 |
+
def apply_transforms(self, images, context=None):
|
| 465 |
+
return self.augmenter(images, context=context)
|
| 466 |
+
|
| 467 |
+
def read_frames_decord(self, path, start_frame):
|
| 468 |
+
indices = list(range(start_frame, start_frame + self.num_frames * self.frame_interval, self.frame_interval))
|
| 469 |
+
file = VideoReader(path, ctx=decord_cpu(0))
|
| 470 |
+
frames = file.get_batch(indices).asnumpy()
|
| 471 |
+
return frames
|
| 472 |
+
|
| 473 |
+
def read_frames_torchcodec(self, path, start_frame):
|
| 474 |
+
file = VideoDecoder(path)
|
| 475 |
+
frames = file[start_frame:start_frame + self.num_frames * self.frame_interval:self.frame_interval]
|
| 476 |
+
return frames
|
| 477 |
+
|
| 478 |
+
def _decode_with_timeout(self, path, start_frame):
|
| 479 |
+
if self.decode_timeout_s <= 0 or not hasattr(signal, "SIGALRM"):
|
| 480 |
+
return self.read_frames(path, start_frame)
|
| 481 |
+
|
| 482 |
+
def _timeout_handler(signum, frame): # pragma: no cover - signal handler
|
| 483 |
+
raise TimeoutError(
|
| 484 |
+
f"Decode timed out after {self.decode_timeout_s}s for {path} at frame {start_frame}"
|
| 485 |
+
)
|
| 486 |
+
|
| 487 |
+
previous_handler = signal.signal(signal.SIGALRM, _timeout_handler)
|
| 488 |
+
signal.setitimer(signal.ITIMER_REAL, float(self.decode_timeout_s))
|
| 489 |
+
try:
|
| 490 |
+
return self.read_frames(path, start_frame)
|
| 491 |
+
finally:
|
| 492 |
+
signal.setitimer(signal.ITIMER_REAL, 0.0)
|
| 493 |
+
signal.signal(signal.SIGALRM, previous_handler)
|
| 494 |
+
|
| 495 |
+
def _warn_decode_error(self, msg):
|
| 496 |
+
if self._decode_warn_count < self.decode_warn_limit:
|
| 497 |
+
warnings.warn(msg)
|
| 498 |
+
self._decode_warn_count += 1
|
| 499 |
+
elif self._decode_warn_count == self.decode_warn_limit:
|
| 500 |
+
warnings.warn(
|
| 501 |
+
"Reached ORBIS_DECODE_WARN_LIMIT; suppressing further decode warnings."
|
| 502 |
+
)
|
| 503 |
+
self._decode_warn_count += 1
|
| 504 |
+
|
| 505 |
+
def get_images_and_indices(self, idx):
|
| 506 |
+
if idx >= len(self.index_to_starting_frame_map):
|
| 507 |
+
raise IndexError(f"Index {idx} out of range for dataset of length {len(self.index_to_starting_frame_map)}")
|
| 508 |
+
map_len = len(self.index_to_starting_frame_map)
|
| 509 |
+
worker_info = get_worker_info()
|
| 510 |
+
worker_id = worker_info.id if worker_info is not None else -1
|
| 511 |
+
|
| 512 |
+
last_exc = None
|
| 513 |
+
for attempt in range(self.decode_max_retries + 1):
|
| 514 |
+
attempt_idx = (idx + attempt) % map_len
|
| 515 |
+
path, start_frame = self.index_to_starting_frame_map[attempt_idx]
|
| 516 |
+
try:
|
| 517 |
+
frames = self._decode_with_timeout(path, start_frame)
|
| 518 |
+
if frames is None:
|
| 519 |
+
raise RuntimeError("Decoder returned None")
|
| 520 |
+
num_decoded = frames.shape[0] if hasattr(frames, "shape") else len(frames)
|
| 521 |
+
if num_decoded != self.num_frames:
|
| 522 |
+
raise RuntimeError(
|
| 523 |
+
f"Decoder returned {num_decoded} frames, expected {self.num_frames}"
|
| 524 |
+
)
|
| 525 |
+
return frames, (None, path, start_frame)
|
| 526 |
+
except Exception as exc:
|
| 527 |
+
last_exc = exc
|
| 528 |
+
self._warn_decode_error(
|
| 529 |
+
f"[MultiMP4Dataset] decode failure worker={worker_id} attempt={attempt + 1}/"
|
| 530 |
+
f"{self.decode_max_retries + 1} idx={attempt_idx} path={path} "
|
| 531 |
+
f"start_frame={start_frame}: {type(exc).__name__}: {exc}"
|
| 532 |
+
)
|
| 533 |
+
|
| 534 |
+
raise RuntimeError(
|
| 535 |
+
f"Failed to decode sample idx={idx} after {self.decode_max_retries + 1} attempts"
|
| 536 |
+
) from last_exc
|
| 537 |
+
|
| 538 |
+
def close(self):
|
| 539 |
+
if self.intrinsics_file is not None:
|
| 540 |
+
self.intrinsics_file.close()
|
| 541 |
+
|
| 542 |
+
|
| 543 |
+
class MultiMP4DatasetMultiFrameIdxMappingWithL2Context(L2ContextMixin, MultiMP4DatasetMultiFrameIdxMapping):
|
| 544 |
+
"""
|
| 545 |
+
Extends MultiMP4DatasetMultiFrameIdxMapping to also return a 'l2_context' key
|
| 546 |
+
containing num_l2_context frames at l2_frame_rate (typically 1 Hz) that end on
|
| 547 |
+
the first L1 frame.
|
| 548 |
+
|
| 549 |
+
The l2_context frames are sampled from the same video at the same spatial crop,
|
| 550 |
+
with l2_context[-1] matching images[0].
|
| 551 |
+
|
| 552 |
+
Args:
|
| 553 |
+
num_l2_context : number of L2 context frames (default 3)
|
| 554 |
+
l2_frame_rate : frame rate for L2 context frames in Hz (default 1.0)
|
| 555 |
+
All other args forwarded to MultiMP4DatasetMultiFrameIdxMapping.
|
| 556 |
+
|
| 557 |
+
Batch output adds:
|
| 558 |
+
'l2_context' : (num_l2_context, C, H, W) pixel tensor in [-1, 1],
|
| 559 |
+
ordered oldest → newest (l2_context[-1] is the same
|
| 560 |
+
frame as images[0]).
|
| 561 |
+
"""
|
| 562 |
+
|
| 563 |
+
def __init__(self, num_l2_context=3, l2_frame_rate=1.0, l1_context_frames=1, **kwargs):
|
| 564 |
+
super().__init__(**kwargs)
|
| 565 |
+
self._init_l2_context(
|
| 566 |
+
num_l2_context=num_l2_context,
|
| 567 |
+
l2_frame_rate=l2_frame_rate,
|
| 568 |
+
l1_context_frames=l1_context_frames,
|
| 569 |
+
)
|
| 570 |
+
self.index_to_starting_frame_map = self.filter_index_map_with_l2_headroom(
|
| 571 |
+
self.index_to_starting_frame_map,
|
| 572 |
+
)
|
| 573 |
+
|
| 574 |
+
def get_images_and_indices(self, idx):
|
| 575 |
+
"""Return (l1_frames, l2_frames, metadata)."""
|
| 576 |
+
if idx >= len(self.index_to_starting_frame_map):
|
| 577 |
+
raise IndexError(f"Index {idx} out of range.")
|
| 578 |
+
path, start_frame = self.index_to_starting_frame_map[idx]
|
| 579 |
+
l1_indices, l2_indices = self.get_l1_and_l2_indices(start_frame, self.num_frames)
|
| 580 |
+
|
| 581 |
+
all_indices = l1_indices + l2_indices
|
| 582 |
+
all_frames = self._decode_indices(path, all_indices)
|
| 583 |
+
|
| 584 |
+
l1_frames = all_frames[:len(l1_indices)]
|
| 585 |
+
l2_frames = all_frames[len(l1_indices):]
|
| 586 |
+
return l1_frames, l2_frames, (None, path, start_frame)
|
| 587 |
+
|
| 588 |
+
def _decode_indices(self, path, indices):
|
| 589 |
+
"""Decode an arbitrary list of frame indices from an MP4."""
|
| 590 |
+
if self.backend == "decord":
|
| 591 |
+
file = VideoReader(path, ctx=decord_cpu(0))
|
| 592 |
+
frames = file.get_batch(indices).asnumpy()
|
| 593 |
+
elif self.backend == "torchcodec":
|
| 594 |
+
file = VideoDecoder(path)
|
| 595 |
+
frames = torch.stack([file[i] for i in indices])
|
| 596 |
+
else:
|
| 597 |
+
raise RuntimeError(f"Unknown backend {self.backend}")
|
| 598 |
+
return frames
|
| 599 |
+
|
| 600 |
+
def __getitem__(self, idx):
|
| 601 |
+
map_len = len(self.index_to_starting_frame_map)
|
| 602 |
+
worker_info = get_worker_info()
|
| 603 |
+
worker_id = worker_info.id if worker_info is not None else -1
|
| 604 |
+
last_exc = None
|
| 605 |
+
|
| 606 |
+
for attempt in range(self.decode_max_retries + 1):
|
| 607 |
+
attempt_idx = (idx + attempt) % map_len
|
| 608 |
+
path, start_frame = self.index_to_starting_frame_map[attempt_idx]
|
| 609 |
+
try:
|
| 610 |
+
l1_frames, l2_frames, _ = self.get_images_and_indices(attempt_idx)
|
| 611 |
+
all_frames = concat_frame_batches(l1_frames, l2_frames)
|
| 612 |
+
all_transformed = self.augmenter(
|
| 613 |
+
all_frames,
|
| 614 |
+
context=self.build_context((None, path, start_frame)),
|
| 615 |
+
)
|
| 616 |
+
|
| 617 |
+
n_l1 = self.num_frames
|
| 618 |
+
images_l1 = all_transformed[:n_l1] # (F, C, H, W)
|
| 619 |
+
images_l2 = all_transformed[n_l1:] # (num_l2_context, C, H, W)
|
| 620 |
+
|
| 621 |
+
return {
|
| 622 |
+
"images": images_l1,
|
| 623 |
+
"l2_context": images_l2,
|
| 624 |
+
"frame_rate": self.frame_rate,
|
| 625 |
+
}
|
| 626 |
+
except Exception as exc:
|
| 627 |
+
last_exc = exc
|
| 628 |
+
self._warn_decode_error(
|
| 629 |
+
f"[MultiMP4DatasetWithL2] decode failure worker={worker_id} "
|
| 630 |
+
f"attempt={attempt + 1}/{self.decode_max_retries + 1} "
|
| 631 |
+
f"idx={attempt_idx} path={path} start_frame={start_frame}: "
|
| 632 |
+
f"{type(exc).__name__}: {exc}"
|
| 633 |
+
)
|
| 634 |
+
|
| 635 |
+
raise RuntimeError(
|
| 636 |
+
f"Failed to decode sample idx={idx} after {self.decode_max_retries + 1} attempts"
|
| 637 |
+
) from last_exc
|
| 638 |
+
|
|
@@ -0,0 +1,183 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
from torch.utils.data import Dataset
|
| 2 |
+
import json
|
| 3 |
+
import os, json
|
| 4 |
+
from collections import OrderedDict
|
| 5 |
+
import numpy as np
|
| 6 |
+
import torch
|
| 7 |
+
from torch.utils.data import Dataset
|
| 8 |
+
import cv2
|
| 9 |
+
from PIL import Image
|
| 10 |
+
import sqlite3
|
| 11 |
+
import pandas as pd
|
| 12 |
+
from .utils import get_trajectory_from_speeds_and_yaw_rates
|
| 13 |
+
from .l2_context import L2ContextMixin
|
| 14 |
+
from .video_loaders import build_clip_augmenter
|
| 15 |
+
|
| 16 |
+
|
| 17 |
+
class VistaStyleNuScenesLoader(Dataset):
|
| 18 |
+
def __init__(self, *, size, json_path, images_root, num_frames=None, frame_rate_multiplier=1, aug="resize_center", sample_indices=None):
|
| 19 |
+
super().__init__()
|
| 20 |
+
self.size = (size, size) if isinstance(size, int) else size
|
| 21 |
+
self.json_path = json_path
|
| 22 |
+
self.num_frames = num_frames
|
| 23 |
+
self.images_root = images_root
|
| 24 |
+
self.aug = aug
|
| 25 |
+
|
| 26 |
+
assert frame_rate_multiplier <= 1, "Frame rate multiplier should be less than or equal to 1"
|
| 27 |
+
assert 1/frame_rate_multiplier == int(1/frame_rate_multiplier), 'reciprocal of frame_rate_multiplier must be an integer'
|
| 28 |
+
self.frame_interval = int(1/frame_rate_multiplier)
|
| 29 |
+
|
| 30 |
+
with open(json_path, 'r') as f:
|
| 31 |
+
self.data = json.load(f)
|
| 32 |
+
|
| 33 |
+
self.sample_indices = sample_indices
|
| 34 |
+
if sample_indices is not None:
|
| 35 |
+
self.data = [self.data[i] for i in sample_indices]
|
| 36 |
+
self.augmenter = build_clip_augmenter(
|
| 37 |
+
aug=self.aug,
|
| 38 |
+
size=self.size,
|
| 39 |
+
input_format="pil",
|
| 40 |
+
)
|
| 41 |
+
|
| 42 |
+
def __getitem__(self, index):
|
| 43 |
+
sample = self.data[index]
|
| 44 |
+
frame_paths = sample['frames'][::self.frame_interval]
|
| 45 |
+
if self.num_frames is not None:
|
| 46 |
+
if len(frame_paths) < self.num_frames:
|
| 47 |
+
print(f"Warning: Number of frames {len(frame_paths)} is less than the required {self.num_frames}")
|
| 48 |
+
raise ValueError(f"Number of frames {len(frame_paths)} is less than the required {self.num_frames}")
|
| 49 |
+
frame_paths = frame_paths[:self.num_frames]
|
| 50 |
+
images = [cv2.imread(os.path.join(self.images_root, frame_path)) for frame_path in frame_paths]
|
| 51 |
+
images = [Image.fromarray(cv2.cvtColor(image, cv2.COLOR_BGR2RGB)) for image in images]
|
| 52 |
+
return self.augmenter(images)
|
| 53 |
+
|
| 54 |
+
def __len__(self):
|
| 55 |
+
return len(self.data)
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def extract_pose_table(db_path):
|
| 60 |
+
"""
|
| 61 |
+
Connects to the nuPlan SQLite database and extracts the entire ego_pose table.
|
| 62 |
+
Returns a pandas DataFrame containing the pose metadata.
|
| 63 |
+
"""
|
| 64 |
+
conn = sqlite3.connect(db_path)
|
| 65 |
+
query = "SELECT * FROM ego_pose;"
|
| 66 |
+
pose_df = pd.read_sql_query(query, conn)
|
| 67 |
+
conn.close()
|
| 68 |
+
# Ensure timestamp is numeric and sort by timestamp
|
| 69 |
+
pose_df['timestamp'] = pd.to_numeric(pose_df['timestamp'], errors='coerce')
|
| 70 |
+
pose_df.sort_values("timestamp", inplace=True)
|
| 71 |
+
return pose_df
|
| 72 |
+
|
| 73 |
+
def get_pose(pose_df, pose_token):
|
| 74 |
+
tk = bytes.fromhex(pose_token)
|
| 75 |
+
pose = pose_df[pose_df['token'] == tk]
|
| 76 |
+
if pose.empty:
|
| 77 |
+
raise ValueError(f"Pose with token {pose_token} not found.")
|
| 78 |
+
return pose
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
def __getattr__(name):
|
| 82 |
+
if name == "VistaStyleNuScenesLoaderSteering":
|
| 83 |
+
from data.steering_loaders import VistaStyleNuScenesLoaderSteering
|
| 84 |
+
return VistaStyleNuScenesLoaderSteering
|
| 85 |
+
if name == "VistaStyleNuScenesLoaderSteeringWithL2Context":
|
| 86 |
+
from data.steering_loaders import VistaStyleNuScenesLoaderSteeringWithL2Context
|
| 87 |
+
return VistaStyleNuScenesLoaderSteeringWithL2Context
|
| 88 |
+
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
|
| 89 |
+
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
class VistaStyleNuScenesLoaderWithL2Context(L2ContextMixin, Dataset):
|
| 93 |
+
def __init__(self, *, size, json_path, images_root, num_frames=None,
|
| 94 |
+
frame_rate_multiplier=1, stored_data_frame_rate=None, frame_rate=None,
|
| 95 |
+
num_l2_context=0, l2_frame_rate=1.0, l1_context_frames=1,
|
| 96 |
+
aug=None, sample_indices=None):
|
| 97 |
+
super().__init__()
|
| 98 |
+
self.size = (size, size) if isinstance(size, int) else size
|
| 99 |
+
self.json_path = json_path
|
| 100 |
+
self.num_frames = num_frames
|
| 101 |
+
self.images_root = images_root
|
| 102 |
+
self.aug = aug or "resize_center"
|
| 103 |
+
|
| 104 |
+
# Compute frame_interval from stored_data_frame_rate/frame_rate when provided,
|
| 105 |
+
# otherwise fall back to frame_rate_multiplier.
|
| 106 |
+
if stored_data_frame_rate is not None and frame_rate is not None:
|
| 107 |
+
assert stored_data_frame_rate % frame_rate == 0, \
|
| 108 |
+
'stored_data_frame_rate must be divisible by frame_rate'
|
| 109 |
+
self.frame_interval = int(stored_data_frame_rate / frame_rate)
|
| 110 |
+
self.stored_data_frame_rate = stored_data_frame_rate
|
| 111 |
+
self.frame_rate = float(frame_rate)
|
| 112 |
+
else:
|
| 113 |
+
assert frame_rate_multiplier <= 1, "Frame rate multiplier should be less than or equal to 1"
|
| 114 |
+
assert 1/frame_rate_multiplier == int(1/frame_rate_multiplier), \
|
| 115 |
+
'reciprocal of frame_rate_multiplier must be an integer'
|
| 116 |
+
self.frame_interval = int(1/frame_rate_multiplier)
|
| 117 |
+
# Only set if not already provided by a subclass before calling super().__init__
|
| 118 |
+
if not hasattr(self, 'stored_data_frame_rate'):
|
| 119 |
+
self.stored_data_frame_rate = stored_data_frame_rate
|
| 120 |
+
if not hasattr(self, 'frame_rate'):
|
| 121 |
+
self.frame_rate = float(frame_rate) if frame_rate is not None else None
|
| 122 |
+
|
| 123 |
+
self._init_l2_context(
|
| 124 |
+
num_l2_context=num_l2_context,
|
| 125 |
+
l2_frame_rate=l2_frame_rate,
|
| 126 |
+
l1_context_frames=l1_context_frames,
|
| 127 |
+
)
|
| 128 |
+
self.l1_start_offset = self.get_required_l1_start_offset()
|
| 129 |
+
|
| 130 |
+
with open(json_path, 'r') as f:
|
| 131 |
+
self.data = json.load(f)
|
| 132 |
+
|
| 133 |
+
self.sample_indices = sample_indices
|
| 134 |
+
if sample_indices is not None:
|
| 135 |
+
self.data = [self.data[i] for i in sample_indices]
|
| 136 |
+
self.augmenter = build_clip_augmenter(
|
| 137 |
+
aug=self.aug,
|
| 138 |
+
size=self.size,
|
| 139 |
+
input_format="tensor",
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
def _load_raw_frames(self, frame_paths):
|
| 143 |
+
images = [cv2.imread(os.path.join(self.images_root, p)) for p in frame_paths]
|
| 144 |
+
images = [Image.fromarray(cv2.cvtColor(img, cv2.COLOR_BGR2RGB)) for img in images]
|
| 145 |
+
return torch.stack(
|
| 146 |
+
[torch.from_numpy(np.array(img)).permute(2, 0, 1).float() / 255.0 for img in images],
|
| 147 |
+
dim=0,
|
| 148 |
+
)
|
| 149 |
+
|
| 150 |
+
def _load_images(self, frame_paths):
|
| 151 |
+
frames = self._load_raw_frames(frame_paths)
|
| 152 |
+
return self.augmenter(frames)
|
| 153 |
+
|
| 154 |
+
def __getitem__(self, index):
|
| 155 |
+
sample = self.data[index]
|
| 156 |
+
all_frames = sample['frames']
|
| 157 |
+
|
| 158 |
+
l1_start = self.l1_start_offset
|
| 159 |
+
l1_indices = self.get_l1_indices(l1_start, self.num_frames)
|
| 160 |
+
l2_indices = self.get_l2_indices(l1_start)
|
| 161 |
+
all_indices = l1_indices + l2_indices
|
| 162 |
+
|
| 163 |
+
if max(all_indices) >= len(all_frames):
|
| 164 |
+
raise ValueError(
|
| 165 |
+
f"Number of available L1 frames {len(all_frames)} is insufficient for "
|
| 166 |
+
f"num_frames={self.num_frames} with l1_start_offset={l1_start}."
|
| 167 |
+
)
|
| 168 |
+
|
| 169 |
+
images_all = self._load_images([all_frames[i] for i in all_indices])
|
| 170 |
+
images = images_all[:len(l1_indices)]
|
| 171 |
+
|
| 172 |
+
if self.l2_context_enabled:
|
| 173 |
+
l2_context = images_all[len(l1_indices):]
|
| 174 |
+
return {
|
| 175 |
+
'images': images,
|
| 176 |
+
'l2_context': l2_context,
|
| 177 |
+
'frame_rate': torch.tensor(self.frame_rate).float(),
|
| 178 |
+
}
|
| 179 |
+
|
| 180 |
+
return images
|
| 181 |
+
|
| 182 |
+
def __len__(self):
|
| 183 |
+
return len(self.data)
|
|
@@ -0,0 +1,706 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import os
|
| 3 |
+
import sys
|
| 4 |
+
import imageio
|
| 5 |
+
import logging
|
| 6 |
+
|
| 7 |
+
sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
|
| 8 |
+
|
| 9 |
+
import cv2
|
| 10 |
+
import numpy as np
|
| 11 |
+
import torch
|
| 12 |
+
from omegaconf import OmegaConf
|
| 13 |
+
from omegaconf.errors import ConfigTypeError
|
| 14 |
+
from PIL import Image
|
| 15 |
+
from pytorch_lightning import seed_everything
|
| 16 |
+
from torchvision.utils import save_image
|
| 17 |
+
|
| 18 |
+
from data.l2_context import L2ContextMixin
|
| 19 |
+
from data.video_loaders import ClipAugmenter, DecordFrameAdapter, ResizeCenterPolicy, TensorFrameAdapter
|
| 20 |
+
from util import instantiate_from_config
|
| 21 |
+
|
| 22 |
+
try:
|
| 23 |
+
from decord import VideoReader, cpu as decord_cpu
|
| 24 |
+
DECORD_AVAILABLE = True
|
| 25 |
+
except ImportError:
|
| 26 |
+
DECORD_AVAILABLE = False
|
| 27 |
+
|
| 28 |
+
try:
|
| 29 |
+
from torchcodec.decoders import VideoDecoder
|
| 30 |
+
TORCHCODEC_AVAILABLE = True
|
| 31 |
+
except ImportError:
|
| 32 |
+
TORCHCODEC_AVAILABLE = False
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
logger = logging.getLogger(__name__)
|
| 36 |
+
|
| 37 |
+
STEERING_FORMAT = "speed_yawrate"
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class _L1L2FrameIndexer(L2ContextMixin):
|
| 41 |
+
"""Computes L1/L2 frame indices into a single source video via `L2ContextMixin`."""
|
| 42 |
+
|
| 43 |
+
def __init__(self, frame_interval, stored_data_frame_rate, num_l2_context, l2_frame_rate, l1_context_frames):
|
| 44 |
+
self.frame_interval = frame_interval
|
| 45 |
+
self.stored_data_frame_rate = stored_data_frame_rate
|
| 46 |
+
self._init_l2_context(
|
| 47 |
+
num_l2_context=num_l2_context,
|
| 48 |
+
l2_frame_rate=l2_frame_rate,
|
| 49 |
+
l1_context_frames=l1_context_frames,
|
| 50 |
+
require_l2_context=True,
|
| 51 |
+
)
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
def get_rollout_future_frame_count(model, num_gen_frames):
|
| 55 |
+
"""Return the total number of future image frames produced by the rollout."""
|
| 56 |
+
return int(num_gen_frames) * int(model.num_pred_frames)
|
| 57 |
+
|
| 58 |
+
|
| 59 |
+
def resolve_context_image_size(args, config):
|
| 60 |
+
"""Return (height, width) to resize context images to, from CLI args or the training config."""
|
| 61 |
+
if args.height is not None and args.width is not None:
|
| 62 |
+
return int(args.height), int(args.width)
|
| 63 |
+
|
| 64 |
+
try:
|
| 65 |
+
size = OmegaConf.select(config, "data.params.validation.params.size")
|
| 66 |
+
except ConfigTypeError:
|
| 67 |
+
size = None
|
| 68 |
+
if size is None:
|
| 69 |
+
size = config.data.params.train[0].params.size
|
| 70 |
+
size = (size, size) if isinstance(size, int) else tuple(size)
|
| 71 |
+
|
| 72 |
+
height = int(args.height) if args.height is not None else int(size[0])
|
| 73 |
+
width = int(args.width) if args.width is not None else int(size[1])
|
| 74 |
+
return height, width
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
def require_l1l2_model(model):
|
| 78 |
+
"""Raise a clear error if the loaded model isn't an L1-L2 hierarchical model."""
|
| 79 |
+
condition_preprocessor = getattr(model, "condition_preprocessor", None)
|
| 80 |
+
if condition_preprocessor is None or not hasattr(condition_preprocessor, "num_context_frames") \
|
| 81 |
+
or not hasattr(condition_preprocessor, "l2_predictor_frame_rate"):
|
| 82 |
+
raise TypeError(
|
| 83 |
+
"This script requires an L1-L2 hierarchical model, i.e. a `condition_preprocessor` "
|
| 84 |
+
"with a frozen `l2_predictor` (num_context_frames/l2_predictor_frame_rate). "
|
| 85 |
+
f"Got {type(condition_preprocessor).__name__ if condition_preprocessor else None}."
|
| 86 |
+
)
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
def resolve_video_backend():
|
| 90 |
+
"""Pick decord as the primary video backend, falling back to the slower torchcodec if unavailable."""
|
| 91 |
+
if DECORD_AVAILABLE:
|
| 92 |
+
return "decord"
|
| 93 |
+
if TORCHCODEC_AVAILABLE:
|
| 94 |
+
return "torchcodec"
|
| 95 |
+
raise ImportError("Either `decord` or `torchcodec` must be installed to read video frames.")
|
| 96 |
+
|
| 97 |
+
|
| 98 |
+
def get_video_fps_and_length(path, backend):
|
| 99 |
+
"""Return (native_fps, num_frames) for a video file."""
|
| 100 |
+
if backend == "decord":
|
| 101 |
+
reader = VideoReader(path, ctx=decord_cpu(0))
|
| 102 |
+
return float(reader.get_avg_fps()), len(reader)
|
| 103 |
+
decoder = VideoDecoder(path)
|
| 104 |
+
return float(decoder.metadata.average_fps), int(decoder.metadata.num_frames)
|
| 105 |
+
|
| 106 |
+
|
| 107 |
+
def decode_video_frames(path, indices, backend):
|
| 108 |
+
"""Decode an arbitrary list of frame indices from a video file."""
|
| 109 |
+
if backend == "decord":
|
| 110 |
+
reader = VideoReader(path, ctx=decord_cpu(0))
|
| 111 |
+
return reader.get_batch(indices).asnumpy()
|
| 112 |
+
decoder = VideoDecoder(path)
|
| 113 |
+
return torch.stack([decoder[i] for i in indices])
|
| 114 |
+
|
| 115 |
+
|
| 116 |
+
def compute_frame_interval(native_fps, target_frame_rate, label):
|
| 117 |
+
"""Return the integer native-frame stride for `target_frame_rate`, or raise if not exact."""
|
| 118 |
+
ratio = native_fps / float(target_frame_rate)
|
| 119 |
+
rounded = round(ratio)
|
| 120 |
+
if abs(ratio - rounded) > 1e-6:
|
| 121 |
+
raise ValueError(
|
| 122 |
+
f"The video's native frame rate ({native_fps:g} Hz) must be an integer multiple of "
|
| 123 |
+
f"{label} ({target_frame_rate:g} Hz), got ratio={ratio:g}."
|
| 124 |
+
)
|
| 125 |
+
return int(rounded)
|
| 126 |
+
|
| 127 |
+
|
| 128 |
+
def resolve_l2_frame_rate(args, model):
|
| 129 |
+
"""Return the L2 sampling rate, defaulting to (and validating against) the frozen L2 predictor's own rate."""
|
| 130 |
+
model_rate = model.condition_preprocessor.l2_predictor_frame_rate
|
| 131 |
+
if args.l2_frame_rate is None:
|
| 132 |
+
return float(model_rate)
|
| 133 |
+
if abs(float(args.l2_frame_rate) - float(model_rate)) > 1e-6:
|
| 134 |
+
raise ValueError(
|
| 135 |
+
f"--l2_frame_rate={args.l2_frame_rate:g} does not match the frozen L2 predictor's "
|
| 136 |
+
f"trained frame rate ({model_rate:g}); the L2 image context must be sampled at the "
|
| 137 |
+
"rate the L2 predictor was trained on."
|
| 138 |
+
)
|
| 139 |
+
return float(args.l2_frame_rate)
|
| 140 |
+
|
| 141 |
+
|
| 142 |
+
def load_l1_l2_context(video_path, start_frame, l1_frame_rate, l2_frame_rate, l1_context_frames, l2_context_frames, height, width, backend, device):
|
| 143 |
+
"""Sample L1 (high-rate) and L2 (low-rate, further back) context windows from one video."""
|
| 144 |
+
native_fps, video_length = get_video_fps_and_length(video_path, backend)
|
| 145 |
+
frame_interval = compute_frame_interval(native_fps, l1_frame_rate, "--l1_frame_rate")
|
| 146 |
+
compute_frame_interval(native_fps, l2_frame_rate, "--l2_frame_rate")
|
| 147 |
+
|
| 148 |
+
indexer = _L1L2FrameIndexer(
|
| 149 |
+
frame_interval=frame_interval,
|
| 150 |
+
stored_data_frame_rate=native_fps,
|
| 151 |
+
num_l2_context=l2_context_frames,
|
| 152 |
+
l2_frame_rate=l2_frame_rate,
|
| 153 |
+
l1_context_frames=l1_context_frames,
|
| 154 |
+
)
|
| 155 |
+
|
| 156 |
+
l1_span = (l1_context_frames - 1) * frame_interval + 1
|
| 157 |
+
if start_frame is None:
|
| 158 |
+
start_frame = video_length - l1_span
|
| 159 |
+
if start_frame < 0 or start_frame + l1_span > video_length:
|
| 160 |
+
raise ValueError(
|
| 161 |
+
f"Video is too short for the requested L1 context: need frames [{start_frame}, "
|
| 162 |
+
f"{start_frame + l1_span - 1}], but the video only has {video_length} frames."
|
| 163 |
+
)
|
| 164 |
+
|
| 165 |
+
required_offset = indexer.get_required_l1_start_offset()
|
| 166 |
+
if start_frame < required_offset:
|
| 167 |
+
raise ValueError(
|
| 168 |
+
f"Video does not have enough lookback for L2 context: start_frame={start_frame} but "
|
| 169 |
+
f"at least {required_offset} frames of history are required before it. "
|
| 170 |
+
"Use a longer video, a later --start_frame, or a lower --l2_frame_rate."
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
l1_indices, l2_indices = indexer.get_l1_and_l2_indices(start_frame, l1_context_frames)
|
| 174 |
+
all_frames = decode_video_frames(video_path, l1_indices + l2_indices, backend)
|
| 175 |
+
|
| 176 |
+
adapter = DecordFrameAdapter() if backend == "decord" else TensorFrameAdapter()
|
| 177 |
+
augmenter = ClipAugmenter(adapter, ResizeCenterPolicy((height, width)))
|
| 178 |
+
all_tensor = augmenter(all_frames) # [F, C, H, W] in [-1, 1]
|
| 179 |
+
|
| 180 |
+
l1_tensor = all_tensor[: len(l1_indices)].unsqueeze(0).to(device)
|
| 181 |
+
l2_tensor = all_tensor[len(l1_indices) :].unsqueeze(0).to(device)
|
| 182 |
+
return l1_tensor, l2_tensor
|
| 183 |
+
|
| 184 |
+
|
| 185 |
+
def load_steering_trajectory(steering_file, min_odo_steps, dtype, device):
|
| 186 |
+
"""Load a raw [speed, yaw_rate] steering trajectory from a .npy/.csv file as a [1, T, 2] tensor."""
|
| 187 |
+
if not os.path.isfile(steering_file):
|
| 188 |
+
raise FileNotFoundError(f"Steering file {steering_file} does not exist")
|
| 189 |
+
|
| 190 |
+
if steering_file.endswith(".npy"):
|
| 191 |
+
loaded = np.load(steering_file)
|
| 192 |
+
elif steering_file.endswith(".csv"):
|
| 193 |
+
loaded = np.loadtxt(steering_file, delimiter=",")
|
| 194 |
+
else:
|
| 195 |
+
raise ValueError("Steering file must end with .npy or .csv")
|
| 196 |
+
|
| 197 |
+
if loaded.ndim != 2 or loaded.shape[1] != 2:
|
| 198 |
+
raise ValueError(
|
| 199 |
+
f"Steering file must contain [T, 2] (speed, yaw_rate) rows, got shape {tuple(loaded.shape)}"
|
| 200 |
+
)
|
| 201 |
+
if min_odo_steps is not None and loaded.shape[0] < min_odo_steps:
|
| 202 |
+
raise ValueError(
|
| 203 |
+
f"Steering file has too few timesteps: got {loaded.shape[0]}, expected at least {min_odo_steps}"
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
return torch.as_tensor(loaded, dtype=dtype, device=device).unsqueeze(0)
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def make_unconditional_steering(min_odo_steps, dtype, device):
|
| 210 |
+
"""Build an all-NaN [1, T, 2] steering placeholder, the framework's own 'no steering data' signal."""
|
| 211 |
+
if min_odo_steps is None:
|
| 212 |
+
raise ValueError(
|
| 213 |
+
"Cannot run unconditionally without --steering_file: the loaded condition_preprocessor "
|
| 214 |
+
"could not report a required odometry length (`get_required_rollout_odometry_steps` "
|
| 215 |
+
"returned None), so there is no safe length to build a NaN placeholder at. "
|
| 216 |
+
"Supply --steering_file explicitly."
|
| 217 |
+
)
|
| 218 |
+
return torch.full((1, int(min_odo_steps), 2), float("nan"), dtype=dtype, device=device)
|
| 219 |
+
|
| 220 |
+
|
| 221 |
+
def maybe_apply_condition_preprocessor_scales(model, speed_scale, yaw_rate_scale):
|
| 222 |
+
condition_preprocessor = getattr(model, "condition_preprocessor", None)
|
| 223 |
+
if condition_preprocessor is None:
|
| 224 |
+
return
|
| 225 |
+
|
| 226 |
+
if hasattr(condition_preprocessor, "speed_scale"):
|
| 227 |
+
condition_preprocessor.speed_scale = float(speed_scale)
|
| 228 |
+
if hasattr(condition_preprocessor, "yaw_rate_scale"):
|
| 229 |
+
condition_preprocessor.yaw_rate_scale = float(yaw_rate_scale)
|
| 230 |
+
|
| 231 |
+
|
| 232 |
+
def _rgb_to_cv2_color(color):
|
| 233 |
+
"""Convert an RGB tuple in [0, 1] to OpenCV BGR channel order."""
|
| 234 |
+
return (color[2], color[1], color[0])
|
| 235 |
+
|
| 236 |
+
|
| 237 |
+
def _resolve_rollout_cursor_index(cursor_index, shared_trajectory, ctx_size, t, num_points):
|
| 238 |
+
cursor_idx = 0
|
| 239 |
+
if cursor_index is not None:
|
| 240 |
+
cursor_idx = int(cursor_index.item())
|
| 241 |
+
elif shared_trajectory and t >= ctx_size:
|
| 242 |
+
cursor_idx = t - ctx_size
|
| 243 |
+
return min(max(cursor_idx, 0), num_points - 1)
|
| 244 |
+
|
| 245 |
+
|
| 246 |
+
def _panel_coords_fit_trajectory(traj_xy, panel_w, panel_h, margin):
|
| 247 |
+
if traj_xy.shape[0] == 0:
|
| 248 |
+
return None
|
| 249 |
+
|
| 250 |
+
forward = traj_xy[:, 0]
|
| 251 |
+
lateral = traj_xy[:, 1]
|
| 252 |
+
|
| 253 |
+
forward_min = np.min(forward)
|
| 254 |
+
forward_max = np.max(forward)
|
| 255 |
+
lateral_min = np.min(lateral)
|
| 256 |
+
lateral_max = np.max(lateral)
|
| 257 |
+
forward_span = forward_max - forward_min
|
| 258 |
+
lateral_span = lateral_max - lateral_min
|
| 259 |
+
usable_w = max(1, panel_w - 2 * margin)
|
| 260 |
+
usable_h = max(1, panel_h - 2 * margin)
|
| 261 |
+
|
| 262 |
+
scales = []
|
| 263 |
+
if lateral_span > 1e-6:
|
| 264 |
+
scales.append(usable_w / lateral_span)
|
| 265 |
+
if forward_span > 1e-6:
|
| 266 |
+
scales.append(usable_h / forward_span)
|
| 267 |
+
scale = min(scales) if scales else 1.0
|
| 268 |
+
|
| 269 |
+
if lateral_span > 1e-6:
|
| 270 |
+
px = margin + (lateral_max - lateral) * scale
|
| 271 |
+
else:
|
| 272 |
+
px = np.full_like(lateral, panel_w / 2)
|
| 273 |
+
if forward_span > 1e-6:
|
| 274 |
+
py = margin + (forward_max - forward) * scale
|
| 275 |
+
else:
|
| 276 |
+
py = np.full_like(forward, panel_h / 2)
|
| 277 |
+
px = np.clip(px, 0, panel_w - 1)
|
| 278 |
+
py = np.clip(py, 0, panel_h - 1)
|
| 279 |
+
return np.stack([px, py], axis=1).astype(np.int32)
|
| 280 |
+
|
| 281 |
+
|
| 282 |
+
def _transform_trajectory_to_ego_frame(traj_xy, traj_heading, cursor_idx):
|
| 283 |
+
"""Translate and rotate the trajectory so the current ego pose is at the origin facing +forward."""
|
| 284 |
+
if traj_xy.shape[0] == 0:
|
| 285 |
+
return traj_xy
|
| 286 |
+
|
| 287 |
+
centered = traj_xy - traj_xy[cursor_idx : cursor_idx + 1]
|
| 288 |
+
heading = float(traj_heading[cursor_idx])
|
| 289 |
+
cos_heading = np.cos(heading)
|
| 290 |
+
sin_heading = np.sin(heading)
|
| 291 |
+
rotation = np.array(
|
| 292 |
+
[
|
| 293 |
+
[cos_heading, sin_heading],
|
| 294 |
+
[-sin_heading, cos_heading],
|
| 295 |
+
],
|
| 296 |
+
dtype=np.float32,
|
| 297 |
+
)
|
| 298 |
+
return centered @ rotation.T
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
def _panel_coords_ego_frame(traj_xy, panel_w, panel_h, margin):
|
| 302 |
+
if traj_xy.shape[0] == 0:
|
| 303 |
+
return None
|
| 304 |
+
|
| 305 |
+
forward = traj_xy[:, 0]
|
| 306 |
+
lateral = traj_xy[:, 1]
|
| 307 |
+
extent = max(
|
| 308 |
+
float(np.max(np.abs(forward))),
|
| 309 |
+
float(np.max(np.abs(lateral))),
|
| 310 |
+
1e-3,
|
| 311 |
+
)
|
| 312 |
+
usable_w = max(1, panel_w - 2 * margin)
|
| 313 |
+
usable_h = max(1, panel_h - 2 * margin)
|
| 314 |
+
scale = min(usable_w, usable_h) / (2.0 * extent)
|
| 315 |
+
center_x = panel_w / 2.0
|
| 316 |
+
center_y = panel_h / 2.0
|
| 317 |
+
|
| 318 |
+
px = center_x - lateral * scale
|
| 319 |
+
py = center_y - forward * scale
|
| 320 |
+
px = np.clip(px, 0, panel_w - 1)
|
| 321 |
+
py = np.clip(py, 0, panel_h - 1)
|
| 322 |
+
return np.stack([px, py], axis=1).astype(np.int32)
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
def overlay_trajectory_on_images(images, visualization, mode="trajectory"):
|
| 326 |
+
"""Draw a compact trajectory panel on top of each rollout frame."""
|
| 327 |
+
cursor_index = None
|
| 328 |
+
headings = None
|
| 329 |
+
trajectory = visualization
|
| 330 |
+
if isinstance(visualization, dict):
|
| 331 |
+
trajectory = visualization.get("trajectory")
|
| 332 |
+
cursor_index = visualization.get("cursor_index")
|
| 333 |
+
headings = visualization.get("heading")
|
| 334 |
+
|
| 335 |
+
if trajectory is None:
|
| 336 |
+
return images
|
| 337 |
+
|
| 338 |
+
if mode not in {"trajectory", "trajectory_ego"}:
|
| 339 |
+
raise ValueError(f"Unsupported trajectory visualization mode: {mode}")
|
| 340 |
+
|
| 341 |
+
shared_trajectory = trajectory.ndim == 3
|
| 342 |
+
if trajectory.ndim == 3:
|
| 343 |
+
trajectory = trajectory.unsqueeze(1).expand(-1, images.shape[1], -1, -1)
|
| 344 |
+
elif trajectory.ndim != 4:
|
| 345 |
+
raise ValueError(f"Expected trajectory with shape [B, T, N, 2] or [B, N, 2], got {tuple(trajectory.shape)}")
|
| 346 |
+
if trajectory.shape[0] != images.shape[0] or trajectory.shape[1] != images.shape[1]:
|
| 347 |
+
raise ValueError(
|
| 348 |
+
f"Trajectory/image batch mismatch: images={tuple(images.shape)}, trajectory={tuple(trajectory.shape)}"
|
| 349 |
+
)
|
| 350 |
+
if cursor_index is not None:
|
| 351 |
+
if not torch.is_tensor(cursor_index):
|
| 352 |
+
cursor_index = torch.as_tensor(cursor_index, dtype=torch.long)
|
| 353 |
+
if cursor_index.ndim == 1:
|
| 354 |
+
cursor_index = cursor_index.unsqueeze(0).expand(images.shape[0], -1)
|
| 355 |
+
if cursor_index.shape[0] != images.shape[0] or cursor_index.shape[1] != images.shape[1]:
|
| 356 |
+
raise ValueError(
|
| 357 |
+
"Cursor/image batch mismatch: "
|
| 358 |
+
f"images={tuple(images.shape)}, cursor_index={tuple(cursor_index.shape)}"
|
| 359 |
+
)
|
| 360 |
+
cursor_index = cursor_index.to(device=trajectory.device)
|
| 361 |
+
if mode == "trajectory_ego":
|
| 362 |
+
if headings is None:
|
| 363 |
+
raise ValueError("Ego-aligned trajectory visualization requires per-point heading data.")
|
| 364 |
+
if headings.ndim == 2:
|
| 365 |
+
headings = headings.unsqueeze(1).expand(-1, images.shape[1], -1)
|
| 366 |
+
elif headings.ndim != 3:
|
| 367 |
+
raise ValueError(f"Expected heading with shape [B, T, N] or [B, N], got {tuple(headings.shape)}")
|
| 368 |
+
if headings.shape[0] != images.shape[0] or headings.shape[1] != images.shape[1]:
|
| 369 |
+
raise ValueError(f"Heading/image batch mismatch: images={tuple(images.shape)}, heading={tuple(headings.shape)}")
|
| 370 |
+
ctx_size = images.shape[1] - trajectory.shape[2] if shared_trajectory else 0
|
| 371 |
+
|
| 372 |
+
height = images.shape[3]
|
| 373 |
+
width = images.shape[4]
|
| 374 |
+
|
| 375 |
+
panel_w = int(height * 0.35)
|
| 376 |
+
panel_h = int(height * 0.35)
|
| 377 |
+
margin = max(2, int(min(height, width) * 0.02))
|
| 378 |
+
panel_x0 = margin
|
| 379 |
+
panel_y0 = height - panel_h - margin
|
| 380 |
+
|
| 381 |
+
traj_color = tuple(c * 2 - 1 for c in _rgb_to_cv2_color((0.0, 1.0, 0.0)))
|
| 382 |
+
cursor_color = tuple(c * 2 - 1 for c in _rgb_to_cv2_color((1.0, 0.25, 0.0)))
|
| 383 |
+
border_color = tuple(c * 2 - 1 for c in _rgb_to_cv2_color((1.0, 1.0, 1.0)))
|
| 384 |
+
|
| 385 |
+
for b in range(images.shape[0]):
|
| 386 |
+
for t in range(images.shape[1]):
|
| 387 |
+
traj_bt = trajectory[b, t].detach().cpu().numpy()
|
| 388 |
+
valid = ~np.isnan(traj_bt).any(axis=1)
|
| 389 |
+
if not np.any(valid):
|
| 390 |
+
continue
|
| 391 |
+
|
| 392 |
+
traj_xy = traj_bt[valid, :2]
|
| 393 |
+
cursor_idx = _resolve_rollout_cursor_index(
|
| 394 |
+
None if cursor_index is None else cursor_index[b, t],
|
| 395 |
+
shared_trajectory=shared_trajectory,
|
| 396 |
+
ctx_size=ctx_size,
|
| 397 |
+
t=t,
|
| 398 |
+
num_points=traj_xy.shape[0],
|
| 399 |
+
)
|
| 400 |
+
|
| 401 |
+
if mode == "trajectory_ego":
|
| 402 |
+
heading_bt = headings[b, t].detach().cpu().numpy()
|
| 403 |
+
heading_valid = heading_bt[valid]
|
| 404 |
+
ego_traj = _transform_trajectory_to_ego_frame(traj_xy, heading_valid, cursor_idx)
|
| 405 |
+
traj_pts = _panel_coords_ego_frame(ego_traj, panel_w, panel_h, margin)
|
| 406 |
+
else:
|
| 407 |
+
traj_pts = _panel_coords_fit_trajectory(traj_xy, panel_w, panel_h, margin)
|
| 408 |
+
|
| 409 |
+
if traj_pts is None:
|
| 410 |
+
continue
|
| 411 |
+
|
| 412 |
+
panel_t = np.full((panel_h, panel_w, 3), -1.0, dtype=np.float32)
|
| 413 |
+
cv2.rectangle(panel_t, (0, 0), (panel_w - 1, panel_h - 1), border_color, 1)
|
| 414 |
+
if traj_pts.shape[0] > 1:
|
| 415 |
+
cv2.polylines(panel_t, [traj_pts.reshape(-1, 1, 2)], False, traj_color, 2)
|
| 416 |
+
else:
|
| 417 |
+
cv2.circle(panel_t, tuple(traj_pts[0]), 2, traj_color, -1)
|
| 418 |
+
if mode == "trajectory_ego":
|
| 419 |
+
ego_marker = np.array([panel_w / 2.0, panel_h / 2.0], dtype=np.float32).astype(np.int32)
|
| 420 |
+
cv2.circle(panel_t, tuple(ego_marker), 3, cursor_color, -1)
|
| 421 |
+
else:
|
| 422 |
+
cv2.circle(panel_t, tuple(traj_pts[cursor_idx]), 3, cursor_color, -1)
|
| 423 |
+
|
| 424 |
+
frame = images[b, t].permute(1, 2, 0).cpu().numpy().copy()
|
| 425 |
+
frame[panel_y0:panel_y0 + panel_h, panel_x0:panel_x0 + panel_w] = panel_t
|
| 426 |
+
images[b, t] = torch.from_numpy(frame).permute(2, 0, 1)
|
| 427 |
+
|
| 428 |
+
return images
|
| 429 |
+
|
| 430 |
+
|
| 431 |
+
@torch.no_grad()
|
| 432 |
+
def generate_images(args, unknown_args):
|
| 433 |
+
"""Run an L1-L2 hierarchical rollout from a single input video and save the resulting frames."""
|
| 434 |
+
if args.seed > 0:
|
| 435 |
+
torch.backends.cudnn.enable = False
|
| 436 |
+
torch.backends.cudnn.deterministic = True
|
| 437 |
+
seed_everything(args.seed)
|
| 438 |
+
|
| 439 |
+
config = OmegaConf.load(args.config)
|
| 440 |
+
config = OmegaConf.merge(config, OmegaConf.from_dotlist(unknown_args))
|
| 441 |
+
model = instantiate_from_config(config.model)
|
| 442 |
+
|
| 443 |
+
_ckpt_result = model.load_state_dict(torch.load(args.ckpt)["state_dict"], strict=False)
|
| 444 |
+
_exempt_prefixes = tuple(getattr(model, "checkpoint_exempt_key_prefixes", ()))
|
| 445 |
+
_unexpected_missing_keys = [k for k in _ckpt_result.missing_keys if not k.startswith(_exempt_prefixes)]
|
| 446 |
+
assert _unexpected_missing_keys == [], _unexpected_missing_keys
|
| 447 |
+
model = model.to(args.device)
|
| 448 |
+
_ = model.eval()
|
| 449 |
+
|
| 450 |
+
require_l1l2_model(model)
|
| 451 |
+
|
| 452 |
+
if os.path.exists(args.output_dir):
|
| 453 |
+
print("Folder exists, new images will be saved to the same folder, delete it if you want to start from scratch")
|
| 454 |
+
else:
|
| 455 |
+
os.makedirs(args.output_dir)
|
| 456 |
+
|
| 457 |
+
if args.compile:
|
| 458 |
+
def _maybe_compile(module, attr):
|
| 459 |
+
net = getattr(module, attr, None)
|
| 460 |
+
if net is not None:
|
| 461 |
+
setattr(module, attr, torch.compile(net, mode=args.compile_mode))
|
| 462 |
+
logger.info(f"Compiled {type(module).__name__}.{attr} with mode={args.compile_mode!r}")
|
| 463 |
+
|
| 464 |
+
_maybe_compile(model, 'ema_vit' if args.evaluate_ema else 'vit')
|
| 465 |
+
|
| 466 |
+
_l2_predictor = getattr(getattr(model, 'condition_preprocessor', None), 'l2_predictor', None)
|
| 467 |
+
if _l2_predictor is not None:
|
| 468 |
+
_maybe_compile(_l2_predictor, 'ema_vit')
|
| 469 |
+
|
| 470 |
+
logger.info("First rollout step will be slow (compilation). Subsequent steps reuse the graph.")
|
| 471 |
+
|
| 472 |
+
if args.compile and args.compile_artifacts:
|
| 473 |
+
if os.path.exists(args.compile_artifacts):
|
| 474 |
+
with open(args.compile_artifacts, "rb") as _f:
|
| 475 |
+
torch.compiler.load_cache_artifacts(_f.read())
|
| 476 |
+
logger.info(f"Loaded compile artifacts from {args.compile_artifacts!r}")
|
| 477 |
+
else:
|
| 478 |
+
logger.info(
|
| 479 |
+
f"Compile artifacts not found at {args.compile_artifacts!r}; "
|
| 480 |
+
"will save after the first batch."
|
| 481 |
+
)
|
| 482 |
+
|
| 483 |
+
maybe_apply_condition_preprocessor_scales(model, args.speed_scale, args.yaw_rate_scale)
|
| 484 |
+
|
| 485 |
+
height, width = resolve_context_image_size(args, config)
|
| 486 |
+
backend = resolve_video_backend()
|
| 487 |
+
l2_frame_rate = resolve_l2_frame_rate(args, model)
|
| 488 |
+
|
| 489 |
+
l1_context_frames = int(model.vit.num_context_frames)
|
| 490 |
+
l2_context_frames = int(model.condition_preprocessor.num_context_frames)
|
| 491 |
+
|
| 492 |
+
l1_tensor, l2_tensor = load_l1_l2_context(
|
| 493 |
+
video_path=args.video,
|
| 494 |
+
start_frame=args.start_frame,
|
| 495 |
+
l1_frame_rate=args.l1_frame_rate,
|
| 496 |
+
l2_frame_rate=l2_frame_rate,
|
| 497 |
+
l1_context_frames=l1_context_frames,
|
| 498 |
+
l2_context_frames=l2_context_frames,
|
| 499 |
+
height=height,
|
| 500 |
+
width=width,
|
| 501 |
+
backend=backend,
|
| 502 |
+
device=args.device,
|
| 503 |
+
)
|
| 504 |
+
|
| 505 |
+
num_future_frames = get_rollout_future_frame_count(model, args.num_gen_frames)
|
| 506 |
+
frame_rate = torch.tensor(float(args.l1_frame_rate), device=args.device)
|
| 507 |
+
data_batch = {"images": l1_tensor, "l2_context": l2_tensor, "frame_rate": frame_rate}
|
| 508 |
+
|
| 509 |
+
get_required_steps = getattr(model.condition_preprocessor, "get_required_rollout_odometry_steps", None)
|
| 510 |
+
min_odo_steps = None
|
| 511 |
+
if callable(get_required_steps):
|
| 512 |
+
min_odo_steps = get_required_steps(
|
| 513 |
+
validation_params=None,
|
| 514 |
+
num_condition_frames=l1_context_frames,
|
| 515 |
+
num_gen_frames=num_future_frames,
|
| 516 |
+
rollout_steps=args.num_gen_frames,
|
| 517 |
+
)
|
| 518 |
+
if args.steering_file is not None:
|
| 519 |
+
data_batch["steering"] = load_steering_trajectory(
|
| 520 |
+
args.steering_file, min_odo_steps, dtype=l1_tensor.dtype, device=args.device
|
| 521 |
+
)
|
| 522 |
+
else:
|
| 523 |
+
data_batch["steering"] = make_unconditional_steering(min_odo_steps, dtype=l1_tensor.dtype, device=args.device)
|
| 524 |
+
data_batch["steering_format"] = STEERING_FORMAT
|
| 525 |
+
|
| 526 |
+
# Roll out `num_videos` futures in parallel from the same context, as a single
|
| 527 |
+
# minibatch. The context (and steering) is tiled along the batch dim; the sampler
|
| 528 |
+
# draws independent initial noise per element, so the futures diverge under one
|
| 529 |
+
# seed. Everything below is computed at the tiled batch size so condition_kwargs
|
| 530 |
+
# stays internally consistent.
|
| 531 |
+
num_videos = max(1, int(args.num_videos))
|
| 532 |
+
if num_videos > 1:
|
| 533 |
+
def _tile_batch(t):
|
| 534 |
+
return t.repeat(num_videos, *([1] * (t.dim() - 1)))
|
| 535 |
+
|
| 536 |
+
l1_tensor = _tile_batch(l1_tensor)
|
| 537 |
+
l2_tensor = _tile_batch(l2_tensor)
|
| 538 |
+
data_batch["images"] = l1_tensor
|
| 539 |
+
data_batch["l2_context"] = l2_tensor
|
| 540 |
+
data_batch["steering"] = _tile_batch(data_batch["steering"])
|
| 541 |
+
|
| 542 |
+
condition_kwargs = model.condition_preprocessor.get_condition_kwargs_from_batch(data_batch, split="rollout")
|
| 543 |
+
|
| 544 |
+
logger.info(f"Steering source: {'none' if args.steering_file is None else args.steering_file}")
|
| 545 |
+
logger.info(f"Steering scales: speed={args.speed_scale:g}, yaw_rate={args.yaw_rate_scale:g}")
|
| 546 |
+
logger.info(f"L1/L2 frame rates: {args.l1_frame_rate:g}/{l2_frame_rate:g} Hz")
|
| 547 |
+
logger.info(f"Saving generated images to {args.output_dir}")
|
| 548 |
+
|
| 549 |
+
autocast_enabled = args.device.startswith("cuda")
|
| 550 |
+
with torch.autocast(dtype=torch.float16, device_type="cuda", enabled=autocast_enabled):
|
| 551 |
+
_latents, gen_frames = model.roll_out(
|
| 552 |
+
x_0={"images": l1_tensor},
|
| 553 |
+
num_gen_frames=args.num_gen_frames,
|
| 554 |
+
latent_input=False,
|
| 555 |
+
NFE=args.num_steps,
|
| 556 |
+
eta=args.eta,
|
| 557 |
+
sample_with_ema=args.evaluate_ema,
|
| 558 |
+
num_samples=l1_tensor.size(0),
|
| 559 |
+
frame_rate=frame_rate.reshape(1).repeat(l1_tensor.size(0)),
|
| 560 |
+
condition_kwargs=condition_kwargs,
|
| 561 |
+
decode_device=args.decode_device,
|
| 562 |
+
num_condition_frames=l1_tensor.size(1),
|
| 563 |
+
)
|
| 564 |
+
|
| 565 |
+
if args.vis_mode in {"trajectory", "trajectory_ego"}:
|
| 566 |
+
overlay_trajectory = model.condition_preprocessor.get_rollout_visualization_trajectory(
|
| 567 |
+
condition_kwargs=model.condition_preprocessor.get_condition_kwargs_from_batch(data_batch, split="rollout"),
|
| 568 |
+
num_condition_frames=l1_context_frames,
|
| 569 |
+
num_gen_steps=args.num_gen_frames,
|
| 570 |
+
num_pred_frames=model.num_pred_frames,
|
| 571 |
+
)
|
| 572 |
+
if overlay_trajectory is not None:
|
| 573 |
+
gen_frames = overlay_trajectory_on_images(gen_frames, overlay_trajectory, mode=args.vis_mode)
|
| 574 |
+
|
| 575 |
+
# Release rollout-time latent state before CPU-side file I/O.
|
| 576 |
+
del _latents, condition_kwargs, l1_tensor, l2_tensor
|
| 577 |
+
|
| 578 |
+
# Save each rollout in the minibatch to its own sequence folder. The layout
|
| 579 |
+
# (fake_images/sequence_XXXX/frame_XXXX.jpg) matches what the demo app reads.
|
| 580 |
+
num_out = gen_frames.shape[0]
|
| 581 |
+
num_frames = gen_frames.shape[1]
|
| 582 |
+
for b in range(num_out):
|
| 583 |
+
seq_dir = os.path.join(args.output_dir, "fake_images", f"sequence_{b:04d}")
|
| 584 |
+
os.makedirs(seq_dir, exist_ok=True)
|
| 585 |
+
for f in range(num_frames):
|
| 586 |
+
save_image(
|
| 587 |
+
(gen_frames[b, f] + 1.0) / 2.0,
|
| 588 |
+
os.path.join(seq_dir, f"frame_{f:04d}.jpg"),
|
| 589 |
+
)
|
| 590 |
+
|
| 591 |
+
imageio.mimsave(
|
| 592 |
+
os.path.join(args.output_dir, f"rollout_{b:04d}.gif"),
|
| 593 |
+
[
|
| 594 |
+
np.array(Image.open(os.path.join(seq_dir, f"frame_{f:04d}.jpg")))
|
| 595 |
+
for f in range(num_frames)
|
| 596 |
+
],
|
| 597 |
+
fps=args.l1_frame_rate,
|
| 598 |
+
loop=0,
|
| 599 |
+
)
|
| 600 |
+
|
| 601 |
+
if args.device.startswith("cuda"):
|
| 602 |
+
logger.info(f"Max memory: {torch.cuda.max_memory_allocated() / 1024**3:.02f} GB")
|
| 603 |
+
|
| 604 |
+
if args.compile and args.compile_artifacts and not os.path.exists(args.compile_artifacts):
|
| 605 |
+
_artifacts = torch.compiler.save_cache_artifacts()
|
| 606 |
+
if _artifacts is not None:
|
| 607 |
+
with open(args.compile_artifacts, "wb") as _f:
|
| 608 |
+
_f.write(_artifacts[0])
|
| 609 |
+
logger.info(f"Saved compile artifacts to {args.compile_artifacts!r}")
|
| 610 |
+
|
| 611 |
+
|
| 612 |
+
def main(args, unknown_args):
|
| 613 |
+
"""Entrypoint that launches rollout generation with resolved CLI arguments."""
|
| 614 |
+
generate_images(args, unknown_args)
|
| 615 |
+
|
| 616 |
+
|
| 617 |
+
if __name__ == "__main__":
|
| 618 |
+
logging.basicConfig(level=logging.INFO)
|
| 619 |
+
|
| 620 |
+
def str2bool(v):
|
| 621 |
+
if isinstance(v, bool):
|
| 622 |
+
return v
|
| 623 |
+
if v.lower() in ("yes", "true", "t", "y", "1"):
|
| 624 |
+
return True
|
| 625 |
+
if v.lower() in ("no", "false", "f", "n", "0"):
|
| 626 |
+
return False
|
| 627 |
+
raise argparse.ArgumentTypeError("Boolean value expected.")
|
| 628 |
+
|
| 629 |
+
parser = argparse.ArgumentParser()
|
| 630 |
+
parser.add_argument("--exp_dir", type=str, default=None, help="Path to the experiment directory, where the config and checkpoints are stored")
|
| 631 |
+
parser.add_argument("--ckpt", type=str, default="checkpoints/last.ckpt", help="Path to the checkpoint file, relative to exp_dir")
|
| 632 |
+
parser.add_argument("--config", type=str, default="config.yaml", help="Path to the config file, relative to exp_dir")
|
| 633 |
+
parser.add_argument("--video", type=str, required=True, help="Path to the input video file to sample L1/L2 context from.")
|
| 634 |
+
parser.add_argument("--l1_frame_rate", type=float, required=True, help="Frame rate (Hz) to sample L1 context/rollout frames at, and the generated GIF's fps.")
|
| 635 |
+
parser.add_argument("--l2_frame_rate", type=float, default=None, help="Frame rate (Hz) to sample L2 context frames at. Defaults to the frozen L2 predictor's own trained frame rate; must match it if given explicitly.")
|
| 636 |
+
parser.add_argument("--start_frame", type=int, default=None, help="Native-video frame index to start the L1 context window at. Defaults to the latest window that fits (the end of the video).")
|
| 637 |
+
parser.add_argument("--height", type=int, default=None, help="Height to resize context images to. Defaults to the training config's size.")
|
| 638 |
+
parser.add_argument("--width", type=int, default=None, help="Width to resize context images to. Defaults to the training config's size.")
|
| 639 |
+
parser.add_argument(
|
| 640 |
+
"--num_gen_frames",
|
| 641 |
+
type=int,
|
| 642 |
+
default=1,
|
| 643 |
+
help="Number of rollout steps to generate; each step predicts `model.num_pred_frames` future frames.",
|
| 644 |
+
)
|
| 645 |
+
parser.add_argument("--output_dir", type=str, required=True, help="Directory to save the generated frames and GIF to.")
|
| 646 |
+
parser.add_argument(
|
| 647 |
+
"--num_videos",
|
| 648 |
+
type=int,
|
| 649 |
+
default=1,
|
| 650 |
+
help="Number of futures to roll out in parallel from the same context, as one minibatch.",
|
| 651 |
+
)
|
| 652 |
+
parser.add_argument(
|
| 653 |
+
"--vis_mode",
|
| 654 |
+
type=str,
|
| 655 |
+
default="none",
|
| 656 |
+
choices=["none", "trajectory", "trajectory_ego"],
|
| 657 |
+
help="Visualization mode",
|
| 658 |
+
)
|
| 659 |
+
parser.add_argument("--steering_file", type=str, default=None, help="Optional .npy or .csv trajectory file (columns: speed, yaw_rate), already at the expected odometry rate, used as steering input.")
|
| 660 |
+
parser.add_argument("--speed_scale", type=float, default=1.0, help="Global multiplicative factor applied to raw speed conditioning")
|
| 661 |
+
parser.add_argument("--yaw_rate_scale", type=float, default=1.0, help="Global multiplicative factor applied to raw yaw-rate conditioning")
|
| 662 |
+
|
| 663 |
+
parser.add_argument("--seed", type=int, default=42, help="Seed for reproducibility")
|
| 664 |
+
parser.add_argument("--device", type=str, default="cuda", help="Device")
|
| 665 |
+
parser.add_argument(
|
| 666 |
+
"--decode_device",
|
| 667 |
+
type=str,
|
| 668 |
+
default="cpu",
|
| 669 |
+
help="Device used for decoded rollout frames. Use 'cpu' to reduce peak GPU memory during saving.",
|
| 670 |
+
)
|
| 671 |
+
parser.add_argument("--num_steps", type=int, default=30, help="Number of steps for sampling")
|
| 672 |
+
parser.add_argument("--eta", type=float, default=0.0, help="Stochasticity for sampling")
|
| 673 |
+
parser.add_argument("--evaluate_ema", "--use_ema", type=str2bool, default=True, help="If the evaluation happen with ema model")
|
| 674 |
+
parser.add_argument(
|
| 675 |
+
"--compile",
|
| 676 |
+
type=str2bool,
|
| 677 |
+
default=False,
|
| 678 |
+
help="Wrap the DiT network with torch.compile for faster inference (PyTorch 2.x).",
|
| 679 |
+
)
|
| 680 |
+
parser.add_argument(
|
| 681 |
+
"--compile_mode",
|
| 682 |
+
type=str,
|
| 683 |
+
default="reduce-overhead",
|
| 684 |
+
choices=["default", "reduce-overhead", "max-autotune"],
|
| 685 |
+
help="torch.compile mode. 'reduce-overhead' uses CUDA graphs; 'max-autotune' adds kernel autotuning.",
|
| 686 |
+
)
|
| 687 |
+
parser.add_argument(
|
| 688 |
+
"--compile_artifacts",
|
| 689 |
+
type=str,
|
| 690 |
+
default=None,
|
| 691 |
+
help=(
|
| 692 |
+
"Path to torch.compiler cache artifacts (.pkl), relative to --exp_dir. "
|
| 693 |
+
"If the file exists, artifacts are loaded before rollout (fast startup). "
|
| 694 |
+
"If it does not exist, artifacts are saved after rollout (for future runs). "
|
| 695 |
+
"Only effective when --compile is True."
|
| 696 |
+
),
|
| 697 |
+
)
|
| 698 |
+
|
| 699 |
+
args, unknown = parser.parse_known_args()
|
| 700 |
+
|
| 701 |
+
args.ckpt = os.path.join(args.exp_dir, args.ckpt)
|
| 702 |
+
args.config = os.path.join(args.exp_dir, args.config)
|
| 703 |
+
if args.compile_artifacts:
|
| 704 |
+
args.compile_artifacts = os.path.join(args.exp_dir, args.compile_artifacts)
|
| 705 |
+
|
| 706 |
+
main(args, unknown)
|
|
@@ -0,0 +1,1190 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import random
|
| 3 |
+
import pytorch_lightning as pl
|
| 4 |
+
import timm
|
| 5 |
+
import torch
|
| 6 |
+
import torch.nn as nn
|
| 7 |
+
import torch.nn.functional as F
|
| 8 |
+
from timm.layers.pos_embed import resample_abs_pos_embed
|
| 9 |
+
from torch.optim.lr_scheduler import LambdaLR
|
| 10 |
+
|
| 11 |
+
from util import instantiate_from_config
|
| 12 |
+
|
| 13 |
+
class VQModel(pl.LightningModule):
|
| 14 |
+
def __init__(
|
| 15 |
+
self,
|
| 16 |
+
encoder_config,
|
| 17 |
+
decoder_config,
|
| 18 |
+
quantizer_config,
|
| 19 |
+
loss_config=None,
|
| 20 |
+
grad_acc_steps=1,
|
| 21 |
+
cont_ratio_trainig= 0.0,
|
| 22 |
+
ignore_keys=None,
|
| 23 |
+
monitor=None,
|
| 24 |
+
entropy_loss_weight_scheduler_config=None,
|
| 25 |
+
distill_model_type="VIT_DINOv2", # Options: VIT_DINO, CNN, VIT_DINOv2, etc.
|
| 26 |
+
min_lr_multiplier=0.1,
|
| 27 |
+
only_decoder=False,
|
| 28 |
+
scale_equivariance=None,
|
| 29 |
+
):
|
| 30 |
+
super().__init__()
|
| 31 |
+
|
| 32 |
+
ignore_keys = ignore_keys or []
|
| 33 |
+
self.automatic_optimization = False
|
| 34 |
+
self.grad_acc_steps = grad_acc_steps
|
| 35 |
+
self.monitor = monitor
|
| 36 |
+
self.distill_model_type = distill_model_type
|
| 37 |
+
self.cont_ratio_trainig = cont_ratio_trainig
|
| 38 |
+
self.only_decoder=only_decoder
|
| 39 |
+
self.min_lr_multiplier = min_lr_multiplier
|
| 40 |
+
|
| 41 |
+
assert (not scale_equivariance) or len(scale_equivariance) == 2, "if defined, scale_equivariance should be a list of two lists"
|
| 42 |
+
self.scale_equivariance = scale_equivariance
|
| 43 |
+
|
| 44 |
+
params_cfg = getattr(loss_config, "params", None)
|
| 45 |
+
if params_cfg is not None and hasattr(params_cfg, "get"):
|
| 46 |
+
default_lr = params_cfg.get("learning_rate", None)
|
| 47 |
+
else:
|
| 48 |
+
default_lr = None
|
| 49 |
+
# Ensure optimizers have sensible defaults even if the caller overrides later.
|
| 50 |
+
self.learning_rate = default_lr if default_lr is not None else 1e-4
|
| 51 |
+
self.num_iters_per_epoch = 1
|
| 52 |
+
|
| 53 |
+
|
| 54 |
+
# Decoder uses encoder params if none provided
|
| 55 |
+
if not hasattr(decoder_config, "params"):
|
| 56 |
+
decoder_config.params = encoder_config.params
|
| 57 |
+
|
| 58 |
+
# Instantiate core components
|
| 59 |
+
self.encoder = instantiate_from_config(encoder_config)
|
| 60 |
+
self.decoder = instantiate_from_config(decoder_config)
|
| 61 |
+
self.quantize = instantiate_from_config(quantizer_config)
|
| 62 |
+
self.loss = instantiate_from_config(loss_config) if loss_config is not None else None
|
| 63 |
+
self.entropy_loss_weight_scheduler = (
|
| 64 |
+
instantiate_from_config(entropy_loss_weight_scheduler_config)
|
| 65 |
+
if entropy_loss_weight_scheduler_config is not None else None
|
| 66 |
+
)
|
| 67 |
+
|
| 68 |
+
# Convolutional layers for quantization
|
| 69 |
+
self.quant_conv = nn.Conv2d(encoder_config.params["z_channels"], quantizer_config.params["e_dim"], 1)
|
| 70 |
+
self.post_quant_conv = nn.Conv2d(quantizer_config.params["e_dim"], decoder_config.params["z_channels"], 1)
|
| 71 |
+
|
| 72 |
+
self.encoder_normalize_embedding = encoder_config.params.get("normalize_embedding", False)
|
| 73 |
+
self.quantizer_normalize_embedding = quantizer_config.params.get("normalize_embedding", False)
|
| 74 |
+
|
| 75 |
+
self.if_distill_loss = (
|
| 76 |
+
False if loss_config is None
|
| 77 |
+
else loss_config.params.get('distill_loss_weight', 0.0) != 0.0
|
| 78 |
+
)
|
| 79 |
+
|
| 80 |
+
# Image and patch size
|
| 81 |
+
self.image_size = encoder_config.params["resolution"]
|
| 82 |
+
self.patch_size = encoder_config.params["patch_size"]
|
| 83 |
+
|
| 84 |
+
self._init_distill_model(distill_model_type, encoder_config, decoder_config, quantizer_config)
|
| 85 |
+
|
| 86 |
+
def _init_distill_model(self, distill_type, encoder_cfg, decoder_cfg, quantizer_cfg):
|
| 87 |
+
image_size = encoder_cfg.params["resolution"]
|
| 88 |
+
patch_size = encoder_cfg.params["patch_size"]
|
| 89 |
+
q_e_dim = quantizer_cfg.params["e_dim"]
|
| 90 |
+
z_channels = decoder_cfg.params["z_channels"]
|
| 91 |
+
|
| 92 |
+
def conv1x1(in_c, out_c): return nn.Conv2d(in_c, out_c, 1)
|
| 93 |
+
|
| 94 |
+
if distill_type == "VIT_DINO":
|
| 95 |
+
self.distill = timm.create_model("timm/vit_base_patch16_224.dino", img_size=image_size, pretrained=True).eval()
|
| 96 |
+
self.post_quant_conv_distill = conv1x1(q_e_dim, z_channels)
|
| 97 |
+
elif distill_type == "VIT_DINOv2":
|
| 98 |
+
img_size = self._compute_scaled_size(image_size, patch_size)
|
| 99 |
+
self.distill = timm.create_model("timm/vit_base_patch14_dinov2.lvd142m", img_size=img_size, pretrained=True).eval()
|
| 100 |
+
self.post_quant_conv_distill = conv1x1(q_e_dim, z_channels)
|
| 101 |
+
elif distill_type == "VIT_DINOv3":
|
| 102 |
+
img_size = self._compute_scaled_size(image_size, patch_size, ckpt_patch_size=16)
|
| 103 |
+
self.distill = torch.hub.load('../dinov3', 'dinov3_vitb16', source='local', weights='./pretrained_models/dinov3_vitb16_pretrain_lvd1689m-73cec8be.pth').eval()
|
| 104 |
+
self.post_quant_conv_distill = conv1x1(q_e_dim, z_channels)
|
| 105 |
+
elif distill_type == "VIT_DINOv2g":
|
| 106 |
+
img_size = int(image_size * 14 / patch_size)
|
| 107 |
+
self.distill = timm.create_model("timm/vit_giant_patch14_dinov2.lvd142m", img_size=img_size, pretrained=True).eval()
|
| 108 |
+
self.post_quant_conv_distill = conv1x1(q_e_dim, 1536)
|
| 109 |
+
elif distill_type == "VIT_DINOv2_large":
|
| 110 |
+
img_size = int(image_size * 14 / patch_size)
|
| 111 |
+
self.distill = timm.create_model("timm/vit_large_patch14_dinov2.lvd142m", img_size=img_size, pretrained=True).eval()
|
| 112 |
+
self.post_quant_conv_distill = conv1x1(q_e_dim, z_channels)
|
| 113 |
+
elif distill_type == "VIT_DINOv2_large_reg4":
|
| 114 |
+
img_size = int(image_size * 14 / patch_size)
|
| 115 |
+
self.distill = timm.create_model("timm/vit_large_patch14_reg4_dinov2.lvd142m", img_size=img_size, pretrained=True).eval()
|
| 116 |
+
self.post_quant_conv_distill = conv1x1(q_e_dim, z_channels)
|
| 117 |
+
elif distill_type == "SAM_VIT":
|
| 118 |
+
self.distill = timm.create_model("samvit_large_patch16.sa1b", pretrained=True)
|
| 119 |
+
self.post_quant_conv_distill = nn.Identity()
|
| 120 |
+
elif distill_type == "SAM_VIT_w_conv":
|
| 121 |
+
self.distill = timm.create_model("samvit_large_patch16.sa1b", pretrained=True)
|
| 122 |
+
self.post_quant_conv_distill = conv1x1(q_e_dim, z_channels)
|
| 123 |
+
|
| 124 |
+
elif distill_type == "depth_anything_VIT_L14":
|
| 125 |
+
self.distill = timm.create_model("vit_large_patch14_dinov2.lvd142m", img_size=224, pretrained=False)
|
| 126 |
+
state_dict = torch.load("./pretrained_models/depth_anything_vitl14.pth")
|
| 127 |
+
state_dict = {k.replace("pretrained.", "", 1): v for k, v in state_dict.items()}
|
| 128 |
+
state_dict["pos_embed"] = resample_abs_pos_embed(state_dict["pos_embed"], new_size=(16, 16))
|
| 129 |
+
self.distill.load_state_dict(state_dict, strict=False)
|
| 130 |
+
self.post_quant_conv_distill = conv1x1(q_e_dim, z_channels)
|
| 131 |
+
|
| 132 |
+
|
| 133 |
+
@staticmethod
|
| 134 |
+
def _compute_scaled_size(image_size, patch_size, ckpt_patch_size=14):
|
| 135 |
+
if isinstance(image_size, int):
|
| 136 |
+
return [image_size * ckpt_patch_size // patch_size] * 2
|
| 137 |
+
return [image_size[0] * ckpt_patch_size // patch_size, image_size[1] * ckpt_patch_size // patch_size]
|
| 138 |
+
|
| 139 |
+
def get_input(self, batch):
|
| 140 |
+
x = batch['images']
|
| 141 |
+
return x.float()
|
| 142 |
+
|
| 143 |
+
def entropy_loss_weight_scheduling(self):
|
| 144 |
+
self.loss.entropy_loss_weight = self.entropy_loss_weight_scheduler(self.global_step)
|
| 145 |
+
|
| 146 |
+
def entropy_loss_weight_scheduling(self):
|
| 147 |
+
self.loss.entropy_loss_weight = self.entropy_loss_weight_scheduler(self.global_step)
|
| 148 |
+
|
| 149 |
+
def encode(self, x):
|
| 150 |
+
h = self.encoder(x)
|
| 151 |
+
h = self.quant_conv(h)
|
| 152 |
+
if self.encoder_normalize_embedding:
|
| 153 |
+
h = F.normalize(h, p=2, dim=1)
|
| 154 |
+
ret = self.quantize(h)
|
| 155 |
+
ret["continuous"] = h
|
| 156 |
+
return ret
|
| 157 |
+
|
| 158 |
+
def decode(self, quant):
|
| 159 |
+
distill_conv_out = self.post_quant_conv_distill(quant)
|
| 160 |
+
quant2 = self.post_quant_conv(quant)
|
| 161 |
+
return self.decoder(quant2), distill_conv_out
|
| 162 |
+
|
| 163 |
+
def forward(self, input):
|
| 164 |
+
encoded = self.encode(input)
|
| 165 |
+
if torch.rand(1) > self.cont_ratio_trainig:
|
| 166 |
+
dec, distill_conv_out = self.decode(encoded["quantized"])
|
| 167 |
+
else:
|
| 168 |
+
dec, distill_conv_out = self.decode(encoded["continuous"])
|
| 169 |
+
return dec, (encoded['quantization_loss'], encoded['entropy_loss']), distill_conv_out
|
| 170 |
+
|
| 171 |
+
def forward_se(self, input):
|
| 172 |
+
random_scale = [random.choice(self.scale_equivariance[0]), random.choice(self.scale_equivariance[1])]
|
| 173 |
+
downscale_factor = [1/random_scale[0], 1/random_scale[1]]
|
| 174 |
+
encoded = self.encode(input)
|
| 175 |
+
if torch.rand(1) > self.cont_ratio_trainig:
|
| 176 |
+
dec, distill_conv_out = self.decode(encoded["quantized"])
|
| 177 |
+
quant_se = F.interpolate(encoded["quantized"], scale_factor=downscale_factor, mode='bilinear', align_corners=False)
|
| 178 |
+
dec_se = self.decode(quant_se)[0]
|
| 179 |
+
else:
|
| 180 |
+
dec, distill_conv_out = self.decode(encoded["continuous"])
|
| 181 |
+
latents_se = F.interpolate(encoded["continuous"], scale_factor=downscale_factor, mode='bilinear', align_corners=False)
|
| 182 |
+
dec_se = self.decode(latents_se)[0]
|
| 183 |
+
|
| 184 |
+
input_se = F.interpolate(input, scale_factor=downscale_factor, mode='bilinear', align_corners=False)
|
| 185 |
+
decs = [dec, dec_se]
|
| 186 |
+
inputs = [input, input_se]
|
| 187 |
+
return inputs, decs, (encoded['quantization_loss'], encoded['entropy_loss']), distill_conv_out
|
| 188 |
+
|
| 189 |
+
def distill_loss(self, distill_output, decoder_distill_output):
|
| 190 |
+
#print(f'DINO loss calculation')
|
| 191 |
+
if 'VIT' in self.distill_model_type:
|
| 192 |
+
if 'reg4' in self.distill_model_type:
|
| 193 |
+
distill_output = distill_output[:, 5:, :] # [CLS, Register*4, Embeddings]
|
| 194 |
+
elif 'reg4' not in self.distill_model_type and 'DINOv2' in self.distill_model_type:
|
| 195 |
+
distill_output = distill_output[:, 1:, :] # uncomment for DINOv1
|
| 196 |
+
elif 'reg4' not in self.distill_model_type and 'DINOv3' in self.distill_model_type:
|
| 197 |
+
distill_output = distill_output['x_norm_patchtokens'] #distill_output[:, 1:, :] # uncomment for DINOv1
|
| 198 |
+
elif 'depth_anything' in self.distill_model_type:
|
| 199 |
+
distill_output = distill_output[:, 1:, :]
|
| 200 |
+
elif self.distill_model_type == 'SAM_VIT':
|
| 201 |
+
distill_output = distill_output.permute(0, 2, 3, 1).contiguous().view(distill_output.shape[0], -1, distill_output.shape[1])
|
| 202 |
+
distill_output = F.normalize(distill_output, p=2, dim=2) # without post_conv layer
|
| 203 |
+
elif self.distill_model_type == 'SAM_VIT_w_conv':
|
| 204 |
+
distill_output = distill_output.permute(0, 2, 3, 1).contiguous().view(distill_output.shape[0], -1, distill_output.shape[1])
|
| 205 |
+
# without L2 normalization
|
| 206 |
+
distill_output = distill_output.permute(0, 2, 1).contiguous()
|
| 207 |
+
|
| 208 |
+
elif self.distill_model_type == 'CNN':
|
| 209 |
+
distill_output = distill_output.view(distill_output.shape[0], distill_output.shape[1], -1)
|
| 210 |
+
decoder_distill_output = decoder_distill_output.view(decoder_distill_output.shape[0], decoder_distill_output.shape[1], -1)
|
| 211 |
+
cos_similarity = F.cosine_similarity(decoder_distill_output, distill_output, dim=1)
|
| 212 |
+
cosine_loss = 1 - cos_similarity
|
| 213 |
+
distill_loss = cosine_loss.mean()
|
| 214 |
+
return distill_loss
|
| 215 |
+
|
| 216 |
+
def get_warmup_scheduler(self, optimizer, warmup_steps, min_lr_multiplier):
|
| 217 |
+
min_lr = self.learning_rate * min_lr_multiplier
|
| 218 |
+
total_steps = self.trainer.max_epochs * self.num_iters_per_epoch
|
| 219 |
+
def lr_lambda(step):
|
| 220 |
+
if step < warmup_steps:
|
| 221 |
+
# Linear warmup
|
| 222 |
+
return step/warmup_steps
|
| 223 |
+
# After warmup_steps, we just return 1. This could be modified to implement your own schedule
|
| 224 |
+
else:
|
| 225 |
+
progress = (step - warmup_steps) / (total_steps - warmup_steps)
|
| 226 |
+
cosine_decay = 0.5 * (1 + math.cos(math.pi * progress))
|
| 227 |
+
decayed = (1 - min_lr) * cosine_decay + min_lr
|
| 228 |
+
return decayed
|
| 229 |
+
#return 1.0
|
| 230 |
+
|
| 231 |
+
return LambdaLR(optimizer, lr_lambda)
|
| 232 |
+
|
| 233 |
+
def configure_optimizers(self):
|
| 234 |
+
lr = self.learning_rate
|
| 235 |
+
opt_ae = torch.optim.Adam(list(self.encoder.parameters())+
|
| 236 |
+
list(self.decoder.parameters())+
|
| 237 |
+
list(self.quantize.parameters())+
|
| 238 |
+
list(self.quant_conv.parameters())+
|
| 239 |
+
list(self.post_quant_conv.parameters())+
|
| 240 |
+
list(self.post_quant_conv_distill.parameters()),
|
| 241 |
+
lr=lr, betas=(self.loss.beta_1, self.loss.beta_2))
|
| 242 |
+
opt_disc = torch.optim.Adam(self.loss.discriminator.parameters(),
|
| 243 |
+
lr=lr, betas=(self.loss.beta_1, self.loss.beta_2))
|
| 244 |
+
|
| 245 |
+
scheduler_ae_warmup = self.get_warmup_scheduler(opt_ae, self.loss.warmup_steps, self.min_lr_multiplier)
|
| 246 |
+
scheduler_disc_warmup = self.get_warmup_scheduler(opt_disc, self.loss.warmup_steps, self.min_lr_multiplier)
|
| 247 |
+
|
| 248 |
+
|
| 249 |
+
return [opt_ae, opt_disc], [scheduler_ae_warmup, scheduler_disc_warmup]
|
| 250 |
+
|
| 251 |
+
def validation_step(self, batch, batch_idx):
|
| 252 |
+
x = self.get_input(batch)
|
| 253 |
+
xrec, qloss, decoder_distill_output = self(x)
|
| 254 |
+
|
| 255 |
+
distill_loss = self.distill_loss(self.get_distill_gt(x), decoder_distill_output) if self.if_distill_loss else torch.tensor(0.0, device=x.device)
|
| 256 |
+
aeloss, log_dict_ae = self.loss(qloss, distill_loss, x, xrec, 0, self.global_step,
|
| 257 |
+
last_layer=self.get_last_layer(), split="val")
|
| 258 |
+
self.log("val/distill_loss", distill_loss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 259 |
+
self.log("val/aeloss", aeloss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 260 |
+
|
| 261 |
+
_, log_dict_disc = self.loss(qloss, distill_loss, x, xrec, 1, self.global_step,
|
| 262 |
+
last_layer=self.get_last_layer(), split="val")
|
| 263 |
+
rec_loss = log_dict_ae["val/rec_loss"]
|
| 264 |
+
self.log("val/rec_loss", rec_loss,
|
| 265 |
+
prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 266 |
+
self.log_dict(log_dict_disc, prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 267 |
+
return self.log_dict
|
| 268 |
+
|
| 269 |
+
def training_step(self, batch, batch_idx):
|
| 270 |
+
self.entropy_loss_weight_scheduling()
|
| 271 |
+
self.log("train/enropy_loss_weight", self.loss.entropy_loss_weight,
|
| 272 |
+
prog_bar=True, logger=True, on_step=True, on_epoch=False)
|
| 273 |
+
|
| 274 |
+
opt_ae, opt_disc = self.optimizers()
|
| 275 |
+
[scheduler_ae_warmup, scheduler_disc_warmup] = self.lr_schedulers()
|
| 276 |
+
|
| 277 |
+
if self.only_decoder:
|
| 278 |
+
for param in self.encoder.parameters():
|
| 279 |
+
param.requires_grad = False
|
| 280 |
+
for param in self.quant_conv.parameters():
|
| 281 |
+
param.requires_grad = False
|
| 282 |
+
for param in self.quantize.parameters():
|
| 283 |
+
param.requires_grad = False
|
| 284 |
+
for param in self.post_quant_conv_distill.parameters():
|
| 285 |
+
param.requires_grad = False
|
| 286 |
+
|
| 287 |
+
x = self.get_input(batch)
|
| 288 |
+
|
| 289 |
+
if self.scale_equivariance:
|
| 290 |
+
xs, xrec, qloss, decoder_distill_output = self.forward_se(x)
|
| 291 |
+
else:
|
| 292 |
+
xrec, qloss, decoder_distill_output = self(x)
|
| 293 |
+
xs = x
|
| 294 |
+
|
| 295 |
+
distill_loss = self.distill_loss(self.get_distill_gt(x), decoder_distill_output) if self.if_distill_loss else torch.tensor(0.0, device=x.device)
|
| 296 |
+
|
| 297 |
+
optimizer_idx = 1
|
| 298 |
+
discloss, log_dict_disc = self.loss(qloss, distill_loss, xs, xrec, optimizer_idx, self.global_step,
|
| 299 |
+
last_layer=self.get_last_layer(), split="train")
|
| 300 |
+
self.log("train/discloss", discloss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 301 |
+
self.log_dict(log_dict_disc, prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 302 |
+
|
| 303 |
+
discloss = discloss / self.grad_acc_steps
|
| 304 |
+
self.manual_backward(discloss)
|
| 305 |
+
if (batch_idx+1) % self.grad_acc_steps == 0:
|
| 306 |
+
opt_disc.step()
|
| 307 |
+
opt_disc.zero_grad()
|
| 308 |
+
scheduler_disc_warmup.step()
|
| 309 |
+
|
| 310 |
+
optimizer_idx = 0
|
| 311 |
+
aeloss, log_dict_ae = self.loss(qloss, distill_loss, xs, xrec, optimizer_idx, self.global_step,
|
| 312 |
+
last_layer=self.get_last_layer(), split="train")
|
| 313 |
+
self.log("train/distill_loss", distill_loss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 314 |
+
self.log("train/aeloss", aeloss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 315 |
+
self.log_dict(log_dict_ae, prog_bar=False, logger=True, on_step=True, on_epoch=True)
|
| 316 |
+
|
| 317 |
+
aeloss = aeloss / self.grad_acc_steps
|
| 318 |
+
self.manual_backward(aeloss)
|
| 319 |
+
if (batch_idx+1) % self.grad_acc_steps == 0:
|
| 320 |
+
opt_ae.step()
|
| 321 |
+
opt_ae.zero_grad()
|
| 322 |
+
scheduler_ae_warmup.step()
|
| 323 |
+
|
| 324 |
+
|
| 325 |
+
def get_distill_gt(self, x):
|
| 326 |
+
with torch.no_grad():
|
| 327 |
+
if 'VIT' in self.distill_model_type:
|
| 328 |
+
# resize image x to 224x224
|
| 329 |
+
if 'VIT_DINOv2' in self.distill_model_type or 'depth_anything' in self.distill_model_type:
|
| 330 |
+
image_size = (self.image_size*14//self.patch_size, self.image_size*14//self.patch_size) if isinstance(self.image_size, int) else (self.image_size[0]*14//self.patch_size, self.image_size[1]*14//self.patch_size)
|
| 331 |
+
x_224 = F.interpolate(x, size=image_size, mode='bilinear', align_corners=False)
|
| 332 |
+
distill_output = self.distill.forward_features(x_224)
|
| 333 |
+
elif 'VIT_DINOv3' in self.distill_model_type:
|
| 334 |
+
image_size = self._compute_scaled_size(self.image_size, self.patch_size, ckpt_patch_size=16)
|
| 335 |
+
x_224 = F.interpolate(x, size=image_size, mode='bilinear', align_corners=False)
|
| 336 |
+
distill_output = self.distill.forward_features(x_224)
|
| 337 |
+
else: # for VIT-DINOv1, VIT-SAM models
|
| 338 |
+
distill_output = self.distill.forward_features(x)
|
| 339 |
+
|
| 340 |
+
elif self.distill_model_type == 'CNN':
|
| 341 |
+
distill_output = self.distill(x)
|
| 342 |
+
return distill_output
|
| 343 |
+
|
| 344 |
+
|
| 345 |
+
def get_last_layer(self):
|
| 346 |
+
try:
|
| 347 |
+
return self.decoder.conv_out.weight
|
| 348 |
+
except:
|
| 349 |
+
return None
|
| 350 |
+
|
| 351 |
+
def log_images(self, batch, **kwargs):
|
| 352 |
+
log = dict()
|
| 353 |
+
x = self.get_input(batch)
|
| 354 |
+
x = x.to(self.device)
|
| 355 |
+
xrec, _, _ = self(x)
|
| 356 |
+
log["inputs"] = x
|
| 357 |
+
log["reconstructions"] = xrec
|
| 358 |
+
return log
|
| 359 |
+
|
| 360 |
+
|
| 361 |
+
class VQModelIF(VQModel):
|
| 362 |
+
def __init__(self,
|
| 363 |
+
encoder_config,
|
| 364 |
+
decoder_config,
|
| 365 |
+
quantizer_config,
|
| 366 |
+
loss_config=None,
|
| 367 |
+
grad_acc_steps=1,
|
| 368 |
+
cont_ratio_trainig= 0.0,
|
| 369 |
+
ignore_keys=[],
|
| 370 |
+
monitor=None,
|
| 371 |
+
entropy_loss_weight_scheduler_config=None,
|
| 372 |
+
distill_model_type='VIT_DINOv2', # 'VIT_DINO' or 'CNN' or VIT_DINOv2, VIT_DINOv2_large_reg4, SAM_VIT
|
| 373 |
+
min_lr_multiplier=0.1,
|
| 374 |
+
only_decoder=False,
|
| 375 |
+
scale_equivariance=[]
|
| 376 |
+
):
|
| 377 |
+
super().__init__(encoder_config, decoder_config, quantizer_config, loss_config,
|
| 378 |
+
grad_acc_steps, cont_ratio_trainig, ignore_keys,
|
| 379 |
+
monitor,
|
| 380 |
+
entropy_loss_weight_scheduler_config,
|
| 381 |
+
distill_model_type, min_lr_multiplier, only_decoder, scale_equivariance)
|
| 382 |
+
|
| 383 |
+
self.encoder2 = instantiate_from_config(encoder_config)
|
| 384 |
+
self.post_quant_conv = torch.nn.Conv2d(quantizer_config.params['e_dim']*2, decoder_config.params["z_channels"], 1)
|
| 385 |
+
self.quant_conv2 = torch.nn.Conv2d(encoder_config.params["z_channels"], quantizer_config.params['e_dim'], 1)
|
| 386 |
+
self.quantize2 = instantiate_from_config(quantizer_config)
|
| 387 |
+
|
| 388 |
+
def configure_optimizers(self):
|
| 389 |
+
lr = self.learning_rate
|
| 390 |
+
opt_ae = torch.optim.Adam(list(self.encoder.parameters())+
|
| 391 |
+
list(self.encoder2.parameters())+
|
| 392 |
+
list(self.decoder.parameters())+
|
| 393 |
+
list(self.quantize.parameters())+
|
| 394 |
+
list(self.quantize2.parameters())+
|
| 395 |
+
list(self.quant_conv.parameters())+
|
| 396 |
+
list(self.quant_conv2.parameters())+
|
| 397 |
+
list(self.post_quant_conv.parameters())+
|
| 398 |
+
list(self.post_quant_conv_distill.parameters()),
|
| 399 |
+
lr=lr, betas=(self.loss.beta_1, self.loss.beta_2))
|
| 400 |
+
opt_disc = torch.optim.Adam(self.loss.discriminator.parameters(),
|
| 401 |
+
lr=lr, betas=(self.loss.beta_1, self.loss.beta_2))
|
| 402 |
+
|
| 403 |
+
scheduler_ae_warmup = self.get_warmup_scheduler(opt_ae, self.loss.warmup_steps, self.min_lr_multiplier)
|
| 404 |
+
scheduler_disc_warmup = self.get_warmup_scheduler(opt_disc, self.loss.warmup_steps, self.min_lr_multiplier)
|
| 405 |
+
|
| 406 |
+
|
| 407 |
+
return [opt_ae, opt_disc], [scheduler_ae_warmup, scheduler_disc_warmup]
|
| 408 |
+
|
| 409 |
+
def encode(self, x):
|
| 410 |
+
h = self.encoder(x)
|
| 411 |
+
h = self.quant_conv(h)
|
| 412 |
+
if self.encoder_normalize_embedding:
|
| 413 |
+
h = F.normalize(h, p=2, dim=1)
|
| 414 |
+
|
| 415 |
+
h2 = self.encoder2(x)
|
| 416 |
+
h2 = self.quant_conv2(h2)
|
| 417 |
+
if self.encoder_normalize_embedding:
|
| 418 |
+
h2 = F.normalize(h2, p=2, dim=1)
|
| 419 |
+
|
| 420 |
+
quant = self.quantize(h)
|
| 421 |
+
quant2 = self.quantize2(h2)
|
| 422 |
+
|
| 423 |
+
quant_loss = quant['quantization_loss'] + quant2['quantization_loss']
|
| 424 |
+
entropy_loss = quant['entropy_loss'] + quant2['entropy_loss'] if quant['entropy_loss'] is not None and quant2['entropy_loss'] is not None else None
|
| 425 |
+
|
| 426 |
+
ret = {
|
| 427 |
+
"quantized": (quant["quantized"], quant2["quantized"]),
|
| 428 |
+
"quantization_loss": quant_loss,
|
| 429 |
+
"entropy_loss": entropy_loss,
|
| 430 |
+
"indices": (quant["indices"], quant2["indices"]),
|
| 431 |
+
"continuous": (h, h2)
|
| 432 |
+
}
|
| 433 |
+
return ret
|
| 434 |
+
|
| 435 |
+
|
| 436 |
+
def decode(self, quant):
|
| 437 |
+
if isinstance(quant, tuple):
|
| 438 |
+
quant_rec = quant[0]
|
| 439 |
+
quant_sem = quant[1]
|
| 440 |
+
else:
|
| 441 |
+
print('Error: quant should be a tuple')
|
| 442 |
+
distill_conv_out = self.post_quant_conv_distill(quant_sem).view(quant_sem.shape[0], -1, quant_sem.shape[2]*quant_sem.shape[3])
|
| 443 |
+
quant_cat = torch.cat((quant_rec, quant_sem), dim=1)
|
| 444 |
+
quant = self.post_quant_conv(quant_cat)
|
| 445 |
+
return self.decoder(quant), distill_conv_out
|
| 446 |
+
|
| 447 |
+
def decode_code(self, code_b):
|
| 448 |
+
code_b_rec, code_b_sem = code_b
|
| 449 |
+
quant_b_rec = self.quantize.get_codebook_entry(code_b_rec, (-1, code_b_rec.size(1), code_b_rec.size(2), self.quantize.e_dim))
|
| 450 |
+
quant_b_sem = self.quantize2.get_codebook_entry(code_b_sem, (-1, code_b_sem.size(1), code_b_sem.size(2), self.quantize.e_dim))
|
| 451 |
+
quant_b = (quant_b_rec, quant_b_sem)
|
| 452 |
+
dec = self.decode(quant_b)
|
| 453 |
+
return dec
|
| 454 |
+
|
| 455 |
+
def forward_se(self, input):
|
| 456 |
+
random_scale = [random.choice(self.scale_equivariance[0]), random.choice(self.scale_equivariance[1])]
|
| 457 |
+
downscale_factor = [1/random_scale[0], 1/random_scale[1]]
|
| 458 |
+
encoded = self.encode(input)
|
| 459 |
+
quantized = encoded["quantized"]
|
| 460 |
+
continuous = encoded["continuous"]
|
| 461 |
+
if torch.rand(1) > self.cont_ratio_trainig:
|
| 462 |
+
dec, distill_conv_out = self.decode(quantized)
|
| 463 |
+
quant_se = F.interpolate(quantized[0], scale_factor=downscale_factor, mode='bilinear', align_corners=False), \
|
| 464 |
+
F.interpolate(quantized[1], scale_factor=downscale_factor, mode='bilinear', align_corners=False)
|
| 465 |
+
dec_se = self.decode(quant_se)[0]
|
| 466 |
+
else:
|
| 467 |
+
dec, distill_conv_out = self.decode(continuous)
|
| 468 |
+
latents_se = F.interpolate(continuous[0], scale_factor=downscale_factor, mode='bilinear', align_corners=False), \
|
| 469 |
+
F.interpolate(continuous[1], scale_factor=downscale_factor, mode='bilinear', align_corners=False)
|
| 470 |
+
dec_se = self.decode(latents_se)[0]
|
| 471 |
+
|
| 472 |
+
input_se = F.interpolate(input, scale_factor=downscale_factor, mode='bilinear', align_corners=False)
|
| 473 |
+
decs = [dec, dec_se]
|
| 474 |
+
inputs = [input, input_se]
|
| 475 |
+
return inputs, decs, (encoded["quantization_loss"], encoded["entropy_loss"]), distill_conv_out
|
| 476 |
+
|
| 477 |
+
def training_step(self, batch, batch_idx):
|
| 478 |
+
self.entropy_loss_weight_scheduling()
|
| 479 |
+
self.log("train/enropy_loss_weight", self.loss.entropy_loss_weight,
|
| 480 |
+
prog_bar=True, logger=True, on_step=True, on_epoch=False)
|
| 481 |
+
|
| 482 |
+
opt_ae, opt_disc = self.optimizers()
|
| 483 |
+
[scheduler_ae_warmup, scheduler_disc_warmup] = self.lr_schedulers()
|
| 484 |
+
|
| 485 |
+
if self.only_decoder:
|
| 486 |
+
for param in self.encoder.parameters():
|
| 487 |
+
param.requires_grad = False
|
| 488 |
+
for param in self.encoder2.parameters():
|
| 489 |
+
param.requires_grad = False
|
| 490 |
+
for param in self.quant_conv.parameters():
|
| 491 |
+
param.requires_grad = False
|
| 492 |
+
for param in self.quant_conv2.parameters():
|
| 493 |
+
param.requires_grad = False
|
| 494 |
+
for param in self.quantize.parameters():
|
| 495 |
+
param.requires_grad = False
|
| 496 |
+
for param in self.quantize2.parameters():
|
| 497 |
+
param.requires_grad = False
|
| 498 |
+
for param in self.post_quant_conv_distill.parameters():
|
| 499 |
+
param.requires_grad = False
|
| 500 |
+
|
| 501 |
+
x = self.get_input(batch)
|
| 502 |
+
|
| 503 |
+
if self.scale_equivariance:
|
| 504 |
+
xs, xrec, qloss, decoder_distill_output = self.forward_se(x)
|
| 505 |
+
else:
|
| 506 |
+
xrec, qloss, decoder_distill_output = self(x)
|
| 507 |
+
xs = x
|
| 508 |
+
|
| 509 |
+
distill_loss = self.distill_loss(self.get_distill_gt(x), decoder_distill_output) if self.if_distill_loss else torch.tensor(0.0, device=x.device)
|
| 510 |
+
|
| 511 |
+
optimizer_idx = 1
|
| 512 |
+
discloss, log_dict_disc = self.loss(qloss, distill_loss, xs, xrec, optimizer_idx, self.global_step,
|
| 513 |
+
last_layer=self.get_last_layer(), split="train")
|
| 514 |
+
self.log("train/discloss", discloss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 515 |
+
self.log_dict(log_dict_disc, prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 516 |
+
|
| 517 |
+
discloss = discloss / self.grad_acc_steps
|
| 518 |
+
self.manual_backward(discloss)
|
| 519 |
+
if (batch_idx+1) % self.grad_acc_steps == 0:
|
| 520 |
+
opt_disc.step()
|
| 521 |
+
opt_disc.zero_grad()
|
| 522 |
+
scheduler_disc_warmup.step()
|
| 523 |
+
|
| 524 |
+
optimizer_idx = 0
|
| 525 |
+
aeloss, log_dict_ae = self.loss(qloss, distill_loss, xs, xrec, optimizer_idx, self.global_step,
|
| 526 |
+
last_layer=self.get_last_layer(), split="train")
|
| 527 |
+
self.log("train/distill_loss", distill_loss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 528 |
+
self.log("train/aeloss", aeloss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 529 |
+
self.log_dict(log_dict_ae, prog_bar=False, logger=True, on_step=True, on_epoch=True)
|
| 530 |
+
|
| 531 |
+
aeloss = aeloss / self.grad_acc_steps
|
| 532 |
+
self.manual_backward(aeloss)
|
| 533 |
+
if (batch_idx+1) % self.grad_acc_steps == 0:
|
| 534 |
+
opt_ae.step()
|
| 535 |
+
opt_ae.zero_grad()
|
| 536 |
+
scheduler_ae_warmup.step()
|
| 537 |
+
|
| 538 |
+
class VQModelIFExtra(VQModelIF):
|
| 539 |
+
def __init__(self,
|
| 540 |
+
encoder_config,
|
| 541 |
+
decoder_config,
|
| 542 |
+
quantizer_config,
|
| 543 |
+
quantizer_config2,
|
| 544 |
+
loss_config=None,
|
| 545 |
+
grad_acc_steps=1,
|
| 546 |
+
cont_ratio_trainig= 0.0,
|
| 547 |
+
ignore_keys=[],
|
| 548 |
+
monitor=None,
|
| 549 |
+
entropy_loss_weight_scheduler_config=None,
|
| 550 |
+
distill_model_type='VIT_DINOv2', # 'VIT_DINO' or 'CNN' or VIT_DINOv2, VIT_DINOv2_large_reg4, SAM_VIT
|
| 551 |
+
min_lr_multiplier=0.1,
|
| 552 |
+
only_decoder=False,
|
| 553 |
+
scale_equivariance=[]
|
| 554 |
+
):
|
| 555 |
+
super().__init__(encoder_config, decoder_config, quantizer_config, loss_config,
|
| 556 |
+
grad_acc_steps, cont_ratio_trainig, ignore_keys,
|
| 557 |
+
monitor,
|
| 558 |
+
entropy_loss_weight_scheduler_config,
|
| 559 |
+
distill_model_type, min_lr_multiplier, only_decoder, scale_equivariance)
|
| 560 |
+
|
| 561 |
+
self.encoder2 = instantiate_from_config(encoder_config)
|
| 562 |
+
self.post_quant_conv = torch.nn.Conv2d(quantizer_config.params['e_dim']+quantizer_config2.params['e_dim'], decoder_config.params["z_channels"], 1)
|
| 563 |
+
self.quant_conv2 = nn.Conv2d(encoder_config.params["z_channels"], quantizer_config2.params['e_dim'], 1)
|
| 564 |
+
self.quantize2 = instantiate_from_config(quantizer_config2)
|
| 565 |
+
|
| 566 |
+
self._init_distill_model(distill_model_type, encoder_config, decoder_config, quantizer_config2)
|
| 567 |
+
|
| 568 |
+
class VQModelIFSepEnc(VQModelIF):
|
| 569 |
+
def __init__(self,
|
| 570 |
+
encoder_config,
|
| 571 |
+
encoder_config2,
|
| 572 |
+
decoder_config,
|
| 573 |
+
quantizer_config,
|
| 574 |
+
loss_config=None,
|
| 575 |
+
grad_acc_steps=1,
|
| 576 |
+
cont_ratio_trainig= 0.0,
|
| 577 |
+
ignore_keys=[],
|
| 578 |
+
monitor=None,
|
| 579 |
+
entropy_loss_weight_scheduler_config=None,
|
| 580 |
+
distill_model_type='VIT_DINOv2', # 'VIT_DINO' or 'CNN' or VIT_DINOv2, VIT_DINOv2_large_reg4, SAM_VIT
|
| 581 |
+
min_lr_multiplier=0.1,
|
| 582 |
+
only_decoder=False,
|
| 583 |
+
scale_equivariance=[]
|
| 584 |
+
):
|
| 585 |
+
super().__init__(encoder_config, decoder_config, quantizer_config, loss_config,
|
| 586 |
+
grad_acc_steps, cont_ratio_trainig, ignore_keys,
|
| 587 |
+
monitor,
|
| 588 |
+
entropy_loss_weight_scheduler_config,
|
| 589 |
+
distill_model_type, min_lr_multiplier, only_decoder, scale_equivariance)
|
| 590 |
+
|
| 591 |
+
self.encoder2 = instantiate_from_config(encoder_config2)
|
| 592 |
+
self.post_quant_conv = torch.nn.Conv2d(quantizer_config.params['e_dim'], decoder_config.params["z_channels"], 1)
|
| 593 |
+
self.quant_conv2 = nn.Conv2d(encoder_config2.params["z_channels"], quantizer_config.params['e_dim'], 1)
|
| 594 |
+
self.quantize2 = instantiate_from_config(quantizer_config)
|
| 595 |
+
|
| 596 |
+
self._init_distill_model(distill_model_type, encoder_config, decoder_config, quantizer_config)
|
| 597 |
+
|
| 598 |
+
def training_step(self, batch, batch_idx):
|
| 599 |
+
self.entropy_loss_weight_scheduling()
|
| 600 |
+
self.log("train/enropy_loss_weight", self.loss.entropy_loss_weight,
|
| 601 |
+
prog_bar=True, logger=True, on_step=True, on_epoch=False)
|
| 602 |
+
|
| 603 |
+
opt_ae, opt_disc = self.optimizers()
|
| 604 |
+
[scheduler_ae_warmup, scheduler_disc_warmup] = self.lr_schedulers()
|
| 605 |
+
|
| 606 |
+
for param in self.encoder2.parameters():
|
| 607 |
+
param.requires_grad = False
|
| 608 |
+
|
| 609 |
+
x = self.get_input(batch)
|
| 610 |
+
|
| 611 |
+
if self.scale_equivariance:
|
| 612 |
+
xs, xrec, qloss, decoder_distill_output = self.forward_se(x)
|
| 613 |
+
else:
|
| 614 |
+
xrec, qloss, decoder_distill_output = self(x)
|
| 615 |
+
xs = x
|
| 616 |
+
|
| 617 |
+
distill_loss = self.distill_loss(self.get_distill_gt(x), decoder_distill_output) if self.if_distill_loss else torch.tensor(0.0, device=x.device)
|
| 618 |
+
|
| 619 |
+
optimizer_idx = 1
|
| 620 |
+
discloss, log_dict_disc = self.loss(qloss, distill_loss, xs, xrec, optimizer_idx, self.global_step,
|
| 621 |
+
last_layer=self.get_last_layer(), split="train")
|
| 622 |
+
self.log("train/discloss", discloss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 623 |
+
self.log_dict(log_dict_disc, prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 624 |
+
|
| 625 |
+
discloss = discloss / self.grad_acc_steps
|
| 626 |
+
self.manual_backward(discloss)
|
| 627 |
+
if (batch_idx+1) % self.grad_acc_steps == 0:
|
| 628 |
+
opt_disc.step()
|
| 629 |
+
opt_disc.zero_grad()
|
| 630 |
+
scheduler_disc_warmup.step()
|
| 631 |
+
|
| 632 |
+
optimizer_idx = 0
|
| 633 |
+
aeloss, log_dict_ae = self.loss(qloss, distill_loss, xs, xrec, optimizer_idx, self.global_step,
|
| 634 |
+
last_layer=self.get_last_layer(), split="train")
|
| 635 |
+
self.log("train/distill_loss", distill_loss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 636 |
+
self.log("train/aeloss", aeloss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 637 |
+
self.log_dict(log_dict_ae, prog_bar=False, logger=True, on_step=True, on_epoch=True)
|
| 638 |
+
|
| 639 |
+
aeloss = aeloss / self.grad_acc_steps
|
| 640 |
+
self.manual_backward(aeloss)
|
| 641 |
+
if (batch_idx+1) % self.grad_acc_steps == 0:
|
| 642 |
+
opt_ae.step()
|
| 643 |
+
opt_ae.zero_grad()
|
| 644 |
+
scheduler_ae_warmup.step()
|
| 645 |
+
|
| 646 |
+
def configure_optimizers(self):
|
| 647 |
+
lr = self.learning_rate
|
| 648 |
+
opt_ae = torch.optim.Adam(list(self.encoder.parameters())+
|
| 649 |
+
list(self.decoder.parameters())+
|
| 650 |
+
list(self.quantize.parameters())+
|
| 651 |
+
list(self.quantize2.parameters())+
|
| 652 |
+
list(self.quant_conv.parameters())+
|
| 653 |
+
list(self.quant_conv2.parameters())+
|
| 654 |
+
list(self.post_quant_conv.parameters())+
|
| 655 |
+
list(self.post_quant_conv_distill.parameters()),
|
| 656 |
+
lr=lr, betas=(self.loss.beta_1, self.loss.beta_2))
|
| 657 |
+
opt_disc = torch.optim.Adam(self.loss.discriminator.parameters(),
|
| 658 |
+
lr=lr, betas=(self.loss.beta_1, self.loss.beta_2))
|
| 659 |
+
|
| 660 |
+
scheduler_ae_warmup = self.get_warmup_scheduler(opt_ae, self.loss.warmup_steps, self.min_lr_multiplier)
|
| 661 |
+
scheduler_disc_warmup = self.get_warmup_scheduler(opt_disc, self.loss.warmup_steps, self.min_lr_multiplier)
|
| 662 |
+
|
| 663 |
+
|
| 664 |
+
return [opt_ae, opt_disc], [scheduler_ae_warmup, scheduler_disc_warmup]
|
| 665 |
+
|
| 666 |
+
def decode(self, quant):
|
| 667 |
+
if isinstance(quant, tuple):
|
| 668 |
+
quant_rec = quant[0]
|
| 669 |
+
quant_sem = quant[1]
|
| 670 |
+
else:
|
| 671 |
+
print('Error: quant should be a tuple')
|
| 672 |
+
distill_conv_out = self.post_quant_conv_distill(quant_sem).view(quant_sem.shape[0], -1, quant_sem.shape[2]*quant_sem.shape[3])
|
| 673 |
+
#quant_cat = torch.cat((quant_rec, quant_sem), dim=1)
|
| 674 |
+
quant = self.post_quant_conv(quant_rec)
|
| 675 |
+
return self.decoder(quant), distill_conv_out
|
| 676 |
+
|
| 677 |
+
|
| 678 |
+
class VQModelIFExtraSepEnc(VQModelIF):
|
| 679 |
+
def __init__(self,
|
| 680 |
+
encoder_config,
|
| 681 |
+
encoder_config2,
|
| 682 |
+
decoder_config,
|
| 683 |
+
quantizer_config,
|
| 684 |
+
quantizer_config2,
|
| 685 |
+
loss_config=None,
|
| 686 |
+
grad_acc_steps=1,
|
| 687 |
+
cont_ratio_trainig= 0.0,
|
| 688 |
+
ignore_keys=[],
|
| 689 |
+
monitor=None,
|
| 690 |
+
entropy_loss_weight_scheduler_config=None,
|
| 691 |
+
distill_model_type='VIT_DINOv2', # 'VIT_DINO' or 'CNN' or VIT_DINOv2, VIT_DINOv2_large_reg4, SAM_VIT
|
| 692 |
+
min_lr_multiplier=0.1,
|
| 693 |
+
only_decoder=False,
|
| 694 |
+
scale_equivariance=[]
|
| 695 |
+
):
|
| 696 |
+
super().__init__(encoder_config, decoder_config, quantizer_config, loss_config,
|
| 697 |
+
grad_acc_steps, cont_ratio_trainig, ignore_keys,
|
| 698 |
+
monitor,
|
| 699 |
+
entropy_loss_weight_scheduler_config,
|
| 700 |
+
distill_model_type, min_lr_multiplier, only_decoder, scale_equivariance)
|
| 701 |
+
|
| 702 |
+
self.encoder2 = instantiate_from_config(encoder_config2)
|
| 703 |
+
self.post_quant_conv = torch.nn.Conv2d(quantizer_config.params['e_dim']+quantizer_config2.params['e_dim'], decoder_config.params["z_channels"], 1)
|
| 704 |
+
self.quant_conv2 = nn.Conv2d(encoder_config2.params["z_channels"], quantizer_config2.params['e_dim'], 1)
|
| 705 |
+
self.quantize2 = instantiate_from_config(quantizer_config2)
|
| 706 |
+
|
| 707 |
+
self._init_distill_model(distill_model_type, encoder_config2, decoder_config, quantizer_config2)
|
| 708 |
+
|
| 709 |
+
def training_step(self, batch, batch_idx):
|
| 710 |
+
self.entropy_loss_weight_scheduling()
|
| 711 |
+
self.log("train/enropy_loss_weight", self.loss.entropy_loss_weight,
|
| 712 |
+
prog_bar=True, logger=True, on_step=True, on_epoch=False)
|
| 713 |
+
|
| 714 |
+
opt_ae, opt_disc = self.optimizers()
|
| 715 |
+
[scheduler_ae_warmup, scheduler_disc_warmup] = self.lr_schedulers()
|
| 716 |
+
|
| 717 |
+
for param in self.encoder2.parameters():
|
| 718 |
+
param.requires_grad = False
|
| 719 |
+
|
| 720 |
+
x = self.get_input(batch)
|
| 721 |
+
|
| 722 |
+
if self.scale_equivariance:
|
| 723 |
+
xs, xrec, qloss, decoder_distill_output = self.forward_se(x)
|
| 724 |
+
else:
|
| 725 |
+
xrec, qloss, decoder_distill_output = self(x)
|
| 726 |
+
xs = x
|
| 727 |
+
|
| 728 |
+
distill_loss = self.distill_loss(self.get_distill_gt(x), decoder_distill_output) if self.if_distill_loss else torch.tensor(0.0, device=x.device)
|
| 729 |
+
|
| 730 |
+
optimizer_idx = 1
|
| 731 |
+
discloss, log_dict_disc = self.loss(qloss, distill_loss, xs, xrec, optimizer_idx, self.global_step,
|
| 732 |
+
last_layer=self.get_last_layer(), split="train")
|
| 733 |
+
self.log("train/discloss", discloss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 734 |
+
self.log_dict(log_dict_disc, prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 735 |
+
|
| 736 |
+
discloss = discloss / self.grad_acc_steps
|
| 737 |
+
self.manual_backward(discloss)
|
| 738 |
+
if (batch_idx+1) % self.grad_acc_steps == 0:
|
| 739 |
+
opt_disc.step()
|
| 740 |
+
opt_disc.zero_grad()
|
| 741 |
+
scheduler_disc_warmup.step()
|
| 742 |
+
|
| 743 |
+
optimizer_idx = 0
|
| 744 |
+
aeloss, log_dict_ae = self.loss(qloss, distill_loss, xs, xrec, optimizer_idx, self.global_step,
|
| 745 |
+
last_layer=self.get_last_layer(), split="train")
|
| 746 |
+
self.log("train/distill_loss", distill_loss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 747 |
+
self.log("train/aeloss", aeloss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 748 |
+
self.log_dict(log_dict_ae, prog_bar=False, logger=True, on_step=True, on_epoch=True)
|
| 749 |
+
|
| 750 |
+
aeloss = aeloss / self.grad_acc_steps
|
| 751 |
+
self.manual_backward(aeloss)
|
| 752 |
+
if (batch_idx+1) % self.grad_acc_steps == 0:
|
| 753 |
+
opt_ae.step()
|
| 754 |
+
opt_ae.zero_grad()
|
| 755 |
+
scheduler_ae_warmup.step()
|
| 756 |
+
|
| 757 |
+
def configure_optimizers(self):
|
| 758 |
+
lr = self.learning_rate
|
| 759 |
+
opt_ae = torch.optim.Adam(list(self.encoder.parameters())+
|
| 760 |
+
list(self.decoder.parameters())+
|
| 761 |
+
list(self.quantize.parameters())+
|
| 762 |
+
list(self.quantize2.parameters())+
|
| 763 |
+
list(self.quant_conv.parameters())+
|
| 764 |
+
list(self.quant_conv2.parameters())+
|
| 765 |
+
list(self.post_quant_conv.parameters())+
|
| 766 |
+
list(self.post_quant_conv_distill.parameters()),
|
| 767 |
+
lr=lr, betas=(self.loss.beta_1, self.loss.beta_2))
|
| 768 |
+
opt_disc = torch.optim.Adam(self.loss.discriminator.parameters(),
|
| 769 |
+
lr=lr, betas=(self.loss.beta_1, self.loss.beta_2))
|
| 770 |
+
|
| 771 |
+
scheduler_ae_warmup = self.get_warmup_scheduler(opt_ae, self.loss.warmup_steps, self.min_lr_multiplier)
|
| 772 |
+
scheduler_disc_warmup = self.get_warmup_scheduler(opt_disc, self.loss.warmup_steps, self.min_lr_multiplier)
|
| 773 |
+
|
| 774 |
+
|
| 775 |
+
return [opt_ae, opt_disc], [scheduler_ae_warmup, scheduler_disc_warmup]
|
| 776 |
+
|
| 777 |
+
def decode(self, quant):
|
| 778 |
+
if isinstance(quant, tuple):
|
| 779 |
+
quant_rec = quant[0]
|
| 780 |
+
quant_sem = quant[1]
|
| 781 |
+
else:
|
| 782 |
+
print('Error: quant should be a tuple')
|
| 783 |
+
distill_conv_out = self.post_quant_conv_distill(quant_sem).view(quant_sem.shape[0], -1, quant_sem.shape[2]*quant_sem.shape[3])
|
| 784 |
+
quant_cat = torch.cat((quant_rec, quant_sem), dim=1)
|
| 785 |
+
quant = self.post_quant_conv(quant_cat)
|
| 786 |
+
return self.decoder(quant), distill_conv_out
|
| 787 |
+
|
| 788 |
+
|
| 789 |
+
class VQModelIFExtraSepEncSepDec(VQModelIFExtraSepEnc):
|
| 790 |
+
"""
|
| 791 |
+
IFExtraSepEnc variant with a dedicated semantic decoder.
|
| 792 |
+
|
| 793 |
+
The main reconstruction decoder sees only the reconstruction branch. The
|
| 794 |
+
semantic branch is decoded by a second decoder with the same architecture,
|
| 795 |
+
while the semantic latents are detached on that auxiliary reconstruction
|
| 796 |
+
path so decoder gradients do not flow back into the semantic
|
| 797 |
+
encoder/quantizer. The semantic decoder is trained with reconstruction /
|
| 798 |
+
perceptual losses only and does not participate in the adversarial path.
|
| 799 |
+
"""
|
| 800 |
+
|
| 801 |
+
def __init__(self,
|
| 802 |
+
encoder_config,
|
| 803 |
+
encoder_config2,
|
| 804 |
+
decoder_config,
|
| 805 |
+
quantizer_config,
|
| 806 |
+
quantizer_config2,
|
| 807 |
+
loss_config=None,
|
| 808 |
+
grad_acc_steps=1,
|
| 809 |
+
cont_ratio_trainig=0.0,
|
| 810 |
+
ignore_keys=[],
|
| 811 |
+
monitor=None,
|
| 812 |
+
entropy_loss_weight_scheduler_config=None,
|
| 813 |
+
distill_model_type='VIT_DINOv2',
|
| 814 |
+
min_lr_multiplier=0.1,
|
| 815 |
+
only_decoder=False,
|
| 816 |
+
scale_equivariance=[]
|
| 817 |
+
):
|
| 818 |
+
super().__init__(
|
| 819 |
+
encoder_config=encoder_config,
|
| 820 |
+
encoder_config2=encoder_config2,
|
| 821 |
+
decoder_config=decoder_config,
|
| 822 |
+
quantizer_config=quantizer_config,
|
| 823 |
+
quantizer_config2=quantizer_config2,
|
| 824 |
+
loss_config=loss_config,
|
| 825 |
+
grad_acc_steps=grad_acc_steps,
|
| 826 |
+
cont_ratio_trainig=cont_ratio_trainig,
|
| 827 |
+
ignore_keys=ignore_keys,
|
| 828 |
+
monitor=monitor,
|
| 829 |
+
entropy_loss_weight_scheduler_config=entropy_loss_weight_scheduler_config,
|
| 830 |
+
distill_model_type=distill_model_type,
|
| 831 |
+
min_lr_multiplier=min_lr_multiplier,
|
| 832 |
+
only_decoder=only_decoder,
|
| 833 |
+
scale_equivariance=scale_equivariance,
|
| 834 |
+
)
|
| 835 |
+
|
| 836 |
+
self.post_quant_conv = torch.nn.Conv2d(
|
| 837 |
+
quantizer_config.params['e_dim'],
|
| 838 |
+
decoder_config.params["z_channels"],
|
| 839 |
+
1,
|
| 840 |
+
)
|
| 841 |
+
self.post_quant_conv_sem = torch.nn.Conv2d(
|
| 842 |
+
quantizer_config2.params['e_dim'],
|
| 843 |
+
decoder_config.params["z_channels"],
|
| 844 |
+
1,
|
| 845 |
+
)
|
| 846 |
+
self.decoder_sem = instantiate_from_config(decoder_config)
|
| 847 |
+
|
| 848 |
+
def configure_optimizers(self):
|
| 849 |
+
lr = self.learning_rate
|
| 850 |
+
opt_ae = torch.optim.Adam(
|
| 851 |
+
list(self.encoder.parameters())
|
| 852 |
+
+ list(self.encoder2.parameters())
|
| 853 |
+
+ list(self.decoder.parameters())
|
| 854 |
+
+ list(self.decoder_sem.parameters())
|
| 855 |
+
+ list(self.quantize.parameters())
|
| 856 |
+
+ list(self.quantize2.parameters())
|
| 857 |
+
+ list(self.quant_conv.parameters())
|
| 858 |
+
+ list(self.quant_conv2.parameters())
|
| 859 |
+
+ list(self.post_quant_conv.parameters())
|
| 860 |
+
+ list(self.post_quant_conv_sem.parameters())
|
| 861 |
+
+ list(self.post_quant_conv_distill.parameters()),
|
| 862 |
+
lr=lr,
|
| 863 |
+
betas=(self.loss.beta_1, self.loss.beta_2),
|
| 864 |
+
)
|
| 865 |
+
opt_disc = torch.optim.Adam(
|
| 866 |
+
self.loss.discriminator.parameters(),
|
| 867 |
+
lr=lr,
|
| 868 |
+
betas=(self.loss.beta_1, self.loss.beta_2),
|
| 869 |
+
)
|
| 870 |
+
|
| 871 |
+
scheduler_ae_warmup = self.get_warmup_scheduler(opt_ae, self.loss.warmup_steps, self.min_lr_multiplier)
|
| 872 |
+
scheduler_disc_warmup = self.get_warmup_scheduler(opt_disc, self.loss.warmup_steps, self.min_lr_multiplier)
|
| 873 |
+
|
| 874 |
+
return [opt_ae, opt_disc], [scheduler_ae_warmup, scheduler_disc_warmup]
|
| 875 |
+
|
| 876 |
+
def _decode_components(self, quant):
|
| 877 |
+
if isinstance(quant, tuple):
|
| 878 |
+
quant_rec = quant[0]
|
| 879 |
+
quant_sem = quant[1]
|
| 880 |
+
else:
|
| 881 |
+
raise ValueError('VQModelIFExtraSepEncSepDec expects quant to be a tuple')
|
| 882 |
+
|
| 883 |
+
distill_conv_out = self.post_quant_conv_distill(quant_sem).view(
|
| 884 |
+
quant_sem.shape[0], -1, quant_sem.shape[2] * quant_sem.shape[3]
|
| 885 |
+
)
|
| 886 |
+
|
| 887 |
+
rec = self.decoder(self.post_quant_conv(quant_rec))
|
| 888 |
+
|
| 889 |
+
# Stop semantic-decoder reconstruction gradients from reaching encoder2/quantize2.
|
| 890 |
+
sem_latent = quant_sem.detach()
|
| 891 |
+
sem = self.decoder_sem(self.post_quant_conv_sem(sem_latent))
|
| 892 |
+
|
| 893 |
+
return rec, sem, distill_conv_out
|
| 894 |
+
|
| 895 |
+
def _select_decode_input(self, encoded):
|
| 896 |
+
if torch.rand(1, device=self.device).item() > self.cont_ratio_trainig:
|
| 897 |
+
return encoded["quantized"]
|
| 898 |
+
return encoded["continuous"]
|
| 899 |
+
|
| 900 |
+
def _forward_branch_outputs(self, x):
|
| 901 |
+
encoded = self.encode(x)
|
| 902 |
+
quant = self._select_decode_input(encoded)
|
| 903 |
+
rec, sem, distill_conv_out = self._decode_components(quant)
|
| 904 |
+
qloss = (encoded["quantization_loss"], encoded["entropy_loss"])
|
| 905 |
+
return rec, sem, qloss, distill_conv_out
|
| 906 |
+
|
| 907 |
+
def _semantic_decoder_loss(self, inputs, reconstructions, split):
|
| 908 |
+
if isinstance(reconstructions, list):
|
| 909 |
+
device = reconstructions[0].device
|
| 910 |
+
else:
|
| 911 |
+
device = reconstructions.device
|
| 912 |
+
|
| 913 |
+
rec_loss = torch.tensor(0.0, device=device)
|
| 914 |
+
p_loss = torch.tensor(0.0, device=device)
|
| 915 |
+
|
| 916 |
+
if isinstance(inputs, list):
|
| 917 |
+
for idx, (input_img, recon_img) in enumerate(zip(inputs, reconstructions)):
|
| 918 |
+
se_weight = 1 if idx == 0 else self.loss.se_weight
|
| 919 |
+
l1 = torch.abs(input_img - recon_img).mean()
|
| 920 |
+
l2 = F.mse_loss(recon_img, input_img)
|
| 921 |
+
rec_loss += (self.loss.l1_loss_weight * l1 + self.loss.l2_loss_weight * l2) * se_weight
|
| 922 |
+
if self.loss.perceptual_weight > 0:
|
| 923 |
+
p_loss += self.loss.perceptual_loss(input_img, recon_img).mean() * se_weight
|
| 924 |
+
else:
|
| 925 |
+
l1 = torch.abs(inputs - reconstructions).mean()
|
| 926 |
+
l2 = F.mse_loss(reconstructions, inputs)
|
| 927 |
+
rec_loss = self.loss.l1_loss_weight * l1 + self.loss.l2_loss_weight * l2
|
| 928 |
+
if self.loss.perceptual_weight > 0:
|
| 929 |
+
p_loss = self.loss.perceptual_loss(inputs, reconstructions).mean()
|
| 930 |
+
|
| 931 |
+
loss = rec_loss + self.loss.perceptual_weight * p_loss
|
| 932 |
+
log = {
|
| 933 |
+
f"{split}/sem_decoder_loss": loss.detach().mean(),
|
| 934 |
+
f"{split}/sem_decoder_rec_loss": rec_loss.detach().mean(),
|
| 935 |
+
f"{split}/sem_decoder_p_loss": p_loss.detach().mean(),
|
| 936 |
+
}
|
| 937 |
+
return loss, log
|
| 938 |
+
|
| 939 |
+
def decode(self, quant):
|
| 940 |
+
rec, _, distill_conv_out = self._decode_components(quant)
|
| 941 |
+
return rec, distill_conv_out
|
| 942 |
+
|
| 943 |
+
def validation_step(self, batch, batch_idx):
|
| 944 |
+
x = self.get_input(batch)
|
| 945 |
+
|
| 946 |
+
if self.scale_equivariance:
|
| 947 |
+
xs, xrec_rec, xrec_sem, qloss, decoder_distill_output = self._forward_se_branch_outputs(x)
|
| 948 |
+
else:
|
| 949 |
+
xrec_rec, xrec_sem, qloss, decoder_distill_output = self._forward_branch_outputs(x)
|
| 950 |
+
xs = x
|
| 951 |
+
|
| 952 |
+
distill_loss = self.distill_loss(self.get_distill_gt(x), decoder_distill_output) if self.if_distill_loss else torch.tensor(0.0, device=x.device)
|
| 953 |
+
|
| 954 |
+
rec_aeloss, log_dict_ae = self.loss(
|
| 955 |
+
qloss,
|
| 956 |
+
distill_loss,
|
| 957 |
+
xs,
|
| 958 |
+
xrec_rec,
|
| 959 |
+
0,
|
| 960 |
+
self.global_step,
|
| 961 |
+
last_layer=self.get_last_layer(),
|
| 962 |
+
split="val",
|
| 963 |
+
)
|
| 964 |
+
_, log_dict_disc = self.loss(
|
| 965 |
+
qloss,
|
| 966 |
+
distill_loss,
|
| 967 |
+
xs,
|
| 968 |
+
xrec_rec,
|
| 969 |
+
1,
|
| 970 |
+
self.global_step,
|
| 971 |
+
last_layer=self.get_last_layer(),
|
| 972 |
+
split="val",
|
| 973 |
+
)
|
| 974 |
+
sem_aeloss, log_dict_sem = self._semantic_decoder_loss(xs, xrec_sem, split="val")
|
| 975 |
+
aeloss = rec_aeloss + sem_aeloss
|
| 976 |
+
|
| 977 |
+
self.log("val/distill_loss", distill_loss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 978 |
+
self.log("val/aeloss", aeloss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 979 |
+
self.log("val/aeloss_rec", rec_aeloss, prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 980 |
+
self.log("val/aeloss_sem", sem_aeloss, prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 981 |
+
self.log_dict(log_dict_ae, prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 982 |
+
self.log_dict(log_dict_disc, prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 983 |
+
self.log_dict(log_dict_sem, prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 984 |
+
return self.log_dict
|
| 985 |
+
|
| 986 |
+
def training_step(self, batch, batch_idx):
|
| 987 |
+
self.entropy_loss_weight_scheduling()
|
| 988 |
+
self.log("train/enropy_loss_weight", self.loss.entropy_loss_weight,
|
| 989 |
+
prog_bar=True, logger=True, on_step=True, on_epoch=False)
|
| 990 |
+
|
| 991 |
+
opt_ae, opt_disc = self.optimizers()
|
| 992 |
+
[scheduler_ae_warmup, scheduler_disc_warmup] = self.lr_schedulers()
|
| 993 |
+
|
| 994 |
+
if self.only_decoder:
|
| 995 |
+
for param in self.encoder.parameters():
|
| 996 |
+
param.requires_grad = False
|
| 997 |
+
for param in self.encoder2.parameters():
|
| 998 |
+
param.requires_grad = False
|
| 999 |
+
for param in self.quant_conv.parameters():
|
| 1000 |
+
param.requires_grad = False
|
| 1001 |
+
for param in self.quant_conv2.parameters():
|
| 1002 |
+
param.requires_grad = False
|
| 1003 |
+
for param in self.quantize.parameters():
|
| 1004 |
+
param.requires_grad = False
|
| 1005 |
+
for param in self.quantize2.parameters():
|
| 1006 |
+
param.requires_grad = False
|
| 1007 |
+
for param in self.post_quant_conv_distill.parameters():
|
| 1008 |
+
param.requires_grad = False
|
| 1009 |
+
|
| 1010 |
+
x = self.get_input(batch)
|
| 1011 |
+
|
| 1012 |
+
if self.scale_equivariance:
|
| 1013 |
+
xs, xrec_rec, xrec_sem, qloss, decoder_distill_output = self._forward_se_branch_outputs(x)
|
| 1014 |
+
else:
|
| 1015 |
+
xrec_rec, xrec_sem, qloss, decoder_distill_output = self._forward_branch_outputs(x)
|
| 1016 |
+
xs = x
|
| 1017 |
+
|
| 1018 |
+
distill_loss = self.distill_loss(self.get_distill_gt(x), decoder_distill_output) if self.if_distill_loss else torch.tensor(0.0, device=x.device)
|
| 1019 |
+
|
| 1020 |
+
discloss, log_dict_disc = self.loss(
|
| 1021 |
+
qloss,
|
| 1022 |
+
distill_loss,
|
| 1023 |
+
xs,
|
| 1024 |
+
xrec_rec,
|
| 1025 |
+
1,
|
| 1026 |
+
self.global_step,
|
| 1027 |
+
last_layer=self.get_last_layer(),
|
| 1028 |
+
split="train",
|
| 1029 |
+
)
|
| 1030 |
+
self.log("train/discloss", discloss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 1031 |
+
self.log_dict(log_dict_disc, prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 1032 |
+
|
| 1033 |
+
discloss = discloss / self.grad_acc_steps
|
| 1034 |
+
self.manual_backward(discloss)
|
| 1035 |
+
if (batch_idx + 1) % self.grad_acc_steps == 0:
|
| 1036 |
+
opt_disc.step()
|
| 1037 |
+
opt_disc.zero_grad()
|
| 1038 |
+
scheduler_disc_warmup.step()
|
| 1039 |
+
|
| 1040 |
+
rec_aeloss, log_dict_ae = self.loss(
|
| 1041 |
+
qloss,
|
| 1042 |
+
distill_loss,
|
| 1043 |
+
xs,
|
| 1044 |
+
xrec_rec,
|
| 1045 |
+
0,
|
| 1046 |
+
self.global_step,
|
| 1047 |
+
last_layer=self.get_last_layer(),
|
| 1048 |
+
split="train",
|
| 1049 |
+
)
|
| 1050 |
+
sem_aeloss, log_dict_sem = self._semantic_decoder_loss(xs, xrec_sem, split="train")
|
| 1051 |
+
aeloss = rec_aeloss + sem_aeloss
|
| 1052 |
+
|
| 1053 |
+
self.log("train/distill_loss", distill_loss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 1054 |
+
self.log("train/aeloss", aeloss, prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 1055 |
+
self.log("train/aeloss_rec", rec_aeloss, prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 1056 |
+
self.log("train/aeloss_sem", sem_aeloss, prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 1057 |
+
self.log_dict(log_dict_ae, prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 1058 |
+
self.log_dict(log_dict_sem, prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 1059 |
+
|
| 1060 |
+
aeloss = aeloss / self.grad_acc_steps
|
| 1061 |
+
self.manual_backward(aeloss)
|
| 1062 |
+
if (batch_idx + 1) % self.grad_acc_steps == 0:
|
| 1063 |
+
opt_ae.step()
|
| 1064 |
+
opt_ae.zero_grad()
|
| 1065 |
+
scheduler_ae_warmup.step()
|
| 1066 |
+
|
| 1067 |
+
def log_images(self, batch, **kwargs):
|
| 1068 |
+
log = dict()
|
| 1069 |
+
x = self.get_input(batch)
|
| 1070 |
+
x = x.to(self.device)
|
| 1071 |
+
rec, sem, _, _ = self._forward_branch_outputs(x)
|
| 1072 |
+
log["inputs"] = x
|
| 1073 |
+
log["reconstructions"] = rec
|
| 1074 |
+
log["reconstructions_rec"] = rec
|
| 1075 |
+
log["reconstructions_sem"] = sem
|
| 1076 |
+
return log
|
| 1077 |
+
|
| 1078 |
+
|
| 1079 |
+
class VQModelIFExtraSepEncSepDecJointSem(VQModelIFExtraSepEncSepDec):
|
| 1080 |
+
"""
|
| 1081 |
+
Variant of VQModelIFExtraSepEncSepDec where the semantic decoder is trained
|
| 1082 |
+
jointly with the semantic encoder (encoder2 / quantize2).
|
| 1083 |
+
|
| 1084 |
+
Unlike the base class, quant_sem is NOT detached before being passed to
|
| 1085 |
+
decoder_sem, so reconstruction gradients flow back into the semantic encoder
|
| 1086 |
+
and quantizer. The semantic decoder loss is weighted by
|
| 1087 |
+
``sem_dec_loss_weight`` (default 0.1) to avoid dominating the primary
|
| 1088 |
+
reconstruction objective.
|
| 1089 |
+
"""
|
| 1090 |
+
|
| 1091 |
+
def __init__(self, sem_dec_loss_weight=0.1, **kwargs):
|
| 1092 |
+
super().__init__(**kwargs)
|
| 1093 |
+
self.sem_dec_loss_weight = sem_dec_loss_weight
|
| 1094 |
+
|
| 1095 |
+
def _decode_components(self, quant):
|
| 1096 |
+
if isinstance(quant, tuple):
|
| 1097 |
+
quant_rec = quant[0]
|
| 1098 |
+
quant_sem = quant[1]
|
| 1099 |
+
else:
|
| 1100 |
+
raise ValueError('VQModelIFExtraSepEncSepDecJointSem expects quant to be a tuple')
|
| 1101 |
+
|
| 1102 |
+
distill_conv_out = self.post_quant_conv_distill(quant_sem).view(
|
| 1103 |
+
quant_sem.shape[0], -1, quant_sem.shape[2] * quant_sem.shape[3]
|
| 1104 |
+
)
|
| 1105 |
+
|
| 1106 |
+
rec = self.decoder(self.post_quant_conv(quant_rec))
|
| 1107 |
+
# No detach: gradients from decoder_sem flow back into encoder2/quantize2.
|
| 1108 |
+
sem = self.decoder_sem(self.post_quant_conv_sem(quant_sem))
|
| 1109 |
+
|
| 1110 |
+
return rec, sem, distill_conv_out
|
| 1111 |
+
|
| 1112 |
+
def _combine_ae_losses(self, rec_aeloss, sem_aeloss):
|
| 1113 |
+
return rec_aeloss + self.sem_dec_loss_weight * sem_aeloss
|
| 1114 |
+
|
| 1115 |
+
|
| 1116 |
+
class VQModelIFExtraSepEncSepDecJointSem(VQModelIFExtraSepEncSepDec):
|
| 1117 |
+
"""
|
| 1118 |
+
Variant of VQModelIFExtraSepEncSepDec where the semantic decoder is trained
|
| 1119 |
+
jointly with the semantic encoder (encoder2 / quantize2).
|
| 1120 |
+
|
| 1121 |
+
Unlike the base class, quant_sem is NOT detached before being passed to
|
| 1122 |
+
decoder_sem, so reconstruction gradients flow back into the semantic encoder
|
| 1123 |
+
and quantizer. The semantic decoder loss is weighted by
|
| 1124 |
+
``sem_dec_loss_weight`` (default 0.1) to avoid dominating the primary
|
| 1125 |
+
reconstruction objective.
|
| 1126 |
+
"""
|
| 1127 |
+
|
| 1128 |
+
def __init__(self, sem_dec_loss_weight=0.1, **kwargs):
|
| 1129 |
+
super().__init__(**kwargs)
|
| 1130 |
+
self.sem_dec_loss_weight = sem_dec_loss_weight
|
| 1131 |
+
|
| 1132 |
+
def _decode_components(self, quant):
|
| 1133 |
+
if isinstance(quant, tuple):
|
| 1134 |
+
quant_rec = quant[0]
|
| 1135 |
+
quant_sem = quant[1]
|
| 1136 |
+
else:
|
| 1137 |
+
raise ValueError('VQModelIFExtraSepEncSepDecJointSem expects quant to be a tuple')
|
| 1138 |
+
|
| 1139 |
+
distill_conv_out = self.post_quant_conv_distill(quant_sem).view(
|
| 1140 |
+
quant_sem.shape[0], -1, quant_sem.shape[2] * quant_sem.shape[3]
|
| 1141 |
+
)
|
| 1142 |
+
|
| 1143 |
+
rec = self.decoder(self.post_quant_conv(quant_rec))
|
| 1144 |
+
# No detach: gradients from decoder_sem flow back into encoder2/quantize2.
|
| 1145 |
+
sem = self.decoder_sem(self.post_quant_conv_sem(quant_sem))
|
| 1146 |
+
|
| 1147 |
+
return rec, sem, distill_conv_out
|
| 1148 |
+
|
| 1149 |
+
def _combine_ae_losses(self, rec_aeloss, sem_aeloss):
|
| 1150 |
+
return rec_aeloss + self.sem_dec_loss_weight * sem_aeloss
|
| 1151 |
+
|
| 1152 |
+
|
| 1153 |
+
class VQModelIFSep(VQModelIF):
|
| 1154 |
+
def __init__(self,
|
| 1155 |
+
encoder_config,
|
| 1156 |
+
decoder_config,
|
| 1157 |
+
quantizer_config,
|
| 1158 |
+
loss_config=None,
|
| 1159 |
+
grad_acc_steps=1,
|
| 1160 |
+
cont_ratio_trainig= 0.0,
|
| 1161 |
+
ignore_keys=[],
|
| 1162 |
+
monitor=None,
|
| 1163 |
+
entropy_loss_weight_scheduler_config=None,
|
| 1164 |
+
distill_model_type='VIT_DINOv2', # 'VIT_DINO' or 'CNN' or VIT_DINOv2, VIT_DINOv2_large_reg4, SAM_VIT
|
| 1165 |
+
min_lr_multiplier=0.1,
|
| 1166 |
+
only_decoder=False,
|
| 1167 |
+
scale_equivariance=[]
|
| 1168 |
+
):
|
| 1169 |
+
super().__init__(encoder_config, decoder_config, quantizer_config, loss_config,
|
| 1170 |
+
grad_acc_steps, cont_ratio_trainig, ignore_keys,
|
| 1171 |
+
monitor,
|
| 1172 |
+
entropy_loss_weight_scheduler_config,
|
| 1173 |
+
distill_model_type, min_lr_multiplier, only_decoder, scale_equivariance)
|
| 1174 |
+
|
| 1175 |
+
self.encoder2 = instantiate_from_config(encoder_config)
|
| 1176 |
+
self.post_quant_conv = torch.nn.Conv2d(quantizer_config.params['e_dim'], decoder_config.params["z_channels"], 1)
|
| 1177 |
+
self.quant_conv2 = nn.Conv2d(encoder_config.params["z_channels"], quantizer_config.params['e_dim'], 1)
|
| 1178 |
+
self.quantize2 = instantiate_from_config(quantizer_config)
|
| 1179 |
+
|
| 1180 |
+
|
| 1181 |
+
def decode(self, quant):
|
| 1182 |
+
if isinstance(quant, tuple):
|
| 1183 |
+
quant_rec = quant[0]
|
| 1184 |
+
quant_sem = quant[1]
|
| 1185 |
+
else:
|
| 1186 |
+
print('Error: quant should be a tuple')
|
| 1187 |
+
distill_conv_out = self.post_quant_conv_distill(quant_sem).view(quant_sem.shape[0], -1, quant_sem.shape[2]*quant_sem.shape[3])
|
| 1188 |
+
#quant_cat = torch.cat((quant_rec, quant_sem), dim=1)
|
| 1189 |
+
quant = self.post_quant_conv(quant_rec)
|
| 1190 |
+
return self.decoder(quant), distill_conv_out
|
|
@@ -0,0 +1,279 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
from copy import deepcopy
|
| 3 |
+
|
| 4 |
+
import torch
|
| 5 |
+
|
| 6 |
+
from .fm_model_v2 import (
|
| 7 |
+
FlowMatchingObjective,
|
| 8 |
+
FlowMatchingSamplerTeacherForcing,
|
| 9 |
+
PredictorModule,
|
| 10 |
+
requires_grad,
|
| 11 |
+
update_ema,
|
| 12 |
+
)
|
| 13 |
+
|
| 14 |
+
_DEFAULT_SAMPLE_GRIDS = {
|
| 15 |
+
1: [0.99],
|
| 16 |
+
2: [0.99, 0.8333],
|
| 17 |
+
4: [0.99, 0.9375, 0.8333, 0.625],
|
| 18 |
+
}
|
| 19 |
+
|
| 20 |
+
|
| 21 |
+
def _endpoint(x, t, v):
|
| 22 |
+
"""G(x, t) = x + t * v -- endpoint (x0) prediction."""
|
| 23 |
+
shape = [t.shape[0]] + [1] * (x.dim() - 1)
|
| 24 |
+
return x + t.view(*shape) * v
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class ConsistencyDistillationObjective(FlowMatchingObjective):
|
| 28 |
+
"""
|
| 29 |
+
Causal consistency distillation loss for a teacher-forced flow-matching
|
| 30 |
+
predictor: distills a frozen `teacher_vit` into a few-step student `vit`,
|
| 31 |
+
whose EMA (`ema_vit`) is the deliverable.
|
| 32 |
+
"""
|
| 33 |
+
|
| 34 |
+
def __init__(
|
| 35 |
+
self,
|
| 36 |
+
predictor_module,
|
| 37 |
+
sigma_min=1e-5,
|
| 38 |
+
cd_num_timesteps=48,
|
| 39 |
+
cd_t_min=0.01,
|
| 40 |
+
cd_t_max=0.99,
|
| 41 |
+
cd_grid="uniform",
|
| 42 |
+
cd_loss="l2",
|
| 43 |
+
cd_pseudo_huber_c=0.00054,
|
| 44 |
+
):
|
| 45 |
+
super().__init__(predictor_module, sigma_min=sigma_min)
|
| 46 |
+
self.cd_loss = cd_loss
|
| 47 |
+
self.cd_pseudo_huber_c = cd_pseudo_huber_c
|
| 48 |
+
|
| 49 |
+
if cd_grid == "uniform":
|
| 50 |
+
grid = torch.linspace(cd_t_min, cd_t_max, cd_num_timesteps)
|
| 51 |
+
else:
|
| 52 |
+
grid = torch.as_tensor(list(cd_grid), dtype=torch.float32)
|
| 53 |
+
if grid.numel() != cd_num_timesteps:
|
| 54 |
+
raise ValueError(
|
| 55 |
+
f"cd_grid has {grid.numel()} entries, expected "
|
| 56 |
+
f"cd_num_timesteps={cd_num_timesteps}."
|
| 57 |
+
)
|
| 58 |
+
predictor_module.register_buffer("cd_t_grid", grid, persistent=False)
|
| 59 |
+
|
| 60 |
+
def _split_sequence(self, x):
|
| 61 |
+
context = x[:, : -self.module.num_pred_frames]
|
| 62 |
+
target = x[:, -self.module.num_pred_frames :]
|
| 63 |
+
return context, target
|
| 64 |
+
|
| 65 |
+
def _forward_v(self, net, context, target_t, t, frame_rate, model_condition_kwargs):
|
| 66 |
+
sampler = self.module.sampler
|
| 67 |
+
model_input = sampler._build_model_inputs(context, target_t, t)
|
| 68 |
+
model_t = sampler._build_model_t(context, target_t, t)
|
| 69 |
+
pred = net(model_input, t=model_t * sampler.timescale, frame_rate=frame_rate, **model_condition_kwargs)
|
| 70 |
+
return sampler._extract_target_prediction(pred)
|
| 71 |
+
|
| 72 |
+
def compute_loss(self, pred, target):
|
| 73 |
+
diff = pred.float() - target.float()
|
| 74 |
+
if self.cd_loss == "pseudo_huber":
|
| 75 |
+
c = self.cd_pseudo_huber_c * (diff[0].numel() ** 0.5)
|
| 76 |
+
return (diff.flatten(1).pow(2).sum(dim=1) + c * c).sqrt() - c
|
| 77 |
+
return diff.pow(2)
|
| 78 |
+
|
| 79 |
+
def compute_step(self, x, frame_rate, condition_kwargs=None):
|
| 80 |
+
if getattr(self.module, "teacher_vit", None) is None:
|
| 81 |
+
raise RuntimeError(
|
| 82 |
+
"ConsistencyDistillationObjective requires the predictor module to "
|
| 83 |
+
"have a `teacher_vit` (set teacher_ckpt_path on "
|
| 84 |
+
"ConsistencyDistillPredictorModule)."
|
| 85 |
+
)
|
| 86 |
+
|
| 87 |
+
context, target = self._split_sequence(x)
|
| 88 |
+
batch_size = target.shape[0]
|
| 89 |
+
model_condition_kwargs = self.module.condition_preprocessor.get_model_condition_kwargs(condition_kwargs)
|
| 90 |
+
|
| 91 |
+
grid = self.module.cd_t_grid
|
| 92 |
+
n = torch.randint(1, grid.numel(), (batch_size,), device=x.device)
|
| 93 |
+
t, t_prev = grid[n], grid[n - 1]
|
| 94 |
+
|
| 95 |
+
noise = torch.randn_like(target)
|
| 96 |
+
x_t, _ = self.add_noise(target, t, noise=noise)
|
| 97 |
+
|
| 98 |
+
with torch.no_grad():
|
| 99 |
+
v_teacher = self._forward_v(self.module.teacher_vit, context, x_t, t, frame_rate, model_condition_kwargs)
|
| 100 |
+
step_shape = [t.shape[0]] + [1] * (x_t.dim() - 1)
|
| 101 |
+
x_prev = x_t + (t - t_prev).view(*step_shape) * v_teacher
|
| 102 |
+
v_target = self._forward_v(self.module.ema_vit, context, x_prev, t_prev, frame_rate, model_condition_kwargs)
|
| 103 |
+
tgt = _endpoint(x_prev, t_prev, v_target)
|
| 104 |
+
|
| 105 |
+
v_student = self._forward_v(self.module.vit, context, x_t, t, frame_rate, model_condition_kwargs)
|
| 106 |
+
pred = _endpoint(x_t, t, v_student)
|
| 107 |
+
|
| 108 |
+
return self.compute_loss(pred, tgt)
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
class ConsistencyDistillationSampler(FlowMatchingSamplerTeacherForcing):
|
| 112 |
+
"""
|
| 113 |
+
Few-step consistency sampler: repeatedly renoises the current endpoint
|
| 114 |
+
estimate and re-predicts, over a short descending grid of timesteps,
|
| 115 |
+
instead of integrating a many-step Euler ODE.
|
| 116 |
+
"""
|
| 117 |
+
|
| 118 |
+
def __init__(
|
| 119 |
+
self,
|
| 120 |
+
predictor_module,
|
| 121 |
+
timescale=1.0,
|
| 122 |
+
integration_t_eps=0.0,
|
| 123 |
+
timestep_conditioning="global",
|
| 124 |
+
sample_fresh_noise=True,
|
| 125 |
+
sample_t_grids=None,
|
| 126 |
+
):
|
| 127 |
+
super().__init__(
|
| 128 |
+
predictor_module,
|
| 129 |
+
timescale=timescale,
|
| 130 |
+
integration_t_eps=integration_t_eps,
|
| 131 |
+
timestep_conditioning=timestep_conditioning,
|
| 132 |
+
)
|
| 133 |
+
self.sample_fresh_noise = sample_fresh_noise
|
| 134 |
+
self.sample_t_grids = dict(_DEFAULT_SAMPLE_GRIDS)
|
| 135 |
+
if sample_t_grids is not None:
|
| 136 |
+
self.sample_t_grids.update({int(k): list(v) for k, v in dict(sample_t_grids).items()})
|
| 137 |
+
|
| 138 |
+
def _forward_v(self, net, context, target_t, t, frame_rate, model_condition_kwargs):
|
| 139 |
+
model_input = self._build_model_inputs(context, target_t, t)
|
| 140 |
+
model_t = self._build_model_t(context, target_t, t)
|
| 141 |
+
pred = net(model_input, t=model_t * self.timescale, frame_rate=frame_rate, **model_condition_kwargs)
|
| 142 |
+
return self._extract_target_prediction(pred)
|
| 143 |
+
|
| 144 |
+
def _fewstep_grid(self, NFE):
|
| 145 |
+
if NFE in self.sample_t_grids:
|
| 146 |
+
return [float(v) for v in self.sample_t_grids[NFE]]
|
| 147 |
+
t_max = float(self.module.cd_t_grid[-1])
|
| 148 |
+
t_min = float(self.module.cd_t_grid[0])
|
| 149 |
+
return torch.linspace(t_max, t_min, NFE + 1)[:-1].tolist()
|
| 150 |
+
|
| 151 |
+
@torch.no_grad()
|
| 152 |
+
def sample(
|
| 153 |
+
self,
|
| 154 |
+
images=None,
|
| 155 |
+
latent=False,
|
| 156 |
+
eta=0.0,
|
| 157 |
+
NFE=2,
|
| 158 |
+
sample_with_ema=True,
|
| 159 |
+
num_samples=8,
|
| 160 |
+
frame_rate=None,
|
| 161 |
+
condition_kwargs=None,
|
| 162 |
+
return_sample=False,
|
| 163 |
+
):
|
| 164 |
+
del eta # accepted only so PredictorModule.sample/roll_out call sites keep working
|
| 165 |
+
net = self._get_net(sample_with_ema)
|
| 166 |
+
device = next(net.parameters()).device
|
| 167 |
+
context = self._prepare_context(images, latent)
|
| 168 |
+
|
| 169 |
+
condition_kwargs = self.module.condition_preprocessor.prepare_condition_kwargs(
|
| 170 |
+
condition_kwargs,
|
| 171 |
+
batch_size=num_samples,
|
| 172 |
+
device=device,
|
| 173 |
+
split="sample",
|
| 174 |
+
)
|
| 175 |
+
model_condition_kwargs = self.module.condition_preprocessor.get_model_condition_kwargs(condition_kwargs)
|
| 176 |
+
|
| 177 |
+
if frame_rate is None:
|
| 178 |
+
frame_rate = self._default_frame_rate(num_samples, device)
|
| 179 |
+
|
| 180 |
+
input_h, input_w = self._get_input_hw()
|
| 181 |
+
grid = self._fewstep_grid(NFE)
|
| 182 |
+
|
| 183 |
+
eps0 = torch.randn(
|
| 184 |
+
num_samples,
|
| 185 |
+
self.module.num_pred_frames,
|
| 186 |
+
self.module.vit.in_channels,
|
| 187 |
+
input_h,
|
| 188 |
+
input_w,
|
| 189 |
+
device=device,
|
| 190 |
+
)
|
| 191 |
+
x = eps0
|
| 192 |
+
x0_hat = None
|
| 193 |
+
for k, t_k in enumerate(grid):
|
| 194 |
+
t = torch.full((num_samples,), t_k, device=device)
|
| 195 |
+
if k > 0:
|
| 196 |
+
eps_k = torch.randn_like(x) if self.sample_fresh_noise else eps0
|
| 197 |
+
x = (1.0 - t_k) * x0_hat + t_k * eps_k
|
| 198 |
+
v = self._forward_v(net, context, x, t, frame_rate, model_condition_kwargs)
|
| 199 |
+
x0_hat = _endpoint(x, t, v)
|
| 200 |
+
|
| 201 |
+
if return_sample:
|
| 202 |
+
return x0_hat, self.module.decode_frames(x0_hat.clone())
|
| 203 |
+
return x0_hat
|
| 204 |
+
|
| 205 |
+
|
| 206 |
+
class ConsistencyDistillPredictorModule(PredictorModule):
|
| 207 |
+
"""
|
| 208 |
+
PredictorModule specialization that distills a frozen teacher (loaded from
|
| 209 |
+
`teacher_ckpt_path`) into a few-step student via consistency distillation.
|
| 210 |
+
The deliverable is `ema_vit` (evaluate with sample_with_ema=True, NFE 1/2/4).
|
| 211 |
+
"""
|
| 212 |
+
|
| 213 |
+
# `teacher_vit.*` is intentionally absent from saved checkpoints (see
|
| 214 |
+
# on_save_checkpoint below) since the teacher is always reloaded fresh from
|
| 215 |
+
# teacher_ckpt_path at __init__ time. Eval scripts that assert on
|
| 216 |
+
# load_state_dict(...).missing_keys should exempt these prefixes.
|
| 217 |
+
checkpoint_exempt_key_prefixes = ("teacher_vit.",)
|
| 218 |
+
|
| 219 |
+
def __init__(
|
| 220 |
+
self,
|
| 221 |
+
*,
|
| 222 |
+
teacher_ckpt_path,
|
| 223 |
+
cd_ema_mu=0.999,
|
| 224 |
+
cd_weight_decay=0.0,
|
| 225 |
+
**kwargs,
|
| 226 |
+
):
|
| 227 |
+
super().__init__(**kwargs)
|
| 228 |
+
self.cd_ema_mu = cd_ema_mu
|
| 229 |
+
self.cd_weight_decay = cd_weight_decay
|
| 230 |
+
self.strict_loading = False
|
| 231 |
+
|
| 232 |
+
checkpoint_path = os.path.expandvars(teacher_ckpt_path)
|
| 233 |
+
if not os.path.exists(checkpoint_path):
|
| 234 |
+
raise FileNotFoundError(f"teacher_ckpt_path {checkpoint_path} does not exist.")
|
| 235 |
+
|
| 236 |
+
try:
|
| 237 |
+
state_dict = torch.load(checkpoint_path, map_location="cpu", weights_only=True, mmap=True)["state_dict"]
|
| 238 |
+
except (TypeError, RuntimeError):
|
| 239 |
+
state_dict = torch.load(checkpoint_path, map_location="cpu", weights_only=True)["state_dict"]
|
| 240 |
+
|
| 241 |
+
ema_state_dict = {
|
| 242 |
+
key[len("ema_vit.") :]: value.clone()
|
| 243 |
+
for key, value in state_dict.items()
|
| 244 |
+
if key.startswith("ema_vit.")
|
| 245 |
+
}
|
| 246 |
+
del state_dict
|
| 247 |
+
if not ema_state_dict:
|
| 248 |
+
raise ValueError(f"No ema_vit.* keys found in teacher checkpoint {checkpoint_path}.")
|
| 249 |
+
|
| 250 |
+
self.vit.load_state_dict(ema_state_dict, strict=True)
|
| 251 |
+
self.ema_vit.load_state_dict(ema_state_dict, strict=True)
|
| 252 |
+
self.teacher_vit = deepcopy(self.vit)
|
| 253 |
+
requires_grad(self.teacher_vit, False)
|
| 254 |
+
self.teacher_vit.eval()
|
| 255 |
+
|
| 256 |
+
def setup(self, stage=None):
|
| 257 |
+
super().setup(stage)
|
| 258 |
+
if hasattr(self, "teacher_vit") and self.teacher_vit is not None:
|
| 259 |
+
self.teacher_vit.requires_grad_(False)
|
| 260 |
+
|
| 261 |
+
def configure_optimizers(self):
|
| 262 |
+
params = [p for p in self.vit.parameters() if p.requires_grad]
|
| 263 |
+
optimizer = torch.optim.AdamW(params, lr=self.learning_rate, weight_decay=self.cd_weight_decay)
|
| 264 |
+
scheduler = self.get_warmup_scheduler(optimizer, self.warmup_steps, self.min_lr_multiplier)
|
| 265 |
+
return [optimizer], [{"scheduler": scheduler, "interval": "step"}]
|
| 266 |
+
|
| 267 |
+
def on_train_batch_end(self, outputs, batch, batch_idx):
|
| 268 |
+
if not hasattr(self, "_ema_stream"):
|
| 269 |
+
self._ema_stream = torch.cuda.Stream() if torch.cuda.is_available() else None
|
| 270 |
+
if self._ema_stream is not None:
|
| 271 |
+
with torch.cuda.stream(self._ema_stream):
|
| 272 |
+
update_ema(self.ema_vit, self.vit, decay=self.cd_ema_mu)
|
| 273 |
+
else:
|
| 274 |
+
update_ema(self.ema_vit, self.vit, decay=self.cd_ema_mu)
|
| 275 |
+
|
| 276 |
+
def on_save_checkpoint(self, checkpoint):
|
| 277 |
+
super().on_save_checkpoint(checkpoint)
|
| 278 |
+
for key in [k for k in checkpoint["state_dict"] if k.startswith("teacher_vit.")]:
|
| 279 |
+
del checkpoint["state_dict"][key]
|
|
@@ -0,0 +1,1414 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import logging
|
| 2 |
+
import os
|
| 3 |
+
|
| 4 |
+
import numpy as np
|
| 5 |
+
import PIL.Image
|
| 6 |
+
import PIL.ImageDraw
|
| 7 |
+
import torch
|
| 8 |
+
from einops import rearrange
|
| 9 |
+
from omegaconf import OmegaConf
|
| 10 |
+
|
| 11 |
+
from data.utils import get_trajectory_from_speeds_and_yaw_rates_batch
|
| 12 |
+
from util import instantiate_from_config
|
| 13 |
+
|
| 14 |
+
|
| 15 |
+
def _requires_grad(model, flag=True):
|
| 16 |
+
for param in model.parameters():
|
| 17 |
+
param.requires_grad = flag
|
| 18 |
+
|
| 19 |
+
|
| 20 |
+
def _text_on_image(images, texts):
|
| 21 |
+
for i in range(images.shape[0]):
|
| 22 |
+
image = images[i]
|
| 23 |
+
text = texts[i]
|
| 24 |
+
image = PIL.Image.fromarray((((image + 1) / 2).permute(1, 2, 0).cpu().numpy() * 255).astype("uint8"))
|
| 25 |
+
draw = PIL.ImageDraw.Draw(image)
|
| 26 |
+
draw.text((10, 10), text, fill="red", font_size=15)
|
| 27 |
+
image = (torch.from_numpy(np.array(image)).permute(2, 0, 1).float() / 255.0) * 2 - 1
|
| 28 |
+
images[i] = image
|
| 29 |
+
return images
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
class ConditionPreprocessor:
|
| 33 |
+
"""
|
| 34 |
+
Default no-op condition preprocessor.
|
| 35 |
+
|
| 36 |
+
Concrete implementations can extract batch conditions, normalize sampling inputs,
|
| 37 |
+
and update autoregressive rollout state without changing predictor/objective/sampler
|
| 38 |
+
control flow.
|
| 39 |
+
"""
|
| 40 |
+
|
| 41 |
+
def __init__(self, predictor_module):
|
| 42 |
+
self.module = predictor_module
|
| 43 |
+
|
| 44 |
+
def get_condition_kwargs_from_batch(self, batch, split):
|
| 45 |
+
return {}
|
| 46 |
+
|
| 47 |
+
def prepare_condition_kwargs(self, condition_kwargs=None, batch_size=None, device=None, split=None):
|
| 48 |
+
del batch_size, split
|
| 49 |
+
if condition_kwargs is None:
|
| 50 |
+
return {}
|
| 51 |
+
|
| 52 |
+
prepared = {}
|
| 53 |
+
for key, value in condition_kwargs.items():
|
| 54 |
+
if torch.is_tensor(value) and device is not None:
|
| 55 |
+
prepared[key] = value.to(device)
|
| 56 |
+
else:
|
| 57 |
+
prepared[key] = value
|
| 58 |
+
return prepared
|
| 59 |
+
|
| 60 |
+
def get_model_condition_kwargs(self, condition_kwargs):
|
| 61 |
+
if not condition_kwargs:
|
| 62 |
+
return {}
|
| 63 |
+
return {
|
| 64 |
+
key: value
|
| 65 |
+
for key, value in condition_kwargs.items()
|
| 66 |
+
if not key.startswith("_")
|
| 67 |
+
}
|
| 68 |
+
|
| 69 |
+
def slice_condition_kwargs(self, condition_kwargs, item):
|
| 70 |
+
if not condition_kwargs:
|
| 71 |
+
return {}
|
| 72 |
+
|
| 73 |
+
sliced = {}
|
| 74 |
+
for key, value in condition_kwargs.items():
|
| 75 |
+
if torch.is_tensor(value):
|
| 76 |
+
sliced[key] = value[item]
|
| 77 |
+
else:
|
| 78 |
+
sliced[key] = value
|
| 79 |
+
return sliced
|
| 80 |
+
|
| 81 |
+
def update_rollout_condition_kwargs(self, condition_kwargs, prediction, context, step_idx):
|
| 82 |
+
del prediction, context, step_idx
|
| 83 |
+
return condition_kwargs
|
| 84 |
+
|
| 85 |
+
def get_rollout_visualization_state(self, condition_kwargs):
|
| 86 |
+
del condition_kwargs
|
| 87 |
+
return None
|
| 88 |
+
|
| 89 |
+
def get_visualization_trajectory(self, visualization_state):
|
| 90 |
+
del visualization_state
|
| 91 |
+
return None
|
| 92 |
+
|
| 93 |
+
def update_rollout_visualization_state(self, visualization_state, step_idx):
|
| 94 |
+
del step_idx
|
| 95 |
+
return visualization_state
|
| 96 |
+
|
| 97 |
+
def get_rollout_visualization_trajectory(
|
| 98 |
+
self,
|
| 99 |
+
condition_kwargs,
|
| 100 |
+
num_gen_steps,
|
| 101 |
+
num_pred_frames=1,
|
| 102 |
+
num_condition_frames=None,
|
| 103 |
+
):
|
| 104 |
+
del condition_kwargs, num_gen_steps, num_pred_frames, num_condition_frames
|
| 105 |
+
return None
|
| 106 |
+
|
| 107 |
+
def annotate_logged_images(self, images, batch, num_images):
|
| 108 |
+
del batch, num_images
|
| 109 |
+
return images
|
| 110 |
+
|
| 111 |
+
def to_device(self, device):
|
| 112 |
+
pass
|
| 113 |
+
|
| 114 |
+
def get_max_condition_odometry_offset(self):
|
| 115 |
+
"""
|
| 116 |
+
Return the largest future raw-odometry offset needed to construct one
|
| 117 |
+
conditioning tensor from the current anchor position.
|
| 118 |
+
|
| 119 |
+
Preprocessors that do not depend on extra raw odometry should return
|
| 120 |
+
``None``.
|
| 121 |
+
"""
|
| 122 |
+
return None
|
| 123 |
+
|
| 124 |
+
def get_required_rollout_odometry_steps(
|
| 125 |
+
self,
|
| 126 |
+
*,
|
| 127 |
+
validation_params,
|
| 128 |
+
num_condition_frames,
|
| 129 |
+
num_gen_frames,
|
| 130 |
+
rollout_steps,
|
| 131 |
+
):
|
| 132 |
+
del validation_params, num_condition_frames, num_gen_frames, rollout_steps
|
| 133 |
+
return None
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
def _validate_speed_yaw_scale(value, name):
|
| 137 |
+
scale = float(value)
|
| 138 |
+
if not np.isfinite(scale):
|
| 139 |
+
raise ValueError(f"`{name}` must be finite, got {value!r}.")
|
| 140 |
+
return scale
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
def _apply_speed_yaw_scales(steering, speed_scale=1.0, yaw_rate_scale=1.0):
|
| 144 |
+
if not torch.is_tensor(steering):
|
| 145 |
+
raise TypeError(f"`steering` must be a tensor, got {type(steering).__name__}.")
|
| 146 |
+
scaled = steering.clone()
|
| 147 |
+
scaled[..., 0] = scaled[..., 0] * float(speed_scale)
|
| 148 |
+
scaled[..., 1] = scaled[..., 1] * float(yaw_rate_scale)
|
| 149 |
+
return scaled
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
class SpeedYawConditionPreprocessorBase(ConditionPreprocessor):
|
| 153 |
+
"""
|
| 154 |
+
Shared steering-conditioning utilities for preprocessors that consume raw
|
| 155 |
+
`[speed, yaw_rate]` sequences and advance through them during rollout.
|
| 156 |
+
"""
|
| 157 |
+
|
| 158 |
+
def __init__(
|
| 159 |
+
self,
|
| 160 |
+
predictor_module,
|
| 161 |
+
odometry_steps_per_image_frame,
|
| 162 |
+
context_frame_anchor_index=-1,
|
| 163 |
+
steering_format="speed_yawrate",
|
| 164 |
+
speed_scale=1.0,
|
| 165 |
+
yaw_rate_scale=1.0,
|
| 166 |
+
steering_drop=0.0,
|
| 167 |
+
):
|
| 168 |
+
super().__init__(predictor_module)
|
| 169 |
+
self.steering_format = str(steering_format)
|
| 170 |
+
if self.steering_format != "speed_yawrate":
|
| 171 |
+
raise ValueError(
|
| 172 |
+
f"{type(self).__name__} only supports `steering_format='speed_yawrate'`, "
|
| 173 |
+
f"got {self.steering_format!r}."
|
| 174 |
+
)
|
| 175 |
+
|
| 176 |
+
self.odometry_steps_per_image_frame = int(odometry_steps_per_image_frame)
|
| 177 |
+
if self.odometry_steps_per_image_frame <= 0:
|
| 178 |
+
raise ValueError(
|
| 179 |
+
"`odometry_steps_per_image_frame` must be a positive integer, "
|
| 180 |
+
f"got {self.odometry_steps_per_image_frame}."
|
| 181 |
+
)
|
| 182 |
+
self.context_frame_anchor_index = int(context_frame_anchor_index)
|
| 183 |
+
self.speed_scale = _validate_speed_yaw_scale(speed_scale, "speed_scale")
|
| 184 |
+
self.yaw_rate_scale = _validate_speed_yaw_scale(yaw_rate_scale, "yaw_rate_scale")
|
| 185 |
+
self.steering_drop = self._validate_steering_drop(steering_drop)
|
| 186 |
+
|
| 187 |
+
def _validate_steering_drop(self, steering_drop):
|
| 188 |
+
drop_prob = float(steering_drop)
|
| 189 |
+
if not np.isfinite(drop_prob):
|
| 190 |
+
raise ValueError(f"`steering_drop` must be finite, got {steering_drop!r}.")
|
| 191 |
+
if not 0.0 <= drop_prob <= 1.0:
|
| 192 |
+
raise ValueError(f"`steering_drop` must be in [0, 1], got {drop_prob}.")
|
| 193 |
+
return drop_prob
|
| 194 |
+
|
| 195 |
+
def _validate_offsets(self, offsets, name):
|
| 196 |
+
if not offsets:
|
| 197 |
+
raise ValueError(f"`{name}` must contain at least one positive odometry offset.")
|
| 198 |
+
normalized_offsets = [int(offset) for offset in offsets]
|
| 199 |
+
if any(offset <= 0 for offset in normalized_offsets):
|
| 200 |
+
raise ValueError(f"`{name}` must be positive integers, got {normalized_offsets}.")
|
| 201 |
+
if normalized_offsets != sorted(normalized_offsets):
|
| 202 |
+
raise ValueError(f"`{name}` must be sorted in ascending order, got {normalized_offsets}.")
|
| 203 |
+
return normalized_offsets
|
| 204 |
+
|
| 205 |
+
def get_max_condition_odometry_offset(self):
|
| 206 |
+
offsets = None
|
| 207 |
+
if hasattr(self, "goal_offsets"):
|
| 208 |
+
offsets = self.goal_offsets
|
| 209 |
+
elif hasattr(self, "steering_offsets"):
|
| 210 |
+
offsets = self.steering_offsets
|
| 211 |
+
if not offsets:
|
| 212 |
+
return None
|
| 213 |
+
return int(max(offsets))
|
| 214 |
+
|
| 215 |
+
def get_required_rollout_odometry_steps(
|
| 216 |
+
self,
|
| 217 |
+
*,
|
| 218 |
+
validation_params,
|
| 219 |
+
num_condition_frames,
|
| 220 |
+
num_gen_frames,
|
| 221 |
+
rollout_steps,
|
| 222 |
+
):
|
| 223 |
+
del validation_params, num_gen_frames
|
| 224 |
+
max_condition_offset = self.get_max_condition_odometry_offset()
|
| 225 |
+
if max_condition_offset is None:
|
| 226 |
+
return None
|
| 227 |
+
|
| 228 |
+
rollout_steps = int(rollout_steps)
|
| 229 |
+
if rollout_steps <= 0:
|
| 230 |
+
raise ValueError(f"`rollout_steps` must be positive, got {rollout_steps}.")
|
| 231 |
+
|
| 232 |
+
anchor_frame_index = self._resolve_context_frame_anchor_index(int(num_condition_frames))
|
| 233 |
+
initial_anchor_odo_index = anchor_frame_index * int(self.odometry_steps_per_image_frame)
|
| 234 |
+
rollout_step_odo = int(self.odometry_steps_per_image_frame) * int(self.module.num_pred_frames)
|
| 235 |
+
last_anchor_odo_index = initial_anchor_odo_index + (rollout_steps - 1) * rollout_step_odo
|
| 236 |
+
return last_anchor_odo_index + int(max_condition_offset) + 1
|
| 237 |
+
|
| 238 |
+
def _normalize_batch_steering_format(self, steering_format):
|
| 239 |
+
if steering_format is None:
|
| 240 |
+
return None
|
| 241 |
+
if isinstance(steering_format, str):
|
| 242 |
+
return steering_format
|
| 243 |
+
if isinstance(steering_format, (list, tuple)):
|
| 244 |
+
unique_formats = {str(item) for item in steering_format}
|
| 245 |
+
if len(unique_formats) != 1:
|
| 246 |
+
raise ValueError(f"Batch contains mixed steering formats: {sorted(unique_formats)}.")
|
| 247 |
+
return next(iter(unique_formats))
|
| 248 |
+
return str(steering_format)
|
| 249 |
+
|
| 250 |
+
def _validate_batch_steering_format(self, batch):
|
| 251 |
+
batch_format = self._normalize_batch_steering_format(batch.get("steering_format"))
|
| 252 |
+
if batch_format is None:
|
| 253 |
+
return
|
| 254 |
+
if batch_format != self.steering_format:
|
| 255 |
+
raise ValueError(
|
| 256 |
+
f"{type(self).__name__} expected batch steering format "
|
| 257 |
+
f"{self.steering_format!r}, got {batch_format!r}. Check the dataset "
|
| 258 |
+
"`odo_transform_config` or `annotation_key`."
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
def _validate_steering(self, steering):
|
| 262 |
+
"""Validate and return raw steering with expected shape `[B, T, 2]`."""
|
| 263 |
+
if steering is None:
|
| 264 |
+
raise ValueError(f"`steering` is required for {type(self).__name__}.")
|
| 265 |
+
if steering.ndim != 3:
|
| 266 |
+
raise ValueError(
|
| 267 |
+
f"`steering` must have shape [B, T, 2] for {type(self).__name__}, "
|
| 268 |
+
f"got {tuple(steering.shape)}."
|
| 269 |
+
)
|
| 270 |
+
if steering.shape[2] != 2:
|
| 271 |
+
raise ValueError(
|
| 272 |
+
f"{type(self).__name__} expects raw steering features "
|
| 273 |
+
f"[speed, yaw_rate], got shape {tuple(steering.shape)}."
|
| 274 |
+
)
|
| 275 |
+
return steering
|
| 276 |
+
|
| 277 |
+
def _resolve_odometry_dt(self, batch, batch_size, device, required):
|
| 278 |
+
frame_rate = batch.get("frame_rate")
|
| 279 |
+
if frame_rate is None:
|
| 280 |
+
if required:
|
| 281 |
+
raise ValueError(
|
| 282 |
+
f"`frame_rate` is required to compute physical steering trajectories for "
|
| 283 |
+
f"{type(self).__name__}."
|
| 284 |
+
)
|
| 285 |
+
return None
|
| 286 |
+
|
| 287 |
+
frame_rate = torch.as_tensor(frame_rate, device=device, dtype=torch.float32)
|
| 288 |
+
if frame_rate.ndim == 0:
|
| 289 |
+
frame_rate = frame_rate.expand(batch_size)
|
| 290 |
+
elif frame_rate.ndim == 1 and frame_rate.shape[0] == 1 and batch_size != 1:
|
| 291 |
+
frame_rate = frame_rate.expand(batch_size)
|
| 292 |
+
elif frame_rate.ndim != 1 or frame_rate.shape[0] != batch_size:
|
| 293 |
+
raise ValueError(
|
| 294 |
+
"`frame_rate` must be broadcastable to shape [B], got "
|
| 295 |
+
f"{tuple(frame_rate.shape)} for batch_size={batch_size}."
|
| 296 |
+
)
|
| 297 |
+
|
| 298 |
+
if torch.any(frame_rate <= 0):
|
| 299 |
+
raise ValueError(f"`frame_rate` must be positive, got {frame_rate}.")
|
| 300 |
+
|
| 301 |
+
return 1.0 / (frame_rate * float(self.odometry_steps_per_image_frame))
|
| 302 |
+
|
| 303 |
+
def _get_context_num_frames(self, batch, split):
|
| 304 |
+
"""Return the number of conditioning image frames available for the current split."""
|
| 305 |
+
if not isinstance(batch, dict) or "images" not in batch:
|
| 306 |
+
raise ValueError(f"`images` are required in the batch to compute {type(self).__name__} conditions.")
|
| 307 |
+
images = batch["images"]
|
| 308 |
+
if images.ndim != 5:
|
| 309 |
+
raise ValueError(f"`images` must have shape [B, F, C, H, W], got {tuple(images.shape)}.")
|
| 310 |
+
|
| 311 |
+
if split == "rollout":
|
| 312 |
+
return images.shape[1]
|
| 313 |
+
if images.shape[1] < self.module.num_pred_frames:
|
| 314 |
+
raise ValueError(
|
| 315 |
+
f"Need at least {self.module.num_pred_frames} image frames, got {images.shape[1]}."
|
| 316 |
+
)
|
| 317 |
+
return images.shape[1] - self.module.num_pred_frames
|
| 318 |
+
|
| 319 |
+
def _resolve_context_frame_anchor_index(self, context_num_frames):
|
| 320 |
+
"""Resolve the configured anchor index against the current context length."""
|
| 321 |
+
anchor_index = self.context_frame_anchor_index
|
| 322 |
+
if anchor_index < 0:
|
| 323 |
+
anchor_index = context_num_frames + anchor_index
|
| 324 |
+
if not 0 <= anchor_index < context_num_frames:
|
| 325 |
+
raise IndexError(
|
| 326 |
+
f"Resolved context_frame_anchor_index={anchor_index} is out of bounds for "
|
| 327 |
+
f"context_num_frames={context_num_frames}."
|
| 328 |
+
)
|
| 329 |
+
return anchor_index
|
| 330 |
+
|
| 331 |
+
def _build_rollout_state(self, steering, context_num_frames):
|
| 332 |
+
steering = self._validate_steering(steering)
|
| 333 |
+
batch_size = steering.shape[0]
|
| 334 |
+
device = steering.device
|
| 335 |
+
steps_per_image_frame = torch.full(
|
| 336 |
+
(batch_size,),
|
| 337 |
+
self.odometry_steps_per_image_frame,
|
| 338 |
+
device=device,
|
| 339 |
+
dtype=torch.long,
|
| 340 |
+
)
|
| 341 |
+
context_frame_anchor_index = self._resolve_context_frame_anchor_index(context_num_frames)
|
| 342 |
+
anchor_odo_index = context_frame_anchor_index * steps_per_image_frame
|
| 343 |
+
rollout_step_odo = steps_per_image_frame * self.module.num_pred_frames
|
| 344 |
+
return {
|
| 345 |
+
"_raw_speed_yaw": steering,
|
| 346 |
+
"_anchor_odo_index": anchor_odo_index,
|
| 347 |
+
"_rollout_step_odo": rollout_step_odo,
|
| 348 |
+
}
|
| 349 |
+
|
| 350 |
+
def _transform_raw_steering(self, steering):
|
| 351 |
+
steering = self._validate_steering(steering)
|
| 352 |
+
if self.speed_scale == 1.0 and self.yaw_rate_scale == 1.0:
|
| 353 |
+
return steering
|
| 354 |
+
return _apply_speed_yaw_scales(
|
| 355 |
+
steering,
|
| 356 |
+
speed_scale=self.speed_scale,
|
| 357 |
+
yaw_rate_scale=self.yaw_rate_scale,
|
| 358 |
+
)
|
| 359 |
+
|
| 360 |
+
def _build_condition_kwargs(self, steering, context_num_frames, odometry_dt=None):
|
| 361 |
+
condition_kwargs = self._build_rollout_state(steering, context_num_frames)
|
| 362 |
+
if odometry_dt is not None:
|
| 363 |
+
condition_kwargs["_odometry_dt"] = odometry_dt.to(
|
| 364 |
+
device=steering.device,
|
| 365 |
+
dtype=steering.dtype,
|
| 366 |
+
)
|
| 367 |
+
condition_kwargs["steering"] = self._compute_condition_steering(
|
| 368 |
+
steering,
|
| 369 |
+
condition_kwargs["_anchor_odo_index"],
|
| 370 |
+
odometry_dt=condition_kwargs.get("_odometry_dt"),
|
| 371 |
+
)
|
| 372 |
+
return condition_kwargs
|
| 373 |
+
|
| 374 |
+
def _maybe_apply_steering_dropout(self, steering, split):
|
| 375 |
+
if split != "train" or self.steering_drop == 0.0:
|
| 376 |
+
return steering
|
| 377 |
+
|
| 378 |
+
drop_mask = torch.rand(steering.shape[0], device=steering.device) < self.steering_drop
|
| 379 |
+
if not drop_mask.any():
|
| 380 |
+
return steering
|
| 381 |
+
|
| 382 |
+
dropped = steering.clone()
|
| 383 |
+
dropped[drop_mask] = torch.nan
|
| 384 |
+
return dropped
|
| 385 |
+
|
| 386 |
+
def _compute_condition_steering(self, steering, anchor_odo_index, odometry_dt=None):
|
| 387 |
+
raise NotImplementedError
|
| 388 |
+
|
| 389 |
+
def _requires_odometry_dt_for_conditioning(self):
|
| 390 |
+
return False
|
| 391 |
+
|
| 392 |
+
def _make_no_steering_condition(self, batch_size, device):
|
| 393 |
+
"""Return a fully-NaN steering condition tensor for batches with no steering data.
|
| 394 |
+
|
| 395 |
+
The NaN values cause LinearSteeringEmbedder to fall back to its no_value_embeddings,
|
| 396 |
+
making missing-steering batches equivalent to fully-dropped steering (steering_drop=1.0).
|
| 397 |
+
Returns None if the subclass does not support unconditional batches.
|
| 398 |
+
"""
|
| 399 |
+
return None
|
| 400 |
+
|
| 401 |
+
def get_condition_kwargs_from_batch(self, batch, split):
|
| 402 |
+
if not isinstance(batch, dict) or "steering" not in batch:
|
| 403 |
+
if isinstance(batch, dict) and "images" in batch:
|
| 404 |
+
images = batch["images"]
|
| 405 |
+
no_steering = self._make_no_steering_condition(images.shape[0], images.device)
|
| 406 |
+
if no_steering is not None:
|
| 407 |
+
return {"steering": no_steering}
|
| 408 |
+
return {}
|
| 409 |
+
self._validate_batch_steering_format(batch)
|
| 410 |
+
source_steering = self._validate_steering(batch["steering"])
|
| 411 |
+
steering = self._transform_raw_steering(source_steering)
|
| 412 |
+
context_num_frames = self._get_context_num_frames(batch, split)
|
| 413 |
+
odometry_dt = self._resolve_odometry_dt(
|
| 414 |
+
batch,
|
| 415 |
+
batch_size=steering.shape[0],
|
| 416 |
+
device=steering.device,
|
| 417 |
+
required=self._requires_odometry_dt_for_conditioning(),
|
| 418 |
+
)
|
| 419 |
+
condition_kwargs = self._build_condition_kwargs(steering, context_num_frames, odometry_dt=odometry_dt)
|
| 420 |
+
condition_kwargs["steering"] = self._maybe_apply_steering_dropout(condition_kwargs["steering"], split)
|
| 421 |
+
condition_kwargs["_source_raw_speed_yaw"] = source_steering
|
| 422 |
+
return condition_kwargs
|
| 423 |
+
|
| 424 |
+
def update_rollout_condition_kwargs(self, condition_kwargs, prediction, context, step_idx):
|
| 425 |
+
del prediction, context, step_idx
|
| 426 |
+
if not condition_kwargs:
|
| 427 |
+
return {}
|
| 428 |
+
updated = dict(condition_kwargs)
|
| 429 |
+
updated["_anchor_odo_index"] = condition_kwargs["_anchor_odo_index"] + condition_kwargs["_rollout_step_odo"]
|
| 430 |
+
updated["steering"] = self._compute_condition_steering(
|
| 431 |
+
condition_kwargs["_raw_speed_yaw"],
|
| 432 |
+
updated["_anchor_odo_index"],
|
| 433 |
+
odometry_dt=updated.get("_odometry_dt"),
|
| 434 |
+
)
|
| 435 |
+
return updated
|
| 436 |
+
|
| 437 |
+
def get_rollout_visualization_state(self, condition_kwargs):
|
| 438 |
+
if not condition_kwargs:
|
| 439 |
+
return None
|
| 440 |
+
return dict(condition_kwargs)
|
| 441 |
+
|
| 442 |
+
def get_visualization_trajectory(self, visualization_state):
|
| 443 |
+
if not visualization_state:
|
| 444 |
+
return None
|
| 445 |
+
return visualization_state.get("steering")
|
| 446 |
+
|
| 447 |
+
def update_rollout_visualization_state(self, visualization_state, step_idx):
|
| 448 |
+
if not visualization_state:
|
| 449 |
+
return None
|
| 450 |
+
return self.update_rollout_condition_kwargs(
|
| 451 |
+
visualization_state,
|
| 452 |
+
prediction=None,
|
| 453 |
+
context=None,
|
| 454 |
+
step_idx=step_idx,
|
| 455 |
+
)
|
| 456 |
+
|
| 457 |
+
def get_rollout_visualization_trajectory(
|
| 458 |
+
self,
|
| 459 |
+
condition_kwargs,
|
| 460 |
+
num_gen_steps,
|
| 461 |
+
num_pred_frames=1,
|
| 462 |
+
num_condition_frames=None,
|
| 463 |
+
):
|
| 464 |
+
"""Return shared trajectory geometry plus explicit per-frame cursor indices."""
|
| 465 |
+
if not condition_kwargs:
|
| 466 |
+
return None
|
| 467 |
+
|
| 468 |
+
odometry_dt = condition_kwargs.get("_odometry_dt")
|
| 469 |
+
if odometry_dt is None:
|
| 470 |
+
return None
|
| 471 |
+
|
| 472 |
+
rollout_steps = max(1, int(num_gen_steps))
|
| 473 |
+
rendered_frames_per_step = max(1, int(num_pred_frames))
|
| 474 |
+
num_context_frames = max(0, int(num_condition_frames or 0))
|
| 475 |
+
|
| 476 |
+
steering = self._validate_steering(condition_kwargs["_raw_speed_yaw"])
|
| 477 |
+
anchor_odo_index = condition_kwargs["_anchor_odo_index"]
|
| 478 |
+
rollout_step_odo = condition_kwargs["_rollout_step_odo"]
|
| 479 |
+
trajectories = []
|
| 480 |
+
|
| 481 |
+
for batch_idx in range(steering.shape[0]):
|
| 482 |
+
anchor_idx = int(anchor_odo_index[batch_idx].item())
|
| 483 |
+
step_odo = int(rollout_step_odo[batch_idx].item())
|
| 484 |
+
end_idx = anchor_idx + rollout_steps * step_odo
|
| 485 |
+
if end_idx >= steering.shape[1]:
|
| 486 |
+
raise ValueError(
|
| 487 |
+
f"Need steering horizon of at least {end_idx + 1} odometry steps for visualization, "
|
| 488 |
+
f"got {steering.shape[1]}. Anchor={anchor_idx}, rollout_step_odo={step_odo}, "
|
| 489 |
+
f"num_gen_steps={num_gen_steps}, num_pred_frames={num_pred_frames}."
|
| 490 |
+
)
|
| 491 |
+
|
| 492 |
+
trajectory = get_trajectory_from_speeds_and_yaw_rates_batch(
|
| 493 |
+
speeds=steering[batch_idx : batch_idx + 1, anchor_idx : end_idx + 1, 0],
|
| 494 |
+
yaw_rates=steering[batch_idx : batch_idx + 1, anchor_idx : end_idx + 1, 1],
|
| 495 |
+
dt=odometry_dt[batch_idx : batch_idx + 1],
|
| 496 |
+
)
|
| 497 |
+
trajectories.append(trajectory)
|
| 498 |
+
|
| 499 |
+
trajectory = torch.cat(trajectories, dim=0).to(device=steering.device)
|
| 500 |
+
step_indices = torch.arange(
|
| 501 |
+
1,
|
| 502 |
+
rollout_steps + 1,
|
| 503 |
+
device=steering.device,
|
| 504 |
+
dtype=torch.long,
|
| 505 |
+
)
|
| 506 |
+
cursor_index = (step_indices * rollout_step_odo[0]).repeat_interleave(rendered_frames_per_step)
|
| 507 |
+
if num_context_frames > 0:
|
| 508 |
+
context_cursor_index = torch.zeros(
|
| 509 |
+
num_context_frames,
|
| 510 |
+
device=steering.device,
|
| 511 |
+
dtype=torch.long,
|
| 512 |
+
)
|
| 513 |
+
cursor_index = torch.cat([context_cursor_index, cursor_index], dim=0)
|
| 514 |
+
cursor_index = cursor_index.unsqueeze(0).expand(steering.shape[0], -1)
|
| 515 |
+
return {
|
| 516 |
+
"trajectory": trajectory[:, :, :2],
|
| 517 |
+
"heading": trajectory[:, :, 2],
|
| 518 |
+
"cursor_index": cursor_index,
|
| 519 |
+
}
|
| 520 |
+
|
| 521 |
+
def annotate_logged_images(self, images, batch, num_images):
|
| 522 |
+
if not isinstance(batch, dict) or "steering" not in batch:
|
| 523 |
+
return images
|
| 524 |
+
|
| 525 |
+
condition_batch = {
|
| 526 |
+
"images": batch["images"][:num_images],
|
| 527 |
+
"steering": batch["steering"][:num_images],
|
| 528 |
+
}
|
| 529 |
+
if "frame_rate" in batch:
|
| 530 |
+
frame_rate = batch["frame_rate"]
|
| 531 |
+
condition_batch["frame_rate"] = frame_rate[:num_images] if torch.is_tensor(frame_rate) else frame_rate
|
| 532 |
+
if "steering_format" in batch:
|
| 533 |
+
condition_batch["steering_format"] = batch["steering_format"]
|
| 534 |
+
|
| 535 |
+
condition_kwargs = self.get_condition_kwargs_from_batch(
|
| 536 |
+
condition_batch,
|
| 537 |
+
split="log_images",
|
| 538 |
+
)
|
| 539 |
+
steering = condition_kwargs["steering"]
|
| 540 |
+
steering_strings = []
|
| 541 |
+
for sample in steering.cpu().numpy():
|
| 542 |
+
step_strings = ["[" + ", ".join(f"{value:.1f}" for value in step) + "]" for step in sample]
|
| 543 |
+
steering_strings.append(" ".join(step_strings))
|
| 544 |
+
images[:, -1] = _text_on_image(images[:, -1], steering_strings)
|
| 545 |
+
return images
|
| 546 |
+
|
| 547 |
+
|
| 548 |
+
class SpeedYawMovingGoalConditionPreprocessor(SpeedYawConditionPreprocessorBase):
|
| 549 |
+
"""
|
| 550 |
+
Compute future anchored goal displacements from raw speed/yaw-rate steering using an
|
| 551 |
+
explicit anchor definition.
|
| 552 |
+
|
| 553 |
+
Assumptions:
|
| 554 |
+
- `batch["steering"]` has shape [B, T, 2] with features [speed, yaw_rate]
|
| 555 |
+
- odometry/image frame-rate ratio is fixed and integer
|
| 556 |
+
- the anchor is a configured image-frame index within the context window
|
| 557 |
+
"""
|
| 558 |
+
|
| 559 |
+
def __init__(
|
| 560 |
+
self,
|
| 561 |
+
predictor_module,
|
| 562 |
+
goal_offsets,
|
| 563 |
+
odometry_steps_per_image_frame,
|
| 564 |
+
context_frame_anchor_index=-1,
|
| 565 |
+
steering_format="speed_yawrate",
|
| 566 |
+
speed_scale=1.0,
|
| 567 |
+
yaw_rate_scale=1.0,
|
| 568 |
+
**kwargs,
|
| 569 |
+
):
|
| 570 |
+
"""Configure moving-goal conditioning from raw `[speed, yaw_rate]` sequences."""
|
| 571 |
+
super().__init__(
|
| 572 |
+
predictor_module=predictor_module,
|
| 573 |
+
odometry_steps_per_image_frame=odometry_steps_per_image_frame,
|
| 574 |
+
context_frame_anchor_index=context_frame_anchor_index,
|
| 575 |
+
steering_format=steering_format,
|
| 576 |
+
speed_scale=speed_scale,
|
| 577 |
+
yaw_rate_scale=yaw_rate_scale,
|
| 578 |
+
**kwargs
|
| 579 |
+
)
|
| 580 |
+
self.goal_offsets = self._validate_offsets(goal_offsets, "goal_offsets")
|
| 581 |
+
|
| 582 |
+
def _make_no_steering_condition(self, batch_size, device):
|
| 583 |
+
return torch.full((batch_size, len(self.goal_offsets), 2), float("nan"), device=device)
|
| 584 |
+
|
| 585 |
+
def _requires_odometry_dt_for_conditioning(self):
|
| 586 |
+
return True
|
| 587 |
+
|
| 588 |
+
def _compute_condition_steering(self, steering, anchor_odo_index, odometry_dt=None):
|
| 589 |
+
"""Convert raw speed/yaw windows into future goal points anchored at odometry indices."""
|
| 590 |
+
steering = self._validate_steering(steering)
|
| 591 |
+
batch_size, steering_len, _dims = steering.shape
|
| 592 |
+
device = steering.device
|
| 593 |
+
if odometry_dt is None:
|
| 594 |
+
raise ValueError(
|
| 595 |
+
"SpeedYawMovingGoalConditionPreprocessor requires `odometry_dt` to compute goal-point "
|
| 596 |
+
"conditioning."
|
| 597 |
+
)
|
| 598 |
+
odometry_dt = torch.as_tensor(odometry_dt, device=device, dtype=steering.dtype)
|
| 599 |
+
if odometry_dt.ndim == 0:
|
| 600 |
+
odometry_dt = odometry_dt.expand(batch_size)
|
| 601 |
+
elif odometry_dt.ndim != 1 or odometry_dt.shape[0] != batch_size:
|
| 602 |
+
raise ValueError(
|
| 603 |
+
"`odometry_dt` must be broadcastable to shape [B], got "
|
| 604 |
+
f"{tuple(odometry_dt.shape)} for batch_size={batch_size}."
|
| 605 |
+
)
|
| 606 |
+
max_offset = self.goal_offsets[-1]
|
| 607 |
+
goals = []
|
| 608 |
+
|
| 609 |
+
for batch_idx in range(batch_size):
|
| 610 |
+
current_anchor = int(anchor_odo_index[batch_idx].item())
|
| 611 |
+
goal_end_idx = current_anchor + max_offset
|
| 612 |
+
if current_anchor < 0:
|
| 613 |
+
raise ValueError(f"Anchor index must be non-negative, got {current_anchor}.")
|
| 614 |
+
if goal_end_idx >= steering_len:
|
| 615 |
+
raise ValueError(
|
| 616 |
+
f"Need steering horizon of at least {goal_end_idx + 1} odometry steps, "
|
| 617 |
+
f"got {steering_len}. Anchor={current_anchor}, max_offset={max_offset}."
|
| 618 |
+
)
|
| 619 |
+
|
| 620 |
+
centered_traj = get_trajectory_from_speeds_and_yaw_rates_batch(
|
| 621 |
+
speeds=steering[batch_idx : batch_idx + 1, current_anchor : goal_end_idx + 1, 0],
|
| 622 |
+
yaw_rates=steering[batch_idx : batch_idx + 1, current_anchor : goal_end_idx + 1, 1],
|
| 623 |
+
dt=odometry_dt[batch_idx : batch_idx + 1],
|
| 624 |
+
)
|
| 625 |
+
goals.append(centered_traj[:, self.goal_offsets, :2])
|
| 626 |
+
|
| 627 |
+
return torch.cat(goals, dim=0).to(device=device)
|
| 628 |
+
|
| 629 |
+
|
| 630 |
+
class SpeedYawDirectConditionPreprocessor(SpeedYawConditionPreprocessorBase):
|
| 631 |
+
"""
|
| 632 |
+
Slice future raw speed/yaw-rate commands directly from the odometry sequence.
|
| 633 |
+
|
| 634 |
+
This shares the same rollout alignment logic as moving-goal conditioning, but
|
| 635 |
+
returns raw `[speed, yaw_rate]` values instead of integrating them into XY goals.
|
| 636 |
+
"""
|
| 637 |
+
|
| 638 |
+
def __init__(
|
| 639 |
+
self,
|
| 640 |
+
predictor_module,
|
| 641 |
+
steering_offsets,
|
| 642 |
+
odometry_steps_per_image_frame,
|
| 643 |
+
context_frame_anchor_index=-1,
|
| 644 |
+
steering_format="speed_yawrate",
|
| 645 |
+
speed_scale=1.0,
|
| 646 |
+
yaw_rate_scale=1.0,
|
| 647 |
+
):
|
| 648 |
+
super().__init__(
|
| 649 |
+
predictor_module=predictor_module,
|
| 650 |
+
odometry_steps_per_image_frame=odometry_steps_per_image_frame,
|
| 651 |
+
context_frame_anchor_index=context_frame_anchor_index,
|
| 652 |
+
steering_format=steering_format,
|
| 653 |
+
speed_scale=speed_scale,
|
| 654 |
+
yaw_rate_scale=yaw_rate_scale,
|
| 655 |
+
)
|
| 656 |
+
self.steering_offsets = self._validate_offsets(steering_offsets, "steering_offsets")
|
| 657 |
+
|
| 658 |
+
def _make_no_steering_condition(self, batch_size, device):
|
| 659 |
+
return torch.full((batch_size, len(self.steering_offsets), 2), float("nan"), device=device)
|
| 660 |
+
|
| 661 |
+
def _compute_condition_steering(self, steering, anchor_odo_index, odometry_dt=None):
|
| 662 |
+
del odometry_dt
|
| 663 |
+
steering = self._validate_steering(steering)
|
| 664 |
+
batch_size, steering_len, _dims = steering.shape
|
| 665 |
+
commands = []
|
| 666 |
+
max_offset = self.steering_offsets[-1]
|
| 667 |
+
steering_offsets = torch.as_tensor(
|
| 668 |
+
self.steering_offsets,
|
| 669 |
+
device=steering.device,
|
| 670 |
+
dtype=torch.long,
|
| 671 |
+
)
|
| 672 |
+
|
| 673 |
+
for batch_idx in range(batch_size):
|
| 674 |
+
current_anchor = int(anchor_odo_index[batch_idx].item())
|
| 675 |
+
end_idx = current_anchor + max_offset
|
| 676 |
+
if current_anchor < 0:
|
| 677 |
+
raise ValueError(f"Anchor index must be non-negative, got {current_anchor}.")
|
| 678 |
+
if end_idx >= steering_len:
|
| 679 |
+
raise ValueError(
|
| 680 |
+
f"Need steering horizon of at least {end_idx + 1} odometry steps, "
|
| 681 |
+
f"got {steering_len}. Anchor={current_anchor}, max_offset={max_offset}."
|
| 682 |
+
)
|
| 683 |
+
commands.append(steering[batch_idx : batch_idx + 1, current_anchor + steering_offsets])
|
| 684 |
+
|
| 685 |
+
return torch.cat(commands, dim=0).to(device=steering.device)
|
| 686 |
+
|
| 687 |
+
|
| 688 |
+
class L2EndpointConditionPreprocessor(ConditionPreprocessor):
|
| 689 |
+
"""
|
| 690 |
+
Inference-only L2 endpoint conditioning for the CleanCtx PredL2 v1 model.
|
| 691 |
+
|
| 692 |
+
The preprocessor owns the frozen L2 predictor and supplies only
|
| 693 |
+
`z_l2_start` / `z_l2_end` to the DiT. Private keys are retained as rollout
|
| 694 |
+
state and filtered before model calls by ConditionPreprocessor.
|
| 695 |
+
"""
|
| 696 |
+
|
| 697 |
+
def __init__(
|
| 698 |
+
self,
|
| 699 |
+
predictor_module,
|
| 700 |
+
l2_predictor_config,
|
| 701 |
+
l2_predictor_frame_rate=1.0,
|
| 702 |
+
l2_context_latent=False,
|
| 703 |
+
l2_pred_NFE=10,
|
| 704 |
+
num_context_frames=1,
|
| 705 |
+
use_z_l2_start=True,
|
| 706 |
+
):
|
| 707 |
+
super().__init__(predictor_module)
|
| 708 |
+
self.l2_predictor_frame_rate = float(l2_predictor_frame_rate)
|
| 709 |
+
self.l2_context_latent = bool(l2_context_latent)
|
| 710 |
+
self.l2_pred_NFE = int(l2_pred_NFE)
|
| 711 |
+
self.num_context_frames = int(num_context_frames)
|
| 712 |
+
self.use_z_l2_start = bool(use_z_l2_start)
|
| 713 |
+
self.l2_predictor_config = l2_predictor_config
|
| 714 |
+
self.l2_predictor = self._build_l2_predictor(l2_predictor_config)
|
| 715 |
+
self.l2_predictor_encoder_branch = self._get_l2_predictor_encoder_branch(
|
| 716 |
+
predictor=self.l2_predictor,
|
| 717 |
+
predictor_config=l2_predictor_config,
|
| 718 |
+
)
|
| 719 |
+
|
| 720 |
+
# --- BEGIN: rollout debug path info (safe to remove) ---
|
| 721 |
+
def get_l2_predictor_path_info(self):
|
| 722 |
+
"""Return (exp_folder_name, ckpt_name) describing where the L2 predictor was loaded from."""
|
| 723 |
+
folder = os.path.expandvars(self.l2_predictor_config.folder)
|
| 724 |
+
ckpt_path = self.l2_predictor_config.ckpt_path if self.l2_predictor_config.ckpt_path else "checkpoints/last.ckpt"
|
| 725 |
+
return os.path.basename(os.path.normpath(folder)), os.path.basename(ckpt_path)
|
| 726 |
+
# --- END: rollout debug path info (safe to remove) ---
|
| 727 |
+
|
| 728 |
+
@property
|
| 729 |
+
def ae(self):
|
| 730 |
+
return self.module.ae
|
| 731 |
+
|
| 732 |
+
@property
|
| 733 |
+
def enc_scale(self):
|
| 734 |
+
return self.module.enc_scale
|
| 735 |
+
|
| 736 |
+
@property
|
| 737 |
+
def enc_scale_dino(self):
|
| 738 |
+
return self.module.enc_scale_dino
|
| 739 |
+
|
| 740 |
+
def _build_l2_predictor(self, cfg):
|
| 741 |
+
folder = os.path.expandvars(cfg.folder)
|
| 742 |
+
ckpt_path = cfg.ckpt_path if cfg.ckpt_path else "checkpoints/last.ckpt"
|
| 743 |
+
logging.info(f"Loading L2 checkpoint from {os.path.join(folder, ckpt_path)}")
|
| 744 |
+
model_cfg = OmegaConf.load(os.path.join(folder, "config.yaml"))
|
| 745 |
+
predictor = instantiate_from_config(model_cfg.model)
|
| 746 |
+
state_dict = torch.load(
|
| 747 |
+
os.path.join(folder, ckpt_path),
|
| 748 |
+
map_location="cpu",
|
| 749 |
+
weights_only=True,
|
| 750 |
+
)["state_dict"]
|
| 751 |
+
predictor.load_state_dict(state_dict, strict=False)
|
| 752 |
+
predictor.eval()
|
| 753 |
+
_requires_grad(predictor, False)
|
| 754 |
+
return predictor
|
| 755 |
+
|
| 756 |
+
def _normalize_l2_predictor_encoder_branch(self, encoder_branch):
|
| 757 |
+
if isinstance(encoder_branch, int):
|
| 758 |
+
if encoder_branch == 0:
|
| 759 |
+
return "rec"
|
| 760 |
+
if encoder_branch == 1:
|
| 761 |
+
return "sem"
|
| 762 |
+
|
| 763 |
+
branch = str(encoder_branch).strip().lower()
|
| 764 |
+
aliases = {
|
| 765 |
+
"rec": "rec",
|
| 766 |
+
"x0": "rec",
|
| 767 |
+
"h": "rec",
|
| 768 |
+
"sem": "sem",
|
| 769 |
+
"x1": "sem",
|
| 770 |
+
"h2": "sem",
|
| 771 |
+
}
|
| 772 |
+
if branch not in aliases:
|
| 773 |
+
raise ValueError(
|
| 774 |
+
f"Unsupported delegated L2 encoder_branch={encoder_branch!r}. "
|
| 775 |
+
"Expected one of: rec, x0, h, sem, x1, h2."
|
| 776 |
+
)
|
| 777 |
+
return aliases[branch]
|
| 778 |
+
|
| 779 |
+
def _get_l2_predictor_encoder_branch(self, predictor, predictor_config):
|
| 780 |
+
first_stage = getattr(predictor, "first_stage", None)
|
| 781 |
+
if first_stage is not None and hasattr(first_stage, "encoder_branch"):
|
| 782 |
+
return self._normalize_l2_predictor_encoder_branch(first_stage.encoder_branch)
|
| 783 |
+
|
| 784 |
+
if hasattr(predictor, "encoder_branch"):
|
| 785 |
+
return self._normalize_l2_predictor_encoder_branch(predictor.encoder_branch)
|
| 786 |
+
|
| 787 |
+
folder = os.path.expandvars(predictor_config.folder)
|
| 788 |
+
model_cfg = OmegaConf.load(os.path.join(folder, "config.yaml"))
|
| 789 |
+
first_stage_cfg = model_cfg.model.params.get("first_stage_handler_config")
|
| 790 |
+
if first_stage_cfg and "params" in first_stage_cfg and "encoder_branch" in first_stage_cfg.params:
|
| 791 |
+
return self._normalize_l2_predictor_encoder_branch(first_stage_cfg.params.encoder_branch)
|
| 792 |
+
|
| 793 |
+
if "encoder_branch" in model_cfg.model.params:
|
| 794 |
+
return self._normalize_l2_predictor_encoder_branch(model_cfg.model.params.encoder_branch)
|
| 795 |
+
|
| 796 |
+
raise ValueError(
|
| 797 |
+
"Could not determine the delegated L2 predictor encoder branch from the loaded model "
|
| 798 |
+
"or its config. Expected a rec/sem branch selection."
|
| 799 |
+
)
|
| 800 |
+
|
| 801 |
+
def to_device(self, device):
|
| 802 |
+
self.l2_predictor.to(device)
|
| 803 |
+
|
| 804 |
+
def get_l2_predictor_encoder_branch(self):
|
| 805 |
+
return self.l2_predictor_encoder_branch
|
| 806 |
+
|
| 807 |
+
def get_l2_predictor_latent_scale(self):
|
| 808 |
+
if self.l2_predictor_encoder_branch == "rec":
|
| 809 |
+
return self.enc_scale
|
| 810 |
+
return self.enc_scale_dino
|
| 811 |
+
|
| 812 |
+
def _reject_training_split(self, split):
|
| 813 |
+
if split in {"train", "val"}:
|
| 814 |
+
raise RuntimeError(
|
| 815 |
+
"L2EndpointConditionPreprocessor is inference-only. "
|
| 816 |
+
"It supports sample/log_images/rollout conditioning, not train/val."
|
| 817 |
+
)
|
| 818 |
+
|
| 819 |
+
def _validate_guidance_scale(self, condition_kwargs):
|
| 820 |
+
if not condition_kwargs or "l2_guidance_scale" not in condition_kwargs:
|
| 821 |
+
return
|
| 822 |
+
guidance_scale = condition_kwargs["l2_guidance_scale"]
|
| 823 |
+
if torch.is_tensor(guidance_scale):
|
| 824 |
+
guidance_scale = float(guidance_scale.detach().cpu().item())
|
| 825 |
+
else:
|
| 826 |
+
guidance_scale = float(guidance_scale)
|
| 827 |
+
if abs(guidance_scale - 1.0) > 1e-6:
|
| 828 |
+
raise RuntimeError(
|
| 829 |
+
"L2EndpointConditionPreprocessor only supports "
|
| 830 |
+
"l2_guidance_scale == 1.0."
|
| 831 |
+
)
|
| 832 |
+
|
| 833 |
+
@torch.no_grad()
|
| 834 |
+
def _encode_l2_predictor_frames(self, images):
|
| 835 |
+
if images.ndim == 5:
|
| 836 |
+
b, f, _c, _h, _w = images.size()
|
| 837 |
+
images = rearrange(images, "b f c h w -> (b f) c h w")
|
| 838 |
+
else:
|
| 839 |
+
b, _c, _h, _w = images.size()
|
| 840 |
+
f = 1
|
| 841 |
+
|
| 842 |
+
continuous = self.ae.encode(images)["continuous"]
|
| 843 |
+
if not isinstance(continuous, tuple) or len(continuous) != 2:
|
| 844 |
+
raise ValueError(
|
| 845 |
+
"L2EndpointConditionPreprocessor expects tokenizer.encode(...)[\"continuous\"] "
|
| 846 |
+
f"to return a 2-tuple, got {type(continuous)}."
|
| 847 |
+
)
|
| 848 |
+
branch_index = 0 if self.l2_predictor_encoder_branch == "rec" else 1
|
| 849 |
+
latent_scale = self.get_l2_predictor_latent_scale()
|
| 850 |
+
latents = continuous[branch_index] * latent_scale
|
| 851 |
+
return rearrange(latents, "(b f) c h w -> b f c h w", b=b, f=f)
|
| 852 |
+
|
| 853 |
+
@torch.no_grad()
|
| 854 |
+
def _encode_l2_context(self, l2_context):
|
| 855 |
+
if self.l2_context_latent:
|
| 856 |
+
return l2_context * self.get_l2_predictor_latent_scale()
|
| 857 |
+
return self._encode_l2_predictor_frames(l2_context)
|
| 858 |
+
|
| 859 |
+
@torch.no_grad()
|
| 860 |
+
def _predict_l2_end(self, l2_context_latent):
|
| 861 |
+
batch_size = l2_context_latent.shape[0]
|
| 862 |
+
device = l2_context_latent.device
|
| 863 |
+
if next(self.l2_predictor.parameters()).device != device:
|
| 864 |
+
self.l2_predictor.to(device)
|
| 865 |
+
predictor = self.l2_predictor
|
| 866 |
+
frame_rate = torch.full(
|
| 867 |
+
(batch_size,),
|
| 868 |
+
self.l2_predictor_frame_rate,
|
| 869 |
+
device=device,
|
| 870 |
+
)
|
| 871 |
+
pred = predictor.sample(
|
| 872 |
+
images=l2_context_latent,
|
| 873 |
+
latent=True,
|
| 874 |
+
eta=0.0,
|
| 875 |
+
NFE=self.l2_pred_NFE,
|
| 876 |
+
sample_with_ema=True,
|
| 877 |
+
num_samples=batch_size,
|
| 878 |
+
frame_rate=frame_rate,
|
| 879 |
+
)
|
| 880 |
+
return pred[:, -1]
|
| 881 |
+
|
| 882 |
+
@torch.no_grad()
|
| 883 |
+
def _sample_l2_next_latent(self, l2_context_latent):
|
| 884 |
+
return self._predict_l2_end(l2_context_latent)
|
| 885 |
+
|
| 886 |
+
def _get_z_l2_start_from_context(self, l2_context_latent, z_l2_end):
|
| 887 |
+
if self.use_z_l2_start:
|
| 888 |
+
if l2_context_latent is None:
|
| 889 |
+
raise ValueError(
|
| 890 |
+
"L2EndpointConditionPreprocessor requires z_l2_start or "
|
| 891 |
+
"l2_context/_l2_context_latent when use_z_l2_start=True."
|
| 892 |
+
)
|
| 893 |
+
return l2_context_latent[:, -1]
|
| 894 |
+
return torch.zeros_like(z_l2_end)
|
| 895 |
+
|
| 896 |
+
def get_condition_kwargs_from_batch(self, batch, split):
|
| 897 |
+
self._reject_training_split(split)
|
| 898 |
+
if not isinstance(batch, dict) or "l2_context" not in batch:
|
| 899 |
+
raise ValueError(
|
| 900 |
+
"L2EndpointConditionPreprocessor requires batch['l2_context'] "
|
| 901 |
+
f"for split={split!r}."
|
| 902 |
+
)
|
| 903 |
+
return {"l2_context": batch["l2_context"]}
|
| 904 |
+
|
| 905 |
+
def _move_public_condition_kwargs(self, condition_kwargs, device):
|
| 906 |
+
prepared = {}
|
| 907 |
+
for key, value in condition_kwargs.items():
|
| 908 |
+
if key == "l2_guidance_scale":
|
| 909 |
+
continue
|
| 910 |
+
if torch.is_tensor(value) and device is not None:
|
| 911 |
+
prepared[key] = value.to(device)
|
| 912 |
+
else:
|
| 913 |
+
prepared[key] = value
|
| 914 |
+
return prepared
|
| 915 |
+
|
| 916 |
+
@torch.no_grad()
|
| 917 |
+
def prepare_condition_kwargs(self, condition_kwargs=None, batch_size=None, device=None, split=None):
|
| 918 |
+
self._reject_training_split(split)
|
| 919 |
+
condition_kwargs = condition_kwargs or {}
|
| 920 |
+
self._validate_guidance_scale(condition_kwargs)
|
| 921 |
+
prepared = self._move_public_condition_kwargs(condition_kwargs, device)
|
| 922 |
+
|
| 923 |
+
has_start = "z_l2_start" in prepared
|
| 924 |
+
has_end = "z_l2_end" in prepared
|
| 925 |
+
prepared["_z_l2_start_fixed"] = bool(prepared.get("_z_l2_start_fixed", has_start))
|
| 926 |
+
prepared["_z_l2_end_fixed"] = bool(prepared.get("_z_l2_end_fixed", has_end))
|
| 927 |
+
|
| 928 |
+
l2_context_latent = prepared.get("_l2_context_latent")
|
| 929 |
+
if l2_context_latent is None and "l2_context" in prepared:
|
| 930 |
+
l2_context_latent = self._encode_l2_context(prepared.pop("l2_context"))
|
| 931 |
+
prepared["_l2_context_latent"] = l2_context_latent
|
| 932 |
+
elif l2_context_latent is not None:
|
| 933 |
+
prepared["_l2_context_latent"] = l2_context_latent
|
| 934 |
+
|
| 935 |
+
if "z_l2_end" not in prepared:
|
| 936 |
+
if l2_context_latent is None:
|
| 937 |
+
raise ValueError(
|
| 938 |
+
"L2EndpointConditionPreprocessor requires z_l2_end or "
|
| 939 |
+
"l2_context/_l2_context_latent to predict it."
|
| 940 |
+
)
|
| 941 |
+
prepared["_l2_next_latent"] = self._sample_l2_next_latent(l2_context_latent)
|
| 942 |
+
prepared["z_l2_end"] = prepared["_l2_next_latent"]
|
| 943 |
+
elif "_l2_next_latent" not in prepared and not prepared["_z_l2_end_fixed"]:
|
| 944 |
+
prepared["_l2_next_latent"] = prepared["z_l2_end"]
|
| 945 |
+
|
| 946 |
+
if "z_l2_start" not in prepared:
|
| 947 |
+
prepared["z_l2_start"] = self._get_z_l2_start_from_context(
|
| 948 |
+
l2_context_latent,
|
| 949 |
+
prepared["z_l2_end"],
|
| 950 |
+
)
|
| 951 |
+
|
| 952 |
+
if batch_size is not None:
|
| 953 |
+
for key in ("z_l2_start", "z_l2_end"):
|
| 954 |
+
if prepared[key].shape[0] != batch_size:
|
| 955 |
+
raise ValueError(
|
| 956 |
+
f"{key} batch size {prepared[key].shape[0]} does not match "
|
| 957 |
+
f"expected batch_size={batch_size}."
|
| 958 |
+
)
|
| 959 |
+
|
| 960 |
+
return prepared
|
| 961 |
+
|
| 962 |
+
@torch.no_grad()
|
| 963 |
+
def update_rollout_condition_kwargs(self, condition_kwargs, prediction, context, step_idx):
|
| 964 |
+
del prediction, context, step_idx
|
| 965 |
+
if not condition_kwargs:
|
| 966 |
+
return {}
|
| 967 |
+
|
| 968 |
+
updated = dict(condition_kwargs)
|
| 969 |
+
l2_context_latent = updated.get("_l2_context_latent")
|
| 970 |
+
start_fixed = bool(updated.get("_z_l2_start_fixed", False))
|
| 971 |
+
end_fixed = bool(updated.get("_z_l2_end_fixed", False))
|
| 972 |
+
if l2_context_latent is None or (start_fixed and end_fixed):
|
| 973 |
+
return updated
|
| 974 |
+
|
| 975 |
+
l2_next_latent = updated.get("_l2_next_latent")
|
| 976 |
+
if l2_next_latent is None:
|
| 977 |
+
if end_fixed:
|
| 978 |
+
l2_next_latent = updated["z_l2_end"]
|
| 979 |
+
else:
|
| 980 |
+
raise ValueError(
|
| 981 |
+
"L2EndpointConditionPreprocessor requires cached `_l2_next_latent` "
|
| 982 |
+
"or explicit `z_l2_end` during rollout updates."
|
| 983 |
+
)
|
| 984 |
+
l2_next_latent = l2_next_latent.to(l2_context_latent.device)
|
| 985 |
+
updated["_l2_context_latent"] = torch.cat(
|
| 986 |
+
[l2_context_latent[:, 1:], l2_next_latent.unsqueeze(1)],
|
| 987 |
+
dim=1,
|
| 988 |
+
)
|
| 989 |
+
if not start_fixed:
|
| 990 |
+
updated["z_l2_start"] = self._get_z_l2_start_from_context(
|
| 991 |
+
updated["_l2_context_latent"],
|
| 992 |
+
updated["z_l2_end"],
|
| 993 |
+
)
|
| 994 |
+
if not end_fixed:
|
| 995 |
+
updated["_l2_next_latent"] = self._sample_l2_next_latent(updated["_l2_context_latent"])
|
| 996 |
+
updated["z_l2_end"] = updated["_l2_next_latent"]
|
| 997 |
+
return updated
|
| 998 |
+
|
| 999 |
+
|
| 1000 |
+
class L2SteeringEndpointConditionPreprocessor(L2EndpointConditionPreprocessor):
|
| 1001 |
+
"""
|
| 1002 |
+
Steering-aware variant of L2EndpointConditionPreprocessor.
|
| 1003 |
+
|
| 1004 |
+
Raw speed/yaw-rate steering is converted into the conditioning expected by
|
| 1005 |
+
the frozen L2 predictor. That steering state remains private to the L2
|
| 1006 |
+
branch and is updated in lockstep with the autoregressive L2-context slide.
|
| 1007 |
+
"""
|
| 1008 |
+
|
| 1009 |
+
def __init__(
|
| 1010 |
+
self,
|
| 1011 |
+
predictor_module,
|
| 1012 |
+
l2_predictor_config,
|
| 1013 |
+
l2_predictor_frame_rate=1.0,
|
| 1014 |
+
l2_context_latent=False,
|
| 1015 |
+
l2_pred_NFE=10,
|
| 1016 |
+
num_context_frames=1,
|
| 1017 |
+
use_z_l2_start=True,
|
| 1018 |
+
speed_scale=1.0,
|
| 1019 |
+
yaw_rate_scale=1.0,
|
| 1020 |
+
):
|
| 1021 |
+
super().__init__(
|
| 1022 |
+
predictor_module=predictor_module,
|
| 1023 |
+
l2_predictor_config=l2_predictor_config,
|
| 1024 |
+
l2_predictor_frame_rate=l2_predictor_frame_rate,
|
| 1025 |
+
l2_context_latent=l2_context_latent,
|
| 1026 |
+
l2_pred_NFE=l2_pred_NFE,
|
| 1027 |
+
num_context_frames=num_context_frames,
|
| 1028 |
+
use_z_l2_start=use_z_l2_start,
|
| 1029 |
+
)
|
| 1030 |
+
self.speed_scale = _validate_speed_yaw_scale(speed_scale, "speed_scale")
|
| 1031 |
+
self.yaw_rate_scale = _validate_speed_yaw_scale(yaw_rate_scale, "yaw_rate_scale")
|
| 1032 |
+
self.l2_condition_preprocessor = self._get_l2_condition_preprocessor()
|
| 1033 |
+
if hasattr(self.l2_condition_preprocessor, "goal_offsets"):
|
| 1034 |
+
self.goal_offsets = [int(offset) for offset in self.l2_condition_preprocessor.goal_offsets]
|
| 1035 |
+
if hasattr(self.l2_condition_preprocessor, "steering_offsets"):
|
| 1036 |
+
self.steering_offsets = [int(offset) for offset in self.l2_condition_preprocessor.steering_offsets]
|
| 1037 |
+
self._validate_l2_condition_preprocessor()
|
| 1038 |
+
|
| 1039 |
+
def _get_l2_condition_preprocessor(self):
|
| 1040 |
+
preprocessor = getattr(self.l2_predictor, "condition_preprocessor", None)
|
| 1041 |
+
if preprocessor is None:
|
| 1042 |
+
raise TypeError(
|
| 1043 |
+
"L2SteeringEndpointConditionPreprocessor requires the frozen L2 predictor "
|
| 1044 |
+
"to expose `condition_preprocessor`."
|
| 1045 |
+
)
|
| 1046 |
+
return preprocessor
|
| 1047 |
+
|
| 1048 |
+
def _validate_l2_condition_preprocessor(self):
|
| 1049 |
+
required_methods = (
|
| 1050 |
+
"get_condition_kwargs_from_batch",
|
| 1051 |
+
"prepare_condition_kwargs",
|
| 1052 |
+
"update_rollout_condition_kwargs",
|
| 1053 |
+
)
|
| 1054 |
+
missing = [
|
| 1055 |
+
method_name
|
| 1056 |
+
for method_name in required_methods
|
| 1057 |
+
if not callable(getattr(self.l2_condition_preprocessor, method_name, None))
|
| 1058 |
+
]
|
| 1059 |
+
if missing:
|
| 1060 |
+
raise TypeError(
|
| 1061 |
+
"Frozen L2 condition preprocessor is incompatible with steering delegation; "
|
| 1062 |
+
f"missing methods: {missing}."
|
| 1063 |
+
)
|
| 1064 |
+
|
| 1065 |
+
def get_max_condition_odometry_offset(self):
|
| 1066 |
+
if hasattr(self.l2_condition_preprocessor, "get_max_condition_odometry_offset"):
|
| 1067 |
+
return self.l2_condition_preprocessor.get_max_condition_odometry_offset()
|
| 1068 |
+
return None
|
| 1069 |
+
|
| 1070 |
+
def get_required_rollout_odometry_steps(
|
| 1071 |
+
self,
|
| 1072 |
+
*,
|
| 1073 |
+
validation_params,
|
| 1074 |
+
num_condition_frames,
|
| 1075 |
+
num_gen_frames,
|
| 1076 |
+
rollout_steps,
|
| 1077 |
+
):
|
| 1078 |
+
del num_condition_frames
|
| 1079 |
+
delegated_required_steps = getattr(self.l2_condition_preprocessor, "get_required_rollout_odometry_steps", None)
|
| 1080 |
+
if callable(delegated_required_steps):
|
| 1081 |
+
delegated_num_pred_frames = int(getattr(self.l2_predictor, "num_pred_frames", 1))
|
| 1082 |
+
return delegated_required_steps(
|
| 1083 |
+
validation_params=validation_params,
|
| 1084 |
+
num_condition_frames=int(self.num_context_frames),
|
| 1085 |
+
num_gen_frames=int(rollout_steps) * delegated_num_pred_frames,
|
| 1086 |
+
rollout_steps=rollout_steps,
|
| 1087 |
+
)
|
| 1088 |
+
|
| 1089 |
+
del num_gen_frames
|
| 1090 |
+
max_condition_offset = self.get_max_condition_odometry_offset()
|
| 1091 |
+
if max_condition_offset is None:
|
| 1092 |
+
return None
|
| 1093 |
+
|
| 1094 |
+
source_odometry_rate = float(
|
| 1095 |
+
getattr(validation_params, "odometry_frame_rate", getattr(validation_params, "frame_rate"))
|
| 1096 |
+
)
|
| 1097 |
+
target_odometry_rate = float(self.l2_predictor_frame_rate)
|
| 1098 |
+
ratio = source_odometry_rate / target_odometry_rate
|
| 1099 |
+
rounded_ratio = round(ratio)
|
| 1100 |
+
if abs(ratio - rounded_ratio) > 1e-8:
|
| 1101 |
+
raise ValueError(
|
| 1102 |
+
"Delegated L2 rollout requires the parent odometry rate to be an integer multiple of "
|
| 1103 |
+
f"the frozen L2 rate, got odometry_frame_rate={source_odometry_rate} and "
|
| 1104 |
+
f"l2_predictor_frame_rate={target_odometry_rate}."
|
| 1105 |
+
)
|
| 1106 |
+
|
| 1107 |
+
required_l2_steps = self.num_context_frames + int(rollout_steps) + int(max_condition_offset) - 1
|
| 1108 |
+
return int((required_l2_steps - 1) * rounded_ratio + 1)
|
| 1109 |
+
|
| 1110 |
+
def _maybe_resample_raw_steering_for_l2(self, steering, batch):
|
| 1111 |
+
if not torch.is_tensor(steering):
|
| 1112 |
+
raise TypeError(f"`steering` must be a tensor, got {type(steering).__name__}.")
|
| 1113 |
+
if "frame_rate" not in batch:
|
| 1114 |
+
return steering
|
| 1115 |
+
|
| 1116 |
+
source_frame_rate = torch.as_tensor(
|
| 1117 |
+
batch["frame_rate"],
|
| 1118 |
+
device=steering.device,
|
| 1119 |
+
dtype=torch.float32,
|
| 1120 |
+
)
|
| 1121 |
+
if source_frame_rate.ndim == 0:
|
| 1122 |
+
source_frame_rate = source_frame_rate.expand(steering.shape[0])
|
| 1123 |
+
elif source_frame_rate.ndim == 1 and source_frame_rate.shape[0] == 1 and steering.shape[0] != 1:
|
| 1124 |
+
source_frame_rate = source_frame_rate.expand(steering.shape[0])
|
| 1125 |
+
elif source_frame_rate.ndim != 1 or source_frame_rate.shape[0] != steering.shape[0]:
|
| 1126 |
+
raise ValueError(
|
| 1127 |
+
"`frame_rate` must be broadcastable to shape [B] for delegated L2 steering, got "
|
| 1128 |
+
f"{tuple(source_frame_rate.shape)} for batch_size={steering.shape[0]}."
|
| 1129 |
+
)
|
| 1130 |
+
|
| 1131 |
+
target_frame_rate = torch.full_like(source_frame_rate, float(self.l2_predictor_frame_rate))
|
| 1132 |
+
if torch.any(target_frame_rate <= 0):
|
| 1133 |
+
raise ValueError(f"`l2_predictor_frame_rate` must be positive, got {self.l2_predictor_frame_rate}.")
|
| 1134 |
+
|
| 1135 |
+
if torch.allclose(source_frame_rate, target_frame_rate):
|
| 1136 |
+
return steering
|
| 1137 |
+
|
| 1138 |
+
ratios = source_frame_rate / target_frame_rate
|
| 1139 |
+
rounded_ratios = torch.round(ratios)
|
| 1140 |
+
if not torch.allclose(ratios, rounded_ratios, atol=1e-8, rtol=0.0):
|
| 1141 |
+
raise ValueError(
|
| 1142 |
+
"Delegated L2 steering requires the parent frame rate to be an integer multiple of "
|
| 1143 |
+
f"the frozen L2 frame rate. Got source_frame_rate={source_frame_rate.tolist()} and "
|
| 1144 |
+
f"l2_predictor_frame_rate={self.l2_predictor_frame_rate}."
|
| 1145 |
+
)
|
| 1146 |
+
|
| 1147 |
+
resampled = []
|
| 1148 |
+
for batch_idx in range(steering.shape[0]):
|
| 1149 |
+
step = int(rounded_ratios[batch_idx].item())
|
| 1150 |
+
if step <= 0:
|
| 1151 |
+
raise ValueError(
|
| 1152 |
+
f"Delegated L2 steering subsample step must be positive, got {step}."
|
| 1153 |
+
)
|
| 1154 |
+
resampled.append(steering[batch_idx : batch_idx + 1, ::step])
|
| 1155 |
+
return torch.cat(resampled, dim=0)
|
| 1156 |
+
|
| 1157 |
+
def _build_l2_rollout_batch(self, batch):
|
| 1158 |
+
if "l2_context" not in batch:
|
| 1159 |
+
raise ValueError(
|
| 1160 |
+
"L2SteeringEndpointConditionPreprocessor requires batch['l2_context'] "
|
| 1161 |
+
"to build delegated L2 conditions."
|
| 1162 |
+
)
|
| 1163 |
+
if "steering" not in batch:
|
| 1164 |
+
raise ValueError(
|
| 1165 |
+
"L2SteeringEndpointConditionPreprocessor requires batch['steering'] "
|
| 1166 |
+
"to build delegated L2 conditions."
|
| 1167 |
+
)
|
| 1168 |
+
source_steering = batch["steering"]
|
| 1169 |
+
scaled_steering = _apply_speed_yaw_scales(
|
| 1170 |
+
source_steering,
|
| 1171 |
+
speed_scale=self.speed_scale,
|
| 1172 |
+
yaw_rate_scale=self.yaw_rate_scale,
|
| 1173 |
+
)
|
| 1174 |
+
l2_batch = {
|
| 1175 |
+
"images": batch["l2_context"],
|
| 1176 |
+
# Keep steering at raw odometry resolution and let the frozen L2
|
| 1177 |
+
# preprocessor interpret it via its own odometry alignment config.
|
| 1178 |
+
"steering": scaled_steering,
|
| 1179 |
+
}
|
| 1180 |
+
if "steering_format" in batch:
|
| 1181 |
+
l2_batch["steering_format"] = batch["steering_format"]
|
| 1182 |
+
if "frame_rate" in batch:
|
| 1183 |
+
frame_rate = torch.as_tensor(
|
| 1184 |
+
batch["frame_rate"],
|
| 1185 |
+
device=batch["steering"].device,
|
| 1186 |
+
dtype=torch.float32,
|
| 1187 |
+
)
|
| 1188 |
+
l2_batch["frame_rate"] = torch.full_like(
|
| 1189 |
+
frame_rate,
|
| 1190 |
+
float(self.l2_predictor_frame_rate),
|
| 1191 |
+
dtype=torch.float32,
|
| 1192 |
+
)
|
| 1193 |
+
else:
|
| 1194 |
+
batch_size = batch["steering"].shape[0]
|
| 1195 |
+
l2_batch["frame_rate"] = torch.full(
|
| 1196 |
+
(batch_size,),
|
| 1197 |
+
float(self.l2_predictor_frame_rate),
|
| 1198 |
+
device=batch["steering"].device,
|
| 1199 |
+
dtype=torch.float32,
|
| 1200 |
+
)
|
| 1201 |
+
return l2_batch
|
| 1202 |
+
|
| 1203 |
+
def _prepare_l2_predictor_condition_kwargs(self, l2_condition_kwargs, device):
|
| 1204 |
+
return self.l2_condition_preprocessor.prepare_condition_kwargs(
|
| 1205 |
+
l2_condition_kwargs,
|
| 1206 |
+
batch_size=None,
|
| 1207 |
+
device=device,
|
| 1208 |
+
split="rollout",
|
| 1209 |
+
)
|
| 1210 |
+
|
| 1211 |
+
def _update_l2_predictor_condition_kwargs(self, l2_condition_kwargs):
|
| 1212 |
+
return self.l2_condition_preprocessor.update_rollout_condition_kwargs(
|
| 1213 |
+
l2_condition_kwargs,
|
| 1214 |
+
prediction=None,
|
| 1215 |
+
context=None,
|
| 1216 |
+
step_idx=0,
|
| 1217 |
+
)
|
| 1218 |
+
|
| 1219 |
+
@torch.no_grad()
|
| 1220 |
+
def _predict_l2_end(self, l2_context_latent, l2_condition_kwargs=None):
|
| 1221 |
+
batch_size = l2_context_latent.shape[0]
|
| 1222 |
+
device = l2_context_latent.device
|
| 1223 |
+
if next(self.l2_predictor.parameters()).device != device:
|
| 1224 |
+
self.l2_predictor.to(device)
|
| 1225 |
+
predictor = self.l2_predictor
|
| 1226 |
+
frame_rate = torch.full(
|
| 1227 |
+
(batch_size,),
|
| 1228 |
+
self.l2_predictor_frame_rate,
|
| 1229 |
+
device=device,
|
| 1230 |
+
)
|
| 1231 |
+
pred = predictor.sample(
|
| 1232 |
+
images=l2_context_latent,
|
| 1233 |
+
latent=True,
|
| 1234 |
+
eta=0.0,
|
| 1235 |
+
NFE=self.l2_pred_NFE,
|
| 1236 |
+
sample_with_ema=True,
|
| 1237 |
+
num_samples=batch_size,
|
| 1238 |
+
frame_rate=frame_rate,
|
| 1239 |
+
condition_kwargs=l2_condition_kwargs,
|
| 1240 |
+
)
|
| 1241 |
+
return pred[:, -1]
|
| 1242 |
+
|
| 1243 |
+
@torch.no_grad()
|
| 1244 |
+
def _sample_l2_next_latent(self, l2_context_latent, l2_condition_kwargs=None):
|
| 1245 |
+
return self._predict_l2_end(l2_context_latent, l2_condition_kwargs=l2_condition_kwargs)
|
| 1246 |
+
|
| 1247 |
+
def get_condition_kwargs_from_batch(self, batch, split):
|
| 1248 |
+
self._reject_training_split(split)
|
| 1249 |
+
if not isinstance(batch, dict) or "l2_context" not in batch:
|
| 1250 |
+
raise ValueError(
|
| 1251 |
+
"L2SteeringEndpointConditionPreprocessor requires batch['l2_context'] "
|
| 1252 |
+
f"for split={split!r}."
|
| 1253 |
+
)
|
| 1254 |
+
if "steering" not in batch:
|
| 1255 |
+
raise ValueError(
|
| 1256 |
+
"L2SteeringEndpointConditionPreprocessor requires batch['steering'] "
|
| 1257 |
+
f"for split={split!r}."
|
| 1258 |
+
)
|
| 1259 |
+
l2_context = batch["l2_context"]
|
| 1260 |
+
if l2_context.ndim != 5:
|
| 1261 |
+
raise ValueError(f"`l2_context` must have shape [B, F, C, H, W], got {tuple(l2_context.shape)}.")
|
| 1262 |
+
source_l2_steering = self._maybe_resample_raw_steering_for_l2(batch["steering"], batch)
|
| 1263 |
+
l2_condition_kwargs = self.l2_condition_preprocessor.get_condition_kwargs_from_batch(
|
| 1264 |
+
self._build_l2_rollout_batch(batch),
|
| 1265 |
+
split="rollout",
|
| 1266 |
+
)
|
| 1267 |
+
l2_condition_kwargs["_source_raw_speed_yaw"] = source_l2_steering
|
| 1268 |
+
return {
|
| 1269 |
+
"l2_context": l2_context,
|
| 1270 |
+
"_l2_condition_kwargs": l2_condition_kwargs,
|
| 1271 |
+
}
|
| 1272 |
+
|
| 1273 |
+
@torch.no_grad()
|
| 1274 |
+
def prepare_condition_kwargs(self, condition_kwargs=None, batch_size=None, device=None, split=None):
|
| 1275 |
+
self._reject_training_split(split)
|
| 1276 |
+
condition_kwargs = condition_kwargs or {}
|
| 1277 |
+
self._validate_guidance_scale(condition_kwargs)
|
| 1278 |
+
prepared = self._move_public_condition_kwargs(condition_kwargs, device)
|
| 1279 |
+
|
| 1280 |
+
has_start = "z_l2_start" in prepared
|
| 1281 |
+
has_end = "z_l2_end" in prepared
|
| 1282 |
+
prepared["_z_l2_start_fixed"] = bool(prepared.get("_z_l2_start_fixed", has_start))
|
| 1283 |
+
prepared["_z_l2_end_fixed"] = bool(prepared.get("_z_l2_end_fixed", has_end))
|
| 1284 |
+
|
| 1285 |
+
l2_context_latent = prepared.get("_l2_context_latent")
|
| 1286 |
+
if l2_context_latent is None and "l2_context" in prepared:
|
| 1287 |
+
l2_context_latent = self._encode_l2_context(prepared.pop("l2_context"))
|
| 1288 |
+
prepared["_l2_context_latent"] = l2_context_latent
|
| 1289 |
+
elif l2_context_latent is not None:
|
| 1290 |
+
prepared["_l2_context_latent"] = l2_context_latent
|
| 1291 |
+
|
| 1292 |
+
l2_condition_kwargs = self._prepare_l2_predictor_condition_kwargs(
|
| 1293 |
+
prepared.get("_l2_condition_kwargs"),
|
| 1294 |
+
device=device,
|
| 1295 |
+
)
|
| 1296 |
+
prepared["_l2_condition_kwargs"] = l2_condition_kwargs
|
| 1297 |
+
|
| 1298 |
+
if "z_l2_end" not in prepared:
|
| 1299 |
+
if l2_context_latent is None:
|
| 1300 |
+
raise ValueError(
|
| 1301 |
+
"L2SteeringEndpointConditionPreprocessor requires z_l2_end or "
|
| 1302 |
+
"l2_context/_l2_context_latent to predict it."
|
| 1303 |
+
)
|
| 1304 |
+
prepared["_l2_next_latent"] = self._sample_l2_next_latent(
|
| 1305 |
+
l2_context_latent,
|
| 1306 |
+
l2_condition_kwargs=l2_condition_kwargs,
|
| 1307 |
+
)
|
| 1308 |
+
prepared["z_l2_end"] = prepared["_l2_next_latent"]
|
| 1309 |
+
elif "_l2_next_latent" not in prepared and not prepared["_z_l2_end_fixed"]:
|
| 1310 |
+
prepared["_l2_next_latent"] = prepared["z_l2_end"]
|
| 1311 |
+
|
| 1312 |
+
if "z_l2_start" not in prepared:
|
| 1313 |
+
prepared["z_l2_start"] = self._get_z_l2_start_from_context(
|
| 1314 |
+
l2_context_latent,
|
| 1315 |
+
prepared["z_l2_end"],
|
| 1316 |
+
)
|
| 1317 |
+
|
| 1318 |
+
if batch_size is not None:
|
| 1319 |
+
for key in ("z_l2_start", "z_l2_end"):
|
| 1320 |
+
if prepared[key].shape[0] != batch_size:
|
| 1321 |
+
raise ValueError(
|
| 1322 |
+
f"{key} batch size {prepared[key].shape[0]} does not match "
|
| 1323 |
+
f"expected batch_size={batch_size}."
|
| 1324 |
+
)
|
| 1325 |
+
|
| 1326 |
+
return prepared
|
| 1327 |
+
|
| 1328 |
+
@torch.no_grad()
|
| 1329 |
+
def update_rollout_condition_kwargs(self, condition_kwargs, prediction, context, step_idx):
|
| 1330 |
+
del prediction, context, step_idx
|
| 1331 |
+
if not condition_kwargs:
|
| 1332 |
+
return {}
|
| 1333 |
+
|
| 1334 |
+
updated = dict(condition_kwargs)
|
| 1335 |
+
l2_context_latent = updated.get("_l2_context_latent")
|
| 1336 |
+
start_fixed = bool(updated.get("_z_l2_start_fixed", False))
|
| 1337 |
+
end_fixed = bool(updated.get("_z_l2_end_fixed", False))
|
| 1338 |
+
if l2_context_latent is None:
|
| 1339 |
+
return updated
|
| 1340 |
+
|
| 1341 |
+
if not (start_fixed and end_fixed):
|
| 1342 |
+
l2_next_latent = updated.get("_l2_next_latent")
|
| 1343 |
+
if l2_next_latent is None:
|
| 1344 |
+
if end_fixed:
|
| 1345 |
+
l2_next_latent = updated["z_l2_end"]
|
| 1346 |
+
else:
|
| 1347 |
+
raise ValueError(
|
| 1348 |
+
"L2SteeringEndpointConditionPreprocessor requires cached `_l2_next_latent` "
|
| 1349 |
+
"or explicit `z_l2_end` during rollout updates."
|
| 1350 |
+
)
|
| 1351 |
+
l2_next_latent = l2_next_latent.to(l2_context_latent.device)
|
| 1352 |
+
updated["_l2_context_latent"] = torch.cat(
|
| 1353 |
+
[l2_context_latent[:, 1:], l2_next_latent.unsqueeze(1)],
|
| 1354 |
+
dim=1,
|
| 1355 |
+
)
|
| 1356 |
+
|
| 1357 |
+
if "_l2_condition_kwargs" in updated:
|
| 1358 |
+
updated["_l2_condition_kwargs"] = self._update_l2_predictor_condition_kwargs(
|
| 1359 |
+
updated["_l2_condition_kwargs"],
|
| 1360 |
+
)
|
| 1361 |
+
|
| 1362 |
+
if not start_fixed:
|
| 1363 |
+
updated["z_l2_start"] = self._get_z_l2_start_from_context(
|
| 1364 |
+
updated["_l2_context_latent"],
|
| 1365 |
+
updated["z_l2_end"],
|
| 1366 |
+
)
|
| 1367 |
+
|
| 1368 |
+
if not end_fixed:
|
| 1369 |
+
updated["_l2_next_latent"] = self._sample_l2_next_latent(
|
| 1370 |
+
updated["_l2_context_latent"],
|
| 1371 |
+
l2_condition_kwargs=updated.get("_l2_condition_kwargs"),
|
| 1372 |
+
)
|
| 1373 |
+
updated["z_l2_end"] = updated["_l2_next_latent"]
|
| 1374 |
+
return updated
|
| 1375 |
+
|
| 1376 |
+
def get_rollout_visualization_state(self, condition_kwargs):
|
| 1377 |
+
if not condition_kwargs:
|
| 1378 |
+
return None
|
| 1379 |
+
l2_condition_kwargs = condition_kwargs.get("_l2_condition_kwargs")
|
| 1380 |
+
if not l2_condition_kwargs:
|
| 1381 |
+
return None
|
| 1382 |
+
return self.l2_condition_preprocessor.get_rollout_visualization_state(l2_condition_kwargs)
|
| 1383 |
+
|
| 1384 |
+
def get_visualization_trajectory(self, visualization_state):
|
| 1385 |
+
if visualization_state is None:
|
| 1386 |
+
return None
|
| 1387 |
+
return self.l2_condition_preprocessor.get_visualization_trajectory(visualization_state)
|
| 1388 |
+
|
| 1389 |
+
def update_rollout_visualization_state(self, visualization_state, step_idx):
|
| 1390 |
+
if visualization_state is None:
|
| 1391 |
+
return None
|
| 1392 |
+
return self.l2_condition_preprocessor.update_rollout_visualization_state(
|
| 1393 |
+
visualization_state,
|
| 1394 |
+
step_idx=step_idx,
|
| 1395 |
+
)
|
| 1396 |
+
|
| 1397 |
+
def get_rollout_visualization_trajectory(
|
| 1398 |
+
self,
|
| 1399 |
+
condition_kwargs,
|
| 1400 |
+
num_gen_steps,
|
| 1401 |
+
num_pred_frames=1,
|
| 1402 |
+
num_condition_frames=None,
|
| 1403 |
+
):
|
| 1404 |
+
if not condition_kwargs:
|
| 1405 |
+
return None
|
| 1406 |
+
l2_condition_kwargs = condition_kwargs.get("_l2_condition_kwargs")
|
| 1407 |
+
if not l2_condition_kwargs:
|
| 1408 |
+
return None
|
| 1409 |
+
return self.l2_condition_preprocessor.get_rollout_visualization_trajectory(
|
| 1410 |
+
l2_condition_kwargs,
|
| 1411 |
+
num_gen_steps=num_gen_steps,
|
| 1412 |
+
num_pred_frames=num_pred_frames,
|
| 1413 |
+
num_condition_frames=num_condition_frames,
|
| 1414 |
+
)
|
|
@@ -0,0 +1,1319 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import math
|
| 2 |
+
import os
|
| 3 |
+
from collections import OrderedDict
|
| 4 |
+
from copy import deepcopy
|
| 5 |
+
|
| 6 |
+
import pytorch_lightning as pl
|
| 7 |
+
import torch
|
| 8 |
+
import torchvision.utils as vutils
|
| 9 |
+
from einops import rearrange
|
| 10 |
+
from omegaconf import ListConfig, OmegaConf
|
| 11 |
+
from timm.layers.pos_embed import resample_abs_pos_embed
|
| 12 |
+
from torch.optim.lr_scheduler import LambdaLR
|
| 13 |
+
from tqdm import tqdm
|
| 14 |
+
|
| 15 |
+
from util import get_obj_from_str, instantiate_from_config
|
| 16 |
+
from .fm_conditions_v2 import (
|
| 17 |
+
ConditionPreprocessor,
|
| 18 |
+
L2EndpointConditionPreprocessor,
|
| 19 |
+
SpeedYawDirectConditionPreprocessor,
|
| 20 |
+
SpeedYawMovingGoalConditionPreprocessor,
|
| 21 |
+
)
|
| 22 |
+
@torch.no_grad()
|
| 23 |
+
def update_ema(ema_model, model, decay=0.9999):
|
| 24 |
+
ema_params = OrderedDict(ema_model.named_parameters())
|
| 25 |
+
model_params = OrderedDict(model.named_parameters())
|
| 26 |
+
|
| 27 |
+
for name, param in model_params.items():
|
| 28 |
+
ema_params[name].mul_(decay).add_(param.data, alpha=1 - decay)
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
def requires_grad(model, flag=True):
|
| 32 |
+
for p in model.parameters():
|
| 33 |
+
p.requires_grad = flag
|
| 34 |
+
|
| 35 |
+
|
| 36 |
+
def init_ema_model(model):
|
| 37 |
+
ema_model = deepcopy(model)
|
| 38 |
+
requires_grad(ema_model, False)
|
| 39 |
+
update_ema(ema_model, model, decay=0)
|
| 40 |
+
ema_model.eval()
|
| 41 |
+
return ema_model
|
| 42 |
+
|
| 43 |
+
|
| 44 |
+
def _extract_tokenizer_decode_samples(decoded):
|
| 45 |
+
if isinstance(decoded, tuple):
|
| 46 |
+
return decoded[0]
|
| 47 |
+
return decoded
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
def _decode_selected_if_branch(tokenizer, branch, latents):
|
| 51 |
+
if branch == "rec":
|
| 52 |
+
decoder = getattr(tokenizer, "decoder", None)
|
| 53 |
+
post_quant_conv = getattr(tokenizer, "post_quant_conv", None)
|
| 54 |
+
else:
|
| 55 |
+
decoder = getattr(tokenizer, "decoder_sem", None)
|
| 56 |
+
post_quant_conv = getattr(tokenizer, "post_quant_conv_sem", None)
|
| 57 |
+
|
| 58 |
+
if (
|
| 59 |
+
decoder is not None
|
| 60 |
+
and post_quant_conv is not None
|
| 61 |
+
and getattr(post_quant_conv, "in_channels", None) == latents.size(1)
|
| 62 |
+
):
|
| 63 |
+
return _extract_tokenizer_decode_samples(decoder(post_quant_conv(latents)))
|
| 64 |
+
|
| 65 |
+
rec_channels = tokenizer.quant_conv.out_channels
|
| 66 |
+
sem_channels = tokenizer.quant_conv2.out_channels
|
| 67 |
+
if branch == "rec":
|
| 68 |
+
quant = (
|
| 69 |
+
latents,
|
| 70 |
+
torch.zeros(
|
| 71 |
+
latents.size(0),
|
| 72 |
+
sem_channels,
|
| 73 |
+
latents.size(2),
|
| 74 |
+
latents.size(3),
|
| 75 |
+
device=latents.device,
|
| 76 |
+
dtype=latents.dtype,
|
| 77 |
+
),
|
| 78 |
+
)
|
| 79 |
+
else:
|
| 80 |
+
quant = (
|
| 81 |
+
torch.zeros(
|
| 82 |
+
latents.size(0),
|
| 83 |
+
rec_channels,
|
| 84 |
+
latents.size(2),
|
| 85 |
+
latents.size(3),
|
| 86 |
+
device=latents.device,
|
| 87 |
+
dtype=latents.dtype,
|
| 88 |
+
),
|
| 89 |
+
latents,
|
| 90 |
+
)
|
| 91 |
+
return _extract_tokenizer_decode_samples(tokenizer.decode(quant))
|
| 92 |
+
|
| 93 |
+
|
| 94 |
+
class FirstStageHandler:
|
| 95 |
+
def __init__(self, predictor_module):
|
| 96 |
+
self.module = predictor_module
|
| 97 |
+
|
| 98 |
+
@property
|
| 99 |
+
def ae(self):
|
| 100 |
+
return self.module.ae
|
| 101 |
+
|
| 102 |
+
@property
|
| 103 |
+
def vit(self):
|
| 104 |
+
return self.module.vit
|
| 105 |
+
|
| 106 |
+
@property
|
| 107 |
+
def enc_scale(self):
|
| 108 |
+
return self.module.enc_scale
|
| 109 |
+
|
| 110 |
+
@property
|
| 111 |
+
def use_precomputed_training_inputs(self):
|
| 112 |
+
return self.module.use_precomputed_training_inputs
|
| 113 |
+
|
| 114 |
+
@torch.no_grad()
|
| 115 |
+
def encode_frames(self, images):
|
| 116 |
+
if self.use_precomputed_training_inputs and images.shape[-2:] == self.vit.input_size:
|
| 117 |
+
return images * self.enc_scale
|
| 118 |
+
|
| 119 |
+
if images.ndim == 5:
|
| 120 |
+
b, f, _e, _h, _w = images.size()
|
| 121 |
+
images = rearrange(images, "b f e h w -> (b f) e h w")
|
| 122 |
+
else:
|
| 123 |
+
b, _e, _h, _w = images.size()
|
| 124 |
+
f = 1
|
| 125 |
+
|
| 126 |
+
x = self.ae.encode(images)["continuous"]
|
| 127 |
+
x = x * self.enc_scale
|
| 128 |
+
return rearrange(x, "(b f) e h w -> b f e h w", b=b, f=f)
|
| 129 |
+
|
| 130 |
+
@torch.no_grad()
|
| 131 |
+
def decode_frames(self, x, output_device=None):
|
| 132 |
+
samples = []
|
| 133 |
+
for idx in range(x.shape[1]):
|
| 134 |
+
frame = x[:, idx] / self.enc_scale
|
| 135 |
+
frame = self.ae.post_quant_conv(frame)
|
| 136 |
+
frame = self.ae.decoder(frame)
|
| 137 |
+
if output_device is not None:
|
| 138 |
+
frame = frame.to(output_device)
|
| 139 |
+
samples.append(frame.unsqueeze(1))
|
| 140 |
+
return torch.cat(samples, dim=1)
|
| 141 |
+
|
| 142 |
+
|
| 143 |
+
class IFSelectBranchFirstStageHandler(FirstStageHandler):
|
| 144 |
+
"""
|
| 145 |
+
First-stage handler that selects a single IF tokenizer branch, mirroring
|
| 146 |
+
`ModelIFSelectBranch` from the v1 codepath.
|
| 147 |
+
"""
|
| 148 |
+
|
| 149 |
+
def __init__(self, predictor_module, encoder_branch="rec"):
|
| 150 |
+
super().__init__(predictor_module)
|
| 151 |
+
self.encoder_branch = self._normalize_encoder_branch(encoder_branch)
|
| 152 |
+
expected_channels = self._selected_branch_channels()
|
| 153 |
+
actual_channels = getattr(self.vit, "in_channels", None)
|
| 154 |
+
if actual_channels is not None and actual_channels != expected_channels:
|
| 155 |
+
raise ValueError(
|
| 156 |
+
f"generator_config.params.in_channels={actual_channels} does not match "
|
| 157 |
+
f"the selected {self.encoder_branch} branch channels ({expected_channels})."
|
| 158 |
+
)
|
| 159 |
+
|
| 160 |
+
@property
|
| 161 |
+
def enc_scale_dino(self):
|
| 162 |
+
return self.module.enc_scale_dino
|
| 163 |
+
|
| 164 |
+
def _normalize_encoder_branch(self, encoder_branch):
|
| 165 |
+
if isinstance(encoder_branch, int):
|
| 166 |
+
if encoder_branch == 0:
|
| 167 |
+
return "rec"
|
| 168 |
+
if encoder_branch == 1:
|
| 169 |
+
return "sem"
|
| 170 |
+
|
| 171 |
+
branch = str(encoder_branch).strip().lower()
|
| 172 |
+
aliases = {
|
| 173 |
+
"rec": "rec",
|
| 174 |
+
"x0": "rec",
|
| 175 |
+
"h": "rec",
|
| 176 |
+
"sem": "sem",
|
| 177 |
+
"x1": "sem",
|
| 178 |
+
"h2": "sem",
|
| 179 |
+
}
|
| 180 |
+
if branch not in aliases:
|
| 181 |
+
raise ValueError(
|
| 182 |
+
f"Unsupported encoder_branch={encoder_branch!r}. "
|
| 183 |
+
"Expected one of: rec, x0, h, sem, x1, h2."
|
| 184 |
+
)
|
| 185 |
+
return aliases[branch]
|
| 186 |
+
|
| 187 |
+
def _selected_branch_index(self):
|
| 188 |
+
return 0 if self.encoder_branch == "rec" else 1
|
| 189 |
+
|
| 190 |
+
def _selected_branch_scale(self):
|
| 191 |
+
return self.enc_scale if self.encoder_branch == "rec" else self.enc_scale_dino
|
| 192 |
+
|
| 193 |
+
def _selected_branch_channels(self):
|
| 194 |
+
if self.encoder_branch == "rec":
|
| 195 |
+
return self.ae.quant_conv.out_channels
|
| 196 |
+
return self.ae.quant_conv2.out_channels
|
| 197 |
+
|
| 198 |
+
@torch.no_grad()
|
| 199 |
+
def encode_frames(self, images):
|
| 200 |
+
if self.use_precomputed_training_inputs and images.shape[-2:] == self.vit.input_size:
|
| 201 |
+
return images * self._selected_branch_scale()
|
| 202 |
+
|
| 203 |
+
if images.ndim == 5:
|
| 204 |
+
b, f, _e, _h, _w = images.size()
|
| 205 |
+
images = rearrange(images, "b f e h w -> (b f) e h w")
|
| 206 |
+
else:
|
| 207 |
+
b, _e, _h, _w = images.size()
|
| 208 |
+
f = 1
|
| 209 |
+
|
| 210 |
+
continuous = self.ae.encode(images)["continuous"]
|
| 211 |
+
if not isinstance(continuous, tuple) or len(continuous) != 2:
|
| 212 |
+
raise ValueError(
|
| 213 |
+
"IFSelectBranchFirstStageHandler expects tokenizer.encode(...)[\"continuous\"] "
|
| 214 |
+
f"to return a 2-tuple, got {type(continuous)}."
|
| 215 |
+
)
|
| 216 |
+
|
| 217 |
+
x = continuous[self._selected_branch_index()] * self._selected_branch_scale()
|
| 218 |
+
expected_channels = self._selected_branch_channels()
|
| 219 |
+
if x.size(1) != expected_channels:
|
| 220 |
+
raise ValueError(
|
| 221 |
+
f"Selected {self.encoder_branch} branch has {x.size(1)} channels, "
|
| 222 |
+
f"expected {expected_channels}."
|
| 223 |
+
)
|
| 224 |
+
|
| 225 |
+
return rearrange(x, "(b f) e h w -> b f e h w", b=b, f=f)
|
| 226 |
+
|
| 227 |
+
@torch.no_grad()
|
| 228 |
+
def decode_frames(self, x, output_device=None):
|
| 229 |
+
b, f, c, _h, _w = x.size()
|
| 230 |
+
|
| 231 |
+
expected_channels = self._selected_branch_channels()
|
| 232 |
+
if c != expected_channels:
|
| 233 |
+
raise ValueError(
|
| 234 |
+
f"decode_frames expected {expected_channels} channels for "
|
| 235 |
+
f"encoder_branch={self.encoder_branch}, got {c}."
|
| 236 |
+
)
|
| 237 |
+
|
| 238 |
+
if output_device is None:
|
| 239 |
+
x = rearrange(x, "b f c h w -> (b f) c h w")
|
| 240 |
+
selected = x / self._selected_branch_scale()
|
| 241 |
+
samples = _decode_selected_if_branch(self.ae, self.encoder_branch, selected)
|
| 242 |
+
return rearrange(samples, "(b f) c h w -> b f c h w", b=b, f=f)
|
| 243 |
+
|
| 244 |
+
samples = []
|
| 245 |
+
for idx in range(f):
|
| 246 |
+
selected = x[:, idx] / self._selected_branch_scale()
|
| 247 |
+
frame = _decode_selected_if_branch(self.ae, self.encoder_branch, selected)
|
| 248 |
+
samples.append(frame.to(output_device).unsqueeze(1))
|
| 249 |
+
return torch.cat(samples, dim=1)
|
| 250 |
+
|
| 251 |
+
|
| 252 |
+
class FlowMatchingObjective:
|
| 253 |
+
def __init__(self, predictor_module, sigma_min=1e-5):
|
| 254 |
+
self.module = predictor_module
|
| 255 |
+
self.sigma_min = sigma_min
|
| 256 |
+
|
| 257 |
+
def alpha(self, t):
|
| 258 |
+
return 1.0 - t
|
| 259 |
+
|
| 260 |
+
def sigma(self, t):
|
| 261 |
+
return self.sigma_min + t * (1.0 - self.sigma_min)
|
| 262 |
+
|
| 263 |
+
def A(self, t):
|
| 264 |
+
return 1.0
|
| 265 |
+
|
| 266 |
+
def B(self, t):
|
| 267 |
+
return -(1.0 - self.sigma_min)
|
| 268 |
+
|
| 269 |
+
def sample_t(self, x):
|
| 270 |
+
return torch.rand((x.shape[0],), device=x.device)
|
| 271 |
+
|
| 272 |
+
def add_noise(self, x, t, noise=None):
|
| 273 |
+
noise = torch.randn_like(x) if noise is None else noise
|
| 274 |
+
if t.dim() == 2:
|
| 275 |
+
shape = [x.shape[0], x.shape[1]] + [1] * (x.dim() - 2)
|
| 276 |
+
else:
|
| 277 |
+
shape = [x.shape[0]] + [1] * (x.dim() - 1)
|
| 278 |
+
x_t = self.alpha(t).view(*shape) * x + self.sigma(t).view(*shape) * noise
|
| 279 |
+
return x_t, noise
|
| 280 |
+
|
| 281 |
+
def get_supervision_target(self, target, noise, t):
|
| 282 |
+
return self.A(t) * target + self.B(t) * noise
|
| 283 |
+
|
| 284 |
+
def compute_loss(self, pred, target):
|
| 285 |
+
return (pred.float() - target.float()) ** 2
|
| 286 |
+
|
| 287 |
+
def prepare_training_inputs(self, x):
|
| 288 |
+
raise NotImplementedError
|
| 289 |
+
|
| 290 |
+
def compute_step(self, x, frame_rate, condition_kwargs=None):
|
| 291 |
+
model_input, t, supervision_target, prediction_slice = self.prepare_training_inputs(x)
|
| 292 |
+
model_condition_kwargs = self.module.condition_preprocessor.get_model_condition_kwargs(
|
| 293 |
+
condition_kwargs
|
| 294 |
+
)
|
| 295 |
+
pred = self.module.vit(model_input, t, frame_rate=frame_rate, **model_condition_kwargs)
|
| 296 |
+
if prediction_slice is not None:
|
| 297 |
+
pred = pred[:, prediction_slice]
|
| 298 |
+
return self.compute_loss(pred, supervision_target)
|
| 299 |
+
|
| 300 |
+
|
| 301 |
+
class FlowMatchingObjectiveTeacherForcing(FlowMatchingObjective):
|
| 302 |
+
def __init__(
|
| 303 |
+
self,
|
| 304 |
+
predictor_module,
|
| 305 |
+
sigma_min=1e-5,
|
| 306 |
+
ctx_noise_aug_ratio=0.1,
|
| 307 |
+
ctx_noise_aug_prob=0.5,
|
| 308 |
+
drop_ctx_rate=0.2,
|
| 309 |
+
):
|
| 310 |
+
super().__init__(predictor_module, sigma_min=sigma_min)
|
| 311 |
+
self.ctx_noise_aug_ratio = ctx_noise_aug_ratio
|
| 312 |
+
self.ctx_noise_aug_prob = ctx_noise_aug_prob
|
| 313 |
+
self.drop_ctx_rate = drop_ctx_rate
|
| 314 |
+
|
| 315 |
+
def _split_sequence(self, x):
|
| 316 |
+
context = x[:, :-self.module.num_pred_frames]
|
| 317 |
+
target = x[:, -self.module.num_pred_frames:]
|
| 318 |
+
return context, target
|
| 319 |
+
|
| 320 |
+
def _maybe_drop_context(self, context, target):
|
| 321 |
+
if context.size(1) == 0:
|
| 322 |
+
return context
|
| 323 |
+
if torch.rand(1, device=target.device) < self.drop_ctx_rate:
|
| 324 |
+
return context[:, :0]
|
| 325 |
+
return context
|
| 326 |
+
|
| 327 |
+
def _maybe_noise_context(self, context, t):
|
| 328 |
+
if context.size(1) == 0:
|
| 329 |
+
return context
|
| 330 |
+
if torch.rand(1, device=context.device) >= self.ctx_noise_aug_prob:
|
| 331 |
+
return context
|
| 332 |
+
|
| 333 |
+
mask = t >= self.ctx_noise_aug_ratio
|
| 334 |
+
if not mask.any():
|
| 335 |
+
return context
|
| 336 |
+
|
| 337 |
+
context = context.clone()
|
| 338 |
+
aug_noise = torch.randn_like(context)
|
| 339 |
+
context[mask] = context[mask] + aug_noise[mask] * self.ctx_noise_aug_ratio
|
| 340 |
+
return context
|
| 341 |
+
|
| 342 |
+
def _prepare_context(self, context, target, t):
|
| 343 |
+
if not self.module.training:
|
| 344 |
+
return context
|
| 345 |
+
|
| 346 |
+
context = self._maybe_drop_context(context, target)
|
| 347 |
+
if context.size(1) == 0:
|
| 348 |
+
return context
|
| 349 |
+
return self._maybe_noise_context(context, t)
|
| 350 |
+
|
| 351 |
+
def prepare_training_inputs(self, x):
|
| 352 |
+
context, target = self._split_sequence(x)
|
| 353 |
+
t = self.sample_t(target)
|
| 354 |
+
context = self._prepare_context(context, target, t)
|
| 355 |
+
target_t, noise = self.add_noise(target, t)
|
| 356 |
+
|
| 357 |
+
model_input = torch.cat([context, target_t], dim=1) if context.size(1) > 0 else target_t
|
| 358 |
+
|
| 359 |
+
supervision_target = self.get_supervision_target(target, noise, t)
|
| 360 |
+
prediction_slice = slice(-self.module.num_pred_frames, None)
|
| 361 |
+
model_t = self.module.sampler._build_model_t(context, target_t, t)
|
| 362 |
+
return model_input, model_t, supervision_target, prediction_slice
|
| 363 |
+
|
| 364 |
+
|
| 365 |
+
class FlowMatchingObjectiveDiffusionForcing(FlowMatchingObjective):
|
| 366 |
+
def sample_t(self, x):
|
| 367 |
+
return torch.rand(x.shape[0], x.shape[1], device=x.device)
|
| 368 |
+
|
| 369 |
+
def prepare_training_inputs(self, x):
|
| 370 |
+
t = self.sample_t(x)
|
| 371 |
+
x_t, noise = self.add_noise(x, t)
|
| 372 |
+
supervision_target = self.get_supervision_target(x, noise, t)
|
| 373 |
+
return x_t, t, supervision_target, None
|
| 374 |
+
|
| 375 |
+
|
| 376 |
+
class FlowMatchingSampler:
|
| 377 |
+
def __init__(self, predictor_module, timescale=1.0, integration_t_eps=0.0):
|
| 378 |
+
self.module = predictor_module
|
| 379 |
+
self.timescale = timescale
|
| 380 |
+
self.integration_t_eps = float(integration_t_eps)
|
| 381 |
+
|
| 382 |
+
def _get_net(self, sample_with_ema):
|
| 383 |
+
return self.module.ema_vit if sample_with_ema else self.module.vit
|
| 384 |
+
|
| 385 |
+
def _prepare_context(self, images, latent):
|
| 386 |
+
if images is None:
|
| 387 |
+
return None
|
| 388 |
+
if latent:
|
| 389 |
+
return images.clone()
|
| 390 |
+
return self.module.encode_frames(images)
|
| 391 |
+
|
| 392 |
+
def _default_frame_rate(self, num_samples, device):
|
| 393 |
+
return torch.full_like(torch.ones((num_samples,)), 5, device=device)
|
| 394 |
+
|
| 395 |
+
def _get_input_hw(self):
|
| 396 |
+
if isinstance(self.module.vit.input_size, (list, tuple, ListConfig)):
|
| 397 |
+
return self.module.vit.input_size[0], self.module.vit.input_size[1]
|
| 398 |
+
return self.module.vit.input_size, self.module.vit.input_size
|
| 399 |
+
|
| 400 |
+
def _build_model_inputs(self, context, target_t, t):
|
| 401 |
+
if context is not None and context.size(1) > 0:
|
| 402 |
+
model_input = torch.cat([context, target_t], dim=1)
|
| 403 |
+
else:
|
| 404 |
+
model_input = target_t
|
| 405 |
+
return model_input
|
| 406 |
+
|
| 407 |
+
def _build_model_t(self, context, target_t, t_scalar):
|
| 408 |
+
del context, target_t, t_scalar
|
| 409 |
+
raise NotImplementedError(
|
| 410 |
+
f"{self.__class__.__name__} must implement `_build_model_t`."
|
| 411 |
+
)
|
| 412 |
+
|
| 413 |
+
def _extract_target_prediction(self, pred):
|
| 414 |
+
return pred[:, -self.module.num_pred_frames :]
|
| 415 |
+
|
| 416 |
+
def _prepare_sampling_state(
|
| 417 |
+
self, images, latent, sample_with_ema, num_samples, frame_rate, condition_kwargs
|
| 418 |
+
):
|
| 419 |
+
net = self._get_net(sample_with_ema)
|
| 420 |
+
device = next(net.parameters()).device
|
| 421 |
+
context = self._prepare_context(images, latent)
|
| 422 |
+
condition_kwargs = self.module.condition_preprocessor.prepare_condition_kwargs(
|
| 423 |
+
condition_kwargs,
|
| 424 |
+
batch_size=num_samples,
|
| 425 |
+
device=device,
|
| 426 |
+
split="sample",
|
| 427 |
+
)
|
| 428 |
+
model_condition_kwargs = self.module.condition_preprocessor.get_model_condition_kwargs(
|
| 429 |
+
condition_kwargs
|
| 430 |
+
)
|
| 431 |
+
|
| 432 |
+
if frame_rate is None:
|
| 433 |
+
frame_rate = self._default_frame_rate(num_samples, device)
|
| 434 |
+
|
| 435 |
+
input_h, input_w = self._get_input_hw()
|
| 436 |
+
target_t = torch.randn(
|
| 437 |
+
num_samples,
|
| 438 |
+
self.module.num_pred_frames,
|
| 439 |
+
self.module.vit.in_channels,
|
| 440 |
+
input_h,
|
| 441 |
+
input_w,
|
| 442 |
+
device=device,
|
| 443 |
+
)
|
| 444 |
+
return net, device, context, model_condition_kwargs, frame_rate, target_t
|
| 445 |
+
|
| 446 |
+
def _build_t_steps(self, NFE, device):
|
| 447 |
+
if not 0.0 <= self.integration_t_eps < 0.5:
|
| 448 |
+
raise ValueError(
|
| 449 |
+
"integration_t_eps must be in [0, 0.5), "
|
| 450 |
+
f"got {self.integration_t_eps}."
|
| 451 |
+
)
|
| 452 |
+
return torch.linspace(
|
| 453 |
+
1.0 - self.integration_t_eps,
|
| 454 |
+
self.integration_t_eps,
|
| 455 |
+
NFE + 1,
|
| 456 |
+
device=device,
|
| 457 |
+
)
|
| 458 |
+
|
| 459 |
+
def _eval_velocity(self, net, context, target_t, t_scalar, frame_rate, model_condition_kwargs):
|
| 460 |
+
model_input = self._build_model_inputs(context, target_t, t_scalar)
|
| 461 |
+
model_t = self._build_model_t(context, target_t, t_scalar)
|
| 462 |
+
pred = net(model_input, t=model_t * self.timescale, frame_rate=frame_rate, **model_condition_kwargs)
|
| 463 |
+
return self._extract_target_prediction(pred)
|
| 464 |
+
|
| 465 |
+
def _euler_maruyama_step(self, target_t, neg_v, t_i, t_ip1, eta):
|
| 466 |
+
dt = t_i - t_ip1
|
| 467 |
+
dw = torch.randn(target_t.size(), device=target_t.device) * torch.sqrt(dt)
|
| 468 |
+
diffusion = dt
|
| 469 |
+
return target_t + neg_v * dt + eta * torch.sqrt(2 * diffusion) * dw
|
| 470 |
+
|
| 471 |
+
def _heun_step(self, net, context, target_t, t_i, t_ip1, frame_rate, model_condition_kwargs):
|
| 472 |
+
t_i_scalar = t_i.repeat(target_t.shape[0])
|
| 473 |
+
t_ip1_scalar = t_ip1.repeat(target_t.shape[0])
|
| 474 |
+
dt = t_i - t_ip1
|
| 475 |
+
v1 = self._eval_velocity(net, context, target_t, t_i_scalar, frame_rate, model_condition_kwargs)
|
| 476 |
+
x_pred = target_t + v1 * dt
|
| 477 |
+
v2 = self._eval_velocity(net, context, x_pred, t_ip1_scalar, frame_rate, model_condition_kwargs)
|
| 478 |
+
return target_t + 0.5 * (v1 + v2) * dt
|
| 479 |
+
|
| 480 |
+
def _snapshot_condition_kwargs(self, condition_kwargs):
|
| 481 |
+
if not condition_kwargs:
|
| 482 |
+
return {}
|
| 483 |
+
snapshot = {}
|
| 484 |
+
for key, value in condition_kwargs.items():
|
| 485 |
+
if torch.is_tensor(value):
|
| 486 |
+
snapshot[key] = value.detach().cpu().clone()
|
| 487 |
+
else:
|
| 488 |
+
snapshot[key] = value
|
| 489 |
+
return snapshot
|
| 490 |
+
|
| 491 |
+
@torch.no_grad()
|
| 492 |
+
def sample(
|
| 493 |
+
self,
|
| 494 |
+
images=None,
|
| 495 |
+
latent=False,
|
| 496 |
+
eta=0.0,
|
| 497 |
+
NFE=20,
|
| 498 |
+
sample_with_ema=True,
|
| 499 |
+
num_samples=8,
|
| 500 |
+
frame_rate=None,
|
| 501 |
+
condition_kwargs=None,
|
| 502 |
+
return_sample=False,
|
| 503 |
+
):
|
| 504 |
+
net, device, context, model_condition_kwargs, frame_rate, target_t = self._prepare_sampling_state(
|
| 505 |
+
images, latent, sample_with_ema, num_samples, frame_rate, condition_kwargs
|
| 506 |
+
)
|
| 507 |
+
t_steps = self._build_t_steps(NFE, device)
|
| 508 |
+
for i in range(NFE):
|
| 509 |
+
t_scalar = t_steps[i].repeat(target_t.shape[0])
|
| 510 |
+
neg_v = self._eval_velocity(net, context, target_t, t_scalar, frame_rate, model_condition_kwargs)
|
| 511 |
+
target_t = self._euler_maruyama_step(target_t, neg_v, t_steps[i], t_steps[i + 1], eta)
|
| 512 |
+
|
| 513 |
+
if return_sample:
|
| 514 |
+
return target_t, self.module.decode_frames(target_t.clone())
|
| 515 |
+
return target_t
|
| 516 |
+
|
| 517 |
+
def _update_rollout_context(self, context, prediction):
|
| 518 |
+
latest = prediction[:, -self.module.num_pred_frames:]
|
| 519 |
+
if self.module.num_pred_frames > context.size(1):
|
| 520 |
+
return latest
|
| 521 |
+
return torch.cat([context[:, self.module.num_pred_frames :], latest], dim=1)
|
| 522 |
+
|
| 523 |
+
@torch.no_grad()
|
| 524 |
+
def roll_out(
|
| 525 |
+
self,
|
| 526 |
+
x_0,
|
| 527 |
+
num_gen_frames=25,
|
| 528 |
+
latent_input=True,
|
| 529 |
+
eta=0.0,
|
| 530 |
+
NFE=20,
|
| 531 |
+
sample_with_ema=True,
|
| 532 |
+
num_samples=8,
|
| 533 |
+
frame_rate=None,
|
| 534 |
+
condition_kwargs=None,
|
| 535 |
+
decode_device=None,
|
| 536 |
+
return_condition_history=False,
|
| 537 |
+
):
|
| 538 |
+
context = x_0.clone() if latent_input else self.module.encode_frames(x_0)
|
| 539 |
+
all_latents = context.clone()
|
| 540 |
+
condition_kwargs = self.module.condition_preprocessor.prepare_condition_kwargs(
|
| 541 |
+
condition_kwargs,
|
| 542 |
+
batch_size=context.size(0),
|
| 543 |
+
device=context.device,
|
| 544 |
+
split="rollout",
|
| 545 |
+
)
|
| 546 |
+
condition_history = []
|
| 547 |
+
|
| 548 |
+
for _idx in tqdm(range(num_gen_frames), desc="Rolling out frames"):
|
| 549 |
+
if return_condition_history:
|
| 550 |
+
condition_history.append(self._snapshot_condition_kwargs(condition_kwargs))
|
| 551 |
+
prediction = self.sample(
|
| 552 |
+
images=context,
|
| 553 |
+
latent=True,
|
| 554 |
+
eta=eta,
|
| 555 |
+
NFE=NFE,
|
| 556 |
+
sample_with_ema=sample_with_ema,
|
| 557 |
+
num_samples=num_samples,
|
| 558 |
+
frame_rate=frame_rate,
|
| 559 |
+
condition_kwargs=condition_kwargs,
|
| 560 |
+
)
|
| 561 |
+
all_latents = torch.cat([all_latents, prediction[:, -self.module.num_pred_frames :]], dim=1)
|
| 562 |
+
if _idx < num_gen_frames - 1:
|
| 563 |
+
condition_kwargs = self.module.condition_preprocessor.update_rollout_condition_kwargs(
|
| 564 |
+
condition_kwargs,
|
| 565 |
+
prediction=prediction,
|
| 566 |
+
context=context,
|
| 567 |
+
step_idx=_idx,
|
| 568 |
+
)
|
| 569 |
+
context = self._update_rollout_context(context, prediction)
|
| 570 |
+
|
| 571 |
+
result = (all_latents, self.module.decode_frames(all_latents, output_device=decode_device))
|
| 572 |
+
if return_condition_history:
|
| 573 |
+
return result + (condition_history,)
|
| 574 |
+
return result
|
| 575 |
+
|
| 576 |
+
|
| 577 |
+
class FlowMatchingSamplerTeacherForcing(FlowMatchingSampler):
|
| 578 |
+
"""
|
| 579 |
+
Inference sampler used for all v2 models.
|
| 580 |
+
|
| 581 |
+
The context frames are kept clean in `model_input`; only the target frame
|
| 582 |
+
block is initialized with noise and integrated during sampling.
|
| 583 |
+
"""
|
| 584 |
+
|
| 585 |
+
def __init__(
|
| 586 |
+
self,
|
| 587 |
+
predictor_module,
|
| 588 |
+
timescale=1.0,
|
| 589 |
+
integration_t_eps=0.0,
|
| 590 |
+
timestep_conditioning="global",
|
| 591 |
+
):
|
| 592 |
+
super().__init__(
|
| 593 |
+
predictor_module,
|
| 594 |
+
timescale=timescale,
|
| 595 |
+
integration_t_eps=integration_t_eps,
|
| 596 |
+
)
|
| 597 |
+
self.timestep_conditioning = self._normalize_timestep_conditioning(timestep_conditioning)
|
| 598 |
+
|
| 599 |
+
def _normalize_timestep_conditioning(self, timestep_conditioning):
|
| 600 |
+
mode = str(timestep_conditioning).strip().lower()
|
| 601 |
+
if mode not in {"global", "per_frame"}:
|
| 602 |
+
raise ValueError(
|
| 603 |
+
"timestep_conditioning must be one of {'global', 'per_frame'}, "
|
| 604 |
+
f"got {timestep_conditioning!r}."
|
| 605 |
+
)
|
| 606 |
+
return mode
|
| 607 |
+
|
| 608 |
+
def _build_model_t(self, context, target_t, t_scalar):
|
| 609 |
+
if self.timestep_conditioning == "global":
|
| 610 |
+
return t_scalar
|
| 611 |
+
|
| 612 |
+
target_t_full = t_scalar.unsqueeze(1).expand(-1, target_t.size(1))
|
| 613 |
+
if context is None or context.size(1) == 0:
|
| 614 |
+
return target_t_full
|
| 615 |
+
context_t = torch.zeros(
|
| 616 |
+
t_scalar.size(0),
|
| 617 |
+
context.size(1),
|
| 618 |
+
device=t_scalar.device,
|
| 619 |
+
dtype=t_scalar.dtype,
|
| 620 |
+
)
|
| 621 |
+
return torch.cat([context_t, target_t_full], dim=1)
|
| 622 |
+
|
| 623 |
+
def _validate_num_heun_steps(self, num_heun_steps, NFE, eta):
|
| 624 |
+
if isinstance(num_heun_steps, bool) or not isinstance(num_heun_steps, int):
|
| 625 |
+
raise TypeError(
|
| 626 |
+
f"num_heun_steps must be an int, got {type(num_heun_steps).__name__}."
|
| 627 |
+
)
|
| 628 |
+
if not 0 <= num_heun_steps <= NFE:
|
| 629 |
+
raise ValueError(
|
| 630 |
+
f"num_heun_steps must be in [0, NFE] (NFE={NFE}), got {num_heun_steps}."
|
| 631 |
+
)
|
| 632 |
+
if num_heun_steps > 0 and eta != 0.0:
|
| 633 |
+
raise ValueError(
|
| 634 |
+
"Heun sampling (num_heun_steps > 0) is deterministic-only and cannot "
|
| 635 |
+
f"be combined with the stochastic SDE term; got eta={eta}. "
|
| 636 |
+
"Set eta=0.0 when num_heun_steps > 0."
|
| 637 |
+
)
|
| 638 |
+
|
| 639 |
+
@torch.no_grad()
|
| 640 |
+
def sample(
|
| 641 |
+
self,
|
| 642 |
+
images=None,
|
| 643 |
+
latent=False,
|
| 644 |
+
eta=0.0,
|
| 645 |
+
NFE=20,
|
| 646 |
+
sample_with_ema=True,
|
| 647 |
+
num_samples=8,
|
| 648 |
+
frame_rate=None,
|
| 649 |
+
condition_kwargs=None,
|
| 650 |
+
return_sample=False,
|
| 651 |
+
num_heun_steps=0,
|
| 652 |
+
):
|
| 653 |
+
self._validate_num_heun_steps(num_heun_steps, NFE, eta)
|
| 654 |
+
net, device, context, model_condition_kwargs, frame_rate, target_t = self._prepare_sampling_state(
|
| 655 |
+
images, latent, sample_with_ema, num_samples, frame_rate, condition_kwargs
|
| 656 |
+
)
|
| 657 |
+
t_steps = self._build_t_steps(NFE, device)
|
| 658 |
+
for i in range(NFE):
|
| 659 |
+
if i < num_heun_steps:
|
| 660 |
+
target_t = self._heun_step(
|
| 661 |
+
net, context, target_t, t_steps[i], t_steps[i + 1], frame_rate, model_condition_kwargs
|
| 662 |
+
)
|
| 663 |
+
else:
|
| 664 |
+
t_scalar = t_steps[i].repeat(target_t.shape[0])
|
| 665 |
+
neg_v = self._eval_velocity(net, context, target_t, t_scalar, frame_rate, model_condition_kwargs)
|
| 666 |
+
target_t = self._euler_maruyama_step(target_t, neg_v, t_steps[i], t_steps[i + 1], eta)
|
| 667 |
+
|
| 668 |
+
if return_sample:
|
| 669 |
+
return target_t, self.module.decode_frames(target_t.clone())
|
| 670 |
+
return target_t
|
| 671 |
+
|
| 672 |
+
@torch.no_grad()
|
| 673 |
+
def roll_out(
|
| 674 |
+
self,
|
| 675 |
+
x_0,
|
| 676 |
+
num_gen_frames=25,
|
| 677 |
+
latent_input=True,
|
| 678 |
+
eta=0.0,
|
| 679 |
+
NFE=20,
|
| 680 |
+
sample_with_ema=True,
|
| 681 |
+
num_samples=8,
|
| 682 |
+
frame_rate=None,
|
| 683 |
+
condition_kwargs=None,
|
| 684 |
+
decode_device=None,
|
| 685 |
+
return_condition_history=False,
|
| 686 |
+
num_heun_steps=0,
|
| 687 |
+
):
|
| 688 |
+
# num_heun_steps validation happens inside self.sample() on the first
|
| 689 |
+
# generated block, before any expensive work in this loop.
|
| 690 |
+
context = x_0.clone() if latent_input else self.module.encode_frames(x_0)
|
| 691 |
+
all_latents = context.clone()
|
| 692 |
+
condition_kwargs = self.module.condition_preprocessor.prepare_condition_kwargs(
|
| 693 |
+
condition_kwargs,
|
| 694 |
+
batch_size=context.size(0),
|
| 695 |
+
device=context.device,
|
| 696 |
+
split="rollout",
|
| 697 |
+
)
|
| 698 |
+
condition_history = []
|
| 699 |
+
|
| 700 |
+
for _idx in tqdm(range(num_gen_frames), desc="Rolling out frames"):
|
| 701 |
+
if return_condition_history:
|
| 702 |
+
condition_history.append(self._snapshot_condition_kwargs(condition_kwargs))
|
| 703 |
+
prediction = self.sample(
|
| 704 |
+
images=context,
|
| 705 |
+
latent=True,
|
| 706 |
+
eta=eta,
|
| 707 |
+
NFE=NFE,
|
| 708 |
+
sample_with_ema=sample_with_ema,
|
| 709 |
+
num_samples=num_samples,
|
| 710 |
+
frame_rate=frame_rate,
|
| 711 |
+
condition_kwargs=condition_kwargs,
|
| 712 |
+
num_heun_steps=num_heun_steps,
|
| 713 |
+
)
|
| 714 |
+
all_latents = torch.cat([all_latents, prediction[:, -self.module.num_pred_frames :]], dim=1)
|
| 715 |
+
if _idx < num_gen_frames - 1:
|
| 716 |
+
condition_kwargs = self.module.condition_preprocessor.update_rollout_condition_kwargs(
|
| 717 |
+
condition_kwargs,
|
| 718 |
+
prediction=prediction,
|
| 719 |
+
context=context,
|
| 720 |
+
step_idx=_idx,
|
| 721 |
+
)
|
| 722 |
+
context = self._update_rollout_context(context, prediction)
|
| 723 |
+
|
| 724 |
+
result = (all_latents, self.module.decode_frames(all_latents, output_device=decode_device))
|
| 725 |
+
if return_condition_history:
|
| 726 |
+
return result + (condition_history,)
|
| 727 |
+
return result
|
| 728 |
+
|
| 729 |
+
|
| 730 |
+
class FlowMatchingSamplerDiffusionForcing(FlowMatchingSampler):
|
| 731 |
+
"""
|
| 732 |
+
Deprecated: diffusion forcing is a training objective, not a separate
|
| 733 |
+
inference scheme in v2. Use FlowMatchingSamplerTeacherForcing for clean
|
| 734 |
+
context / noisy target sampling.
|
| 735 |
+
"""
|
| 736 |
+
|
| 737 |
+
def __init__(self, *args, **kwargs):
|
| 738 |
+
raise RuntimeError(
|
| 739 |
+
"FlowMatchingSamplerDiffusionForcing is deprecated. "
|
| 740 |
+
"Use FlowMatchingSamplerTeacherForcing for inference."
|
| 741 |
+
)
|
| 742 |
+
|
| 743 |
+
def _build_model_t(self, context, target_t, t_scalar):
|
| 744 |
+
del context
|
| 745 |
+
return t_scalar.unsqueeze(1).expand(-1, target_t.size(1))
|
| 746 |
+
|
| 747 |
+
|
| 748 |
+
class PredictorModule(pl.LightningModule):
|
| 749 |
+
def __init__(
|
| 750 |
+
self,
|
| 751 |
+
*,
|
| 752 |
+
tokenizer_config,
|
| 753 |
+
generator_config,
|
| 754 |
+
objective_config=None,
|
| 755 |
+
sampler_config=None,
|
| 756 |
+
condition_preprocessor_config=None,
|
| 757 |
+
first_stage_handler_config=None,
|
| 758 |
+
adjust_lr_to_batch_size=False,
|
| 759 |
+
num_pred_frames=1,
|
| 760 |
+
warmup_steps=5000,
|
| 761 |
+
min_lr_multiplier=0.1,
|
| 762 |
+
enc_scale=4,
|
| 763 |
+
enc_scale_dino=3.45062,
|
| 764 |
+
use_precomputed_training_inputs=False,
|
| 765 |
+
init_weights_path=None,
|
| 766 |
+
allow_different_resolution_checkpoint=False,
|
| 767 |
+
):
|
| 768 |
+
super().__init__()
|
| 769 |
+
|
| 770 |
+
self.num_pred_frames = num_pred_frames
|
| 771 |
+
self.enc_scale = enc_scale
|
| 772 |
+
self.enc_scale_dino = enc_scale_dino
|
| 773 |
+
self.allow_different_resolution_checkpoint = allow_different_resolution_checkpoint
|
| 774 |
+
self.adjust_lr_to_batch_size = adjust_lr_to_batch_size
|
| 775 |
+
self.warmup_steps = warmup_steps
|
| 776 |
+
self.min_lr_multiplier = min_lr_multiplier
|
| 777 |
+
self.use_precomputed_training_inputs = use_precomputed_training_inputs
|
| 778 |
+
|
| 779 |
+
self.vit = self.build_generator(generator_config)
|
| 780 |
+
self.ae = self.build_tokenizer(tokenizer_config)
|
| 781 |
+
self.ema_vit = init_ema_model(self.vit)
|
| 782 |
+
|
| 783 |
+
self.first_stage = self.build_first_stage(first_stage_handler_config)
|
| 784 |
+
self.condition_preprocessor = self.build_condition_preprocessor(condition_preprocessor_config)
|
| 785 |
+
self.objective = self.build_objective(objective_config)
|
| 786 |
+
self.sampler = self.build_sampler(sampler_config)
|
| 787 |
+
|
| 788 |
+
if init_weights_path is not None:
|
| 789 |
+
self.init_weights_from_checkpoint(init_weights_path)
|
| 790 |
+
|
| 791 |
+
def setup(self, stage=None):
|
| 792 |
+
"""Exclude 'unused' parameters from DDP gradient reduction."""
|
| 793 |
+
super().setup(stage)
|
| 794 |
+
# EMA
|
| 795 |
+
if hasattr(self, "ema_vit") and self.ema_vit is not None:
|
| 796 |
+
self.ema_vit.requires_grad_(False)
|
| 797 |
+
self.ae.requires_grad_(False)
|
| 798 |
+
|
| 799 |
+
def init_weights_from_checkpoint(self, init_weights_path):
|
| 800 |
+
checkpoint_path = os.path.expandvars(init_weights_path)
|
| 801 |
+
if not os.path.exists(checkpoint_path):
|
| 802 |
+
raise FileNotFoundError(f"Initial weights {init_weights_path} does not exist.")
|
| 803 |
+
|
| 804 |
+
state_dict = torch.load(checkpoint_path, map_location="cpu")["state_dict"]
|
| 805 |
+
state_dict = self._prepare_second_stage_checkpoint(state_dict, checkpoint_path)
|
| 806 |
+
outcome = self.load_state_dict(state_dict, strict=False)
|
| 807 |
+
assert outcome.missing_keys == [], outcome.missing_keys
|
| 808 |
+
print(f"Loaded model from {init_weights_path}")
|
| 809 |
+
|
| 810 |
+
def build_tokenizer(self, tokenizer_config):
|
| 811 |
+
tokenizer_folder = os.path.expandvars(tokenizer_config.folder)
|
| 812 |
+
ckpt_path = tokenizer_config.ckpt_path if tokenizer_config.ckpt_path else "checkpoints/last.ckpt"
|
| 813 |
+
|
| 814 |
+
tokenizer_config = OmegaConf.load(os.path.join(tokenizer_folder, "config.yaml"))
|
| 815 |
+
model_cfg = OmegaConf.to_container(tokenizer_config.model, resolve=False)
|
| 816 |
+
for key in ("loss_config", "entropy_loss_weight_scheduler_config"):
|
| 817 |
+
model_cfg.get("params", {}).pop(key, None)
|
| 818 |
+
model_cfg.get("params", {})["distill_model_type"] = None
|
| 819 |
+
encoder_params = model_cfg.get("params", {}).get("encoder_config", {}).get("params", {})
|
| 820 |
+
if isinstance(encoder_params, dict):
|
| 821 |
+
encoder_params["use_pretrained_weights"] = False
|
| 822 |
+
model = instantiate_from_config(OmegaConf.create(model_cfg))
|
| 823 |
+
|
| 824 |
+
checkpoint_path = os.path.join(tokenizer_folder, ckpt_path)
|
| 825 |
+
checkpoint = torch.load(checkpoint_path, map_location="cpu", weights_only=True)["state_dict"]
|
| 826 |
+
checkpoint = self._prepare_tokenizer_checkpoint(model, checkpoint, tokenizer_config.model, checkpoint_path)
|
| 827 |
+
model.load_state_dict(checkpoint, strict=False)
|
| 828 |
+
model.eval()
|
| 829 |
+
return model
|
| 830 |
+
|
| 831 |
+
def build_generator(self, generator_config):
|
| 832 |
+
return instantiate_from_config(generator_config)
|
| 833 |
+
|
| 834 |
+
def _build_helper_from_config(self, config, default_target):
|
| 835 |
+
config = config or {"target": default_target, "params": {}}
|
| 836 |
+
params = dict(config.get("params", {}))
|
| 837 |
+
params["predictor_module"] = self
|
| 838 |
+
helper_cls = get_obj_from_str(config["target"])
|
| 839 |
+
return helper_cls(**params)
|
| 840 |
+
|
| 841 |
+
def build_objective(self, objective_config):
|
| 842 |
+
return self._build_helper_from_config(
|
| 843 |
+
objective_config,
|
| 844 |
+
"models.second_stage.fm_model_v2.FlowMatchingObjectiveTeacherForcing",
|
| 845 |
+
)
|
| 846 |
+
|
| 847 |
+
def build_sampler(self, sampler_config):
|
| 848 |
+
return self._build_helper_from_config(
|
| 849 |
+
sampler_config,
|
| 850 |
+
"models.second_stage.fm_model_v2.FlowMatchingSamplerTeacherForcing",
|
| 851 |
+
)
|
| 852 |
+
|
| 853 |
+
def build_condition_preprocessor(self, condition_preprocessor_config):
|
| 854 |
+
return self._build_helper_from_config(
|
| 855 |
+
condition_preprocessor_config,
|
| 856 |
+
"models.second_stage.fm_conditions_v2.ConditionPreprocessor",
|
| 857 |
+
)
|
| 858 |
+
|
| 859 |
+
def build_first_stage(self, first_stage_handler_config):
|
| 860 |
+
return self._build_helper_from_config(
|
| 861 |
+
first_stage_handler_config,
|
| 862 |
+
"models.second_stage.fm_model_v2.FirstStageHandler",
|
| 863 |
+
)
|
| 864 |
+
|
| 865 |
+
def _require_or_allow_resolution_mismatch(self, *, name, checkpoint_shape, model_shape, source):
|
| 866 |
+
if checkpoint_shape == model_shape:
|
| 867 |
+
return
|
| 868 |
+
if not self.allow_different_resolution_checkpoint:
|
| 869 |
+
raise ValueError(
|
| 870 |
+
f"{name} shape mismatch between checkpoint and current model: "
|
| 871 |
+
f"checkpoint={checkpoint_shape}, model={model_shape}. "
|
| 872 |
+
f"Source: {source}. "
|
| 873 |
+
"Set allow_different_resolution_checkpoint=True to adapt or skip "
|
| 874 |
+
"resolution-dependent positional embeddings."
|
| 875 |
+
)
|
| 876 |
+
|
| 877 |
+
def _prepare_tokenizer_checkpoint(self, model, checkpoint, tokenizer_model_config, checkpoint_path):
|
| 878 |
+
state_dict = checkpoint.copy()
|
| 879 |
+
target = tokenizer_model_config.get("target", "")
|
| 880 |
+
encoder_config = tokenizer_model_config.params.get("encoder_config")
|
| 881 |
+
encoder_target = encoder_config.get("target", "") if encoder_config else ""
|
| 882 |
+
|
| 883 |
+
if target != "models.first_stage.vqgan.VQModel" or encoder_target != "networks.tokenizer.pretrained_models.Encoder":
|
| 884 |
+
return state_dict
|
| 885 |
+
|
| 886 |
+
model_pos_embed = getattr(getattr(model.encoder, "encoder", None), "pos_embed", None)
|
| 887 |
+
checkpoint_key = "encoder.encoder.pos_embed"
|
| 888 |
+
checkpoint_pos_embed = state_dict.get(checkpoint_key)
|
| 889 |
+
|
| 890 |
+
if model_pos_embed is None or checkpoint_pos_embed is None:
|
| 891 |
+
return state_dict
|
| 892 |
+
|
| 893 |
+
self._require_or_allow_resolution_mismatch(
|
| 894 |
+
name=f"Tokenizer positional embedding ({checkpoint_key})",
|
| 895 |
+
checkpoint_shape=tuple(checkpoint_pos_embed.shape),
|
| 896 |
+
model_shape=tuple(model_pos_embed.shape),
|
| 897 |
+
source=checkpoint_path,
|
| 898 |
+
)
|
| 899 |
+
|
| 900 |
+
if checkpoint_pos_embed.shape != model_pos_embed.shape:
|
| 901 |
+
num_prefix_tokens = getattr(model.encoder.encoder, "num_prefix_tokens", 1)
|
| 902 |
+
target_grid = model.encoder.encoder.patch_embed.grid_size
|
| 903 |
+
state_dict[checkpoint_key] = resample_abs_pos_embed(
|
| 904 |
+
checkpoint_pos_embed,
|
| 905 |
+
new_size=target_grid,
|
| 906 |
+
num_prefix_tokens=num_prefix_tokens,
|
| 907 |
+
)
|
| 908 |
+
|
| 909 |
+
return state_dict
|
| 910 |
+
|
| 911 |
+
def _prepare_second_stage_checkpoint(self, checkpoint, checkpoint_path):
|
| 912 |
+
state_dict = checkpoint.copy()
|
| 913 |
+
for key in ("vit.pos_embed", "ema_vit.pos_embed"):
|
| 914 |
+
checkpoint_pos_embed = state_dict.get(key)
|
| 915 |
+
model_pos_embed = self.state_dict().get(key)
|
| 916 |
+
if checkpoint_pos_embed is None or model_pos_embed is None:
|
| 917 |
+
continue
|
| 918 |
+
|
| 919 |
+
self._require_or_allow_resolution_mismatch(
|
| 920 |
+
name=f"Second-stage positional embedding ({key})",
|
| 921 |
+
checkpoint_shape=tuple(checkpoint_pos_embed.shape),
|
| 922 |
+
model_shape=tuple(model_pos_embed.shape),
|
| 923 |
+
source=checkpoint_path,
|
| 924 |
+
)
|
| 925 |
+
|
| 926 |
+
if checkpoint_pos_embed.shape != model_pos_embed.shape:
|
| 927 |
+
del state_dict[key]
|
| 928 |
+
|
| 929 |
+
return state_dict
|
| 930 |
+
|
| 931 |
+
def get_warmup_scheduler(self, optimizer, warmup_steps=1, min_lr_multiplier=1.0):
|
| 932 |
+
batches = len(self.trainer.datamodule.train_dataloader())
|
| 933 |
+
steps_per_epoch = batches // self.trainer.accumulate_grad_batches
|
| 934 |
+
total_steps = self.trainer.max_epochs * steps_per_epoch
|
| 935 |
+
|
| 936 |
+
def lr_lambda(step):
|
| 937 |
+
if step < warmup_steps:
|
| 938 |
+
return step / warmup_steps
|
| 939 |
+
progress = (min(step, total_steps) - warmup_steps) / (total_steps - warmup_steps)
|
| 940 |
+
cosine_decay = 0.5 * (1 + math.cos(math.pi * progress))
|
| 941 |
+
return (1 - min_lr_multiplier) * cosine_decay + min_lr_multiplier
|
| 942 |
+
|
| 943 |
+
return LambdaLR(optimizer, lr_lambda)
|
| 944 |
+
|
| 945 |
+
def configure_optimizers(self):
|
| 946 |
+
params = [p for p in self.vit.parameters() if p.requires_grad]
|
| 947 |
+
optimizer = torch.optim.AdamW(params, lr=self.learning_rate, weight_decay=0.01)
|
| 948 |
+
scheduler = self.get_warmup_scheduler(optimizer, self.warmup_steps, self.min_lr_multiplier)
|
| 949 |
+
return [optimizer], [{"scheduler": scheduler, "interval": "step"}]
|
| 950 |
+
|
| 951 |
+
def get_input(self, batch, k):
|
| 952 |
+
if isinstance(batch, dict):
|
| 953 |
+
x = batch[k]
|
| 954 |
+
frame_rate = batch["frame_rate"]
|
| 955 |
+
else:
|
| 956 |
+
x = batch
|
| 957 |
+
frame_rate = None
|
| 958 |
+
assert len(x.shape) == 5 or self.use_precomputed_training_inputs, "When using images, input must be 5D tensor"
|
| 959 |
+
return x, frame_rate
|
| 960 |
+
|
| 961 |
+
@torch.no_grad()
|
| 962 |
+
def encode_frames(self, images):
|
| 963 |
+
return self.first_stage.encode_frames(images)
|
| 964 |
+
|
| 965 |
+
@torch.no_grad()
|
| 966 |
+
def decode_frames(self, x, output_device=None):
|
| 967 |
+
return self.first_stage.decode_frames(x, output_device=output_device)
|
| 968 |
+
|
| 969 |
+
def compute_prediction_loss(self, pred, target):
|
| 970 |
+
return self.objective.compute_loss(pred, target)
|
| 971 |
+
|
| 972 |
+
def log_training_losses(self, loss):
|
| 973 |
+
self.log("train/loss", loss.mean(), prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 974 |
+
|
| 975 |
+
def log_validation_losses(self, loss):
|
| 976 |
+
self.log("val/loss", loss.mean(), prog_bar=True, logger=True, on_step=True, on_epoch=True, sync_dist=True)
|
| 977 |
+
|
| 978 |
+
def log_adaln_mean_abs(self, split, condition_kwargs=None):
|
| 979 |
+
stats_getter = getattr(self.vit, "get_last_adaln_mean_abs", None)
|
| 980 |
+
if not callable(stats_getter):
|
| 981 |
+
return
|
| 982 |
+
|
| 983 |
+
stats = stats_getter()
|
| 984 |
+
if not stats:
|
| 985 |
+
return
|
| 986 |
+
|
| 987 |
+
aggregated = {}
|
| 988 |
+
for name, value in stats.items():
|
| 989 |
+
_, stat_name = name.split("/", 1)
|
| 990 |
+
aggregated.setdefault(stat_name, []).append(value)
|
| 991 |
+
|
| 992 |
+
group_masks = self._get_adaln_stat_group_masks(condition_kwargs)
|
| 993 |
+
for stat_name, values in aggregated.items():
|
| 994 |
+
values_tensor = torch.stack(values)
|
| 995 |
+
for group_name, mask in group_masks.items():
|
| 996 |
+
group_values = values_tensor if mask is None else values_tensor[:, mask]
|
| 997 |
+
self._log_adaln_stat_values(split, group_name, stat_name, group_values)
|
| 998 |
+
|
| 999 |
+
def _get_adaln_stat_group_masks(self, condition_kwargs):
|
| 1000 |
+
if not condition_kwargs or "steering" not in condition_kwargs:
|
| 1001 |
+
return {"all": None}
|
| 1002 |
+
|
| 1003 |
+
steering = condition_kwargs["steering"]
|
| 1004 |
+
if not torch.is_tensor(steering):
|
| 1005 |
+
return {"all": None}
|
| 1006 |
+
|
| 1007 |
+
present_mask = ~torch.isnan(steering).flatten(1).all(dim=1)
|
| 1008 |
+
return {
|
| 1009 |
+
"present": present_mask,
|
| 1010 |
+
"missing": ~present_mask,
|
| 1011 |
+
}
|
| 1012 |
+
|
| 1013 |
+
def _log_adaln_stat_values(self, split, group_name, stat_name, values):
|
| 1014 |
+
# Always emit exactly 2 log calls (mean + std) regardless of group size so that
|
| 1015 |
+
# sync_dist=True allreduces are consistent across ranks even when different ranks
|
| 1016 |
+
# receive batches with different steering availability (e.g. mixed-dataset training).
|
| 1017 |
+
values = values.reshape(-1)
|
| 1018 |
+
nan = torch.tensor(float("nan"), device=values.device)
|
| 1019 |
+
mean_val = values.mean() if values.numel() > 0 else nan
|
| 1020 |
+
std_val = values.std(unbiased=False) if values.numel() >= 2 else nan
|
| 1021 |
+
|
| 1022 |
+
self.log(
|
| 1023 |
+
f"{split}/adaln/{group_name}/{stat_name}/mean",
|
| 1024 |
+
mean_val,
|
| 1025 |
+
prog_bar=False,
|
| 1026 |
+
logger=True,
|
| 1027 |
+
on_step=True,
|
| 1028 |
+
on_epoch=True,
|
| 1029 |
+
sync_dist=True,
|
| 1030 |
+
)
|
| 1031 |
+
self.log(
|
| 1032 |
+
f"{split}/adaln/{group_name}/{stat_name}/std",
|
| 1033 |
+
std_val,
|
| 1034 |
+
prog_bar=False,
|
| 1035 |
+
logger=True,
|
| 1036 |
+
on_step=True,
|
| 1037 |
+
on_epoch=True,
|
| 1038 |
+
sync_dist=True,
|
| 1039 |
+
)
|
| 1040 |
+
|
| 1041 |
+
def log_condition_embeddings(self, split, condition_kwargs=None):
|
| 1042 |
+
getter = getattr(self.vit, 'get_last_condition_embeddings', None)
|
| 1043 |
+
if not callable(getter):
|
| 1044 |
+
return
|
| 1045 |
+
embeddings = getter()
|
| 1046 |
+
if not embeddings:
|
| 1047 |
+
return
|
| 1048 |
+
group_masks = self._get_adaln_stat_group_masks(condition_kwargs)
|
| 1049 |
+
for emb_name, embedding in embeddings.items():
|
| 1050 |
+
per_sample_mean_abs = embedding.abs().mean(dim=-1)
|
| 1051 |
+
per_sample_norm = embedding.norm(dim=-1)
|
| 1052 |
+
for stat_name, values in (("mean_abs", per_sample_mean_abs), ("norm", per_sample_norm)):
|
| 1053 |
+
for group_name, mask in group_masks.items():
|
| 1054 |
+
group_values = values if mask is None else values[mask]
|
| 1055 |
+
group_values = group_values.reshape(-1)
|
| 1056 |
+
# Always emit exactly 2 log calls per group so sync_dist=True allreduces
|
| 1057 |
+
# are consistent across ranks regardless of per-rank batch composition.
|
| 1058 |
+
nan = torch.tensor(float("nan"), device=group_values.device)
|
| 1059 |
+
mean_val = group_values.mean() if group_values.numel() > 0 else nan
|
| 1060 |
+
std_val = group_values.std(unbiased=False) if group_values.numel() >= 2 else nan
|
| 1061 |
+
self.log(
|
| 1062 |
+
f"{split}/cond_emb/{emb_name}/{group_name}/{stat_name}/mean",
|
| 1063 |
+
mean_val,
|
| 1064 |
+
prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True,
|
| 1065 |
+
)
|
| 1066 |
+
self.log(
|
| 1067 |
+
f"{split}/cond_emb/{emb_name}/{group_name}/{stat_name}/std",
|
| 1068 |
+
std_val,
|
| 1069 |
+
prog_bar=False, logger=True, on_step=True, on_epoch=True, sync_dist=True,
|
| 1070 |
+
)
|
| 1071 |
+
|
| 1072 |
+
def log_adaln_gradients(self):
|
| 1073 |
+
getter = getattr(self.vit, 'get_adaln_grad_stats', None)
|
| 1074 |
+
if not callable(getter):
|
| 1075 |
+
return
|
| 1076 |
+
stats = getter()
|
| 1077 |
+
for name, g in stats.items():
|
| 1078 |
+
g_flat = g.float().flatten()
|
| 1079 |
+
self.log(
|
| 1080 |
+
f"train/adaln_grad/{name}/norm",
|
| 1081 |
+
g_flat.norm(),
|
| 1082 |
+
prog_bar=False, logger=True, on_step=True, on_epoch=False, sync_dist=True,
|
| 1083 |
+
)
|
| 1084 |
+
self.log(
|
| 1085 |
+
f"train/adaln_grad/{name}/mean_abs",
|
| 1086 |
+
g_flat.abs().mean(),
|
| 1087 |
+
prog_bar=False, logger=True, on_step=True, on_epoch=False, sync_dist=True,
|
| 1088 |
+
)
|
| 1089 |
+
|
| 1090 |
+
def on_after_backward(self):
|
| 1091 |
+
self.log_adaln_gradients()
|
| 1092 |
+
|
| 1093 |
+
def _shared_step(self, batch, split):
|
| 1094 |
+
images, frame_rate = self.get_input(batch, "images")
|
| 1095 |
+
x = self.encode_frames(images)
|
| 1096 |
+
condition_kwargs = self.condition_preprocessor.get_condition_kwargs_from_batch(batch, split=split)
|
| 1097 |
+
loss = self.objective.compute_step(x, frame_rate, condition_kwargs=condition_kwargs)
|
| 1098 |
+
|
| 1099 |
+
if split == "train":
|
| 1100 |
+
self.log_training_losses(loss)
|
| 1101 |
+
self.log_adaln_mean_abs("train", condition_kwargs)
|
| 1102 |
+
self.log_condition_embeddings("train", condition_kwargs)
|
| 1103 |
+
return loss.mean()
|
| 1104 |
+
|
| 1105 |
+
self.log_validation_losses(loss)
|
| 1106 |
+
self.log_adaln_mean_abs("val", condition_kwargs)
|
| 1107 |
+
self.log_condition_embeddings("val", condition_kwargs)
|
| 1108 |
+
return loss.mean()
|
| 1109 |
+
|
| 1110 |
+
def training_step(self, batch, batch_idx):
|
| 1111 |
+
return self._shared_step(batch, "train")
|
| 1112 |
+
|
| 1113 |
+
def validation_step(self, batch, batch_idx):
|
| 1114 |
+
self._shared_step(batch, "val")
|
| 1115 |
+
|
| 1116 |
+
def _sync_ema_stream(self):
|
| 1117 |
+
"""Block until the async EMA update finishes before reading ema_vit."""
|
| 1118 |
+
stream = getattr(self, "_ema_stream", None)
|
| 1119 |
+
if stream is not None:
|
| 1120 |
+
stream.synchronize()
|
| 1121 |
+
|
| 1122 |
+
def on_train_batch_end(self, outputs, batch, batch_idx):
|
| 1123 |
+
"""Update EMA asynchronously on a separate CUDA stream."""
|
| 1124 |
+
if not hasattr(self, "_ema_stream"):
|
| 1125 |
+
self._ema_stream = torch.cuda.Stream() if torch.cuda.is_available() else None
|
| 1126 |
+
if self._ema_stream is not None:
|
| 1127 |
+
with torch.cuda.stream(self._ema_stream):
|
| 1128 |
+
update_ema(self.ema_vit, self.vit)
|
| 1129 |
+
else:
|
| 1130 |
+
update_ema(self.ema_vit, self.vit)
|
| 1131 |
+
|
| 1132 |
+
# def on_train_batch_end(self, outputs, batch, batch_idx):
|
| 1133 |
+
# update_ema(self.ema_vit, self.vit)
|
| 1134 |
+
|
| 1135 |
+
@torch.no_grad()
|
| 1136 |
+
def sample(
|
| 1137 |
+
self,
|
| 1138 |
+
images=None,
|
| 1139 |
+
latent=False,
|
| 1140 |
+
eta=0.0,
|
| 1141 |
+
NFE=20,
|
| 1142 |
+
sample_with_ema=True,
|
| 1143 |
+
num_samples=8,
|
| 1144 |
+
frame_rate=None,
|
| 1145 |
+
condition_kwargs=None,
|
| 1146 |
+
return_sample=False,
|
| 1147 |
+
):
|
| 1148 |
+
if sample_with_ema:
|
| 1149 |
+
self._sync_ema_stream()
|
| 1150 |
+
|
| 1151 |
+
return self.sampler.sample(
|
| 1152 |
+
images=images,
|
| 1153 |
+
latent=latent,
|
| 1154 |
+
eta=eta,
|
| 1155 |
+
NFE=NFE,
|
| 1156 |
+
sample_with_ema=sample_with_ema,
|
| 1157 |
+
num_samples=num_samples,
|
| 1158 |
+
frame_rate=frame_rate,
|
| 1159 |
+
condition_kwargs=condition_kwargs,
|
| 1160 |
+
return_sample=return_sample,
|
| 1161 |
+
)
|
| 1162 |
+
|
| 1163 |
+
def _validate_rollout_context(self, x_0):
|
| 1164 |
+
if not isinstance(x_0, dict):
|
| 1165 |
+
raise TypeError(
|
| 1166 |
+
f"{self.__class__.__name__}.roll_out expects x_0 to be a dict, "
|
| 1167 |
+
f"got {type(x_0).__name__}."
|
| 1168 |
+
)
|
| 1169 |
+
if "images" not in x_0:
|
| 1170 |
+
raise KeyError(f"{self.__class__.__name__}.roll_out requires x_0['images'].")
|
| 1171 |
+
|
| 1172 |
+
def _move_rollout_context_to_device(self, x_0, device):
|
| 1173 |
+
moved = {}
|
| 1174 |
+
for key, value in x_0.items():
|
| 1175 |
+
if torch.is_tensor(value):
|
| 1176 |
+
moved[key] = value.to(device)
|
| 1177 |
+
else:
|
| 1178 |
+
moved[key] = value
|
| 1179 |
+
return moved
|
| 1180 |
+
|
| 1181 |
+
def _get_rollout_condition_frames(self, images, num_condition_frames=None):
|
| 1182 |
+
if num_condition_frames is None:
|
| 1183 |
+
num_condition_frames = getattr(self.vit, "num_context_frames", None)
|
| 1184 |
+
if num_condition_frames is None:
|
| 1185 |
+
num_condition_frames = images.size(1) - self.num_pred_frames
|
| 1186 |
+
num_condition_frames = int(num_condition_frames)
|
| 1187 |
+
if num_condition_frames <= 0:
|
| 1188 |
+
raise ValueError(
|
| 1189 |
+
f"rollout batch has {images.size(1)} frames, but "
|
| 1190 |
+
f"num_pred_frames={self.num_pred_frames}; expected at least one "
|
| 1191 |
+
"conditioning frame."
|
| 1192 |
+
)
|
| 1193 |
+
if num_condition_frames > images.size(1):
|
| 1194 |
+
raise ValueError(
|
| 1195 |
+
f"num_condition_frames={num_condition_frames} exceeds rollout "
|
| 1196 |
+
f"batch length {images.size(1)}."
|
| 1197 |
+
)
|
| 1198 |
+
return num_condition_frames
|
| 1199 |
+
|
| 1200 |
+
def roll_out(
|
| 1201 |
+
self,
|
| 1202 |
+
x_0,
|
| 1203 |
+
num_gen_frames=25,
|
| 1204 |
+
latent_input=True,
|
| 1205 |
+
eta=0.0,
|
| 1206 |
+
NFE=20,
|
| 1207 |
+
sample_with_ema=True,
|
| 1208 |
+
num_samples=8,
|
| 1209 |
+
frame_rate=None,
|
| 1210 |
+
condition_kwargs=None,
|
| 1211 |
+
decode_device=None,
|
| 1212 |
+
return_condition_history=False,
|
| 1213 |
+
num_condition_frames=None,
|
| 1214 |
+
):
|
| 1215 |
+
device = next(self.parameters()).device
|
| 1216 |
+
self._validate_rollout_context(x_0)
|
| 1217 |
+
x_0 = self._move_rollout_context_to_device(x_0, device)
|
| 1218 |
+
images = x_0["images"]
|
| 1219 |
+
num_condition_frames = self._get_rollout_condition_frames(
|
| 1220 |
+
images,
|
| 1221 |
+
num_condition_frames=num_condition_frames,
|
| 1222 |
+
)
|
| 1223 |
+
cond_x = images[:, :num_condition_frames]
|
| 1224 |
+
rollout_context = dict(x_0)
|
| 1225 |
+
rollout_context["images"] = cond_x
|
| 1226 |
+
|
| 1227 |
+
if torch.is_tensor(frame_rate):
|
| 1228 |
+
frame_rate = frame_rate.to(device)
|
| 1229 |
+
elif frame_rate is None and torch.is_tensor(rollout_context.get("frame_rate")):
|
| 1230 |
+
frame_rate = rollout_context["frame_rate"]
|
| 1231 |
+
|
| 1232 |
+
if condition_kwargs is None:
|
| 1233 |
+
condition_kwargs = self.condition_preprocessor.get_condition_kwargs_from_batch(
|
| 1234 |
+
rollout_context,
|
| 1235 |
+
split="rollout",
|
| 1236 |
+
)
|
| 1237 |
+
|
| 1238 |
+
return self.sampler.roll_out(
|
| 1239 |
+
cond_x,
|
| 1240 |
+
num_gen_frames=num_gen_frames,
|
| 1241 |
+
latent_input=latent_input,
|
| 1242 |
+
eta=eta,
|
| 1243 |
+
NFE=NFE,
|
| 1244 |
+
sample_with_ema=sample_with_ema,
|
| 1245 |
+
num_samples=num_samples,
|
| 1246 |
+
frame_rate=frame_rate,
|
| 1247 |
+
condition_kwargs=condition_kwargs,
|
| 1248 |
+
decode_device=decode_device,
|
| 1249 |
+
return_condition_history=return_condition_history,
|
| 1250 |
+
)
|
| 1251 |
+
|
| 1252 |
+
@torch.no_grad()
|
| 1253 |
+
def log_images(self, batch, **kwargs):
|
| 1254 |
+
log = {}
|
| 1255 |
+
images, frame_rate = self.get_input(batch, "images")
|
| 1256 |
+
N = min(5, images.size(0))
|
| 1257 |
+
images = images[:N]
|
| 1258 |
+
condition_kwargs = self.condition_preprocessor.get_condition_kwargs_from_batch(batch, split="log_images")
|
| 1259 |
+
condition_kwargs = self.condition_preprocessor.slice_condition_kwargs(condition_kwargs, slice(0, N))
|
| 1260 |
+
|
| 1261 |
+
if self.use_precomputed_training_inputs and images.shape[-2:] == self.vit.input_size:
|
| 1262 |
+
images = self.decode_frames(images)
|
| 1263 |
+
|
| 1264 |
+
images = self.condition_preprocessor.annotate_logged_images(images, batch=batch, num_images=N)
|
| 1265 |
+
frame_rate = frame_rate[:N] if frame_rate is not None else None
|
| 1266 |
+
num_frames = images.size(1)
|
| 1267 |
+
|
| 1268 |
+
visual = [images[:, frame_idx] for frame_idx in range(num_frames)]
|
| 1269 |
+
visual_ema = [images[:, frame_idx] for frame_idx in range(num_frames)]
|
| 1270 |
+
|
| 1271 |
+
context = images[:, :-self.num_pred_frames] if num_frames - self.num_pred_frames > 0 else None
|
| 1272 |
+
|
| 1273 |
+
samples = self.sample(
|
| 1274 |
+
context,
|
| 1275 |
+
eta=0.0,
|
| 1276 |
+
NFE=30,
|
| 1277 |
+
sample_with_ema=False,
|
| 1278 |
+
num_samples=N,
|
| 1279 |
+
frame_rate=frame_rate,
|
| 1280 |
+
condition_kwargs=condition_kwargs,
|
| 1281 |
+
return_sample=True,
|
| 1282 |
+
)[1]
|
| 1283 |
+
for frame_idx in range(samples.size(1)):
|
| 1284 |
+
visual.append(samples[:, frame_idx])
|
| 1285 |
+
|
| 1286 |
+
samples_ema = self.sample(
|
| 1287 |
+
context,
|
| 1288 |
+
eta=0.0,
|
| 1289 |
+
NFE=30,
|
| 1290 |
+
sample_with_ema=True,
|
| 1291 |
+
num_samples=N,
|
| 1292 |
+
frame_rate=frame_rate,
|
| 1293 |
+
condition_kwargs=condition_kwargs,
|
| 1294 |
+
return_sample=True,
|
| 1295 |
+
)[1]
|
| 1296 |
+
for frame_idx in range(samples_ema.size(1)):
|
| 1297 |
+
visual_ema.append(samples_ema[:, frame_idx])
|
| 1298 |
+
|
| 1299 |
+
sampled = vutils.make_grid(torch.cat(torch.chunk(torch.cat(visual, dim=0), 4, dim=0), dim=0), nrow=N, padding=2, normalize=False)
|
| 1300 |
+
sampled_ema = vutils.make_grid(torch.cat(torch.chunk(torch.cat(visual_ema, dim=0), 4, dim=0), dim=0), nrow=N, padding=2, normalize=False)
|
| 1301 |
+
|
| 1302 |
+
log["sampled"] = sampled
|
| 1303 |
+
log["ema_sampled"] = sampled_ema
|
| 1304 |
+
self.vit.train()
|
| 1305 |
+
return log
|
| 1306 |
+
|
| 1307 |
+
|
| 1308 |
+
class L1L2PredL2PredictorModule(PredictorModule):
|
| 1309 |
+
"""
|
| 1310 |
+
PredictorModule specialization for L1 rollout conditioned by an L2 endpoint
|
| 1311 |
+
predictor.
|
| 1312 |
+
|
| 1313 |
+
The base PredictorModule keeps a tensor-level rollout API with explicit
|
| 1314 |
+
condition_kwargs. This subclass treats rollout as a batch-level L1/L2
|
| 1315 |
+
operation: extract the L1 context, derive the L2 condition through the
|
| 1316 |
+
configured condition preprocessor, then delegate to the generic sampler.
|
| 1317 |
+
"""
|
| 1318 |
+
|
| 1319 |
+
pass
|
|
@@ -0,0 +1,483 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
"""
|
| 2 |
+
Reusable DiT building blocks needed for inference.
|
| 3 |
+
Extracted from networks/DiT/dit.py so the inference set avoids importing
|
| 4 |
+
the full training-only model classes in that file.
|
| 5 |
+
"""
|
| 6 |
+
import math
|
| 7 |
+
|
| 8 |
+
import numpy as np
|
| 9 |
+
import torch
|
| 10 |
+
import torch.nn as nn
|
| 11 |
+
from einops import rearrange
|
| 12 |
+
from timm.layers.mlp import SwiGLU
|
| 13 |
+
from timm.models.vision_transformer import Attention, Mlp
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
def get_norm_layer(norm_layer):
|
| 17 |
+
if isinstance(norm_layer, str):
|
| 18 |
+
if norm_layer == 'layer_norm':
|
| 19 |
+
return nn.LayerNorm
|
| 20 |
+
elif norm_layer == 'rms_norm':
|
| 21 |
+
return nn.RMSNorm
|
| 22 |
+
else:
|
| 23 |
+
raise ValueError(f"Unsupported norm layer: {norm_layer}")
|
| 24 |
+
return norm_layer
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def modulate(x, shift, scale):
|
| 28 |
+
n = x.ndim - shift.ndim
|
| 29 |
+
for _ in range(n):
|
| 30 |
+
shift = shift.unsqueeze(-2)
|
| 31 |
+
scale = scale.unsqueeze(-2)
|
| 32 |
+
return x * (1 + scale) + shift
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def _broadcast_gate(gate, x):
|
| 36 |
+
n = x.ndim - gate.ndim
|
| 37 |
+
for _ in range(n):
|
| 38 |
+
gate = gate.unsqueeze(-2)
|
| 39 |
+
return gate
|
| 40 |
+
|
| 41 |
+
|
| 42 |
+
class FrequencyEncoder:
|
| 43 |
+
def __init__(self, embed_dim, freq_min=1, freq_max=5):
|
| 44 |
+
"""
|
| 45 |
+
Deterministic frequency encoder with fixed normalization.
|
| 46 |
+
|
| 47 |
+
|
| 48 |
+
Args:
|
| 49 |
+
embed_dim (int): Dimensionality of the token embeddings.
|
| 50 |
+
freq_min (float): Minimum frequency value.
|
| 51 |
+
freq_max (float): Maximum frequency value.
|
| 52 |
+
"""
|
| 53 |
+
self.embed_dim = embed_dim
|
| 54 |
+
self.freq_min = freq_min
|
| 55 |
+
self.freq_max = freq_max
|
| 56 |
+
|
| 57 |
+
|
| 58 |
+
def encode(self, frequencies):
|
| 59 |
+
"""
|
| 60 |
+
Encodes frequencies into embeddings using sine-cosine features.
|
| 61 |
+
|
| 62 |
+
|
| 63 |
+
Args:
|
| 64 |
+
frequencies (torch.Tensor): Tensor of shape (batch_size,) containing frequencies.
|
| 65 |
+
|
| 66 |
+
|
| 67 |
+
Returns:
|
| 68 |
+
torch.Tensor: Encoded frequency embeddings of shape (batch_size, embed_dim).
|
| 69 |
+
"""
|
| 70 |
+
batch_size = frequencies.size(0)
|
| 71 |
+
|
| 72 |
+
|
| 73 |
+
# Fixed normalization: Scale frequencies to [0, 1]
|
| 74 |
+
normalized_freq = (frequencies - self.freq_min) / (self.freq_max - self.freq_min)
|
| 75 |
+
|
| 76 |
+
|
| 77 |
+
# Generate positional features using sine and cosine
|
| 78 |
+
positions = torch.arange(0, self.embed_dim, dtype=torch.float32, device=frequencies.device)
|
| 79 |
+
scaling_factors = 1 / (10000 ** (2 * (positions // 2) / self.embed_dim))
|
| 80 |
+
frequency_features = normalized_freq.unsqueeze(1) * scaling_factors # Shape: (batch_size, embed_dim)
|
| 81 |
+
|
| 82 |
+
|
| 83 |
+
# Apply sine to even indices and cosine to odd indices
|
| 84 |
+
encoded_freq = torch.zeros(batch_size, self.embed_dim, device=frequencies.device)
|
| 85 |
+
encoded_freq[:, 0::2] = torch.sin(frequency_features[:, 0::2]) # Sine for even indices
|
| 86 |
+
encoded_freq[:, 1::2] = torch.cos(frequency_features[:, 1::2]) # Cosine for odd indices
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
return encoded_freq
|
| 90 |
+
|
| 91 |
+
|
| 92 |
+
def get_2d_sincos_pos_embed(embed_dim, grid_size, cls_token=False, extra_tokens=0):
|
| 93 |
+
"""
|
| 94 |
+
grid_size: int of the grid height and width
|
| 95 |
+
return:
|
| 96 |
+
pos_embed: [grid_size*grid_size, embed_dim] or [1+grid_size*grid_size, embed_dim] (w/ or w/o cls_token)
|
| 97 |
+
"""
|
| 98 |
+
grid_h = np.arange(grid_size[0], dtype=np.float32)
|
| 99 |
+
grid_w = np.arange(grid_size[1], dtype=np.float32)
|
| 100 |
+
grid = np.meshgrid(grid_w, grid_h) # here w goes first
|
| 101 |
+
grid = np.stack(grid, axis=0)
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
grid = grid.reshape([2, 1, grid_size[0], grid_size[1]])
|
| 105 |
+
pos_embed = get_2d_sincos_pos_embed_from_grid(embed_dim, grid)
|
| 106 |
+
if cls_token and extra_tokens > 0:
|
| 107 |
+
pos_embed = np.concatenate([np.zeros([extra_tokens, embed_dim]), pos_embed], axis=0)
|
| 108 |
+
return pos_embed
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
def get_2d_sincos_pos_embed_from_grid(embed_dim, grid):
|
| 112 |
+
assert embed_dim % 2 == 0
|
| 113 |
+
|
| 114 |
+
|
| 115 |
+
# use half of dimensions to encode grid_h
|
| 116 |
+
emb_h = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[0]) # (H*W, D/2)
|
| 117 |
+
emb_w = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[1]) # (H*W, D/2)
|
| 118 |
+
|
| 119 |
+
|
| 120 |
+
emb = np.concatenate([emb_h, emb_w], axis=1) # (H*W, D)
|
| 121 |
+
return emb
|
| 122 |
+
|
| 123 |
+
|
| 124 |
+
def get_1d_sincos_pos_embed_from_grid(embed_dim, pos):
|
| 125 |
+
"""
|
| 126 |
+
embed_dim: output dimension for each position
|
| 127 |
+
pos: a list of positions to be encoded: size (M,)
|
| 128 |
+
out: (M, D)
|
| 129 |
+
"""
|
| 130 |
+
assert embed_dim % 2 == 0
|
| 131 |
+
omega = np.arange(embed_dim // 2, dtype=np.float64)
|
| 132 |
+
omega /= embed_dim / 2.
|
| 133 |
+
omega = 1. / 10000**omega # (D/2,)
|
| 134 |
+
|
| 135 |
+
|
| 136 |
+
pos = pos.reshape(-1) # (M,)
|
| 137 |
+
out = np.einsum('m,d->md', pos, omega) # (M, D/2), outer product
|
| 138 |
+
|
| 139 |
+
|
| 140 |
+
emb_sin = np.sin(out) # (M, D/2)
|
| 141 |
+
emb_cos = np.cos(out) # (M, D/2)
|
| 142 |
+
|
| 143 |
+
|
| 144 |
+
emb = np.concatenate([emb_sin, emb_cos], axis=1) # (M, D)
|
| 145 |
+
return emb
|
| 146 |
+
|
| 147 |
+
|
| 148 |
+
class TimestepEmbedder(nn.Module):
|
| 149 |
+
"""
|
| 150 |
+
Embeds scalar timesteps into vector representations.
|
| 151 |
+
"""
|
| 152 |
+
def __init__(self, hidden_size, frequency_embedding_size=256):
|
| 153 |
+
super().__init__()
|
| 154 |
+
self.mlp = nn.Sequential(
|
| 155 |
+
nn.Linear(frequency_embedding_size, hidden_size, bias=True),
|
| 156 |
+
nn.SiLU(),
|
| 157 |
+
nn.Linear(hidden_size, hidden_size, bias=True),
|
| 158 |
+
)
|
| 159 |
+
self.frequency_embedding_size = frequency_embedding_size
|
| 160 |
+
|
| 161 |
+
|
| 162 |
+
@staticmethod
|
| 163 |
+
def timestep_embedding(t, dim, max_period=10000):
|
| 164 |
+
"""
|
| 165 |
+
Create sinusoidal timestep embeddings.
|
| 166 |
+
:param t: a 1-D Tensor of N indices, one per batch element.
|
| 167 |
+
These may be fractional.
|
| 168 |
+
:param dim: the dimension of the output.
|
| 169 |
+
:param max_period: controls the minimum frequency of the embeddings.
|
| 170 |
+
:return: an (N, D) Tensor of positional embeddings.
|
| 171 |
+
"""
|
| 172 |
+
# https://github.com/openai/glide-text2im/blob/main/glide_text2im/nn.py
|
| 173 |
+
half = dim // 2
|
| 174 |
+
freqs = torch.exp(
|
| 175 |
+
-math.log(max_period) * torch.arange(start=0, end=half, dtype=torch.float32) / half
|
| 176 |
+
).to(device=t.device)
|
| 177 |
+
args = t[:, None].float() * freqs[None]
|
| 178 |
+
embedding = torch.cat([torch.cos(args), torch.sin(args)], dim=-1)
|
| 179 |
+
if dim % 2:
|
| 180 |
+
embedding = torch.cat([embedding, torch.zeros_like(embedding[:, :1])], dim=-1)
|
| 181 |
+
return embedding
|
| 182 |
+
|
| 183 |
+
|
| 184 |
+
def forward(self, t):
|
| 185 |
+
t_freq = self.timestep_embedding(t, self.frequency_embedding_size)
|
| 186 |
+
t_emb = self.mlp(t_freq)
|
| 187 |
+
return t_emb
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
class STBlock(nn.Module):
|
| 191 |
+
# Used for temporal compression in context
|
| 192 |
+
def __init__(self, hidden_size, num_heads, mlp_ratio=4.0, dropout_rate=0.0,
|
| 193 |
+
norm_layer=nn.LayerNorm,
|
| 194 |
+
mlp_block='mlp',
|
| 195 |
+
act_layer=lambda: nn.GELU(approximate="tanh"),
|
| 196 |
+
**block_kwargs):
|
| 197 |
+
super().__init__()
|
| 198 |
+
mlp_hidden_dim = int(hidden_size * mlp_ratio)
|
| 199 |
+
self.norm1 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 200 |
+
self.norm2 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 201 |
+
self.norm3 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 202 |
+
self.norm4 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 203 |
+
self.space_attn = Attention(hidden_size, num_heads=num_heads, qkv_bias=True, qk_norm=True,
|
| 204 |
+
attn_drop=dropout_rate, proj_drop=dropout_rate, norm_layer=norm_layer, **block_kwargs)
|
| 205 |
+
self.time_attn = Attention(hidden_size, num_heads=num_heads, qkv_bias=True, qk_norm=True,
|
| 206 |
+
attn_drop=dropout_rate, proj_drop=dropout_rate, norm_layer=norm_layer, **block_kwargs)
|
| 207 |
+
if mlp_block == 'mlp':
|
| 208 |
+
self.space_mlp = Mlp(in_features=hidden_size, hidden_features=mlp_hidden_dim, act_layer=act_layer, norm_layer=norm_layer, drop=0)
|
| 209 |
+
self.time_mlp = Mlp(in_features=hidden_size, hidden_features=mlp_hidden_dim, act_layer=act_layer, norm_layer=norm_layer, drop=0)
|
| 210 |
+
elif mlp_block == 'swiglu':
|
| 211 |
+
self.space_mlp = SwiGLU(in_features=hidden_size, hidden_features=(mlp_hidden_dim*2)//3)
|
| 212 |
+
self.time_mlp = SwiGLU(in_features=hidden_size, hidden_features=(mlp_hidden_dim*2)//3)
|
| 213 |
+
else:
|
| 214 |
+
raise NotImplementedError(f"mlp_block {mlp_block} not implemented")
|
| 215 |
+
|
| 216 |
+
def forward(self, x):
|
| 217 |
+
B, F, N, D = x.shape
|
| 218 |
+
x = rearrange(x, 'b f n d -> (b f) n d')
|
| 219 |
+
x = x + self.space_attn(self.norm1(x))
|
| 220 |
+
x = x + self.space_mlp(self.norm2(x))
|
| 221 |
+
x = rearrange(x, '(b f) n d -> (b n) f d', b=B, f=F, n=N)
|
| 222 |
+
x = x + self.time_attn(self.norm3(x))
|
| 223 |
+
x = x + self.time_mlp(self.norm4(x))
|
| 224 |
+
x = rearrange(x, '(b n) f d -> b f n d', b=B, n=N, f=F)
|
| 225 |
+
return x
|
| 226 |
+
|
| 227 |
+
|
| 228 |
+
class DiTBlock(nn.Module):
|
| 229 |
+
"""
|
| 230 |
+
A DiT block with adaptive layer norm zero (adaLN-Zero) conditioning.
|
| 231 |
+
"""
|
| 232 |
+
def __init__(self, hidden_size, num_heads, mlp_ratio=4.0, dropout_rate=0.0, norm_layer=nn.LayerNorm, mlp_block='mlp', **block_kwargs):
|
| 233 |
+
super().__init__()
|
| 234 |
+
if isinstance(norm_layer, str):
|
| 235 |
+
norm_layer = get_norm_layer(norm_layer)
|
| 236 |
+
self.norm1 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 237 |
+
self.attn = Attention(hidden_size, num_heads=num_heads, qkv_bias=True, qk_norm=True, norm_layer=norm_layer, attn_drop=dropout_rate, proj_drop=dropout_rate, **block_kwargs)
|
| 238 |
+
self.norm2 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 239 |
+
mlp_hidden_dim = int(hidden_size * mlp_ratio)
|
| 240 |
+
if mlp_block == 'mlp':
|
| 241 |
+
approx_gelu = lambda: nn.GELU(approximate="tanh")
|
| 242 |
+
self.mlp = Mlp(in_features=hidden_size, hidden_features=mlp_hidden_dim, act_layer=approx_gelu, norm_layer=norm_layer, drop=0)
|
| 243 |
+
elif mlp_block == 'swiglu':
|
| 244 |
+
self.mlp = SwiGLU(in_features=hidden_size, hidden_features=(mlp_hidden_dim*2)//3, bias=True)
|
| 245 |
+
self.adaLN_modulation = nn.Sequential(
|
| 246 |
+
nn.SiLU(),
|
| 247 |
+
nn.Linear(hidden_size, 6 * hidden_size, bias=True)
|
| 248 |
+
)
|
| 249 |
+
|
| 250 |
+
def initialize_adaln_weights(self, gate_init_std=0.0):
|
| 251 |
+
nn.init.constant_(self.adaLN_modulation[-1].weight, 0)
|
| 252 |
+
nn.init.constant_(self.adaLN_modulation[-1].bias, 0)
|
| 253 |
+
if gate_init_std != 0.0:
|
| 254 |
+
hidden_size = self.adaLN_modulation[-1].out_features // 6
|
| 255 |
+
for gate_idx in (2, 5):
|
| 256 |
+
start = gate_idx * hidden_size
|
| 257 |
+
end = start + hidden_size
|
| 258 |
+
nn.init.normal_(self.adaLN_modulation[-1].weight[start:end], std=gate_init_std)
|
| 259 |
+
|
| 260 |
+
def forward(self, x, c):
|
| 261 |
+
shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(c).chunk(6, dim=1)
|
| 262 |
+
x = x + gate_msa.unsqueeze(1) * self.attn(modulate(self.norm1(x), shift_msa, scale_msa))
|
| 263 |
+
x = x + gate_mlp.unsqueeze(1) * self.mlp(modulate(self.norm2(x), shift_mlp, scale_mlp))
|
| 264 |
+
return x
|
| 265 |
+
|
| 266 |
+
|
| 267 |
+
class CDiTBlock(nn.Module):
|
| 268 |
+
"""
|
| 269 |
+
A DiT block with cross-attention conditioning.
|
| 270 |
+
"""
|
| 271 |
+
def __init__(self, hidden_size, num_heads, mlp_ratio=4.0, norm_layer=nn.LayerNorm, mlp_block='mlp', **block_kwargs):
|
| 272 |
+
super().__init__()
|
| 273 |
+
self.norm1 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 274 |
+
self.attn = Attention(hidden_size, num_heads=num_heads, qkv_bias=True, norm_layer=norm_layer, **block_kwargs)
|
| 275 |
+
self.norm2 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 276 |
+
self.norm_cond = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 277 |
+
self.cttn = nn.MultiheadAttention(hidden_size, num_heads=num_heads, add_bias_kv=True, bias=True, batch_first=True, **block_kwargs)
|
| 278 |
+
self.adaLN_modulation = nn.Sequential(
|
| 279 |
+
nn.SiLU(),
|
| 280 |
+
nn.Linear(hidden_size, 11 * hidden_size, bias=True)
|
| 281 |
+
)
|
| 282 |
+
self.norm3 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 283 |
+
mlp_hidden_dim = int(hidden_size * mlp_ratio)
|
| 284 |
+
if mlp_block == 'mlp':
|
| 285 |
+
approx_gelu = lambda: nn.GELU(approximate="tanh")
|
| 286 |
+
self.mlp = Mlp(in_features=hidden_size, hidden_features=mlp_hidden_dim, act_layer=approx_gelu, norm_layer=norm_layer, drop=0)
|
| 287 |
+
elif mlp_block == 'swiglu':
|
| 288 |
+
self.mlp = SwiGLU(in_features=hidden_size, hidden_features=(mlp_hidden_dim*2)//3, bias=True)
|
| 289 |
+
|
| 290 |
+
def initialize_adaln_weights(self, gate_init_std=0.0):
|
| 291 |
+
nn.init.constant_(self.adaLN_modulation[-1].weight, 0)
|
| 292 |
+
nn.init.constant_(self.adaLN_modulation[-1].bias, 0)
|
| 293 |
+
if gate_init_std != 0.0:
|
| 294 |
+
hidden_size = self.adaLN_modulation[-1].out_features // 11
|
| 295 |
+
for gate_idx in (2, 7, 10):
|
| 296 |
+
start = gate_idx * hidden_size
|
| 297 |
+
end = start + hidden_size
|
| 298 |
+
nn.init.normal_(self.adaLN_modulation[-1].weight[start:end], std=gate_init_std)
|
| 299 |
+
|
| 300 |
+
def forward(self, x, c, x_cond):
|
| 301 |
+
shift_msa, scale_msa, gate_msa, shift_ca_xcond, scale_ca_xcond, shift_ca_x, scale_ca_x, gate_ca_x, shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(c).chunk(11, dim=1)
|
| 302 |
+
x = x + gate_msa.unsqueeze(1) * self.attn(modulate(self.norm1(x), shift_msa, scale_msa))
|
| 303 |
+
x_cond_norm = modulate(self.norm_cond(x_cond), shift_ca_xcond, scale_ca_xcond)
|
| 304 |
+
x = x + gate_ca_x.unsqueeze(1) * self.cttn(query=modulate(self.norm2(x), shift_ca_x, scale_ca_x), key=x_cond_norm, value=x_cond_norm, need_weights=False)[0]
|
| 305 |
+
x = x + gate_mlp.unsqueeze(1) * self.mlp(modulate(self.norm3(x), shift_mlp, scale_mlp))
|
| 306 |
+
return x
|
| 307 |
+
|
| 308 |
+
|
| 309 |
+
class STDiTBlock(nn.Module):
|
| 310 |
+
"""
|
| 311 |
+
A DiT block with adaptive layer norm zero (adaLN-Zero) conditioning.
|
| 312 |
+
"""
|
| 313 |
+
def __init__(self, hidden_size, num_heads, mlp_ratio=4.0, dropout_rate=0.0, causal_time_attn=False, modulate_time_attn=False, norm_layer=nn.LayerNorm, mlp_block='mlp', **block_kwargs):
|
| 314 |
+
super().__init__()
|
| 315 |
+
self.norm1 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 316 |
+
self.space_attn = Attention(hidden_size, num_heads=num_heads, qkv_bias=True, qk_norm=True, norm_layer=norm_layer, attn_drop=dropout_rate, proj_drop=dropout_rate, **block_kwargs)
|
| 317 |
+
self.time_attn = Attention(hidden_size, num_heads=num_heads, qkv_bias=True, qk_norm=True, norm_layer=norm_layer, attn_drop=dropout_rate, proj_drop=dropout_rate, **block_kwargs)
|
| 318 |
+
self.norm2 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 319 |
+
self.norm3 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 320 |
+
self.norm4 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 321 |
+
mlp_hidden_dim = int(hidden_size * mlp_ratio)
|
| 322 |
+
|
| 323 |
+
if mlp_block == 'mlp':
|
| 324 |
+
approx_gelu = lambda: nn.GELU(approximate="tanh")
|
| 325 |
+
self.space_mlp = Mlp(in_features=hidden_size, hidden_features=mlp_hidden_dim, act_layer=approx_gelu, norm_layer=None, drop=0)
|
| 326 |
+
self.time_mlp = Mlp(in_features=hidden_size, hidden_features=mlp_hidden_dim, act_layer=approx_gelu, norm_layer=None, drop=0)
|
| 327 |
+
elif mlp_block == 'swiglu':
|
| 328 |
+
self.space_mlp = SwiGLU(in_features=hidden_size, hidden_features=(mlp_hidden_dim*2)//3)
|
| 329 |
+
self.time_mlp = SwiGLU(in_features=hidden_size, hidden_features=(mlp_hidden_dim*2)//3)
|
| 330 |
+
else:
|
| 331 |
+
raise NotImplementedError(f"mlp_block {mlp_block} not implemented")
|
| 332 |
+
|
| 333 |
+
self.adaLN_modulation = nn.Sequential(
|
| 334 |
+
nn.SiLU(),
|
| 335 |
+
nn.Linear(hidden_size, 9 * hidden_size, bias=True)
|
| 336 |
+
)
|
| 337 |
+
self.causal_time_attn = causal_time_attn
|
| 338 |
+
self.modulate_time_attn = modulate_time_attn
|
| 339 |
+
self.layer_idx = block_kwargs.get("layer_idx")
|
| 340 |
+
self.log_adaln_mean_abs = False
|
| 341 |
+
self._last_adaln_mean_abs = None
|
| 342 |
+
|
| 343 |
+
if modulate_time_attn:
|
| 344 |
+
self.adaLN_time_attn_modulation = nn.Sequential(
|
| 345 |
+
nn.SiLU(),
|
| 346 |
+
nn.Linear(hidden_size, 3 * hidden_size, bias=True)
|
| 347 |
+
)
|
| 348 |
+
self.norm_time_attn = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 349 |
+
# initialize
|
| 350 |
+
nn.init.constant_(self.adaLN_time_attn_modulation[-1].weight, 0)
|
| 351 |
+
nn.init.constant_(self.adaLN_time_attn_modulation[-1].bias, 0)
|
| 352 |
+
else:
|
| 353 |
+
self.norm_time_attn = nn.Identity()
|
| 354 |
+
|
| 355 |
+
def initialize_adaln_weights(self, gate_init_std=0.0):
|
| 356 |
+
nn.init.constant_(self.adaLN_modulation[-1].weight, 0)
|
| 357 |
+
nn.init.constant_(self.adaLN_modulation[-1].bias, 0)
|
| 358 |
+
if gate_init_std != 0.0:
|
| 359 |
+
hidden_size = self.adaLN_modulation[-1].out_features // 9
|
| 360 |
+
for gate_idx in (2, 5, 8):
|
| 361 |
+
start = gate_idx * hidden_size
|
| 362 |
+
end = start + hidden_size
|
| 363 |
+
nn.init.normal_(self.adaLN_modulation[-1].weight[start:end], std=gate_init_std)
|
| 364 |
+
|
| 365 |
+
if hasattr(self, "adaLN_time_attn_modulation"):
|
| 366 |
+
nn.init.constant_(self.adaLN_time_attn_modulation[-1].weight, 0)
|
| 367 |
+
nn.init.constant_(self.adaLN_time_attn_modulation[-1].bias, 0)
|
| 368 |
+
if gate_init_std != 0.0:
|
| 369 |
+
hidden_size = self.adaLN_time_attn_modulation[-1].out_features // 3
|
| 370 |
+
start = 2 * hidden_size
|
| 371 |
+
end = start + hidden_size
|
| 372 |
+
nn.init.normal_(self.adaLN_time_attn_modulation[-1].weight[start:end], std=gate_init_std)
|
| 373 |
+
|
| 374 |
+
def _collect_adaln_mean_abs(self, **tensors):
|
| 375 |
+
self._last_adaln_mean_abs = {
|
| 376 |
+
name: tensor.detach().abs().flatten(1).mean(dim=1)
|
| 377 |
+
for name, tensor in tensors.items()
|
| 378 |
+
}
|
| 379 |
+
|
| 380 |
+
def forward(self, x, c):
|
| 381 |
+
B, F, N, D = x.shape
|
| 382 |
+
|
| 383 |
+
# chunk into 9 [B, C] vectors
|
| 384 |
+
(shift_msa, scale_msa, gate_msa,
|
| 385 |
+
shift_mlp_s, scale_mlp_s, gate_mlp_s,
|
| 386 |
+
shift_mlp_t, scale_mlp_t, gate_mlp_t) = self.adaLN_modulation(c).chunk(9, dim=-1)
|
| 387 |
+
|
| 388 |
+
x_modulated = modulate(self.norm1(x), shift_msa, scale_msa)
|
| 389 |
+
x_modulated = rearrange(x_modulated, 'b f n d -> (b f) n d', b=B, f=F)
|
| 390 |
+
x_ = self.space_attn(x_modulated)
|
| 391 |
+
x_ = rearrange(x_, '(b f) n d -> b f n d', b=B, f=F)
|
| 392 |
+
x = x + _broadcast_gate(gate_msa, x) * x_
|
| 393 |
+
|
| 394 |
+
x_modulated = modulate(self.norm2(x), shift_mlp_s, scale_mlp_s)
|
| 395 |
+
x = x + _broadcast_gate(gate_mlp_s, x) * self.space_mlp(x_modulated)
|
| 396 |
+
|
| 397 |
+
# — temporal attention path —
|
| 398 |
+
if self.modulate_time_attn:
|
| 399 |
+
shift_mta, scale_mta, gate_mta = self.adaLN_time_attn_modulation(c).chunk(3, dim=-1)
|
| 400 |
+
else:
|
| 401 |
+
shift_mta, scale_mta, gate_mta = torch.zeros_like(shift_mlp_t), torch.zeros_like(scale_mlp_t), torch.ones_like(gate_mlp_t)
|
| 402 |
+
|
| 403 |
+
if self.log_adaln_mean_abs:
|
| 404 |
+
self._collect_adaln_mean_abs(
|
| 405 |
+
msa_shift=shift_msa,
|
| 406 |
+
msa_scale=scale_msa,
|
| 407 |
+
msa_gate=gate_msa,
|
| 408 |
+
mlp_s_shift=shift_mlp_s,
|
| 409 |
+
mlp_s_scale=scale_mlp_s,
|
| 410 |
+
mlp_s_gate=gate_mlp_s,
|
| 411 |
+
mta_shift=shift_mta,
|
| 412 |
+
mta_scale=scale_mta,
|
| 413 |
+
mta_gate=gate_mta,
|
| 414 |
+
mlp_t_shift=shift_mlp_t,
|
| 415 |
+
mlp_t_scale=scale_mlp_t,
|
| 416 |
+
mlp_t_gate=gate_mlp_t,
|
| 417 |
+
)
|
| 418 |
+
|
| 419 |
+
x_modulated = modulate(self.norm_time_attn(x), shift_mta, scale_mta)
|
| 420 |
+
x_modulated = rearrange(x_modulated, 'b f n d -> (b n) f d', b=B, f=F, n=N)
|
| 421 |
+
time_attn_mask = torch.tril(torch.ones(F, F, device=x.device)) if self.causal_time_attn else None
|
| 422 |
+
x_ = self.time_attn(x_modulated, attn_mask=time_attn_mask)
|
| 423 |
+
x_ = rearrange(x_, '(b n) f d -> b f n d', b=B, n=N, f=F)
|
| 424 |
+
x = x + _broadcast_gate(gate_mta, x) * x_
|
| 425 |
+
|
| 426 |
+
x_modulated = modulate(self.norm3(x), shift_mlp_t, scale_mlp_t)
|
| 427 |
+
x = x + _broadcast_gate(gate_mlp_t, x) * self.time_mlp(x_modulated)
|
| 428 |
+
|
| 429 |
+
return x
|
| 430 |
+
|
| 431 |
+
|
| 432 |
+
class FinalLayer(nn.Module):
|
| 433 |
+
"""
|
| 434 |
+
The final layer of DiT.
|
| 435 |
+
"""
|
| 436 |
+
def __init__(self, hidden_size, patch_size, out_channels, norm_layer=nn.LayerNorm, act_layer=nn.SiLU):
|
| 437 |
+
super().__init__()
|
| 438 |
+
self.norm_final = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 439 |
+
self.linear = nn.Linear(hidden_size, patch_size * patch_size * out_channels, bias=True)
|
| 440 |
+
self.adaLN_modulation = nn.Sequential(
|
| 441 |
+
act_layer(),
|
| 442 |
+
nn.Linear(hidden_size, 2 * hidden_size, bias=True)
|
| 443 |
+
)
|
| 444 |
+
|
| 445 |
+
|
| 446 |
+
def forward(self, x, c):
|
| 447 |
+
shift, scale = self.adaLN_modulation(c).chunk(2, dim=-1)
|
| 448 |
+
x = modulate(self.norm_final(x), shift, scale)
|
| 449 |
+
x = self.linear(x)
|
| 450 |
+
return x
|
| 451 |
+
|
| 452 |
+
|
| 453 |
+
class STDiTBlockWithSpatialL2(STDiTBlock):
|
| 454 |
+
"""
|
| 455 |
+
STDiTBlock augmented with per-token gated spatial conditioning from L2 tokens.
|
| 456 |
+
|
| 457 |
+
z_spatial (B, F, N, D) → (shift_s, scale_s, gate_s) per token via a small MLP.
|
| 458 |
+
Applied as an additive gated adaLN before the standard block operations:
|
| 459 |
+
x = x + gate_s * modulate(spatial_norm(x), shift_s, scale_s)
|
| 460 |
+
|
| 461 |
+
Zero-init of spatial_to_mod guarantees a no-op at the start of training,
|
| 462 |
+
so pre-trained STDiTDF weights can be fine-tuned without disruption.
|
| 463 |
+
"""
|
| 464 |
+
|
| 465 |
+
def __init__(self, hidden_size, num_heads, norm_layer=nn.LayerNorm, **kwargs):
|
| 466 |
+
super().__init__(hidden_size, num_heads, norm_layer=norm_layer, **kwargs)
|
| 467 |
+
self.spatial_norm = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 468 |
+
self.spatial_to_mod = nn.Sequential(
|
| 469 |
+
nn.SiLU(),
|
| 470 |
+
nn.Linear(hidden_size, 3 * hidden_size, bias=True),
|
| 471 |
+
)
|
| 472 |
+
# Small weight init so shift/scale start as genuine L2-dependent perturbations.
|
| 473 |
+
# Gate bias initialised to 2.0 → sigmoid(2.0) ≈ 0.88, so the gate starts open
|
| 474 |
+
# and the model must learn to close it rather than having to learn to open it.
|
| 475 |
+
nn.init.normal_(self.spatial_to_mod[-1].weight, std=0.02)
|
| 476 |
+
nn.init.zeros_(self.spatial_to_mod[-1].bias)
|
| 477 |
+
self.spatial_to_mod[-1].bias.data[2 * hidden_size:].fill_(2.0)
|
| 478 |
+
|
| 479 |
+
def forward(self, x, c, z_spatial):
|
| 480 |
+
# z_spatial: (B, F, N, D) — pre-computed L2 spatial tokens (patchified + position)
|
| 481 |
+
shift_s, scale_s, gate_s = self.spatial_to_mod(z_spatial).chunk(3, dim=-1)
|
| 482 |
+
x = x + torch.sigmoid(gate_s) * modulate(self.spatial_norm(x), shift_s, scale_s)
|
| 483 |
+
return super().forward(x, c)
|
|
@@ -0,0 +1,174 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import numpy as np
|
| 2 |
+
import torch
|
| 3 |
+
import torch.nn as nn
|
| 4 |
+
import torch.nn.functional as F
|
| 5 |
+
from einops import rearrange, reduce
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
def log(t, eps=1e-5):
|
| 9 |
+
return t.clamp(min=eps).log()
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def entropy(prob):
|
| 13 |
+
return (-prob * log(prob)).sum(dim=-1)
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
class VectorQuantizer(nn.Module):
|
| 17 |
+
def __init__(
|
| 18 |
+
self,
|
| 19 |
+
n_e,
|
| 20 |
+
e_dim,
|
| 21 |
+
beta,
|
| 22 |
+
normalize_embedding,
|
| 23 |
+
remap=None,
|
| 24 |
+
unknown_index="random",
|
| 25 |
+
sane_index_shape=False,
|
| 26 |
+
legacy=True,
|
| 27 |
+
diversity_gamma=1.0,
|
| 28 |
+
frac_per_sample_entropy=1.0,
|
| 29 |
+
token_noise=0.0,
|
| 30 |
+
):
|
| 31 |
+
super().__init__()
|
| 32 |
+
self.n_e = n_e
|
| 33 |
+
self.e_dim = e_dim
|
| 34 |
+
self.beta = beta
|
| 35 |
+
self.legacy = legacy
|
| 36 |
+
self.normalize_embedding = normalize_embedding
|
| 37 |
+
self.diversity_gamma = diversity_gamma
|
| 38 |
+
self.frac_per_sample_entropy = frac_per_sample_entropy
|
| 39 |
+
self.token_noise = token_noise
|
| 40 |
+
self.sane_index_shape = sane_index_shape
|
| 41 |
+
|
| 42 |
+
# Codebook
|
| 43 |
+
self.embedding = nn.Embedding(n_e, e_dim)
|
| 44 |
+
self.embedding.weight.data.uniform_(-1.0 / n_e, 1.0 / n_e)
|
| 45 |
+
|
| 46 |
+
if self.normalize_embedding:
|
| 47 |
+
self.embedding.weight.data = F.normalize(self.embedding.weight.data, dim=1)
|
| 48 |
+
|
| 49 |
+
# Optional remapping
|
| 50 |
+
self.remap = remap
|
| 51 |
+
if remap is not None:
|
| 52 |
+
self.register_buffer("used", torch.tensor(np.load(remap)))
|
| 53 |
+
self.re_embed = self.used.shape[0]
|
| 54 |
+
self.unknown_index = unknown_index
|
| 55 |
+
if unknown_index == "extra":
|
| 56 |
+
self.unknown_index = self.re_embed
|
| 57 |
+
self.re_embed += 1
|
| 58 |
+
print(
|
| 59 |
+
f"Remapping {n_e} indices to {self.re_embed} indices. "
|
| 60 |
+
f"Using {self.unknown_index} for unknown indices."
|
| 61 |
+
)
|
| 62 |
+
else:
|
| 63 |
+
self.re_embed = n_e
|
| 64 |
+
|
| 65 |
+
def remap_to_used(self, indices):
|
| 66 |
+
ishape = indices.shape
|
| 67 |
+
indices = indices.view(ishape[0], -1)
|
| 68 |
+
used = self.used.to(indices)
|
| 69 |
+
match = (indices[:, :, None] == used[None, None, :]).long()
|
| 70 |
+
new = match.argmax(-1)
|
| 71 |
+
unknown = match.sum(2) < 1
|
| 72 |
+
if self.unknown_index == "random":
|
| 73 |
+
new[unknown] = torch.randint(0, self.re_embed, size=new[unknown].shape).to(indices.device)
|
| 74 |
+
else:
|
| 75 |
+
new[unknown] = self.unknown_index
|
| 76 |
+
return new.view(ishape)
|
| 77 |
+
|
| 78 |
+
def unmap_to_all(self, indices):
|
| 79 |
+
ishape = indices.shape
|
| 80 |
+
indices = indices.view(ishape[0], -1)
|
| 81 |
+
used = self.used.to(indices)
|
| 82 |
+
if self.re_embed > used.shape[0]:
|
| 83 |
+
indices[indices >= used.shape[0]] = 0
|
| 84 |
+
gathered = torch.gather(used.expand(indices.shape[0], -1), 1, indices)
|
| 85 |
+
return gathered.view(ishape)
|
| 86 |
+
|
| 87 |
+
def entropy_loss(self, distances, inv_temperature=100.0):
|
| 88 |
+
prob = (-distances * inv_temperature).softmax(dim=-1)
|
| 89 |
+
|
| 90 |
+
if self.frac_per_sample_entropy < 1.0:
|
| 91 |
+
num_tokens = prob.shape[0]
|
| 92 |
+
sample_size = int(num_tokens * self.frac_per_sample_entropy)
|
| 93 |
+
mask = torch.randperm(num_tokens, device=prob.device)[:sample_size]
|
| 94 |
+
per_sample_probs = prob[mask]
|
| 95 |
+
else:
|
| 96 |
+
per_sample_probs = prob
|
| 97 |
+
|
| 98 |
+
per_sample_entropy = entropy(per_sample_probs).mean()
|
| 99 |
+
avg_prob = reduce(per_sample_probs, "... d -> d", "mean")
|
| 100 |
+
codebook_entropy = entropy(avg_prob).mean()
|
| 101 |
+
|
| 102 |
+
return per_sample_entropy - self.diversity_gamma * codebook_entropy
|
| 103 |
+
|
| 104 |
+
def forward(self, z, temp=None, rescale_logits=False, return_logits=False):
|
| 105 |
+
assert temp in (None, 1.0)
|
| 106 |
+
assert not rescale_logits and not return_logits
|
| 107 |
+
|
| 108 |
+
if self.normalize_embedding:
|
| 109 |
+
self.embedding.weight.data = F.normalize(self.embedding.weight.data, dim=1)
|
| 110 |
+
|
| 111 |
+
# Flatten input
|
| 112 |
+
z = rearrange(z, "b c h w -> b h w c").contiguous()
|
| 113 |
+
z_flat = z.view(-1, self.e_dim)
|
| 114 |
+
|
| 115 |
+
# Compute distances
|
| 116 |
+
e = self.embedding.weight
|
| 117 |
+
d = (
|
| 118 |
+
torch.sum(z_flat ** 2, dim=1, keepdim=True)
|
| 119 |
+
+ torch.sum(e ** 2, dim=1)
|
| 120 |
+
- 2 * torch.einsum("bd,dn->bn", z_flat, e.T)
|
| 121 |
+
)
|
| 122 |
+
|
| 123 |
+
min_indices = torch.argmin(d, dim=1)
|
| 124 |
+
|
| 125 |
+
# Optional token noise
|
| 126 |
+
if self.token_noise > 0.0 and self.training:
|
| 127 |
+
noise_mask = torch.rand_like(min_indices.float()) < self.token_noise
|
| 128 |
+
rand_indices = torch.randint(0, self.n_e, min_indices.shape, device=z.device)
|
| 129 |
+
min_indices[noise_mask] = rand_indices[noise_mask]
|
| 130 |
+
|
| 131 |
+
z_q = self.embedding(min_indices).view_as(z)
|
| 132 |
+
|
| 133 |
+
# Compute VQ loss
|
| 134 |
+
if self.legacy:
|
| 135 |
+
loss = F.mse_loss(z_q.detach(), z) + self.beta * F.mse_loss(z_q, z.detach())
|
| 136 |
+
else:
|
| 137 |
+
loss = self.beta * F.mse_loss(z_q.detach(), z) + F.mse_loss(z_q, z.detach())
|
| 138 |
+
|
| 139 |
+
# Optional entropy loss
|
| 140 |
+
entropy_aux = self.entropy_loss(d) if self.training else None
|
| 141 |
+
|
| 142 |
+
# Straight-through estimator
|
| 143 |
+
z_q = z + (z_q - z).detach()
|
| 144 |
+
|
| 145 |
+
# Reshape to original
|
| 146 |
+
z_q = rearrange(z_q, "b h w c -> b c h w")
|
| 147 |
+
z = rearrange(z, "b h w c -> b c h w")
|
| 148 |
+
|
| 149 |
+
# Remap if needed
|
| 150 |
+
if self.remap is not None:
|
| 151 |
+
min_indices = min_indices.view(z.shape[0], -1)
|
| 152 |
+
min_indices = self.remap_to_used(min_indices).view(-1, 1)
|
| 153 |
+
|
| 154 |
+
if self.sane_index_shape:
|
| 155 |
+
min_indices = min_indices.view(z_q.shape[0], z_q.shape[2], z_q.shape[3])
|
| 156 |
+
|
| 157 |
+
return {
|
| 158 |
+
"quantized": z_q,
|
| 159 |
+
"quantization_loss": loss,
|
| 160 |
+
"entropy_loss": entropy_aux,
|
| 161 |
+
"indices": min_indices,
|
| 162 |
+
}
|
| 163 |
+
|
| 164 |
+
def get_codebook_entry(self, indices, shape):
|
| 165 |
+
if self.remap is not None:
|
| 166 |
+
indices = indices.view(shape[0], -1)
|
| 167 |
+
indices = self.unmap_to_all(indices).view(-1)
|
| 168 |
+
|
| 169 |
+
z_q = self.embedding(indices)
|
| 170 |
+
|
| 171 |
+
if shape is not None:
|
| 172 |
+
z_q = z_q.view(shape).permute(0, 3, 1, 2).contiguous()
|
| 173 |
+
|
| 174 |
+
return z_q
|
|
@@ -0,0 +1,52 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import torch.nn as nn
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
def _make_scalar_embedder(hidden_size):
|
| 6 |
+
embedder = nn.Sequential(
|
| 7 |
+
nn.Linear(1, hidden_size, bias=True),
|
| 8 |
+
nn.SiLU(),
|
| 9 |
+
nn.Linear(hidden_size, hidden_size, bias=True),
|
| 10 |
+
)
|
| 11 |
+
nn.init.normal_(embedder[0].weight, std=0.02)
|
| 12 |
+
nn.init.normal_(embedder[0].bias, std=0.02)
|
| 13 |
+
nn.init.normal_(embedder[2].weight, std=0.02)
|
| 14 |
+
nn.init.normal_(embedder[2].bias, std=0.02)
|
| 15 |
+
return embedder
|
| 16 |
+
|
| 17 |
+
|
| 18 |
+
class LinearSteeringEmbedder(torch.nn.Module):
|
| 19 |
+
"""
|
| 20 |
+
Generic linear embedding layer for steering signals. When steering is missing, outputs a fixed zero embedding.
|
| 21 |
+
"""
|
| 22 |
+
def __init__(self, num_input_features, hidden_size, learnable_no_value_embedding=False):
|
| 23 |
+
super().__init__()
|
| 24 |
+
self.embedders = nn.ModuleList()
|
| 25 |
+
self.register_buffer("no_value_embeddings", torch.zeros(num_input_features, hidden_size))
|
| 26 |
+
if learnable_no_value_embedding:
|
| 27 |
+
self.no_value_embeddings = nn.Parameter(torch.zeros(num_input_features, hidden_size))
|
| 28 |
+
for _i in range(num_input_features):
|
| 29 |
+
self.embedders.append(_make_scalar_embedder(hidden_size))
|
| 30 |
+
|
| 31 |
+
def forward(self, steering):
|
| 32 |
+
squeeze_k = False
|
| 33 |
+
if steering.ndim == 2:
|
| 34 |
+
steering = steering.unsqueeze(1)
|
| 35 |
+
squeeze_k = True
|
| 36 |
+
elif steering.ndim != 3:
|
| 37 |
+
raise ValueError(f"Expected steering to have shape [B, D] or [B, K, D], got {tuple(steering.shape)}")
|
| 38 |
+
|
| 39 |
+
_b, _k, num_features = steering.shape
|
| 40 |
+
assert num_features == len(self.embedders), f"Expected {len(self.embedders)} features, but got {num_features}"
|
| 41 |
+
embeddings = []
|
| 42 |
+
for i in range(num_features):
|
| 43 |
+
feature = steering[:, :, [i]]
|
| 44 |
+
missing = torch.isnan(feature).squeeze(-1).unsqueeze(-1)
|
| 45 |
+
embedding = self.embedders[i](torch.nan_to_num(feature, nan=0.0))
|
| 46 |
+
if missing.any():
|
| 47 |
+
embedding = torch.where(missing, self.no_value_embeddings[i].to(embedding.dtype).view(1, 1, -1), embedding)
|
| 48 |
+
embeddings.append(embedding)
|
| 49 |
+
steering_embedding = torch.stack(embeddings, dim=1).sum(dim=1)
|
| 50 |
+
if squeeze_k:
|
| 51 |
+
return steering_embedding[:, 0]
|
| 52 |
+
return steering_embedding
|
|
@@ -0,0 +1,1026 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# Copyright (c) Meta Platforms, Inc. and affiliates.
|
| 2 |
+
# All rights reserved.
|
| 3 |
+
|
| 4 |
+
|
| 5 |
+
# This source code is licensed under the license found in the
|
| 6 |
+
# LICENSE file in the root directory of this source tree.
|
| 7 |
+
# --------------------------------------------------------
|
| 8 |
+
# References:
|
| 9 |
+
# GLIDE: https://github.com/openai/glide-text2im
|
| 10 |
+
# MAE: https://github.com/facebookresearch/mae/blob/main/models_mae.py
|
| 11 |
+
# --------------------------------------------------------
|
| 12 |
+
import math
|
| 13 |
+
import numpy as np
|
| 14 |
+
|
| 15 |
+
|
| 16 |
+
from timm.layers.mlp import SwiGLU
|
| 17 |
+
import torch
|
| 18 |
+
import torch.nn as nn
|
| 19 |
+
import torch.nn.functional as F
|
| 20 |
+
import torch.utils.checkpoint as checkpoint
|
| 21 |
+
from torch import nn
|
| 22 |
+
|
| 23 |
+
|
| 24 |
+
from einops import rearrange
|
| 25 |
+
from timm.models.vision_transformer import PatchEmbed, Attention, Mlp
|
| 26 |
+
from omegaconf import ListConfig
|
| 27 |
+
|
| 28 |
+
from ..swin.swin_free_aspect_ratio import SwinTransformerBlock, SwinAttention
|
| 29 |
+
|
| 30 |
+
|
| 31 |
+
|
| 32 |
+
from modules.dit import (
|
| 33 |
+
get_norm_layer, modulate, FrequencyEncoder,
|
| 34 |
+
get_2d_sincos_pos_embed, get_2d_sincos_pos_embed_from_grid,
|
| 35 |
+
get_1d_sincos_pos_embed_from_grid, TimestepEmbedder,
|
| 36 |
+
STBlock, DiTBlock, CDiTBlock,
|
| 37 |
+
STDiTBlock, FinalLayer, STDiTBlockWithSpatialL2,
|
| 38 |
+
)
|
| 39 |
+
|
| 40 |
+
|
| 41 |
+
class Lambda(nn.Module):
|
| 42 |
+
def __init__(self, func):
|
| 43 |
+
super().__init__()
|
| 44 |
+
self.func = func
|
| 45 |
+
|
| 46 |
+
|
| 47 |
+
def forward(self, x):
|
| 48 |
+
return self.func(x)
|
| 49 |
+
|
| 50 |
+
#################################################################################
|
| 51 |
+
# Cross Attention #
|
| 52 |
+
#################################################################################
|
| 53 |
+
|
| 54 |
+
|
| 55 |
+
class CrossAttention(nn.Module):
|
| 56 |
+
"""
|
| 57 |
+
Cross-attention mechanism that computes attention between target (x) and context (y).
|
| 58 |
+
Args:
|
| 59 |
+
dim (int): Input dimension.
|
| 60 |
+
num_heads (int): Number of attention heads.
|
| 61 |
+
qkv_bias (bool): Whether to include bias terms.
|
| 62 |
+
attn_drop (float): Dropout rate for attention weights.
|
| 63 |
+
proj_drop (float): Dropout rate after projection.
|
| 64 |
+
"""
|
| 65 |
+
def __init__(self, dim, num_heads=8, qkv_bias=True, qk_norm=True, attn_drop=0.0, proj_drop=0.0, norm_layer=nn.LayerNorm):
|
| 66 |
+
super(CrossAttention, self).__init__()
|
| 67 |
+
self.num_heads = num_heads
|
| 68 |
+
self.head_dim = dim // num_heads
|
| 69 |
+
self.scale = self.head_dim ** -0.5
|
| 70 |
+
|
| 71 |
+
|
| 72 |
+
self.q_proj = nn.Linear(dim, dim, bias=qkv_bias)
|
| 73 |
+
self.k_proj = nn.Linear(dim, dim, bias=qkv_bias)
|
| 74 |
+
self.v_proj = nn.Linear(dim, dim, bias=qkv_bias)
|
| 75 |
+
self.proj = nn.Linear(dim, dim)
|
| 76 |
+
self.attn_drop = nn.Dropout(attn_drop)
|
| 77 |
+
self.proj_drop = nn.Dropout(proj_drop)
|
| 78 |
+
self.qk_norm = qk_norm
|
| 79 |
+
if qk_norm:
|
| 80 |
+
self.q_norm = norm_layer(self.head_dim, eps=1e-6, elementwise_affine=False, bias=False)
|
| 81 |
+
self.k_norm = norm_layer(self.head_dim, eps=1e-6, elementwise_affine=False, bias=False)
|
| 82 |
+
|
| 83 |
+
|
| 84 |
+
def forward(self, x, y):
|
| 85 |
+
B, N, C = x.shape
|
| 86 |
+
B, M, _ = y.shape
|
| 87 |
+
|
| 88 |
+
|
| 89 |
+
q = self.q_proj(x).reshape(B, N, self.num_heads, self.head_dim).transpose(1, 2)
|
| 90 |
+
k = self.k_proj(y).reshape(B, M, self.num_heads, self.head_dim).transpose(1, 2)
|
| 91 |
+
v = self.v_proj(y).reshape(B, M, self.num_heads, self.head_dim).transpose(1, 2)
|
| 92 |
+
if self.qk_norm:
|
| 93 |
+
q, k = self.q_norm(q), self.k_norm(k)
|
| 94 |
+
attn_output = F.scaled_dot_product_attention(q, k, v, dropout_p=self.attn_drop.p if self.training else 0.0)
|
| 95 |
+
attn_output = attn_output.transpose(1, 2).reshape(B, N, C)
|
| 96 |
+
return self.proj_drop(self.proj(attn_output))
|
| 97 |
+
|
| 98 |
+
|
| 99 |
+
|
| 100 |
+
|
| 101 |
+
|
| 102 |
+
|
| 103 |
+
|
| 104 |
+
|
| 105 |
+
#################################################################################
|
| 106 |
+
# Core DiT Model #
|
| 107 |
+
#################################################################################
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
|
| 111 |
+
class STDiTBlock_tmpadaLN(nn.Module):
|
| 112 |
+
"""
|
| 113 |
+
A DiT block with adaptive layer norm zero (adaLN-Zero) conditioning.
|
| 114 |
+
"""
|
| 115 |
+
def __init__(self, hidden_size, num_heads, mlp_ratio=4.0, dropout_rate=0.0, causal_time_attn=False, **block_kwargs):
|
| 116 |
+
raise NotImplementedError("This is a deprecated version of STDiTBlock with temporary adaLN for time attention. Use STDiTBlock instead.")
|
| 117 |
+
super().__init__()
|
| 118 |
+
self.norm1 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 119 |
+
self.space_attn = Attention(hidden_size, num_heads=num_heads, qkv_bias=True, qk_norm=True, norm_layer=nn.LayerNorm, attn_drop=dropout_rate, proj_drop=dropout_rate, **block_kwargs)
|
| 120 |
+
self.time_attn = Attention(hidden_size, num_heads=num_heads, qkv_bias=True, qk_norm=True, norm_layer=nn.LayerNorm, attn_drop=dropout_rate, proj_drop=dropout_rate, **block_kwargs)
|
| 121 |
+
self.norm2 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 122 |
+
self.norm3 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 123 |
+
self.norm4 = nn.LayerNorm(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 124 |
+
mlp_hidden_dim = int(hidden_size * mlp_ratio)
|
| 125 |
+
approx_gelu = lambda: nn.GELU(approximate="tanh")
|
| 126 |
+
self.space_mlp = Mlp(in_features=hidden_size, hidden_features=mlp_hidden_dim, act_layer=approx_gelu, drop=0)
|
| 127 |
+
self.time_mlp = Mlp(in_features=hidden_size, hidden_features=mlp_hidden_dim, act_layer=approx_gelu, drop=0)
|
| 128 |
+
self.adaLN_modulation = nn.Sequential(
|
| 129 |
+
nn.SiLU(),
|
| 130 |
+
nn.Linear(hidden_size, 12 * hidden_size, bias=True)
|
| 131 |
+
)
|
| 132 |
+
self.causal_time_attn = causal_time_attn
|
| 133 |
+
|
| 134 |
+
|
| 135 |
+
def forward(self, x, c):
|
| 136 |
+
B, F, N, D = x.shape
|
| 137 |
+
|
| 138 |
+
|
| 139 |
+
# chunk into 12 [B, C] vectors
|
| 140 |
+
(shift_msa_s, scale_msa_s, gate_msa_s,
|
| 141 |
+
shift_msa_t, scale_msa_t, gate_msa_t,
|
| 142 |
+
shift_mlp_s, scale_mlp_s, gate_mlp_s,
|
| 143 |
+
shift_mlp_t, scale_mlp_t, gate_mlp_t) = self.adaLN_modulation(c).chunk(12, dim=1)
|
| 144 |
+
|
| 145 |
+
|
| 146 |
+
x_modulated = modulate(self.norm1(x), shift_msa_s, scale_msa_s)
|
| 147 |
+
x_modulated = rearrange(x_modulated, 'b f n d -> (b f) n d', b=B, f=F)
|
| 148 |
+
x_ = self.space_attn(x_modulated)
|
| 149 |
+
x = x + gate_msa_s.unsqueeze(1).unsqueeze(1) * rearrange(x_, '(b f) n d -> b f n d', b=B, f=F)
|
| 150 |
+
|
| 151 |
+
|
| 152 |
+
x_modulated = modulate(self.norm2(x), shift_mlp_s, scale_mlp_s)
|
| 153 |
+
x = x + gate_mlp_s.unsqueeze(1).unsqueeze(1) * self.space_mlp(x_modulated)
|
| 154 |
+
|
| 155 |
+
|
| 156 |
+
# — temporal attention path —
|
| 157 |
+
x_modulated = modulate(self.norm3(x), shift_msa_t, scale_msa_t)
|
| 158 |
+
x_modulated = rearrange(x_modulated, 'b f n d -> (b n) f d', b=B, f=F, n=N)
|
| 159 |
+
time_attn_mask = torch.tril(torch.ones(F, F, device=x.device)) if self.causal_time_attn else None
|
| 160 |
+
x_ = self.time_attn(x_modulated, attn_mask=time_attn_mask)
|
| 161 |
+
x = x + gate_msa_t.unsqueeze(1).unsqueeze(1) * rearrange(x_, '(b n) f d -> b f n d', b=B, f=F)
|
| 162 |
+
x_modulated = modulate(self.norm4(x), shift_mlp_t, scale_mlp_t)
|
| 163 |
+
x = x + gate_mlp_t.unsqueeze(1).unsqueeze(1) * self.time_mlp(x_modulated)
|
| 164 |
+
return x
|
| 165 |
+
|
| 166 |
+
|
| 167 |
+
class SwinSTDiTBlock(STDiTBlock):
|
| 168 |
+
def __init__(self, hidden_size, num_heads, input_shape, layer_idx, mlp_ratio=4.0, window_size=[6, 4], dropout_rate=0.0,
|
| 169 |
+
causal_time_attn=False, modulate_time_attn=False,
|
| 170 |
+
norm_layer=nn.LayerNorm, mlp_block='mlp', **block_kwargs):
|
| 171 |
+
super().__init__(hidden_size=hidden_size, num_heads=num_heads, mlp_ratio=mlp_ratio, dropout_rate=dropout_rate,
|
| 172 |
+
causal_time_attn=causal_time_attn, modulate_time_attn=modulate_time_attn, mlp_block=mlp_block)
|
| 173 |
+
self.norm1 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 174 |
+
self.space_attn = SwinTransformerBlock(
|
| 175 |
+
hidden_size,
|
| 176 |
+
input_resolution=input_shape,
|
| 177 |
+
num_heads=num_heads,
|
| 178 |
+
window_size=window_size,
|
| 179 |
+
shift_size=(0,0) if (layer_idx % 2 == 0) else [ws//2 for ws in window_size],
|
| 180 |
+
qk_norm=False,
|
| 181 |
+
mlp_ratio=mlp_ratio,
|
| 182 |
+
drop=dropout_rate,
|
| 183 |
+
attn_drop=dropout_rate,
|
| 184 |
+
norm_layer=norm_layer,
|
| 185 |
+
mlp_block=mlp_block,
|
| 186 |
+
**block_kwargs
|
| 187 |
+
)
|
| 188 |
+
|
| 189 |
+
|
| 190 |
+
class SwinSTDiTBlockNoExtraMLP(STDiTBlock):
|
| 191 |
+
def __init__(self, hidden_size, num_heads, input_shape, layer_idx, mlp_ratio=4.0, window_size=[6, 4], dropout_rate=0.0, norm_layer=nn.LayerNorm, mlp_block='mlp', **block_kwargs):
|
| 192 |
+
super().__init__(hidden_size, num_heads, input_shape, layer_idx, mlp_ratio, dropout_rate, norm_layer=norm_layer, mlp_block=mlp_block, **block_kwargs)
|
| 193 |
+
self.space_attn = SwinAttention(
|
| 194 |
+
hidden_size,
|
| 195 |
+
input_resolution=input_shape,
|
| 196 |
+
num_heads=num_heads,
|
| 197 |
+
window_size=window_size,
|
| 198 |
+
shift_size=(0,0) if (layer_idx % 2 == 0) else [ws//2 for ws in window_size],
|
| 199 |
+
proj_drop=dropout_rate,
|
| 200 |
+
attn_drop=dropout_rate,
|
| 201 |
+
qk_norm=True,
|
| 202 |
+
norm_layer=norm_layer,
|
| 203 |
+
**block_kwargs,
|
| 204 |
+
)
|
| 205 |
+
|
| 206 |
+
|
| 207 |
+
|
| 208 |
+
class STDiTBlockWithRegisters(nn.Module):
|
| 209 |
+
def __init__(
|
| 210 |
+
self,
|
| 211 |
+
hidden_size,
|
| 212 |
+
num_heads,
|
| 213 |
+
mlp_ratio=4.0,
|
| 214 |
+
dropout_rate=0.0,
|
| 215 |
+
causal_time_attn=False,
|
| 216 |
+
modulate_time_attn=True,
|
| 217 |
+
norm_layer=nn.LayerNorm,
|
| 218 |
+
mlp_block='mlp',
|
| 219 |
+
num_reg_tokens=1, # must be > 0
|
| 220 |
+
**block_kwargs
|
| 221 |
+
):
|
| 222 |
+
super().__init__()
|
| 223 |
+
if num_reg_tokens <= 0:
|
| 224 |
+
raise ValueError("STDiTBlockWithRegisters now assumes num_reg_tokens > 0.")
|
| 225 |
+
assert modulate_time_attn, "STDiTBlockWithRegisters currently requires modulate_time_attn=True."
|
| 226 |
+
self.num_reg_tokens = num_reg_tokens
|
| 227 |
+
|
| 228 |
+
# norms...
|
| 229 |
+
self.norm1 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 230 |
+
self.norm2 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 231 |
+
self.norm3 = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 232 |
+
self.norm_reg_attn_s = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 233 |
+
self.norm_reg_s = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 234 |
+
self.norm_reg_attn_t = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 235 |
+
self.norm_reg_t = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 236 |
+
|
| 237 |
+
self.space_attn = Attention(hidden_size, num_heads=num_heads, qkv_bias=True, qk_norm=True,
|
| 238 |
+
norm_layer=norm_layer, attn_drop=dropout_rate, proj_drop=dropout_rate, **block_kwargs)
|
| 239 |
+
self.time_attn = Attention(hidden_size, num_heads=num_heads, qkv_bias=True, qk_norm=True,
|
| 240 |
+
norm_layer=norm_layer, attn_drop=dropout_rate, proj_drop=dropout_rate, **block_kwargs)
|
| 241 |
+
|
| 242 |
+
mlp_hidden_dim = int(hidden_size * mlp_ratio)
|
| 243 |
+
if mlp_block == 'mlp':
|
| 244 |
+
approx_gelu = lambda: nn.GELU(approximate="tanh")
|
| 245 |
+
self.space_mlp = Mlp(in_features=hidden_size, hidden_features=mlp_hidden_dim, act_layer=approx_gelu, norm_layer=None, drop=0)
|
| 246 |
+
self.time_mlp = Mlp(in_features=hidden_size, hidden_features=mlp_hidden_dim, act_layer=approx_gelu, norm_layer=None, drop=0)
|
| 247 |
+
self.space_mlp_reg = Mlp(in_features=hidden_size, hidden_features=mlp_hidden_dim, act_layer=approx_gelu, norm_layer=None, drop=0)
|
| 248 |
+
self.time_mlp_reg = Mlp(in_features=hidden_size, hidden_features=mlp_hidden_dim, act_layer=approx_gelu, norm_layer=None, drop=0)
|
| 249 |
+
elif mlp_block == 'swiglu':
|
| 250 |
+
self.space_mlp = SwiGLU(in_features=hidden_size, hidden_features=(mlp_hidden_dim * 2) // 3)
|
| 251 |
+
self.time_mlp = SwiGLU(in_features=hidden_size, hidden_features=(mlp_hidden_dim * 2) // 3)
|
| 252 |
+
self.space_mlp_reg = SwiGLU(in_features=hidden_size, hidden_features=(mlp_hidden_dim * 2) // 3)
|
| 253 |
+
self.time_mlp_reg = SwiGLU(in_features=hidden_size, hidden_features=(mlp_hidden_dim * 2) // 3)
|
| 254 |
+
else:
|
| 255 |
+
raise NotImplementedError(f"mlp_block {mlp_block} not implemented")
|
| 256 |
+
|
| 257 |
+
self.adaLN_modulation = nn.Sequential(nn.SiLU(), nn.Linear(hidden_size, 9 * hidden_size, bias=True))
|
| 258 |
+
self.adaLN_registers_modulation = nn.Sequential(
|
| 259 |
+
nn.SiLU(),
|
| 260 |
+
nn.Linear(hidden_size, 12 * hidden_size, bias=True),
|
| 261 |
+
)
|
| 262 |
+
|
| 263 |
+
self.causal_time_attn = causal_time_attn
|
| 264 |
+
self.modulate_time_attn = modulate_time_attn
|
| 265 |
+
|
| 266 |
+
# Always-on time-attn modulation
|
| 267 |
+
self.adaLN_time_attn_modulation = nn.Sequential(
|
| 268 |
+
nn.SiLU(),
|
| 269 |
+
nn.Linear(hidden_size, 3 * hidden_size, bias=True)
|
| 270 |
+
)
|
| 271 |
+
self.norm_time_attn = norm_layer(hidden_size, elementwise_affine=False, eps=1e-6)
|
| 272 |
+
nn.init.constant_(self.adaLN_time_attn_modulation[-1].weight, 0)
|
| 273 |
+
nn.init.constant_(self.adaLN_time_attn_modulation[-1].bias, 0)
|
| 274 |
+
|
| 275 |
+
self.log_adaln_mean_abs = False
|
| 276 |
+
self._last_adaln_mean_abs = {}
|
| 277 |
+
|
| 278 |
+
def initialize_adaln_weights(self, gate_init_std=0.0):
|
| 279 |
+
nn.init.constant_(self.adaLN_modulation[-1].weight, 0)
|
| 280 |
+
nn.init.constant_(self.adaLN_modulation[-1].bias, 0)
|
| 281 |
+
nn.init.constant_(self.adaLN_registers_modulation[-1].weight, 0)
|
| 282 |
+
nn.init.constant_(self.adaLN_registers_modulation[-1].bias, 0)
|
| 283 |
+
nn.init.constant_(self.adaLN_time_attn_modulation[-1].weight, 0)
|
| 284 |
+
nn.init.constant_(self.adaLN_time_attn_modulation[-1].bias, 0)
|
| 285 |
+
|
| 286 |
+
if gate_init_std != 0.0:
|
| 287 |
+
hidden_size = self.adaLN_modulation[-1].out_features // 9
|
| 288 |
+
for gate_idx in (2, 5, 8):
|
| 289 |
+
start = gate_idx * hidden_size
|
| 290 |
+
end = start + hidden_size
|
| 291 |
+
nn.init.normal_(self.adaLN_modulation[-1].weight[start:end], std=gate_init_std)
|
| 292 |
+
|
| 293 |
+
hidden_size = self.adaLN_registers_modulation[-1].out_features // 12
|
| 294 |
+
for gate_idx in (2, 5, 8, 11):
|
| 295 |
+
start = gate_idx * hidden_size
|
| 296 |
+
end = start + hidden_size
|
| 297 |
+
nn.init.normal_(self.adaLN_registers_modulation[-1].weight[start:end], std=gate_init_std)
|
| 298 |
+
|
| 299 |
+
hidden_size = self.adaLN_time_attn_modulation[-1].out_features // 3
|
| 300 |
+
start = 2 * hidden_size
|
| 301 |
+
end = start + hidden_size
|
| 302 |
+
nn.init.normal_(self.adaLN_time_attn_modulation[-1].weight[start:end], std=gate_init_std)
|
| 303 |
+
|
| 304 |
+
def _collect_adaln_mean_abs(self, **tensors):
|
| 305 |
+
self._last_adaln_mean_abs = {
|
| 306 |
+
name: tensor.detach().abs().flatten(1).mean(dim=1)
|
| 307 |
+
for name, tensor in tensors.items()
|
| 308 |
+
}
|
| 309 |
+
|
| 310 |
+
def forward(self, x, c, c_registers):
|
| 311 |
+
B, F, N, D = x.shape
|
| 312 |
+
R = self.num_reg_tokens
|
| 313 |
+
|
| 314 |
+
# always has registers
|
| 315 |
+
reg_tokens = x[:, :, :R, :]
|
| 316 |
+
x_patch = x[:, :, R:, :]
|
| 317 |
+
|
| 318 |
+
(shift_msa, scale_msa, gate_msa,
|
| 319 |
+
shift_mlp_s, scale_mlp_s, gate_mlp_s,
|
| 320 |
+
shift_mlp_t, scale_mlp_t, gate_mlp_t) = self.adaLN_modulation(c).chunk(9, dim=-1)
|
| 321 |
+
shift_mta, scale_mta, gate_mta = self.adaLN_time_attn_modulation(c).chunk(3, dim=-1)
|
| 322 |
+
(shift_reg_attn_s, scale_reg_attn_s, gate_reg_attn_s,
|
| 323 |
+
shift_reg_mlp_s, scale_reg_mlp_s, gate_reg_mlp_s,
|
| 324 |
+
shift_reg_attn_t, scale_reg_attn_t, gate_reg_attn_t,
|
| 325 |
+
shift_reg_mlp_t, scale_reg_mlp_t, gate_reg_mlp_t) = self.adaLN_registers_modulation(c_registers).chunk(12, dim=-1)
|
| 326 |
+
|
| 327 |
+
if self.log_adaln_mean_abs:
|
| 328 |
+
self._collect_adaln_mean_abs(
|
| 329 |
+
msa_shift=shift_msa, msa_scale=scale_msa, msa_gate=gate_msa,
|
| 330 |
+
mlp_s_shift=shift_mlp_s, mlp_s_scale=scale_mlp_s, mlp_s_gate=gate_mlp_s,
|
| 331 |
+
mta_shift=shift_mta, mta_scale=scale_mta, mta_gate=gate_mta,
|
| 332 |
+
mlp_t_shift=shift_mlp_t, mlp_t_scale=scale_mlp_t, mlp_t_gate=gate_mlp_t,
|
| 333 |
+
reg_attn_s_shift=shift_reg_attn_s, reg_attn_s_scale=scale_reg_attn_s, reg_attn_s_gate=gate_reg_attn_s,
|
| 334 |
+
reg_mlp_s_shift=shift_reg_mlp_s, reg_mlp_s_scale=scale_reg_mlp_s, reg_mlp_s_gate=gate_reg_mlp_s,
|
| 335 |
+
reg_attn_t_shift=shift_reg_attn_t, reg_attn_t_scale=scale_reg_attn_t, reg_attn_t_gate=gate_reg_attn_t,
|
| 336 |
+
reg_mlp_t_shift=shift_reg_mlp_t, reg_mlp_t_scale=scale_reg_mlp_t, reg_mlp_t_gate=gate_reg_mlp_t,
|
| 337 |
+
)
|
| 338 |
+
|
| 339 |
+
# spatial attn (always concat regs)
|
| 340 |
+
x_mod = rearrange(modulate(self.norm1(x_patch), shift_msa, scale_msa), 'b f n d -> (b f) n d')
|
| 341 |
+
reg_in = rearrange(
|
| 342 |
+
modulate(self.norm_reg_attn_s(reg_tokens), shift_reg_attn_s, scale_reg_attn_s),
|
| 343 |
+
'b f r d -> (b f) r d'
|
| 344 |
+
)
|
| 345 |
+
x_mod = torch.cat([reg_in, x_mod], dim=1)
|
| 346 |
+
x_out = self.space_attn(x_mod)
|
| 347 |
+
|
| 348 |
+
reg_attn_out, x_out = torch.split(x_out, [R, x_patch.size(2)], dim=1)
|
| 349 |
+
reg_attn_out = rearrange(reg_attn_out, '(b f) r d -> b f r d', b=B, f=F)
|
| 350 |
+
x_out = rearrange(x_out, '(b f) n d -> b f n d', b=B, f=F)
|
| 351 |
+
x_patch = x_patch + gate_msa.unsqueeze(1).unsqueeze(1) * x_out if gate_msa.ndim == 2 else x_patch + gate_msa.unsqueeze(2) * x_out
|
| 352 |
+
reg_tokens = reg_tokens + gate_reg_attn_s.unsqueeze(1).unsqueeze(1) * reg_attn_out
|
| 353 |
+
|
| 354 |
+
# spatial mlp
|
| 355 |
+
x_patch = x_patch + gate_mlp_s.unsqueeze(1).unsqueeze(1) * self.space_mlp(modulate(self.norm2(x_patch), shift_mlp_s, scale_mlp_s)) if gate_mlp_s.ndim == 2 else x_patch + gate_mlp_s.unsqueeze(2) * self.space_mlp(modulate(self.norm2(x_patch), shift_mlp_s, scale_mlp_s))
|
| 356 |
+
reg_mlp_out = self.space_mlp_reg(modulate(self.norm_reg_s(reg_tokens), shift_reg_mlp_s, scale_reg_mlp_s))
|
| 357 |
+
reg_tokens = reg_tokens + gate_reg_mlp_s.unsqueeze(1).unsqueeze(1) * reg_mlp_out
|
| 358 |
+
|
| 359 |
+
# temporal attn patches
|
| 360 |
+
x_mod = modulate(self.norm_time_attn(x_patch), shift_mta, scale_mta)
|
| 361 |
+
x_mod = rearrange(x_mod, 'b f n d -> (b n) f d')
|
| 362 |
+
time_attn_mask = torch.tril(torch.ones(F, F, device=x.device)) if self.causal_time_attn else None
|
| 363 |
+
x_out = self.time_attn(x_mod, attn_mask=time_attn_mask)
|
| 364 |
+
x_out = rearrange(x_out, '(b n) f d -> b f n d', b=B, n=x_patch.size(2), f=F)
|
| 365 |
+
x_patch = x_patch + gate_mta.unsqueeze(1).unsqueeze(1) * x_out if gate_mta.ndim == 2 else x_patch + gate_mta.unsqueeze(2) * x_out
|
| 366 |
+
|
| 367 |
+
# temporal attn regs (always)
|
| 368 |
+
reg_time_in = rearrange(
|
| 369 |
+
modulate(self.norm_reg_attn_t(reg_tokens), shift_reg_attn_t, scale_reg_attn_t),
|
| 370 |
+
'b f r d -> (b r) f d'
|
| 371 |
+
)
|
| 372 |
+
reg_time_out = self.time_attn(reg_time_in, attn_mask=time_attn_mask)
|
| 373 |
+
reg_time_out = rearrange(reg_time_out, '(b r) f d -> b f r d', b=B, r=R, f=F)
|
| 374 |
+
reg_tokens = reg_tokens + gate_reg_attn_t.unsqueeze(1).unsqueeze(1) * reg_time_out
|
| 375 |
+
|
| 376 |
+
# temporal mlp
|
| 377 |
+
x_patch = x_patch + gate_mlp_t.unsqueeze(1).unsqueeze(1) * self.time_mlp(modulate(self.norm3(x_patch), shift_mlp_t, scale_mlp_t)) if gate_mlp_t.ndim == 2 else x_patch + gate_mlp_t.unsqueeze(2) * self.time_mlp(modulate(self.norm3(x_patch), shift_mlp_t, scale_mlp_t))
|
| 378 |
+
reg_mlp_out = self.time_mlp_reg(modulate(self.norm_reg_t(reg_tokens), shift_reg_mlp_t, scale_reg_mlp_t))
|
| 379 |
+
reg_tokens = reg_tokens + gate_reg_mlp_t.unsqueeze(1).unsqueeze(1) * reg_mlp_out
|
| 380 |
+
|
| 381 |
+
return torch.cat([reg_tokens, x_patch], dim=2)
|
| 382 |
+
|
| 383 |
+
|
| 384 |
+
|
| 385 |
+
|
| 386 |
+
|
| 387 |
+
class DiT(nn.Module):
|
| 388 |
+
"""
|
| 389 |
+
Diffusion model with a Transformer backbone.
|
| 390 |
+
"""
|
| 391 |
+
def __init__(
|
| 392 |
+
self,
|
| 393 |
+
input_size=16,
|
| 394 |
+
patch_size=2,
|
| 395 |
+
in_channels=32,
|
| 396 |
+
hidden_size=1152,
|
| 397 |
+
depth=28,
|
| 398 |
+
num_heads=16,
|
| 399 |
+
mlp_ratio=4.0,
|
| 400 |
+
max_num_frames=6,
|
| 401 |
+
dropout=0.0,
|
| 402 |
+
ctx_noise_aug_ratio=0.1,
|
| 403 |
+
ctx_noise_aug_prob = 0.5,
|
| 404 |
+
drop_ctx_rate=0.2,
|
| 405 |
+
frequency_range=(2, 15),
|
| 406 |
+
learn_sigma=False,
|
| 407 |
+
norm_layer=nn.LayerNorm,
|
| 408 |
+
mlp_block='mlp',
|
| 409 |
+
):
|
| 410 |
+
super().__init__()
|
| 411 |
+
self.input_size= input_size if isinstance(input_size, (list, tuple, ListConfig)) else [input_size, input_size]
|
| 412 |
+
self.in_channels = in_channels
|
| 413 |
+
self.out_channels = in_channels * 2 if learn_sigma else in_channels
|
| 414 |
+
self.patch_size = patch_size
|
| 415 |
+
self.num_heads = num_heads
|
| 416 |
+
self.ctx_noise_aug_ratio = ctx_noise_aug_ratio
|
| 417 |
+
self.ctx_noise_aug_prob = ctx_noise_aug_prob
|
| 418 |
+
self.drop_ctx_rate = drop_ctx_rate
|
| 419 |
+
|
| 420 |
+
|
| 421 |
+
self.x_embedder = PatchEmbed(input_size, patch_size, in_channels, hidden_size, bias=True)
|
| 422 |
+
self.num_patches = self.x_embedder.num_patches
|
| 423 |
+
self.t_embedder = TimestepEmbedder(hidden_size)
|
| 424 |
+
self.pos_embed = nn.Parameter(torch.zeros(1, self.num_patches, hidden_size), requires_grad=False)
|
| 425 |
+
|
| 426 |
+
self.blocks = nn.ModuleList([
|
| 427 |
+
DiTBlock(hidden_size, num_heads, mlp_ratio=mlp_ratio, dropout_rate=dropout, norm_layer=norm_layer, mlp_block=mlp_block) for _ in range(depth)
|
| 428 |
+
])
|
| 429 |
+
|
| 430 |
+
self.final_layer = FinalLayer(hidden_size, patch_size, self.out_channels, norm_layer=norm_layer)
|
| 431 |
+
self.max_num_frames = max_num_frames
|
| 432 |
+
self.frame_emb = nn.init.trunc_normal_(nn.Parameter(torch.zeros(1, self.max_num_frames, 1, hidden_size)), 0., 0.02)
|
| 433 |
+
self.frame_rate_encoder = FrequencyEncoder(hidden_size, freq_min=frequency_range[0], freq_max=frequency_range[1])
|
| 434 |
+
|
| 435 |
+
self.initialize_weights()
|
| 436 |
+
|
| 437 |
+
|
| 438 |
+
def initialize_weights(self):
|
| 439 |
+
# Initialize transformer layers:
|
| 440 |
+
def _basic_init(module):
|
| 441 |
+
if isinstance(module, nn.Linear):
|
| 442 |
+
torch.nn.init.xavier_uniform_(module.weight)
|
| 443 |
+
if module.bias is not None:
|
| 444 |
+
nn.init.constant_(module.bias, 0)
|
| 445 |
+
self.apply(_basic_init)
|
| 446 |
+
|
| 447 |
+
|
| 448 |
+
# Initialize (and freeze) pos_embed by sin-cos embedding:
|
| 449 |
+
pos_embed = get_2d_sincos_pos_embed(self.pos_embed.shape[-1], [self.input_size[0] // self.patch_size, self.input_size[1] // self.patch_size], cls_token=False, extra_tokens=0)
|
| 450 |
+
self.pos_embed.data.copy_(torch.from_numpy(pos_embed).float().unsqueeze(0))
|
| 451 |
+
|
| 452 |
+
|
| 453 |
+
w = self.x_embedder.proj.weight.data
|
| 454 |
+
nn.init.xavier_uniform_(w.view([w.shape[0], -1]))
|
| 455 |
+
nn.init.constant_(self.x_embedder.proj.bias, 0)
|
| 456 |
+
|
| 457 |
+
|
| 458 |
+
# Initialize timestep embedding MLP:
|
| 459 |
+
nn.init.normal_(self.t_embedder.mlp[0].weight, std=0.02)
|
| 460 |
+
nn.init.normal_(self.t_embedder.mlp[2].weight, std=0.02)
|
| 461 |
+
|
| 462 |
+
|
| 463 |
+
# Zero-out adaLN modulation layers in DiT blocks:
|
| 464 |
+
for block in self.blocks:
|
| 465 |
+
nn.init.constant_(block.adaLN_modulation[-1].weight, 0)
|
| 466 |
+
nn.init.constant_(block.adaLN_modulation[-1].bias, 0)
|
| 467 |
+
|
| 468 |
+
|
| 469 |
+
# Zero-out output layers:
|
| 470 |
+
nn.init.constant_(self.final_layer.adaLN_modulation[-1].weight, 0)
|
| 471 |
+
nn.init.constant_(self.final_layer.adaLN_modulation[-1].bias, 0)
|
| 472 |
+
nn.init.constant_(self.final_layer.linear.weight, 0)
|
| 473 |
+
nn.init.constant_(self.final_layer.linear.bias, 0)
|
| 474 |
+
|
| 475 |
+
|
| 476 |
+
def unpatchify(self, x):
|
| 477 |
+
"""
|
| 478 |
+
x: (N, T, patch_size**2 * C)
|
| 479 |
+
imgs: (N, H, W, C)
|
| 480 |
+
"""
|
| 481 |
+
c = self.out_channels
|
| 482 |
+
p = self.x_embedder.patch_size[0]
|
| 483 |
+
h = self.x_embedder.grid_size[0]
|
| 484 |
+
w = self.x_embedder.grid_size[1]
|
| 485 |
+
|
| 486 |
+
|
| 487 |
+
x = x.reshape(shape=(x.shape[0], x.shape[1], h, w, p, p, c))
|
| 488 |
+
x = torch.einsum('bfhwpqc->bfchpwq', x)
|
| 489 |
+
imgs = x.reshape(shape=(x.shape[0], x.shape[1], c, h * p, w * p))
|
| 490 |
+
return imgs
|
| 491 |
+
|
| 492 |
+
|
| 493 |
+
def get_condition_embeddings(self, t):
|
| 494 |
+
"""
|
| 495 |
+
Get the condition embeddings for the given timesteps.
|
| 496 |
+
t: (N,) tensor of diffusion timesteps
|
| 497 |
+
returns: (N, D) tensor of condition embeddings
|
| 498 |
+
"""
|
| 499 |
+
return self.t_embedder(t)
|
| 500 |
+
|
| 501 |
+
def preprocess_inputs(self, target, context, t, frame_rate):
|
| 502 |
+
b, f_target = target.size()[:2]
|
| 503 |
+
f_context = context.size(1)
|
| 504 |
+
|
| 505 |
+
if self.training:
|
| 506 |
+
# Drop the context frame
|
| 507 |
+
if torch.rand(1, device=target.device)<self.drop_ctx_rate:
|
| 508 |
+
context = None
|
| 509 |
+
f_context = 0
|
| 510 |
+
elif torch.rand(1, device=target.device) < self.ctx_noise_aug_prob:
|
| 511 |
+
# Add noise to context frames (if t is less than ctx_noise_aug_ratio, we do not add noise)
|
| 512 |
+
mask = (t >= self.ctx_noise_aug_ratio)
|
| 513 |
+
aug_noise = torch.randn_like(context)
|
| 514 |
+
context[mask] = context[mask] + aug_noise[mask] * self.ctx_noise_aug_ratio
|
| 515 |
+
|
| 516 |
+
|
| 517 |
+
frame_embeddings = self.frame_rate_encoder.encode(frame_rate)
|
| 518 |
+
frame_embeddings = frame_embeddings.unsqueeze(1).unsqueeze(1).to(target.device)
|
| 519 |
+
x = torch.cat((context, target), dim=1) if context is not None else target
|
| 520 |
+
x = rearrange(x, 'b f c h w -> (b f) c h w')
|
| 521 |
+
x = self.x_embedder(x) + self.pos_embed.to(x.device)
|
| 522 |
+
x = rearrange(x, '(b f) hw c -> b f hw c', b=b)
|
| 523 |
+
x = x + self.frame_emb[:, self.max_num_frames-(f_target+f_context):].to(x.device) + frame_embeddings
|
| 524 |
+
return x
|
| 525 |
+
|
| 526 |
+
def get_condition_embeddings(self, t):
|
| 527 |
+
"""
|
| 528 |
+
Get the condition embeddings for the given timesteps.
|
| 529 |
+
t: (N,) tensor of diffusion timesteps
|
| 530 |
+
returns: (N, D) tensor of condition embeddings
|
| 531 |
+
"""
|
| 532 |
+
return self.t_embedder(t)
|
| 533 |
+
|
| 534 |
+
def preprocess_inputs(self, target, context, t, frame_rate):
|
| 535 |
+
b, f_target = target.size()[:2]
|
| 536 |
+
f_context = context.size(1) if context is not None else 0
|
| 537 |
+
|
| 538 |
+
if self.training:
|
| 539 |
+
# Drop the context frame
|
| 540 |
+
if torch.rand(1, device=target.device)<self.drop_ctx_rate:
|
| 541 |
+
context = None
|
| 542 |
+
f_context = 0
|
| 543 |
+
elif torch.rand(1, device=target.device) < self.ctx_noise_aug_prob:
|
| 544 |
+
# Add noise to context frames (if t is less than ctx_noise_aug_ratio, we do not add noise)
|
| 545 |
+
mask = (t >= self.ctx_noise_aug_ratio)
|
| 546 |
+
aug_noise = torch.randn_like(context)
|
| 547 |
+
context[mask] = context[mask] + aug_noise[mask] * self.ctx_noise_aug_ratio
|
| 548 |
+
|
| 549 |
+
|
| 550 |
+
frame_embeddings = self.frame_rate_encoder.encode(frame_rate)
|
| 551 |
+
frame_embeddings = frame_embeddings.unsqueeze(1).unsqueeze(1).to(target.device)
|
| 552 |
+
x = torch.cat((context, target), dim=1) if context is not None else target
|
| 553 |
+
x = rearrange(x, 'b f c h w -> (b f) c h w')
|
| 554 |
+
x = self.x_embedder(x) + self.pos_embed.to(x.device)
|
| 555 |
+
x = rearrange(x, '(b f) hw c -> b f hw c', b=b)
|
| 556 |
+
x = x + self.frame_emb[:, self.max_num_frames-(f_target+f_context):].to(x.device) + frame_embeddings
|
| 557 |
+
return x
|
| 558 |
+
|
| 559 |
+
def postprocess_outputs(self, out):
|
| 560 |
+
return self.unpatchify(out)
|
| 561 |
+
|
| 562 |
+
def forward(self, target, context, t, frame_rate, return_features=False):
|
| 563 |
+
"""
|
| 564 |
+
Forward pass of DiT.
|
| 565 |
+
x: (N, F, C, H, W) tensor of spatial inputs (images or latent representations of images)
|
| 566 |
+
t: (N,) tensor of diffusion timesteps
|
| 567 |
+
y: (N,) tensor of class labels
|
| 568 |
+
"""
|
| 569 |
+
|
| 570 |
+
num_frames_ctx = context.size(1)
|
| 571 |
+
num_frames_pred = target.size(1)
|
| 572 |
+
|
| 573 |
+
c = self.get_condition_embeddings(t)
|
| 574 |
+
|
| 575 |
+
x = self.preprocess_inputs(target, context, t, frame_rate)
|
| 576 |
+
|
| 577 |
+
x = rearrange(x, 'b f hw c -> b (f hw) c')
|
| 578 |
+
features = []
|
| 579 |
+
for block in self.blocks:
|
| 580 |
+
x = block(x, c)
|
| 581 |
+
features.append(x) if return_features else None
|
| 582 |
+
x = rearrange(x, 'b (f hw) c -> b f hw c', f=(num_frames_ctx+num_frames_pred))[:,-num_frames_pred:]
|
| 583 |
+
out = self.final_layer(x, c)
|
| 584 |
+
|
| 585 |
+
out = self.postprocess_outputs(out)
|
| 586 |
+
if return_features:
|
| 587 |
+
return out, features
|
| 588 |
+
return out
|
| 589 |
+
|
| 590 |
+
|
| 591 |
+
class CDiT(DiT):
|
| 592 |
+
def __init__(self, input_size=16, patch_size=2, in_channels=32, hidden_size=1152, depth=28, num_heads=16, mlp_ratio=4.0, max_num_frames=6, dropout=0.1, ctx_noise_aug_ratio=0.1,ctx_noise_aug_prob=0.5, norm_layer=nn.LayerNorm, mlp_block='mlp', **kwargs):
|
| 593 |
+
if isinstance(norm_layer, str):
|
| 594 |
+
norm_layer = get_norm_layer(norm_layer)
|
| 595 |
+
super().__init__(input_size=input_size, patch_size=patch_size, in_channels=in_channels, hidden_size=hidden_size, depth=depth, num_heads=num_heads, mlp_ratio=mlp_ratio, max_num_frames=max_num_frames, dropout=dropout,
|
| 596 |
+
ctx_noise_aug_ratio=ctx_noise_aug_ratio, ctx_noise_aug_prob=ctx_noise_aug_prob, norm_layer=norm_layer, mlp_block=mlp_block, **kwargs)
|
| 597 |
+
self.blocks = nn.ModuleList([
|
| 598 |
+
CDiTBlock(hidden_size, num_heads, mlp_ratio=mlp_ratio, norm_layer=norm_layer, mlp_block=mlp_block) for _ in range(depth)
|
| 599 |
+
])
|
| 600 |
+
|
| 601 |
+
|
| 602 |
+
def forward(self, target, context, t, frame_rate, return_features=False):
|
| 603 |
+
"""
|
| 604 |
+
Forward pass of DiT.
|
| 605 |
+
x: (N, F, C, H, W) tensor of spatial inputs (images or latent representations of images)
|
| 606 |
+
t: (N,) tensor of diffusion timesteps
|
| 607 |
+
"""
|
| 608 |
+
num_frames_ctx = context.size(1)
|
| 609 |
+
num_frames_pred = target.size(1)
|
| 610 |
+
|
| 611 |
+
c = self.get_condition_embeddings(t) # (N, D)
|
| 612 |
+
|
| 613 |
+
x = self.preprocess_inputs(target, context, t, frame_rate) # (B, F, N, D)
|
| 614 |
+
|
| 615 |
+
|
| 616 |
+
target = rearrange(x[:,-num_frames_pred:], 'b f hw c -> b (f hw) c')
|
| 617 |
+
ctx = rearrange(x[:,:-num_frames_pred], 'b f hw c -> b (f hw) c') if num_frames_ctx>1 else None
|
| 618 |
+
|
| 619 |
+
features = []
|
| 620 |
+
for block in self.blocks:
|
| 621 |
+
target = block(target, c, ctx) # (N, T, D)
|
| 622 |
+
features.append(target)
|
| 623 |
+
|
| 624 |
+
target = rearrange(target, 'b (f hw) c -> b f hw c', f=(num_frames_pred))
|
| 625 |
+
out = self.final_layer(target, c) # (N, T, patch_size * out_channels)
|
| 626 |
+
out = self.postprocess_outputs(out) # (N, T, patch_size ** 2 * out_channels)
|
| 627 |
+
if return_features:
|
| 628 |
+
return out, features
|
| 629 |
+
return out
|
| 630 |
+
|
| 631 |
+
|
| 632 |
+
|
| 633 |
+
class STDiT(DiT):
|
| 634 |
+
def __init__(self, input_size=16, patch_size=2, in_channels=32, hidden_size=1152, depth=28, num_heads=16, mlp_ratio=4.0, max_num_frames=6,
|
| 635 |
+
dropout=0.1, ctx_noise_aug_ratio=0.1, ctx_noise_aug_prob=0.5, drop_ctx_rate=0.2, frequency_range=(2, 15),
|
| 636 |
+
causal_time_attn=False, modulate_time_attn=False, norm_layer=nn.LayerNorm, mlp_block='mlp',
|
| 637 |
+
**kwargs):
|
| 638 |
+
|
| 639 |
+
if isinstance(norm_layer, str):
|
| 640 |
+
norm_layer = get_norm_layer(norm_layer)
|
| 641 |
+
|
| 642 |
+
super().__init__(input_size=input_size, patch_size=patch_size, in_channels=in_channels, hidden_size=hidden_size, depth=depth, num_heads=num_heads, mlp_ratio=mlp_ratio,
|
| 643 |
+
max_num_frames=max_num_frames, dropout=dropout, ctx_noise_aug_ratio=ctx_noise_aug_ratio, ctx_noise_aug_prob=ctx_noise_aug_prob, drop_ctx_rate=drop_ctx_rate,
|
| 644 |
+
norm_layer=norm_layer, mlp_block=mlp_block, **kwargs)
|
| 645 |
+
self.blocks = nn.ModuleList([
|
| 646 |
+
STDiTBlock(hidden_size, num_heads, mlp_ratio=mlp_ratio, dropout_rate=dropout,
|
| 647 |
+
causal_time_attn=causal_time_attn, modulate_time_attn=modulate_time_attn,
|
| 648 |
+
norm_layer=norm_layer, mlp_block=mlp_block) for _ in range(depth)
|
| 649 |
+
])
|
| 650 |
+
|
| 651 |
+
def forward(self, target, context, t, frame_rate, return_features=False):
|
| 652 |
+
"""
|
| 653 |
+
Forward pass of DiT.
|
| 654 |
+
x: (N, F, C, H, W) tensor of spatial inputs (images or latent representations of images)
|
| 655 |
+
t: (N,) tensor of diffusion timesteps
|
| 656 |
+
"""
|
| 657 |
+
f_pred = target.size(1)
|
| 658 |
+
|
| 659 |
+
c = self.get_condition_embeddings(t) # (N, D)
|
| 660 |
+
|
| 661 |
+
x = self.preprocess_inputs(target, context, t, frame_rate) # (B, F, N, D)
|
| 662 |
+
|
| 663 |
+
features = []
|
| 664 |
+
for block in self.blocks:
|
| 665 |
+
x = block(x, c)
|
| 666 |
+
features.append(x) if return_features else None
|
| 667 |
+
|
| 668 |
+
|
| 669 |
+
out = self.final_layer(x[:,-f_pred:], c) # (N, T, patch_size * out_channels)
|
| 670 |
+
out = self.postprocess_outputs(out) # (N, T, patch_size ** 2 * out_channels)
|
| 671 |
+
if return_features:
|
| 672 |
+
return out, features
|
| 673 |
+
return out
|
| 674 |
+
|
| 675 |
+
class STDiTDF(STDiT):
|
| 676 |
+
def __init__(self, input_size=16, patch_size=2, in_channels=32, hidden_size=1152, depth=28, num_heads=16, mlp_ratio=4.0, max_num_frames=6,
|
| 677 |
+
dropout=0.1, ctx_noise_aug_ratio=0.1, ctx_noise_aug_prob=0.5, drop_ctx_rate=0.2, frequency_range=(2, 15),
|
| 678 |
+
causal_time_attn=False, modulate_time_attn=False, norm_layer=nn.LayerNorm, mlp_block='mlp',
|
| 679 |
+
**kwargs):
|
| 680 |
+
|
| 681 |
+
if isinstance(norm_layer, str):
|
| 682 |
+
norm_layer = get_norm_layer(norm_layer)
|
| 683 |
+
|
| 684 |
+
super().__init__(input_size=input_size, patch_size=patch_size, in_channels=in_channels, hidden_size=hidden_size, depth=depth, num_heads=num_heads, mlp_ratio=mlp_ratio,
|
| 685 |
+
max_num_frames=max_num_frames, dropout=dropout, ctx_noise_aug_ratio=ctx_noise_aug_ratio, ctx_noise_aug_prob=ctx_noise_aug_prob, drop_ctx_rate=drop_ctx_rate,
|
| 686 |
+
frequency_range=frequency_range, causal_time_attn=causal_time_attn, modulate_time_attn=modulate_time_attn, norm_layer=norm_layer, mlp_block=mlp_block, **kwargs)
|
| 687 |
+
|
| 688 |
+
self.blocks = nn.ModuleList([
|
| 689 |
+
STDiTBlock(hidden_size, num_heads, mlp_ratio=mlp_ratio, dropout_rate=dropout,
|
| 690 |
+
causal_time_attn=causal_time_attn, modulate_time_attn=modulate_time_attn,
|
| 691 |
+
norm_layer=norm_layer, mlp_block=mlp_block) for _ in range(depth)
|
| 692 |
+
])
|
| 693 |
+
|
| 694 |
+
def preprocess_inputs(self, x, frame_rate):
|
| 695 |
+
b, f = x.size()[:2]
|
| 696 |
+
|
| 697 |
+
frame_embeddings = self.frame_rate_encoder.encode(frame_rate)
|
| 698 |
+
frame_embeddings = frame_embeddings.unsqueeze(1).unsqueeze(1).to(x.device)
|
| 699 |
+
x = rearrange(x, 'b f c h w -> (b f) c h w')
|
| 700 |
+
x = self.x_embedder(x) + self.pos_embed.to(x.device)
|
| 701 |
+
x = rearrange(x, '(b f) hw c -> b f hw c', b=b)
|
| 702 |
+
x = x + self.frame_emb[:, :f].to(x.device) + frame_embeddings
|
| 703 |
+
return x
|
| 704 |
+
|
| 705 |
+
def forward(self, x, t, frame_rate, return_features=False):
|
| 706 |
+
"""
|
| 707 |
+
Forward pass of DiT.
|
| 708 |
+
x: (N, F, C, H, W) tensor of spatial inputs (images or latent representations of images)
|
| 709 |
+
t: (N,) tensor of diffusion timesteps
|
| 710 |
+
"""
|
| 711 |
+
f_pred = x.size(1)
|
| 712 |
+
|
| 713 |
+
t = rearrange(t, 'b f -> (b f)')
|
| 714 |
+
c = self.get_condition_embeddings(t) # (N, D)
|
| 715 |
+
c = rearrange(c, '(b f) d -> b f d', b=x.size(0), f=x.size(1))
|
| 716 |
+
|
| 717 |
+
x = self.preprocess_inputs(x, frame_rate) # (B, F, N, D)
|
| 718 |
+
|
| 719 |
+
features = []
|
| 720 |
+
for block in self.blocks:
|
| 721 |
+
x = block(x, c)
|
| 722 |
+
features.append(x) if return_features else None
|
| 723 |
+
|
| 724 |
+
out = self.final_layer(x, c) # (N, T, patch_size * out_channels)
|
| 725 |
+
out = self.postprocess_outputs(out) # (N, T, patch_size ** 2 * out_channels)
|
| 726 |
+
if return_features:
|
| 727 |
+
return out, features
|
| 728 |
+
return out
|
| 729 |
+
|
| 730 |
+
|
| 731 |
+
class STDiTWithGlobalBlocks(STDiT):
|
| 732 |
+
"""
|
| 733 |
+
A DiT with extra global attention blocks (DiT blocks).
|
| 734 |
+
"""
|
| 735 |
+
def __init__(self, *args, global_block_indices, **kwargs):
|
| 736 |
+
|
| 737 |
+
class DiTBlockWrapper(nn.Module):
|
| 738 |
+
def __init__(self, block):
|
| 739 |
+
super().__init__()
|
| 740 |
+
self.block = block
|
| 741 |
+
|
| 742 |
+
def forward(self, x, c):
|
| 743 |
+
B, F, N, D = x.shape
|
| 744 |
+
x_ = rearrange(x, 'b f n d -> b (f n) d', b=B, f=F)
|
| 745 |
+
x_ = self.block(x_, c)
|
| 746 |
+
x_ = rearrange(x_, 'b (f n) d -> b f n d', b=B, f=F)
|
| 747 |
+
return x_
|
| 748 |
+
|
| 749 |
+
super().__init__(*args, **kwargs)
|
| 750 |
+
self.global_block_indices = global_block_indices
|
| 751 |
+
for idx in global_block_indices:
|
| 752 |
+
dit_block = DiTBlock(hidden_size=kwargs.get('hidden_size',1152), num_heads=kwargs.get('num_heads',16),
|
| 753 |
+
mlp_ratio=kwargs.get('mlp_ratio',4.0), dropout_rate=kwargs.get('dropout',0.0),
|
| 754 |
+
norm_layer=kwargs.get('norm_layer',nn.LayerNorm), mlp_block=kwargs.get('mlp_block','mlp'))
|
| 755 |
+
self.blocks[idx] = DiTBlockWrapper(dit_block)
|
| 756 |
+
|
| 757 |
+
|
| 758 |
+
class STDiT_tmpadaLN(STDiT):
|
| 759 |
+
def __init__(self, input_size=16, patch_size=2, in_channels=32, hidden_size=1152, depth=28, num_heads=16, mlp_ratio=4.0, max_num_frames=6, dropout=0.1, ctx_noise_aug_ratio=0.1, ctx_noise_aug_prob=0.5, drop_ctx_rate=0.2, frequency_range=(2, 15), causal_time_attn=False, norm_layer=nn.LayerNorm, **kwargs):
|
| 760 |
+
super().__init__(input_size=input_size, patch_size=patch_size, in_channels=in_channels, hidden_size=hidden_size, depth=depth, num_heads=num_heads, mlp_ratio=mlp_ratio, max_num_frames=max_num_frames, dropout=dropout, ctx_noise_aug_ratio=ctx_noise_aug_ratio, ctx_noise_aug_prob=ctx_noise_aug_prob, drop_ctx_rate=drop_ctx_rate, norm_layer=norm_layer, **kwargs)
|
| 761 |
+
self.blocks = nn.ModuleList([
|
| 762 |
+
STDiTBlock_tmpadaLN(hidden_size, num_heads, mlp_ratio=mlp_ratio, dropout_rate=dropout, causal_time_attn=causal_time_attn, norm_layer=norm_layer) for _ in range(depth)
|
| 763 |
+
])
|
| 764 |
+
|
| 765 |
+
|
| 766 |
+
class SwinSTDiT(STDiT):
|
| 767 |
+
def __init__(self, input_size=16, patch_size=2, in_channels=32, hidden_size=1152, depth=28, num_heads=16, mlp_ratio=4.0, max_num_frames=6, window_size=[6, 4], dropout=0.1, ctx_noise_aug_ratio=0.1,ctx_noise_aug_prob=0.5, drop_ctx_rate=0.2, frequency_range=(2, 15), norm_layer=nn.LayerNorm, mlp_block='mlp', **kwargs):
|
| 768 |
+
super().__init__(input_size=input_size, patch_size=patch_size, in_channels=in_channels, hidden_size=hidden_size, depth=depth, num_heads=num_heads, mlp_ratio=mlp_ratio, max_num_frames=max_num_frames, dropout=dropout, ctx_noise_aug_ratio=ctx_noise_aug_ratio, ctx_noise_aug_prob=ctx_noise_aug_prob, drop_ctx_rate=drop_ctx_rate, norm_layer=norm_layer, mlp_block=mlp_block, **kwargs)
|
| 769 |
+
self.blocks = nn.ModuleList([
|
| 770 |
+
SwinSTDiTBlock(hidden_size=hidden_size, num_heads=num_heads, input_shape=input_size, layer_idx=layer_idx, mlp_ratio=mlp_ratio, window_size=window_size, dropout_rate=dropout, norm_layer=norm_layer) for layer_idx in range(depth)
|
| 771 |
+
])
|
| 772 |
+
|
| 773 |
+
|
| 774 |
+
class SwinSTDiTNoExtraMLP(STDiT):
|
| 775 |
+
def __init__(self, input_size=16, patch_size=2, in_channels=32, hidden_size=1152, depth=28, num_heads=16, mlp_ratio=4.0, max_num_frames=6, window_size=[6, 4],
|
| 776 |
+
dropout=0.1, ctx_noise_aug_ratio=0.1,ctx_noise_aug_prob=0.5, drop_ctx_rate=0.2, frequency_range=(2, 15),
|
| 777 |
+
norm_layer='layer_norm', mlp_block='mlp', qk_norm=True, **kwargs):
|
| 778 |
+
|
| 779 |
+
if isinstance(norm_layer, str):
|
| 780 |
+
norm_layer = get_norm_layer(norm_layer)
|
| 781 |
+
|
| 782 |
+
super().__init__(input_size=input_size, patch_size=patch_size, in_channels=in_channels, hidden_size=hidden_size, depth=depth,
|
| 783 |
+
num_heads=num_heads, mlp_ratio=mlp_ratio, max_num_frames=max_num_frames, dropout=dropout,
|
| 784 |
+
ctx_noise_aug_ratio=ctx_noise_aug_ratio, ctx_noise_aug_prob=ctx_noise_aug_prob, drop_ctx_rate=drop_ctx_rate,
|
| 785 |
+
norm_layer=norm_layer, mlp_block=mlp_block, **kwargs)
|
| 786 |
+
|
| 787 |
+
for block_idx, block in enumerate(self.blocks):
|
| 788 |
+
block.space_attn = SwinAttention(
|
| 789 |
+
hidden_size,
|
| 790 |
+
input_resolution=input_size,
|
| 791 |
+
num_heads=num_heads,
|
| 792 |
+
window_size=window_size,
|
| 793 |
+
shift_size=(0,0) if (block_idx % 2 == 0) else [ws//2 for ws in window_size],
|
| 794 |
+
proj_drop=dropout,
|
| 795 |
+
attn_drop=dropout,
|
| 796 |
+
qk_norm=qk_norm,
|
| 797 |
+
norm_layer=norm_layer,
|
| 798 |
+
)
|
| 799 |
+
|
| 800 |
+
|
| 801 |
+
|
| 802 |
+
class STDiTDFSpatialL2(STDiTDF):
|
| 803 |
+
"""
|
| 804 |
+
STDiTDF with per-token spatial conditioning from L2 (semantic) latents.
|
| 805 |
+
|
| 806 |
+
The L2 latent (same spatial H×W as the rec latent, sem_in_channels channels) is
|
| 807 |
+
patchified into tokens, combined with a learned frame-position embedding, and
|
| 808 |
+
produces per-token (shift, scale, gate) modulation injected at every block via
|
| 809 |
+
STDiTBlockWithSpatialL2.
|
| 810 |
+
|
| 811 |
+
Conditioning scheme (num_frames = 1 context + num_pred_frames generated):
|
| 812 |
+
Frame 0 (input): z_l2_start, position = 0.0
|
| 813 |
+
Frame k (generated): z_l2_end, position = k / num_pred_frames
|
| 814 |
+
|
| 815 |
+
All new parameters (l2_patchify, position_proj, per-block spatial_to_mod) are
|
| 816 |
+
zero-initialized so that a pre-trained STDiTDF backbone is unchanged at init.
|
| 817 |
+
|
| 818 |
+
Args:
|
| 819 |
+
sem_in_channels: Number of channels in the L2 (sem) latent.
|
| 820 |
+
All other args are passed through to STDiTDF.
|
| 821 |
+
"""
|
| 822 |
+
|
| 823 |
+
def __init__(
|
| 824 |
+
self,
|
| 825 |
+
sem_in_channels,
|
| 826 |
+
patch_size=2,
|
| 827 |
+
hidden_size=1152,
|
| 828 |
+
depth=28,
|
| 829 |
+
num_heads=16,
|
| 830 |
+
mlp_ratio=4.0,
|
| 831 |
+
max_num_frames=6,
|
| 832 |
+
dropout=0.1,
|
| 833 |
+
ctx_noise_aug_ratio=0.1,
|
| 834 |
+
ctx_noise_aug_prob=0.5,
|
| 835 |
+
drop_ctx_rate=0.2,
|
| 836 |
+
frequency_range=(2, 15),
|
| 837 |
+
causal_time_attn=False,
|
| 838 |
+
modulate_time_attn=False,
|
| 839 |
+
norm_layer=nn.LayerNorm,
|
| 840 |
+
mlp_block='mlp',
|
| 841 |
+
**kwargs,
|
| 842 |
+
):
|
| 843 |
+
if isinstance(norm_layer, str):
|
| 844 |
+
norm_layer = get_norm_layer(norm_layer)
|
| 845 |
+
|
| 846 |
+
super().__init__(
|
| 847 |
+
patch_size=patch_size,
|
| 848 |
+
hidden_size=hidden_size,
|
| 849 |
+
depth=depth,
|
| 850 |
+
num_heads=num_heads,
|
| 851 |
+
mlp_ratio=mlp_ratio,
|
| 852 |
+
max_num_frames=max_num_frames,
|
| 853 |
+
dropout=dropout,
|
| 854 |
+
ctx_noise_aug_ratio=ctx_noise_aug_ratio,
|
| 855 |
+
ctx_noise_aug_prob=ctx_noise_aug_prob,
|
| 856 |
+
drop_ctx_rate=drop_ctx_rate,
|
| 857 |
+
frequency_range=frequency_range,
|
| 858 |
+
causal_time_attn=causal_time_attn,
|
| 859 |
+
modulate_time_attn=modulate_time_attn,
|
| 860 |
+
norm_layer=norm_layer,
|
| 861 |
+
mlp_block=mlp_block,
|
| 862 |
+
**kwargs,
|
| 863 |
+
)
|
| 864 |
+
|
| 865 |
+
# Replace blocks with spatially-conditioned variants
|
| 866 |
+
self.blocks = nn.ModuleList([
|
| 867 |
+
STDiTBlockWithSpatialL2(
|
| 868 |
+
hidden_size, num_heads,
|
| 869 |
+
mlp_ratio=mlp_ratio,
|
| 870 |
+
dropout_rate=dropout,
|
| 871 |
+
causal_time_attn=causal_time_attn,
|
| 872 |
+
modulate_time_attn=modulate_time_attn,
|
| 873 |
+
norm_layer=norm_layer,
|
| 874 |
+
mlp_block=mlp_block,
|
| 875 |
+
)
|
| 876 |
+
for _ in range(depth)
|
| 877 |
+
])
|
| 878 |
+
|
| 879 |
+
# Patchify L2 latent: patch_size^2 * sem_in_channels → hidden_size
|
| 880 |
+
# Xavier init so the patchified tokens carry real signal from step 1.
|
| 881 |
+
self.l2_patchify = nn.Linear(patch_size ** 2 * sem_in_channels, hidden_size, bias=True)
|
| 882 |
+
nn.init.xavier_uniform_(self.l2_patchify.weight)
|
| 883 |
+
nn.init.zeros_(self.l2_patchify.bias)
|
| 884 |
+
|
| 885 |
+
# Frame-position embedding: scalar in [0,1] → hidden_size
|
| 886 |
+
# Xavier init so position differences produce non-trivial embeddings from step 1.
|
| 887 |
+
self.position_proj = nn.Sequential(
|
| 888 |
+
nn.Linear(1, hidden_size),
|
| 889 |
+
nn.SiLU(),
|
| 890 |
+
nn.Linear(hidden_size, hidden_size),
|
| 891 |
+
)
|
| 892 |
+
for m in self.position_proj.modules():
|
| 893 |
+
if isinstance(m, nn.Linear):
|
| 894 |
+
nn.init.xavier_uniform_(m.weight)
|
| 895 |
+
nn.init.zeros_(m.bias)
|
| 896 |
+
|
| 897 |
+
def _patchify_l2(self, z_l2):
|
| 898 |
+
"""
|
| 899 |
+
Patchify L2 latent frames.
|
| 900 |
+
|
| 901 |
+
Args:
|
| 902 |
+
z_l2: (B, F, C_sem, H, W)
|
| 903 |
+
Returns:
|
| 904 |
+
(B, F, N, hidden_size) where N = (H/patch_size) * (W/patch_size)
|
| 905 |
+
"""
|
| 906 |
+
b, f, c, h, w = z_l2.shape
|
| 907 |
+
p = self.patch_size
|
| 908 |
+
z = rearrange(z_l2, 'b f c (h p1) (w p2) -> (b f) (h w) (p1 p2 c)', p1=p, p2=p)
|
| 909 |
+
z = self.l2_patchify(z) # (B*F, N, hidden_size)
|
| 910 |
+
return rearrange(z, '(b f) n d -> b f n d', b=b, f=f)
|
| 911 |
+
|
| 912 |
+
def _build_spatial_cond(self, z_l2_start, z_l2_end, num_frames):
|
| 913 |
+
"""
|
| 914 |
+
Build per-frame L2 spatial tokens with position embedding.
|
| 915 |
+
|
| 916 |
+
Args:
|
| 917 |
+
z_l2_start: (B, C_sem, H, W) — sem latent for frame 0 (context/input)
|
| 918 |
+
z_l2_end: (B, C_sem, H, W) — sem latent for the last frame (target end)
|
| 919 |
+
num_frames: total frames in x (= 1 context + num_pred_frames generated)
|
| 920 |
+
Returns:
|
| 921 |
+
(B, num_frames, N, hidden_size)
|
| 922 |
+
"""
|
| 923 |
+
device = z_l2_start.device
|
| 924 |
+
dtype = z_l2_start.dtype
|
| 925 |
+
num_pred = num_frames - 1 # generated frames indexed 1..num_frames-1
|
| 926 |
+
|
| 927 |
+
z_start_tok = self._patchify_l2(z_l2_start.unsqueeze(1)) # (B, 1, N, D)
|
| 928 |
+
z_end_tok = self._patchify_l2(z_l2_end.unsqueeze(1)) # (B, 1, N, D)
|
| 929 |
+
|
| 930 |
+
frames_cond = []
|
| 931 |
+
for i in range(num_frames):
|
| 932 |
+
if i == 0:
|
| 933 |
+
pos_val = 0.0
|
| 934 |
+
z_tok = z_start_tok
|
| 935 |
+
else:
|
| 936 |
+
pos_val = i / num_pred if num_pred > 0 else 1.0
|
| 937 |
+
z_tok = z_end_tok
|
| 938 |
+
|
| 939 |
+
pos = torch.tensor([[pos_val]], device=device, dtype=dtype)
|
| 940 |
+
pos_emb = self.position_proj(pos) # (1, D)
|
| 941 |
+
# Broadcast over batch and spatial tokens
|
| 942 |
+
z_frame = z_tok + pos_emb.unsqueeze(0) # (B, 1, N, D)
|
| 943 |
+
frames_cond.append(z_frame)
|
| 944 |
+
|
| 945 |
+
return torch.cat(frames_cond, dim=1) # (B, F, N, D)
|
| 946 |
+
|
| 947 |
+
def forward(self, x, t, frame_rate, z_l2_start, z_l2_end, return_features=False):
|
| 948 |
+
"""
|
| 949 |
+
Args:
|
| 950 |
+
x: (B, F, C_rec, H, W) — rec latent (DF style: context + targets)
|
| 951 |
+
t: (B, F) — per-frame diffusion timesteps
|
| 952 |
+
frame_rate: (B,)
|
| 953 |
+
z_l2_start: (B, C_sem, H, W) — clean L2 latent for frame 0
|
| 954 |
+
z_l2_end: (B, C_sem, H, W) — clean L2 latent for last frame
|
| 955 |
+
"""
|
| 956 |
+
b, f = x.shape[:2]
|
| 957 |
+
|
| 958 |
+
t_flat = rearrange(t, 'b f -> (b f)')
|
| 959 |
+
c = self.get_condition_embeddings(t_flat) # (B*F, D)
|
| 960 |
+
c = rearrange(c, '(b f) d -> b f d', b=b, f=f) # (B, F, D)
|
| 961 |
+
|
| 962 |
+
x = self.preprocess_inputs(x, frame_rate) # (B, F, N, D)
|
| 963 |
+
|
| 964 |
+
z_spatial = self._build_spatial_cond(z_l2_start, z_l2_end, f) # (B, F, N, D)
|
| 965 |
+
|
| 966 |
+
features = []
|
| 967 |
+
for block in self.blocks:
|
| 968 |
+
x = block(x, c, z_spatial)
|
| 969 |
+
features.append(x) if return_features else None
|
| 970 |
+
|
| 971 |
+
out = self.final_layer(x, c)
|
| 972 |
+
out = self.postprocess_outputs(out)
|
| 973 |
+
if return_features:
|
| 974 |
+
return out, features
|
| 975 |
+
return out
|
| 976 |
+
|
| 977 |
+
|
| 978 |
+
class STDiTDFSpatialL2_CtxAndLast(STDiTDFSpatialL2):
|
| 979 |
+
"""
|
| 980 |
+
Two-point L2 conditioning: only the last context frame and the last
|
| 981 |
+
predicted frame receive a non-zero L2 conditioning token.
|
| 982 |
+
|
| 983 |
+
Conditioning scheme (K = num_context_frames, F = num_frames):
|
| 984 |
+
Frame K-1 (last context frame): z_l2_start, position = (K-1)/(F-1)
|
| 985 |
+
Frame F-1 (last predicted frame): z_l2_end, position = 1.0
|
| 986 |
+
All other frames: zero token, position = i/(F-1)
|
| 987 |
+
|
| 988 |
+
The zero-token frames still receive a position embedding so the network can
|
| 989 |
+
distinguish their temporal location, but carry no semantic conditioning.
|
| 990 |
+
|
| 991 |
+
Args:
|
| 992 |
+
num_context_frames: number of context frames in the L1 sequence (K).
|
| 993 |
+
Must match the NUM_CONTEXT_FRAMES of the model class.
|
| 994 |
+
All other args forwarded to STDiTDFSpatialL2.
|
| 995 |
+
"""
|
| 996 |
+
|
| 997 |
+
def __init__(self, num_context_frames, **kwargs):
|
| 998 |
+
super().__init__(**kwargs)
|
| 999 |
+
self.num_context_frames = int(num_context_frames)
|
| 1000 |
+
|
| 1001 |
+
def _build_spatial_cond(self, z_l2_start, z_l2_end, num_frames):
|
| 1002 |
+
device = z_l2_start.device
|
| 1003 |
+
dtype = z_l2_start.dtype
|
| 1004 |
+
|
| 1005 |
+
z_start_tok = self._patchify_l2(z_l2_start.unsqueeze(1)) # (B, 1, N, D)
|
| 1006 |
+
z_end_tok = self._patchify_l2(z_l2_end.unsqueeze(1)) # (B, 1, N, D)
|
| 1007 |
+
z_zero_tok = torch.zeros_like(z_start_tok) # (B, 1, N, D)
|
| 1008 |
+
|
| 1009 |
+
ctx_idx = self.num_context_frames - 1
|
| 1010 |
+
|
| 1011 |
+
frames_cond = []
|
| 1012 |
+
for i in range(num_frames):
|
| 1013 |
+
pos_val = i / (num_frames - 1) if num_frames > 1 else 0.0
|
| 1014 |
+
pos = torch.tensor([[pos_val]], device=device, dtype=dtype)
|
| 1015 |
+
pos_emb = self.position_proj(pos) # (1, D)
|
| 1016 |
+
|
| 1017 |
+
if i == ctx_idx:
|
| 1018 |
+
z_tok = z_start_tok
|
| 1019 |
+
elif i == num_frames - 1:
|
| 1020 |
+
z_tok = z_end_tok
|
| 1021 |
+
else:
|
| 1022 |
+
z_tok = z_zero_tok
|
| 1023 |
+
|
| 1024 |
+
frames_cond.append(z_tok + pos_emb.unsqueeze(0)) # (B, 1, N, D)
|
| 1025 |
+
|
| 1026 |
+
return torch.cat(frames_cond, dim=1) # (B, F, N, D)
|
|
@@ -0,0 +1,740 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import inspect
|
| 2 |
+
|
| 3 |
+
import torch
|
| 4 |
+
import torch.nn as nn
|
| 5 |
+
from einops import rearrange
|
| 6 |
+
from omegaconf import ListConfig
|
| 7 |
+
from timm.models.vision_transformer import PatchEmbed
|
| 8 |
+
|
| 9 |
+
# Returns True when executing inside a torch.compile region (PyTorch >= 2.1).
|
| 10 |
+
# Guarding side-effect dict writes with this prevents graph breaks during compiled inference.
|
| 11 |
+
_is_compiling = getattr(torch.compiler, 'is_compiling', lambda: False)
|
| 12 |
+
|
| 13 |
+
from modules.dit import (
|
| 14 |
+
FinalLayer,
|
| 15 |
+
FrequencyEncoder,
|
| 16 |
+
TimestepEmbedder,
|
| 17 |
+
get_2d_sincos_pos_embed,
|
| 18 |
+
get_norm_layer,
|
| 19 |
+
)
|
| 20 |
+
from util import get_obj_from_str, instantiate_from_config
|
| 21 |
+
|
| 22 |
+
|
| 23 |
+
def _signature_accepts_kwargs(cls):
|
| 24 |
+
return any(param.kind == inspect.Parameter.VAR_KEYWORD for param in inspect.signature(cls.__init__).parameters.values())
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
def _accepted_init_param_names(cls):
|
| 28 |
+
names = set()
|
| 29 |
+
for base_cls in inspect.getmro(cls):
|
| 30 |
+
if base_cls is object:
|
| 31 |
+
continue
|
| 32 |
+
signature = inspect.signature(base_cls.__init__)
|
| 33 |
+
for name, param in signature.parameters.items():
|
| 34 |
+
if name == "self" or param.kind == inspect.Parameter.VAR_KEYWORD:
|
| 35 |
+
continue
|
| 36 |
+
names.add(name)
|
| 37 |
+
return names
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
def _embed_timesteps_with(t, t_embedder):
|
| 41 |
+
if t.ndim == 1:
|
| 42 |
+
return t_embedder(t)
|
| 43 |
+
if t.ndim == 2:
|
| 44 |
+
b, f = t.shape
|
| 45 |
+
c = t_embedder(rearrange(t, "b f -> (b f)"))
|
| 46 |
+
return rearrange(c, "(b f) d -> b f d", b=b, f=f)
|
| 47 |
+
raise ValueError(f"Unsupported timestep shape: {t.shape}")
|
| 48 |
+
|
| 49 |
+
|
| 50 |
+
class GlobalAdditiveConditioner(nn.Module):
|
| 51 |
+
"""
|
| 52 |
+
Sum optional timestep embeddings and extra global condition embeddings.
|
| 53 |
+
|
| 54 |
+
The DiT module continues to own `t_embedder` for checkpoint compatibility. Extra
|
| 55 |
+
condition embedders are instantiated here and consume keyworded condition tensors.
|
| 56 |
+
"""
|
| 57 |
+
|
| 58 |
+
def __init__(self, hidden_size, condition_configs=None):
|
| 59 |
+
super().__init__()
|
| 60 |
+
self.hidden_size = hidden_size
|
| 61 |
+
self.condition_embedders = nn.ModuleDict()
|
| 62 |
+
|
| 63 |
+
condition_configs = condition_configs or {}
|
| 64 |
+
for condition_name, embedder_config in condition_configs.items():
|
| 65 |
+
self.condition_embedders[condition_name] = instantiate_from_config(embedder_config)
|
| 66 |
+
|
| 67 |
+
def _reduce_global_condition_embedding(self, condition_name, embedding):
|
| 68 |
+
if embedding.ndim == 2:
|
| 69 |
+
return embedding
|
| 70 |
+
if embedding.ndim == 3 and embedding.shape[1] == 1:
|
| 71 |
+
return embedding[:, 0]
|
| 72 |
+
raise ValueError(
|
| 73 |
+
f"GlobalAdditiveConditioner only supports global condition embeddings with shape [B, H] "
|
| 74 |
+
f"or [B, 1, H]. Condition `{condition_name}` produced {tuple(embedding.shape)}."
|
| 75 |
+
)
|
| 76 |
+
|
| 77 |
+
def _combine_condition_embedding(self, conditioning, condition_embedding):
|
| 78 |
+
if torch.is_tensor(conditioning) and conditioning.ndim == 3 and condition_embedding.ndim == 2:
|
| 79 |
+
raise ValueError(f"{type(self).__name__} got per-frame conditioning {tuple(conditioning.shape)} but a global embedding {tuple(condition_embedding.shape)}; use PerFrameSequenceGlobalAdditiveConditioner for per-frame timestep conditioning.")
|
| 80 |
+
return conditioning + condition_embedding
|
| 81 |
+
|
| 82 |
+
def forward(self, *, t=None, t_embedder=None, **condition_kwargs):
|
| 83 |
+
assert (t is None) == (t_embedder is None)
|
| 84 |
+
conditioning = 0 if t is None else _embed_timesteps_with(t, t_embedder)
|
| 85 |
+
if not _is_compiling():
|
| 86 |
+
self._last_condition_embeddings = {}
|
| 87 |
+
for condition_name, embedder in self.condition_embedders.items():
|
| 88 |
+
condition_embedding = embedder(condition_kwargs[condition_name])
|
| 89 |
+
condition_embedding = self._reduce_global_condition_embedding(condition_name, condition_embedding)
|
| 90 |
+
if not _is_compiling():
|
| 91 |
+
self._last_condition_embeddings[condition_name] = condition_embedding.detach()
|
| 92 |
+
conditioning = self._combine_condition_embedding(conditioning, condition_embedding)
|
| 93 |
+
return conditioning
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
class SequenceGlobalAdditiveConditioner(GlobalAdditiveConditioner):
|
| 97 |
+
"""
|
| 98 |
+
Create a single global conditioning vector from sequence-structured conditions.
|
| 99 |
+
|
| 100 |
+
Each condition embedder may return embeddings with shape [B, K, H].
|
| 101 |
+
Optionally, a positional embedding can be added to each of the K embeddings before aggregation.
|
| 102 |
+
This class aggregates across K before adding the result to the timestep embedding.
|
| 103 |
+
"""
|
| 104 |
+
|
| 105 |
+
def __init__(self, hidden_size, condition_configs=None, aggregation="sum", add_positional_embedding=False, sequence_length=None):
|
| 106 |
+
super().__init__(hidden_size=hidden_size, condition_configs=condition_configs)
|
| 107 |
+
if aggregation not in {"sum", "mean"}:
|
| 108 |
+
raise ValueError(f"Unsupported aggregation `{aggregation}`. Expected one of: sum, mean.")
|
| 109 |
+
self.aggregation = aggregation
|
| 110 |
+
self.add_positional_embedding = add_positional_embedding
|
| 111 |
+
self.sequence_length = sequence_length
|
| 112 |
+
if add_positional_embedding:
|
| 113 |
+
if sequence_length is None: raise ValueError("sequence_length must be specified if add_positional_embedding is True.")
|
| 114 |
+
# One learnable positional embedding table per condition_name.
|
| 115 |
+
# Assumes self.condition_configs exists and is iterable/dict-like from parent.
|
| 116 |
+
self.positional_embeddings = nn.ParameterDict()
|
| 117 |
+
for condition_name in self.condition_embedders.keys():
|
| 118 |
+
pe = nn.Parameter(torch.zeros(sequence_length, hidden_size))
|
| 119 |
+
nn.init.normal_(pe, mean=0.0, std=0.02)
|
| 120 |
+
self.positional_embeddings[condition_name] = pe
|
| 121 |
+
else:
|
| 122 |
+
self.positional_embeddings = None
|
| 123 |
+
|
| 124 |
+
def _reduce_global_condition_embedding(self, condition_name, embedding):
|
| 125 |
+
if embedding.ndim == 2:
|
| 126 |
+
return embedding
|
| 127 |
+
if embedding.ndim != 3:
|
| 128 |
+
raise ValueError(
|
| 129 |
+
f"SequenceGlobalAdditiveConditioner expects condition `{condition_name}` to produce "
|
| 130 |
+
f"shape [B, H] or [B, K, H], got {tuple(embedding.shape)}."
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
# Enforce positional embeddings for true sequences (K > 1)
|
| 134 |
+
k = embedding.shape[1]
|
| 135 |
+
if k > 1 and not self.add_positional_embedding:
|
| 136 |
+
raise ValueError(
|
| 137 |
+
f"Condition `{condition_name}` produced sequence embedding with K={k}, but "
|
| 138 |
+
f"`add_positional_embedding=False`. Positional embedding must be enabled when K > 1."
|
| 139 |
+
)
|
| 140 |
+
|
| 141 |
+
if self.add_positional_embedding:
|
| 142 |
+
embedding = self._add_positional_embeddings(condition_name, embedding)
|
| 143 |
+
|
| 144 |
+
if self.aggregation == "sum":
|
| 145 |
+
return embedding.sum(dim=1)
|
| 146 |
+
return embedding.mean(dim=1)
|
| 147 |
+
|
| 148 |
+
def _add_positional_embeddings(self, condition_name, embedding):
|
| 149 |
+
"""
|
| 150 |
+
embedding: [B, K, H]
|
| 151 |
+
returns: [B, K, H] with learnable positional embeddings added
|
| 152 |
+
"""
|
| 153 |
+
if embedding.ndim != 3:
|
| 154 |
+
raise ValueError(
|
| 155 |
+
f"_add_positional_embeddings expects [B, K, H] for `{condition_name}`, "
|
| 156 |
+
f"got {tuple(embedding.shape)}."
|
| 157 |
+
)
|
| 158 |
+
|
| 159 |
+
b, k, h = embedding.shape
|
| 160 |
+
|
| 161 |
+
if h != self.hidden_size:
|
| 162 |
+
raise ValueError(
|
| 163 |
+
f"Hidden size mismatch for condition `{condition_name}`: "
|
| 164 |
+
f"embedding has H={h}, expected hidden_size={self.hidden_size}."
|
| 165 |
+
)
|
| 166 |
+
|
| 167 |
+
if self.positional_embeddings is None:
|
| 168 |
+
raise RuntimeError(
|
| 169 |
+
"Positional embedding is not initialized. "
|
| 170 |
+
"Set add_positional_embedding=True in constructor."
|
| 171 |
+
)
|
| 172 |
+
|
| 173 |
+
if k > self.sequence_length:
|
| 174 |
+
raise ValueError(
|
| 175 |
+
f"Condition `{condition_name}` has sequence length K={k}, "
|
| 176 |
+
f"but configured sequence_length={self.sequence_length}. "
|
| 177 |
+
f"Increase sequence_length or truncate inputs."
|
| 178 |
+
)
|
| 179 |
+
|
| 180 |
+
# [K, H] -> [1, K, H], broadcast over batch
|
| 181 |
+
pos = self.positional_embeddings[condition_name][:k].unsqueeze(0)
|
| 182 |
+
pos = pos.to(device=embedding.device, dtype=embedding.dtype)
|
| 183 |
+
return embedding + pos
|
| 184 |
+
|
| 185 |
+
|
| 186 |
+
class PerFrameSequenceGlobalAdditiveConditioner(SequenceGlobalAdditiveConditioner):
|
| 187 |
+
"""
|
| 188 |
+
Same as SequenceGlobalAdditiveConditioner, but broadcasts global [B, H] condition
|
| 189 |
+
embeddings across frames when timestep conditioning is per-frame (conditioning is
|
| 190 |
+
[B, F, H] instead of [B, H]), instead of colliding F against B.
|
| 191 |
+
"""
|
| 192 |
+
|
| 193 |
+
def _combine_condition_embedding(self, conditioning, condition_embedding):
|
| 194 |
+
if torch.is_tensor(conditioning) and conditioning.ndim == 3:
|
| 195 |
+
condition_embedding = condition_embedding.unsqueeze(1) # [B, H] -> [B, 1, H]
|
| 196 |
+
return conditioning + condition_embedding
|
| 197 |
+
|
| 198 |
+
|
| 199 |
+
class DiT(nn.Module):
|
| 200 |
+
"""
|
| 201 |
+
Generic DiT backbone with configurable block construction and execution layout.
|
| 202 |
+
|
| 203 |
+
The model does not distinguish target and context inputs. Forward accepts only a
|
| 204 |
+
generic sequence of frames `x` with shape [B, F, C, H, W].
|
| 205 |
+
"""
|
| 206 |
+
|
| 207 |
+
def __init__(
|
| 208 |
+
self,
|
| 209 |
+
input_size=16,
|
| 210 |
+
patch_size=2,
|
| 211 |
+
in_channels=32,
|
| 212 |
+
hidden_size=1152,
|
| 213 |
+
depth=28,
|
| 214 |
+
num_heads=16,
|
| 215 |
+
mlp_ratio=4.0,
|
| 216 |
+
max_num_frames=6,
|
| 217 |
+
dropout=0.0,
|
| 218 |
+
frequency_range=(2, 15),
|
| 219 |
+
learn_sigma=False,
|
| 220 |
+
norm_layer="layer_norm",
|
| 221 |
+
mlp_block="mlp",
|
| 222 |
+
block_config=None,
|
| 223 |
+
conditioner_config=None,
|
| 224 |
+
frame_embedding_alignment="suffix",
|
| 225 |
+
ctx_noise_aug_ratio=0.0,
|
| 226 |
+
ctx_noise_aug_prob=0.0,
|
| 227 |
+
drop_ctx_rate=0.0,
|
| 228 |
+
log_adaln_mean_abs=False,
|
| 229 |
+
log_adaln_grad=False,
|
| 230 |
+
adaln_gate_init_std=0.0,
|
| 231 |
+
train_steering_adaln_only=False,
|
| 232 |
+
):
|
| 233 |
+
super().__init__()
|
| 234 |
+
self._validate_legacy_context_args(
|
| 235 |
+
ctx_noise_aug_ratio=ctx_noise_aug_ratio,
|
| 236 |
+
ctx_noise_aug_prob=ctx_noise_aug_prob,
|
| 237 |
+
drop_ctx_rate=drop_ctx_rate,
|
| 238 |
+
)
|
| 239 |
+
|
| 240 |
+
if isinstance(norm_layer, str):
|
| 241 |
+
norm_layer = get_norm_layer(norm_layer)
|
| 242 |
+
|
| 243 |
+
self.input_size = input_size if isinstance(input_size, (list, tuple, ListConfig)) else [input_size, input_size]
|
| 244 |
+
self.in_channels = in_channels
|
| 245 |
+
self.hidden_size = hidden_size
|
| 246 |
+
self.depth = depth
|
| 247 |
+
self.num_heads = num_heads
|
| 248 |
+
self.mlp_ratio = mlp_ratio
|
| 249 |
+
self.dropout = dropout
|
| 250 |
+
self.max_num_frames = max_num_frames
|
| 251 |
+
self.norm_layer = norm_layer
|
| 252 |
+
self.mlp_block = mlp_block
|
| 253 |
+
self.log_adaln_mean_abs = bool(log_adaln_mean_abs)
|
| 254 |
+
self.log_adaln_grad = bool(log_adaln_grad)
|
| 255 |
+
self.adaln_gate_init_std = float(adaln_gate_init_std)
|
| 256 |
+
self._last_adaln_mean_abs = {}
|
| 257 |
+
self.frame_embedding_alignment = self._normalize_frame_embedding_alignment(
|
| 258 |
+
frame_embedding_alignment
|
| 259 |
+
)
|
| 260 |
+
|
| 261 |
+
self.out_channels = in_channels * 2 if learn_sigma else in_channels
|
| 262 |
+
self.patch_size = patch_size
|
| 263 |
+
|
| 264 |
+
self.x_embedder = PatchEmbed(input_size, patch_size, in_channels, hidden_size, bias=True)
|
| 265 |
+
self.num_patches = self.x_embedder.num_patches
|
| 266 |
+
self.t_embedder = TimestepEmbedder(hidden_size)
|
| 267 |
+
self.conditioner = self._build_conditioner(conditioner_config)
|
| 268 |
+
self.pos_embed = nn.Parameter(torch.zeros(1, self.num_patches, hidden_size), requires_grad=False)
|
| 269 |
+
self.frame_emb = nn.init.trunc_normal_(nn.Parameter(torch.zeros(1, self.max_num_frames, 1, hidden_size)), 0.0, 0.02)
|
| 270 |
+
self.frame_rate_encoder = FrequencyEncoder(hidden_size, freq_min=frequency_range[0], freq_max=frequency_range[1])
|
| 271 |
+
|
| 272 |
+
if block_config is None:
|
| 273 |
+
raise ValueError("DiT v2 expects block_config in instantiate_from_config format.")
|
| 274 |
+
if "target" not in block_config:
|
| 275 |
+
raise KeyError("block_config must define `target`.")
|
| 276 |
+
|
| 277 |
+
self.block_config = block_config
|
| 278 |
+
self.block_cls = get_obj_from_str(block_config["target"])
|
| 279 |
+
self.blocks = self._build_blocks()
|
| 280 |
+
self._configure_block_logging()
|
| 281 |
+
|
| 282 |
+
self.final_layer = FinalLayer(hidden_size, patch_size, self.out_channels, norm_layer=norm_layer)
|
| 283 |
+
self.initialize_weights()
|
| 284 |
+
|
| 285 |
+
if train_steering_adaln_only:
|
| 286 |
+
self._freeze_non_steering_adaln_parameters()
|
| 287 |
+
|
| 288 |
+
def _freeze_non_steering_adaln_parameters(self):
|
| 289 |
+
self.requires_grad_(False)
|
| 290 |
+
self.t_embedder.requires_grad_(True)
|
| 291 |
+
cond_embedders = getattr(self.conditioner, "condition_embedders", {})
|
| 292 |
+
if "steering" in cond_embedders:
|
| 293 |
+
cond_embedders["steering"].requires_grad_(True)
|
| 294 |
+
for block in self.blocks:
|
| 295 |
+
block.adaLN_modulation.requires_grad_(True)
|
| 296 |
+
if hasattr(block, "adaLN_time_attn_modulation"):
|
| 297 |
+
block.adaLN_time_attn_modulation.requires_grad_(True)
|
| 298 |
+
self.final_layer.adaLN_modulation.requires_grad_(True)
|
| 299 |
+
|
| 300 |
+
def _validate_legacy_context_args(self, *, ctx_noise_aug_ratio, ctx_noise_aug_prob, drop_ctx_rate):
|
| 301 |
+
unsupported = {
|
| 302 |
+
"ctx_noise_aug_ratio": ctx_noise_aug_ratio,
|
| 303 |
+
"ctx_noise_aug_prob": ctx_noise_aug_prob,
|
| 304 |
+
"drop_ctx_rate": drop_ctx_rate,
|
| 305 |
+
}
|
| 306 |
+
non_zero = {name: value for name, value in unsupported.items() if value not in (None, 0, 0.0)}
|
| 307 |
+
if non_zero:
|
| 308 |
+
raise ValueError(
|
| 309 |
+
"DiT v2 does not implement context dropping or context noise augmentation. "
|
| 310 |
+
f"Received: {non_zero}"
|
| 311 |
+
)
|
| 312 |
+
|
| 313 |
+
def _normalize_frame_embedding_alignment(self, frame_embedding_alignment):
|
| 314 |
+
alignment = str(frame_embedding_alignment).strip().lower()
|
| 315 |
+
if alignment not in {"suffix", "prefix"}:
|
| 316 |
+
raise ValueError(
|
| 317 |
+
"frame_embedding_alignment must be one of {'suffix', 'prefix'}, "
|
| 318 |
+
f"got {frame_embedding_alignment!r}."
|
| 319 |
+
)
|
| 320 |
+
return alignment
|
| 321 |
+
|
| 322 |
+
def _get_frame_embeddings(self, num_frames, device):
|
| 323 |
+
if self.frame_embedding_alignment == "prefix":
|
| 324 |
+
return self.frame_emb[:, :num_frames].to(device)
|
| 325 |
+
return self.frame_emb[:, self.max_num_frames - num_frames :].to(device)
|
| 326 |
+
|
| 327 |
+
def _get_common_block_kwargs(self, layer_idx):
|
| 328 |
+
return {
|
| 329 |
+
"hidden_size": self.hidden_size,
|
| 330 |
+
"num_heads": self.num_heads,
|
| 331 |
+
"mlp_ratio": self.mlp_ratio,
|
| 332 |
+
"dropout_rate": self.dropout,
|
| 333 |
+
"norm_layer": self.norm_layer,
|
| 334 |
+
"mlp_block": self.mlp_block,
|
| 335 |
+
"input_shape": self.input_size,
|
| 336 |
+
"layer_idx": layer_idx,
|
| 337 |
+
}
|
| 338 |
+
|
| 339 |
+
def _build_single_block(self, layer_idx):
|
| 340 |
+
accepts_kwargs = _signature_accepts_kwargs(self.block_cls)
|
| 341 |
+
common_kwargs = self._get_common_block_kwargs(layer_idx)
|
| 342 |
+
user_kwargs = dict(self.block_config.get("params", {}))
|
| 343 |
+
|
| 344 |
+
valid_names = _accepted_init_param_names(self.block_cls)
|
| 345 |
+
if not accepts_kwargs:
|
| 346 |
+
unknown_user_kwargs = sorted(set(user_kwargs) - valid_names)
|
| 347 |
+
if unknown_user_kwargs:
|
| 348 |
+
raise ValueError(
|
| 349 |
+
f"Unsupported block parameters for {self.block_cls.__name__}: {unknown_user_kwargs}"
|
| 350 |
+
)
|
| 351 |
+
|
| 352 |
+
init_kwargs = {}
|
| 353 |
+
for name, value in common_kwargs.items():
|
| 354 |
+
if name in valid_names:
|
| 355 |
+
init_kwargs[name] = value
|
| 356 |
+
init_kwargs.update(user_kwargs)
|
| 357 |
+
return instantiate_from_config(
|
| 358 |
+
{
|
| 359 |
+
"target": self.block_config["target"],
|
| 360 |
+
"params": init_kwargs,
|
| 361 |
+
}
|
| 362 |
+
)
|
| 363 |
+
|
| 364 |
+
def _build_blocks(self):
|
| 365 |
+
return nn.ModuleList([self._build_single_block(layer_idx) for layer_idx in range(self.depth)])
|
| 366 |
+
|
| 367 |
+
def _configure_block_logging(self):
|
| 368 |
+
for block in self.blocks:
|
| 369 |
+
if hasattr(block, "log_adaln_mean_abs"):
|
| 370 |
+
block.log_adaln_mean_abs = self.log_adaln_mean_abs
|
| 371 |
+
|
| 372 |
+
def _build_conditioner(self, conditioner_config):
|
| 373 |
+
if conditioner_config is None:
|
| 374 |
+
return None
|
| 375 |
+
|
| 376 |
+
params = dict(conditioner_config.get("params", {}))
|
| 377 |
+
params.setdefault("hidden_size", self.hidden_size)
|
| 378 |
+
return instantiate_from_config(
|
| 379 |
+
{
|
| 380 |
+
"target": conditioner_config["target"],
|
| 381 |
+
"params": params,
|
| 382 |
+
}
|
| 383 |
+
)
|
| 384 |
+
|
| 385 |
+
def initialize_weights(self):
|
| 386 |
+
def _basic_init(module):
|
| 387 |
+
if isinstance(module, nn.Linear):
|
| 388 |
+
nn.init.xavier_uniform_(module.weight)
|
| 389 |
+
if module.bias is not None:
|
| 390 |
+
nn.init.constant_(module.bias, 0)
|
| 391 |
+
|
| 392 |
+
self.apply(_basic_init)
|
| 393 |
+
|
| 394 |
+
pos_embed = get_2d_sincos_pos_embed(
|
| 395 |
+
self.pos_embed.shape[-1],
|
| 396 |
+
[self.input_size[0] // self.patch_size, self.input_size[1] // self.patch_size],
|
| 397 |
+
cls_token=False,
|
| 398 |
+
extra_tokens=0,
|
| 399 |
+
)
|
| 400 |
+
self.pos_embed.data.copy_(torch.from_numpy(pos_embed).float().unsqueeze(0))
|
| 401 |
+
|
| 402 |
+
w = self.x_embedder.proj.weight.data
|
| 403 |
+
nn.init.xavier_uniform_(w.view([w.shape[0], -1]))
|
| 404 |
+
nn.init.constant_(self.x_embedder.proj.bias, 0)
|
| 405 |
+
|
| 406 |
+
nn.init.normal_(self.t_embedder.mlp[0].weight, std=0.02)
|
| 407 |
+
nn.init.normal_(self.t_embedder.mlp[2].weight, std=0.02)
|
| 408 |
+
|
| 409 |
+
for block in self.blocks:
|
| 410 |
+
if hasattr(block, "initialize_adaln_weights"):
|
| 411 |
+
block.initialize_adaln_weights(self.adaln_gate_init_std)
|
| 412 |
+
|
| 413 |
+
nn.init.constant_(self.final_layer.adaLN_modulation[-1].weight, 0)
|
| 414 |
+
nn.init.constant_(self.final_layer.adaLN_modulation[-1].bias, 0)
|
| 415 |
+
nn.init.constant_(self.final_layer.linear.weight, 0)
|
| 416 |
+
nn.init.constant_(self.final_layer.linear.bias, 0)
|
| 417 |
+
|
| 418 |
+
def unpatchify(self, x):
|
| 419 |
+
c = self.out_channels
|
| 420 |
+
p = self.x_embedder.patch_size[0]
|
| 421 |
+
h = self.x_embedder.grid_size[0]
|
| 422 |
+
w = self.x_embedder.grid_size[1]
|
| 423 |
+
|
| 424 |
+
x = x.reshape(shape=(x.shape[0], x.shape[1], h, w, p, p, c))
|
| 425 |
+
x = torch.einsum("bfhwpqc->bfchpwq", x)
|
| 426 |
+
imgs = x.reshape(shape=(x.shape[0], x.shape[1], c, h * p, w * p))
|
| 427 |
+
return imgs
|
| 428 |
+
|
| 429 |
+
def postprocess_outputs(self, out):
|
| 430 |
+
return self.unpatchify(out)
|
| 431 |
+
|
| 432 |
+
def get_condition_embeddings(self, t, **_kwargs):
|
| 433 |
+
if self.conditioner is None:
|
| 434 |
+
return _embed_timesteps_with(t, self.t_embedder)
|
| 435 |
+
return self.conditioner(t=t, t_embedder=self.t_embedder, **_kwargs)
|
| 436 |
+
|
| 437 |
+
def _get_frame_rate_embeddings(self, frame_rate, batch_size, num_frames, device):
|
| 438 |
+
if frame_rate is None:
|
| 439 |
+
return torch.zeros(batch_size, 1, 1, self.hidden_size, device=device)
|
| 440 |
+
|
| 441 |
+
frame_rate_embeddings = self.frame_rate_encoder.encode(frame_rate).to(device)
|
| 442 |
+
if frame_rate_embeddings.ndim != 2 or frame_rate_embeddings.shape[0] != batch_size:
|
| 443 |
+
raise ValueError(
|
| 444 |
+
"frame_rate must have shape [B]. "
|
| 445 |
+
f"Received embedding source with shape {tuple(frame_rate.shape)}"
|
| 446 |
+
)
|
| 447 |
+
return frame_rate_embeddings.unsqueeze(1).unsqueeze(1)
|
| 448 |
+
|
| 449 |
+
def embed_inputs(self, x, frame_rate=None):
|
| 450 |
+
if x.ndim != 5:
|
| 451 |
+
raise ValueError(f"Expected x to have shape [B, F, C, H, W], got {tuple(x.shape)}")
|
| 452 |
+
|
| 453 |
+
b, f = x.shape[:2]
|
| 454 |
+
if f > self.max_num_frames:
|
| 455 |
+
raise ValueError(f"Received {f} frames, but max_num_frames={self.max_num_frames}")
|
| 456 |
+
|
| 457 |
+
frame_embeddings = self._get_frame_rate_embeddings(frame_rate, batch_size=b, num_frames=f, device=x.device)
|
| 458 |
+
x = rearrange(x, "b f c h w -> (b f) c h w")
|
| 459 |
+
x = self.x_embedder(x) + self.pos_embed.to(x.device)
|
| 460 |
+
x = rearrange(x, "(b f) hw c -> b f hw c", b=b, f=f)
|
| 461 |
+
x = x + self._get_frame_embeddings(f, x.device) + frame_embeddings
|
| 462 |
+
return x
|
| 463 |
+
|
| 464 |
+
def run_blocks(self, x, c, return_features=False):
|
| 465 |
+
features = []
|
| 466 |
+
adaln_stats = {}
|
| 467 |
+
for block_idx, block in enumerate(self.blocks):
|
| 468 |
+
x = block(x, c)
|
| 469 |
+
if self.log_adaln_mean_abs:
|
| 470 |
+
block_stats = getattr(block, "_last_adaln_mean_abs", None)
|
| 471 |
+
if block_stats:
|
| 472 |
+
adaln_stats.update(
|
| 473 |
+
{
|
| 474 |
+
f"block_{block_idx:02d}/{name}": value
|
| 475 |
+
for name, value in block_stats.items()
|
| 476 |
+
}
|
| 477 |
+
)
|
| 478 |
+
if return_features:
|
| 479 |
+
features.append(x)
|
| 480 |
+
if self.log_adaln_mean_abs:
|
| 481 |
+
self._last_adaln_mean_abs = adaln_stats
|
| 482 |
+
return x, features
|
| 483 |
+
|
| 484 |
+
def get_last_adaln_mean_abs(self):
|
| 485 |
+
return self._last_adaln_mean_abs
|
| 486 |
+
|
| 487 |
+
def get_last_condition_embeddings(self):
|
| 488 |
+
if self.conditioner is None:
|
| 489 |
+
return {}
|
| 490 |
+
return dict(getattr(self.conditioner, '_last_condition_embeddings', {}))
|
| 491 |
+
|
| 492 |
+
def _iter_conditioners(self):
|
| 493 |
+
if self.conditioner is not None:
|
| 494 |
+
yield ("", self.conditioner)
|
| 495 |
+
|
| 496 |
+
def get_adaln_grad_stats(self):
|
| 497 |
+
if not self.log_adaln_grad:
|
| 498 |
+
return {}
|
| 499 |
+
stats = {}
|
| 500 |
+
for block_idx, block in enumerate(self.blocks):
|
| 501 |
+
for mod_name in ("adaLN_modulation", "adaLN_time_attn_modulation", "adaLN_registers_modulation"):
|
| 502 |
+
mod = getattr(block, mod_name, None)
|
| 503 |
+
if mod is None:
|
| 504 |
+
continue
|
| 505 |
+
linear = mod[-1]
|
| 506 |
+
for param_name in ("weight", "bias"):
|
| 507 |
+
p = getattr(linear, param_name, None)
|
| 508 |
+
if p is None or p.grad is None:
|
| 509 |
+
continue
|
| 510 |
+
stats[f"block_{block_idx:02d}/{mod_name}/{param_name}"] = p.grad.detach()
|
| 511 |
+
for cond_prefix, conditioner in self._iter_conditioners():
|
| 512 |
+
for cond_name, embedder in conditioner.condition_embedders.items():
|
| 513 |
+
for pname, p in embedder.named_parameters():
|
| 514 |
+
if p.grad is not None:
|
| 515 |
+
stats[f"{cond_prefix}cond_emb/{cond_name}/{pname}"] = p.grad.detach()
|
| 516 |
+
return stats
|
| 517 |
+
|
| 518 |
+
def forward(
|
| 519 |
+
self,
|
| 520 |
+
x,
|
| 521 |
+
t,
|
| 522 |
+
frame_rate=None,
|
| 523 |
+
return_features=False,
|
| 524 |
+
**condition_kwargs,
|
| 525 |
+
):
|
| 526 |
+
c = self.get_condition_embeddings(t, **condition_kwargs)
|
| 527 |
+
x = self.embed_inputs(x, frame_rate=frame_rate)
|
| 528 |
+
x, features = self.run_blocks(x, c, return_features=return_features)
|
| 529 |
+
out = self.final_layer(x, c)
|
| 530 |
+
out = self.postprocess_outputs(out)
|
| 531 |
+
if return_features:
|
| 532 |
+
return out, features
|
| 533 |
+
return out
|
| 534 |
+
|
| 535 |
+
|
| 536 |
+
class DiTWithRegisters(DiT):
|
| 537 |
+
def __init__(self, registers_conditioner_config=None, train_steering_adaln_only=False, **kwargs):
|
| 538 |
+
super().__init__(train_steering_adaln_only=False, **kwargs)
|
| 539 |
+
self.registers_conditioner = self._build_conditioner(registers_conditioner_config)
|
| 540 |
+
if self.registers_conditioner is not None:
|
| 541 |
+
self.registers_conditioner.apply(self._initialize_linear_module)
|
| 542 |
+
if train_steering_adaln_only:
|
| 543 |
+
self._freeze_non_steering_adaln_parameters()
|
| 544 |
+
|
| 545 |
+
@staticmethod
|
| 546 |
+
def _initialize_linear_module(module):
|
| 547 |
+
if isinstance(module, nn.Linear):
|
| 548 |
+
nn.init.xavier_uniform_(module.weight)
|
| 549 |
+
if module.bias is not None:
|
| 550 |
+
nn.init.constant_(module.bias, 0)
|
| 551 |
+
|
| 552 |
+
def _freeze_non_steering_adaln_parameters(self):
|
| 553 |
+
self.requires_grad_(False)
|
| 554 |
+
if self.registers_conditioner is not None:
|
| 555 |
+
self.registers_conditioner.requires_grad_(True)
|
| 556 |
+
for block in self.blocks:
|
| 557 |
+
if hasattr(block, "adaLN_registers_modulation"):
|
| 558 |
+
block.adaLN_registers_modulation.requires_grad_(True)
|
| 559 |
+
|
| 560 |
+
def get_registers_condition_embeddings(self, **condition_kwargs):
|
| 561 |
+
if self.registers_conditioner is None:
|
| 562 |
+
return None
|
| 563 |
+
return self.registers_conditioner(**condition_kwargs)
|
| 564 |
+
|
| 565 |
+
def get_last_condition_embeddings(self):
|
| 566 |
+
result = super().get_last_condition_embeddings()
|
| 567 |
+
if self.registers_conditioner is not None:
|
| 568 |
+
reg_embs = getattr(self.registers_conditioner, '_last_condition_embeddings', {})
|
| 569 |
+
result.update({f"registers/{k}": v for k, v in reg_embs.items()})
|
| 570 |
+
return result
|
| 571 |
+
|
| 572 |
+
def _iter_conditioners(self):
|
| 573 |
+
yield from super()._iter_conditioners()
|
| 574 |
+
if self.registers_conditioner is not None:
|
| 575 |
+
yield ("registers/", self.registers_conditioner)
|
| 576 |
+
|
| 577 |
+
def run_blocks(self, x, c, c_registers=None, return_features=False):
|
| 578 |
+
features = []
|
| 579 |
+
adaln_stats = {}
|
| 580 |
+
for block_idx, block in enumerate(self.blocks):
|
| 581 |
+
if c_registers is None:
|
| 582 |
+
x = block(x, c=c)
|
| 583 |
+
else:
|
| 584 |
+
x = block(x, c=c, c_registers=c_registers)
|
| 585 |
+
if self.log_adaln_mean_abs:
|
| 586 |
+
block_stats = getattr(block, "_last_adaln_mean_abs", None)
|
| 587 |
+
if block_stats:
|
| 588 |
+
adaln_stats.update(
|
| 589 |
+
{
|
| 590 |
+
f"block_{block_idx:02d}/{name}": value
|
| 591 |
+
for name, value in block_stats.items()
|
| 592 |
+
}
|
| 593 |
+
)
|
| 594 |
+
if return_features:
|
| 595 |
+
features.append(x)
|
| 596 |
+
if self.log_adaln_mean_abs:
|
| 597 |
+
self._last_adaln_mean_abs = adaln_stats
|
| 598 |
+
return x, features
|
| 599 |
+
|
| 600 |
+
def forward(
|
| 601 |
+
self,
|
| 602 |
+
x,
|
| 603 |
+
t,
|
| 604 |
+
frame_rate=None,
|
| 605 |
+
return_features=False,
|
| 606 |
+
**condition_kwargs,
|
| 607 |
+
):
|
| 608 |
+
c = self.get_condition_embeddings(t, **condition_kwargs)
|
| 609 |
+
c_registers = self.get_registers_condition_embeddings(**condition_kwargs)
|
| 610 |
+
x = self.embed_inputs(x, frame_rate=frame_rate)
|
| 611 |
+
x, features = self.run_blocks(x, c, c_registers=c_registers, return_features=return_features)
|
| 612 |
+
out = self.final_layer(x, c)
|
| 613 |
+
out = self.postprocess_outputs(out)
|
| 614 |
+
if return_features:
|
| 615 |
+
return out, features
|
| 616 |
+
return out
|
| 617 |
+
|
| 618 |
+
|
| 619 |
+
class SpatialL2CtxAndLastDiT(DiT):
|
| 620 |
+
"""
|
| 621 |
+
v2 DiT equivalent of the v1 STDiTDFSpatialL2_CtxAndLast network.
|
| 622 |
+
|
| 623 |
+
Spatial L2 endpoint latents are patchified and injected into blocks that
|
| 624 |
+
accept `block(x, c, z_spatial)`. Only the last context frame and last frame
|
| 625 |
+
receive non-zero L2 tokens; all frames receive a scalar position embedding.
|
| 626 |
+
"""
|
| 627 |
+
|
| 628 |
+
def __init__(self, sem_in_channels, num_context_frames, frame_embedding_alignment, **kwargs):
|
| 629 |
+
super().__init__(frame_embedding_alignment=frame_embedding_alignment, **kwargs)
|
| 630 |
+
self.sem_in_channels = int(sem_in_channels)
|
| 631 |
+
self.num_context_frames = int(num_context_frames)
|
| 632 |
+
|
| 633 |
+
self.l2_patchify = nn.Linear(
|
| 634 |
+
self.patch_size ** 2 * self.sem_in_channels,
|
| 635 |
+
self.hidden_size,
|
| 636 |
+
bias=True,
|
| 637 |
+
)
|
| 638 |
+
nn.init.xavier_uniform_(self.l2_patchify.weight)
|
| 639 |
+
nn.init.zeros_(self.l2_patchify.bias)
|
| 640 |
+
|
| 641 |
+
self.position_proj = nn.Sequential(
|
| 642 |
+
nn.Linear(1, self.hidden_size),
|
| 643 |
+
nn.SiLU(),
|
| 644 |
+
nn.Linear(self.hidden_size, self.hidden_size),
|
| 645 |
+
)
|
| 646 |
+
for module in self.position_proj.modules():
|
| 647 |
+
if isinstance(module, nn.Linear):
|
| 648 |
+
nn.init.xavier_uniform_(module.weight)
|
| 649 |
+
nn.init.zeros_(module.bias)
|
| 650 |
+
|
| 651 |
+
def _patchify_l2(self, z_l2):
|
| 652 |
+
if z_l2.ndim != 5:
|
| 653 |
+
raise ValueError(f"Expected z_l2 to have shape [B, F, C, H, W], got {tuple(z_l2.shape)}")
|
| 654 |
+
b, f, c, h, w = z_l2.shape
|
| 655 |
+
if c != self.sem_in_channels:
|
| 656 |
+
raise ValueError(
|
| 657 |
+
f"Expected L2 latents with {self.sem_in_channels} channels, got {c}."
|
| 658 |
+
)
|
| 659 |
+
if h % self.patch_size != 0 or w % self.patch_size != 0:
|
| 660 |
+
raise ValueError(
|
| 661 |
+
f"L2 latent spatial size {(h, w)} must be divisible by patch_size={self.patch_size}."
|
| 662 |
+
)
|
| 663 |
+
|
| 664 |
+
z = rearrange(
|
| 665 |
+
z_l2,
|
| 666 |
+
"b f c (h p1) (w p2) -> (b f) (h w) (p1 p2 c)",
|
| 667 |
+
p1=self.patch_size,
|
| 668 |
+
p2=self.patch_size,
|
| 669 |
+
)
|
| 670 |
+
z = self.l2_patchify(z)
|
| 671 |
+
return rearrange(z, "(b f) n d -> b f n d", b=b, f=f)
|
| 672 |
+
|
| 673 |
+
def _build_spatial_cond(self, z_l2_start, z_l2_end, num_frames):
|
| 674 |
+
if z_l2_start is None or z_l2_end is None:
|
| 675 |
+
raise ValueError("SpatialL2CtxAndLastDiT requires z_l2_start and z_l2_end.")
|
| 676 |
+
if z_l2_start.shape != z_l2_end.shape:
|
| 677 |
+
raise ValueError(
|
| 678 |
+
"z_l2_start and z_l2_end must have matching shapes, got "
|
| 679 |
+
f"{tuple(z_l2_start.shape)} and {tuple(z_l2_end.shape)}."
|
| 680 |
+
)
|
| 681 |
+
if self.num_context_frames < 1:
|
| 682 |
+
raise ValueError("num_context_frames must be at least 1.")
|
| 683 |
+
if self.num_context_frames > num_frames:
|
| 684 |
+
raise ValueError(
|
| 685 |
+
f"num_context_frames={self.num_context_frames} exceeds num_frames={num_frames}."
|
| 686 |
+
)
|
| 687 |
+
|
| 688 |
+
device = z_l2_start.device
|
| 689 |
+
dtype = z_l2_start.dtype
|
| 690 |
+
z_start_tok = self._patchify_l2(z_l2_start.unsqueeze(1))
|
| 691 |
+
z_end_tok = self._patchify_l2(z_l2_end.unsqueeze(1))
|
| 692 |
+
z_zero_tok = torch.zeros_like(z_start_tok)
|
| 693 |
+
ctx_idx = self.num_context_frames - 1
|
| 694 |
+
|
| 695 |
+
frames_cond = []
|
| 696 |
+
for frame_idx in range(num_frames):
|
| 697 |
+
pos_val = frame_idx / (num_frames - 1) if num_frames > 1 else 0.0
|
| 698 |
+
pos = torch.tensor([[pos_val]], device=device, dtype=dtype)
|
| 699 |
+
pos_emb = self.position_proj(pos)
|
| 700 |
+
|
| 701 |
+
if frame_idx == ctx_idx:
|
| 702 |
+
z_tok = z_start_tok
|
| 703 |
+
elif frame_idx == num_frames - 1:
|
| 704 |
+
z_tok = z_end_tok
|
| 705 |
+
else:
|
| 706 |
+
z_tok = z_zero_tok
|
| 707 |
+
frames_cond.append(z_tok + pos_emb.unsqueeze(0))
|
| 708 |
+
|
| 709 |
+
return torch.cat(frames_cond, dim=1)
|
| 710 |
+
|
| 711 |
+
def run_spatial_l2_blocks(self, x, c, z_spatial, return_features=False):
|
| 712 |
+
features = []
|
| 713 |
+
for block in self.blocks:
|
| 714 |
+
x = block(x, c, z_spatial)
|
| 715 |
+
if return_features:
|
| 716 |
+
features.append(x)
|
| 717 |
+
return x, features
|
| 718 |
+
|
| 719 |
+
def forward(
|
| 720 |
+
self,
|
| 721 |
+
x,
|
| 722 |
+
t,
|
| 723 |
+
frame_rate=None,
|
| 724 |
+
z_l2_start=None,
|
| 725 |
+
z_l2_end=None,
|
| 726 |
+
return_features=False,
|
| 727 |
+
**condition_kwargs,
|
| 728 |
+
):
|
| 729 |
+
if z_l2_start is None or z_l2_end is None:
|
| 730 |
+
raise ValueError("SpatialL2CtxAndLastDiT requires z_l2_start and z_l2_end.")
|
| 731 |
+
|
| 732 |
+
c = self.get_condition_embeddings(t, **condition_kwargs)
|
| 733 |
+
x = self.embed_inputs(x, frame_rate=frame_rate)
|
| 734 |
+
z_spatial = self._build_spatial_cond(z_l2_start, z_l2_end, x.size(1))
|
| 735 |
+
x, features = self.run_spatial_l2_blocks(x, c, z_spatial, return_features=return_features)
|
| 736 |
+
out = self.final_layer(x, c)
|
| 737 |
+
out = self.postprocess_outputs(out)
|
| 738 |
+
if return_features:
|
| 739 |
+
return out, features
|
| 740 |
+
return out
|
|
@@ -0,0 +1,740 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# --------------------------------------------------------
|
| 2 |
+
# Swin Transformer
|
| 3 |
+
# Copyright (c) 2021 Microsoft
|
| 4 |
+
# Licensed under The MIT License [see LICENSE for details]
|
| 5 |
+
# Written by Ze Liu
|
| 6 |
+
# --------------------------------------------------------
|
| 7 |
+
|
| 8 |
+
import torch
|
| 9 |
+
import torch.nn as nn
|
| 10 |
+
import torch.utils.checkpoint as checkpoint
|
| 11 |
+
from timm.models.layers import DropPath, to_2tuple, trunc_normal_
|
| 12 |
+
from timm.layers.mlp import SwiGLU
|
| 13 |
+
|
| 14 |
+
try:
|
| 15 |
+
import os, sys
|
| 16 |
+
|
| 17 |
+
kernel_path = os.path.abspath(os.path.join('..'))
|
| 18 |
+
sys.path.append(kernel_path)
|
| 19 |
+
from kernels.window_process.window_process import WindowProcess, WindowProcessReverse
|
| 20 |
+
|
| 21 |
+
except:
|
| 22 |
+
WindowProcess = None
|
| 23 |
+
WindowProcessReverse = None
|
| 24 |
+
print("[Warning] Fused window process have not been installed. Please refer to get_started.md for installation.")
|
| 25 |
+
|
| 26 |
+
|
| 27 |
+
class Mlp(nn.Module):
|
| 28 |
+
def __init__(self, in_features, hidden_features=None, out_features=None, act_layer=nn.GELU, drop=0.):
|
| 29 |
+
super().__init__()
|
| 30 |
+
out_features = out_features or in_features
|
| 31 |
+
hidden_features = hidden_features or in_features
|
| 32 |
+
self.fc1 = nn.Linear(in_features, hidden_features)
|
| 33 |
+
self.act = act_layer()
|
| 34 |
+
self.fc2 = nn.Linear(hidden_features, out_features)
|
| 35 |
+
self.drop = nn.Dropout(drop)
|
| 36 |
+
|
| 37 |
+
def forward(self, x):
|
| 38 |
+
x = self.fc1(x)
|
| 39 |
+
x = self.act(x)
|
| 40 |
+
x = self.drop(x)
|
| 41 |
+
x = self.fc2(x)
|
| 42 |
+
x = self.drop(x)
|
| 43 |
+
return x
|
| 44 |
+
|
| 45 |
+
|
| 46 |
+
def window_partition(x, window_size):
|
| 47 |
+
"""
|
| 48 |
+
Args:
|
| 49 |
+
x: (B, H, W, C)
|
| 50 |
+
window_size (int): window size
|
| 51 |
+
|
| 52 |
+
Returns:
|
| 53 |
+
windows: (num_windows*B, window_size, window_size, C)
|
| 54 |
+
"""
|
| 55 |
+
B, H, W, C = x.shape
|
| 56 |
+
window_size = to_2tuple(window_size)
|
| 57 |
+
x = x.view(B, H // window_size[0], window_size[0], W // window_size[1], window_size[1], C)
|
| 58 |
+
windows = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(-1, *window_size, C)
|
| 59 |
+
return windows
|
| 60 |
+
|
| 61 |
+
|
| 62 |
+
def window_reverse(windows, window_size, H, W):
|
| 63 |
+
"""
|
| 64 |
+
Args:
|
| 65 |
+
windows: (num_windows*B, window_size, window_size, C)
|
| 66 |
+
window_size (int): Window size
|
| 67 |
+
H (int): Height of image
|
| 68 |
+
W (int): Width of image
|
| 69 |
+
|
| 70 |
+
Returns:
|
| 71 |
+
x: (B, H, W, C)
|
| 72 |
+
"""
|
| 73 |
+
window_size = to_2tuple(window_size)
|
| 74 |
+
B = int(windows.shape[0] / (H * W / window_size[0] / window_size[1]))
|
| 75 |
+
x = windows.view(B, H // window_size[0], W // window_size[1], window_size[0], window_size[1], -1)
|
| 76 |
+
x = x.permute(0, 1, 3, 2, 4, 5).contiguous().view(B, H, W, -1)
|
| 77 |
+
return x
|
| 78 |
+
|
| 79 |
+
|
| 80 |
+
class WindowAttention(nn.Module):
|
| 81 |
+
r""" Window based multi-head self attention (W-MSA) module with relative position bias.
|
| 82 |
+
It supports both of shifted and non-shifted window.
|
| 83 |
+
|
| 84 |
+
Args:
|
| 85 |
+
dim (int): Number of input channels.
|
| 86 |
+
window_size (tuple[int]): The height and width of the window.
|
| 87 |
+
num_heads (int): Number of attention heads.
|
| 88 |
+
qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True
|
| 89 |
+
qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set
|
| 90 |
+
attn_drop (float, optional): Dropout ratio of attention weight. Default: 0.0
|
| 91 |
+
proj_drop (float, optional): Dropout ratio of output. Default: 0.0
|
| 92 |
+
"""
|
| 93 |
+
|
| 94 |
+
def __init__(self, dim, window_size, num_heads, qkv_bias=True, qk_scale=None, qk_norm=True, attn_drop=0., proj_drop=0., fused_attn=True, norm_layer=nn.LayerNorm):
|
| 95 |
+
|
| 96 |
+
super().__init__()
|
| 97 |
+
self.dim = dim
|
| 98 |
+
self.window_size = window_size # Wh, Ww
|
| 99 |
+
self.num_heads = num_heads
|
| 100 |
+
head_dim = dim // num_heads
|
| 101 |
+
self.scale = qk_scale or head_dim ** -0.5
|
| 102 |
+
self.use_fused_attn = fused_attn
|
| 103 |
+
self.qk_norm = qk_norm
|
| 104 |
+
|
| 105 |
+
# define a parameter table of relative position bias
|
| 106 |
+
self.relative_position_bias_table = nn.Parameter(
|
| 107 |
+
torch.zeros((2 * window_size[0] - 1) * (2 * window_size[1] - 1), num_heads)) # 2*Wh-1 * 2*Ww-1, nH
|
| 108 |
+
|
| 109 |
+
# get pair-wise relative position index for each token inside the window
|
| 110 |
+
coords_h = torch.arange(self.window_size[0])
|
| 111 |
+
coords_w = torch.arange(self.window_size[1])
|
| 112 |
+
coords = torch.stack(torch.meshgrid([coords_h, coords_w])) # 2, Wh, Ww
|
| 113 |
+
coords_flatten = torch.flatten(coords, 1) # 2, Wh*Ww
|
| 114 |
+
relative_coords = coords_flatten[:, :, None] - coords_flatten[:, None, :] # 2, Wh*Ww, Wh*Ww
|
| 115 |
+
relative_coords = relative_coords.permute(1, 2, 0).contiguous() # Wh*Ww, Wh*Ww, 2
|
| 116 |
+
relative_coords[:, :, 0] += self.window_size[0] - 1 # shift to start from 0
|
| 117 |
+
relative_coords[:, :, 1] += self.window_size[1] - 1
|
| 118 |
+
relative_coords[:, :, 0] *= 2 * self.window_size[1] - 1
|
| 119 |
+
relative_position_index = relative_coords.sum(-1) # Wh*Ww, Wh*Ww
|
| 120 |
+
self.register_buffer("relative_position_index", relative_position_index)
|
| 121 |
+
|
| 122 |
+
self.qkv = nn.Linear(dim, dim * 3, bias=qkv_bias)
|
| 123 |
+
self.attn_drop = nn.Dropout(attn_drop)
|
| 124 |
+
self.proj = nn.Linear(dim, dim)
|
| 125 |
+
self.proj_drop = nn.Dropout(proj_drop)
|
| 126 |
+
|
| 127 |
+
self.q_norm = norm_layer(head_dim, eps=1e-6) if qk_norm else nn.Identity()
|
| 128 |
+
self.k_norm = norm_layer(head_dim, eps=1e-6) if qk_norm else nn.Identity()
|
| 129 |
+
|
| 130 |
+
trunc_normal_(self.relative_position_bias_table, std=.02)
|
| 131 |
+
self.softmax = nn.Softmax(dim=-1)
|
| 132 |
+
|
| 133 |
+
def old_attn(self, q, k, v, B_, N, relative_position_bias, mask=None):
|
| 134 |
+
q = q * self.scale
|
| 135 |
+
attn = (q @ k.transpose(-2, -1))
|
| 136 |
+
attn = attn + relative_position_bias.unsqueeze(0)
|
| 137 |
+
|
| 138 |
+
if mask is not None:
|
| 139 |
+
nW = mask.shape[0]
|
| 140 |
+
attn = attn.view(B_ // nW, nW, self.num_heads, N, N) + mask.unsqueeze(1).unsqueeze(0)
|
| 141 |
+
attn = attn.view(-1, self.num_heads, N, N)
|
| 142 |
+
attn = self.softmax(attn)
|
| 143 |
+
else:
|
| 144 |
+
attn = self.softmax(attn)
|
| 145 |
+
attn = self.attn_drop(attn)
|
| 146 |
+
x = (attn @ v)
|
| 147 |
+
return x
|
| 148 |
+
|
| 149 |
+
def fused_attn(self, q, k, v, B_, N, relative_position_bias, mask=None):
|
| 150 |
+
attn_mask = relative_position_bias
|
| 151 |
+
if mask is not None:
|
| 152 |
+
num_win = mask.shape[0]
|
| 153 |
+
mask = mask.view(1, num_win, 1, N, N).expand(B_ // num_win, -1, self.num_heads, -1, -1)
|
| 154 |
+
attn_mask = attn_mask + mask.reshape(-1, self.num_heads, N, N)
|
| 155 |
+
x = torch.nn.functional.scaled_dot_product_attention(
|
| 156 |
+
q, k, v,
|
| 157 |
+
attn_mask=attn_mask,
|
| 158 |
+
dropout_p=self.attn_drop.p if self.training else 0.,
|
| 159 |
+
)
|
| 160 |
+
return x
|
| 161 |
+
|
| 162 |
+
def forward(self, x, mask=None):
|
| 163 |
+
"""
|
| 164 |
+
Args:
|
| 165 |
+
x: input features with shape of (num_windows*B, N, C)
|
| 166 |
+
mask: (0/-inf) mask with shape of (num_windows, Wh*Ww, Wh*Ww) or None
|
| 167 |
+
"""
|
| 168 |
+
B_, N, C = x.shape
|
| 169 |
+
qkv = self.qkv(x).reshape(B_, N, 3, self.num_heads, C // self.num_heads).permute(2, 0, 3, 1, 4)
|
| 170 |
+
q, k, v = qkv[0], qkv[1], qkv[2] # make torchscript happy (cannot use tensor as tuple)
|
| 171 |
+
q, k = self.q_norm(q), self.k_norm(k)
|
| 172 |
+
|
| 173 |
+
relative_position_bias = self.relative_position_bias_table[self.relative_position_index.view(-1)].view(
|
| 174 |
+
self.window_size[0] * self.window_size[1], self.window_size[0] * self.window_size[1], -1) # Wh*Ww,Wh*Ww,nH
|
| 175 |
+
relative_position_bias = relative_position_bias.permute(2, 0, 1).contiguous() # nH, Wh*Ww, Wh*Ww
|
| 176 |
+
|
| 177 |
+
if self.use_fused_attn: # test using scaled dot product attention
|
| 178 |
+
x = self.fused_attn(q, k, v, B_, N, relative_position_bias, mask=mask)
|
| 179 |
+
else:
|
| 180 |
+
x = self.old_attn(q, k, v, B_, N, relative_position_bias, mask=mask)
|
| 181 |
+
|
| 182 |
+
x = x.transpose(1, 2).reshape(B_, N, C)
|
| 183 |
+
x = self.proj(x)
|
| 184 |
+
x = self.proj_drop(x)
|
| 185 |
+
return x
|
| 186 |
+
|
| 187 |
+
def extra_repr(self) -> str:
|
| 188 |
+
return f'dim={self.dim}, window_size={self.window_size}, num_heads={self.num_heads}'
|
| 189 |
+
|
| 190 |
+
def flops(self, N):
|
| 191 |
+
# calculate flops for 1 window with token length of N
|
| 192 |
+
flops = 0
|
| 193 |
+
# qkv = self.qkv(x)
|
| 194 |
+
flops += N * self.dim * 3 * self.dim
|
| 195 |
+
# attn = (q @ k.transpose(-2, -1))
|
| 196 |
+
flops += self.num_heads * N * (self.dim // self.num_heads) * N
|
| 197 |
+
# x = (attn @ v)
|
| 198 |
+
flops += self.num_heads * N * N * (self.dim // self.num_heads)
|
| 199 |
+
# x = self.proj(x)
|
| 200 |
+
flops += N * self.dim * self.dim
|
| 201 |
+
return flops
|
| 202 |
+
|
| 203 |
+
|
| 204 |
+
class SwinAttention(nn.Module):
|
| 205 |
+
""" Swin attention only, no MLP. """
|
| 206 |
+
def __init__(self, dim, input_resolution, num_heads, window_size=(7,7), shift_size=(0,0),
|
| 207 |
+
qkv_bias=True, qk_scale=None, qk_norm=False, proj_drop=0., attn_drop=0., norm_layer=nn.LayerNorm,
|
| 208 |
+
fused_window_process=False):
|
| 209 |
+
super().__init__()
|
| 210 |
+
self.dim = dim
|
| 211 |
+
self.input_resolution = input_resolution
|
| 212 |
+
self.num_heads = num_heads
|
| 213 |
+
self.window_size = window_size
|
| 214 |
+
self.shift_size = shift_size
|
| 215 |
+
|
| 216 |
+
if min(self.input_resolution) <= min(self.window_size) or max(self.input_resolution) <= max(self.window_size):
|
| 217 |
+
raise ValueError("window size cannot be larger than input resolution")
|
| 218 |
+
# if window size is larger than input resolution, we don't partition windows
|
| 219 |
+
self.shift_size = 0
|
| 220 |
+
self.window_size = min(self.input_resolution)
|
| 221 |
+
assert 0 <= self.shift_size[0] < self.window_size[0] and 0 <= self.shift_size[1] < self.window_size[1], "shift_size must in [0, window_size)"
|
| 222 |
+
|
| 223 |
+
self.attn = WindowAttention(
|
| 224 |
+
dim, window_size=self.window_size, num_heads=num_heads,
|
| 225 |
+
qkv_bias=qkv_bias, qk_scale=qk_scale, qk_norm=qk_norm,
|
| 226 |
+
attn_drop=attn_drop, proj_drop=proj_drop, fused_attn=True, norm_layer=norm_layer)
|
| 227 |
+
|
| 228 |
+
if self.shift_size[0] > 0 or self.shift_size[1] > 0:
|
| 229 |
+
# calculate attention mask for SW-MSA
|
| 230 |
+
H, W = self.input_resolution
|
| 231 |
+
img_mask = torch.zeros((1, H, W, 1)) # 1 H W 1
|
| 232 |
+
h_slices = (slice(0, -self.window_size[0]),
|
| 233 |
+
slice(-self.window_size[0], -self.shift_size[0]),
|
| 234 |
+
slice(-self.shift_size[0], None))
|
| 235 |
+
w_slices = (slice(0, -self.window_size[1]),
|
| 236 |
+
slice(-self.window_size[1], -self.shift_size[1]),
|
| 237 |
+
slice(-self.shift_size[1], None))
|
| 238 |
+
cnt = 0
|
| 239 |
+
for h in h_slices:
|
| 240 |
+
for w in w_slices:
|
| 241 |
+
img_mask[:, h, w, :] = cnt
|
| 242 |
+
cnt += 1
|
| 243 |
+
|
| 244 |
+
mask_windows = window_partition(img_mask, self.window_size) # nW, window_size, window_size, 1
|
| 245 |
+
mask_windows = mask_windows.view(-1, self.window_size[0] * self.window_size[1])
|
| 246 |
+
attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2)
|
| 247 |
+
attn_mask = attn_mask.masked_fill(attn_mask != 0, float(-100.0)).masked_fill(attn_mask == 0, float(0.0))
|
| 248 |
+
else:
|
| 249 |
+
attn_mask = None
|
| 250 |
+
|
| 251 |
+
self.register_buffer("attn_mask", attn_mask)
|
| 252 |
+
self.fused_window_process = fused_window_process
|
| 253 |
+
|
| 254 |
+
def forward(self, x):
|
| 255 |
+
H, W = self.input_resolution
|
| 256 |
+
B, L, C = x.shape
|
| 257 |
+
assert L == H * W, "input feature has wrong size"
|
| 258 |
+
|
| 259 |
+
x = x.view(B, H, W, C)
|
| 260 |
+
|
| 261 |
+
# cyclic shift
|
| 262 |
+
if self.shift_size[0] > 0 or self.shift_size[1] > 0:
|
| 263 |
+
if not self.fused_window_process:
|
| 264 |
+
shifted_x = torch.roll(x, shifts=(-self.shift_size[0], -self.shift_size[1]), dims=(1, 2))
|
| 265 |
+
# partition windows
|
| 266 |
+
x_windows = window_partition(shifted_x, self.window_size) # nW*B, window_size, window_size, C
|
| 267 |
+
else:
|
| 268 |
+
x_windows = WindowProcess.apply(x, B, H, W, C, -self.shift_size[0], self.window_size[1])
|
| 269 |
+
else:
|
| 270 |
+
shifted_x = x
|
| 271 |
+
# partition windows
|
| 272 |
+
x_windows = window_partition(shifted_x, self.window_size) # nW*B, window_size, window_size, C
|
| 273 |
+
|
| 274 |
+
x_windows = x_windows.view(-1, self.window_size[0] * self.window_size[1], C) # nW*B, window_size*window_size, C
|
| 275 |
+
|
| 276 |
+
# W-MSA/SW-MSA
|
| 277 |
+
attn_windows = self.attn(x_windows, mask=self.attn_mask) # nW*B, window_size*window_size, C
|
| 278 |
+
|
| 279 |
+
# merge windows
|
| 280 |
+
attn_windows = attn_windows.view(-1, self.window_size[0], self.window_size[1], C)
|
| 281 |
+
|
| 282 |
+
# reverse cyclic shift
|
| 283 |
+
if self.shift_size[0] > 0 or self.shift_size[1] > 0:
|
| 284 |
+
if not self.fused_window_process:
|
| 285 |
+
shifted_x = window_reverse(attn_windows, self.window_size, H, W) # B H' W' C
|
| 286 |
+
x = torch.roll(shifted_x, shifts=(self.shift_size[0], self.shift_size[1]), dims=(1, 2))
|
| 287 |
+
else:
|
| 288 |
+
x = WindowProcessReverse.apply(attn_windows, B, H, W, C, self.shift_size, self.window_size)
|
| 289 |
+
else:
|
| 290 |
+
shifted_x = window_reverse(attn_windows, self.window_size, H, W) # B H' W' C
|
| 291 |
+
x = shifted_x
|
| 292 |
+
x = x.view(B, H * W, C)
|
| 293 |
+
|
| 294 |
+
return x
|
| 295 |
+
|
| 296 |
+
class SwinTransformerBlock(nn.Module):
|
| 297 |
+
r""" Swin Transformer Block.
|
| 298 |
+
|
| 299 |
+
Args:
|
| 300 |
+
dim (int): Number of input channels.
|
| 301 |
+
input_resolution (tuple[int]): Input resulotion.
|
| 302 |
+
num_heads (int): Number of attention heads.
|
| 303 |
+
window_size (int): Window size.
|
| 304 |
+
shift_size (int): Shift size for SW-MSA.
|
| 305 |
+
mlp_ratio (float): Ratio of mlp hidden dim to embedding dim.
|
| 306 |
+
qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True
|
| 307 |
+
qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set.
|
| 308 |
+
drop (float, optional): Dropout rate. Default: 0.0
|
| 309 |
+
attn_drop (float, optional): Attention dropout rate. Default: 0.0
|
| 310 |
+
drop_path (float, optional): Stochastic depth rate. Default: 0.0
|
| 311 |
+
act_layer (nn.Module, optional): Activation layer. Default: nn.GELU
|
| 312 |
+
norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm
|
| 313 |
+
fused_window_process (bool, optional): If True, use one kernel to fused window shift & window partition for acceleration, similar for the reversed part. Default: False
|
| 314 |
+
"""
|
| 315 |
+
|
| 316 |
+
def __init__(self, dim, input_resolution, num_heads, window_size=(7,7), shift_size=(0,0),
|
| 317 |
+
mlp_ratio=4., qkv_bias=True, qk_scale=None, qk_norm=True, drop=0., attn_drop=0., drop_path=0.,
|
| 318 |
+
act_layer=nn.GELU, norm_layer=nn.LayerNorm,
|
| 319 |
+
mlp_block='mlp',
|
| 320 |
+
fused_window_process=False):
|
| 321 |
+
super().__init__()
|
| 322 |
+
self.dim = dim
|
| 323 |
+
self.input_resolution = input_resolution
|
| 324 |
+
self.num_heads = num_heads
|
| 325 |
+
self.window_size = window_size
|
| 326 |
+
self.shift_size = shift_size
|
| 327 |
+
self.mlp_ratio = mlp_ratio
|
| 328 |
+
if min(self.input_resolution) <= min(self.window_size) or max(self.input_resolution) <= max(self.window_size):
|
| 329 |
+
raise ValueError("window size cannot be larger than input resolution")
|
| 330 |
+
# if window size is larger than input resolution, we don't partition windows
|
| 331 |
+
self.shift_size = 0
|
| 332 |
+
self.window_size = min(self.input_resolution)
|
| 333 |
+
assert 0 <= self.shift_size[0] < self.window_size[0] and 0 <= self.shift_size[1] < self.window_size[1], "shift_size must in [0, window_size)"
|
| 334 |
+
|
| 335 |
+
self.norm1 = norm_layer(dim)
|
| 336 |
+
self.attn = WindowAttention(
|
| 337 |
+
dim, window_size=self.window_size, num_heads=num_heads,
|
| 338 |
+
qkv_bias=qkv_bias, qk_scale=qk_scale, qk_norm=qk_norm, attn_drop=attn_drop, proj_drop=drop)
|
| 339 |
+
|
| 340 |
+
self.drop_path = DropPath(drop_path) if drop_path > 0. else nn.Identity()
|
| 341 |
+
self.norm2 = norm_layer(dim)
|
| 342 |
+
mlp_hidden_dim = int(dim * mlp_ratio)
|
| 343 |
+
if mlp_block == 'mlp':
|
| 344 |
+
self.mlp = Mlp(in_features=dim, hidden_features=mlp_hidden_dim, act_layer=act_layer, drop=drop)
|
| 345 |
+
elif mlp_block == 'swiglu':
|
| 346 |
+
self.mlp = SwiGLU(in_features=dim, hidden_features=(mlp_hidden_dim*2)//3)
|
| 347 |
+
|
| 348 |
+
if self.shift_size[0] > 0 or self.shift_size[1] > 0:
|
| 349 |
+
# calculate attention mask for SW-MSA
|
| 350 |
+
H, W = self.input_resolution
|
| 351 |
+
img_mask = torch.zeros((1, H, W, 1)) # 1 H W 1
|
| 352 |
+
h_slices = (slice(0, -self.window_size[0]),
|
| 353 |
+
slice(-self.window_size[0], -self.shift_size[0]),
|
| 354 |
+
slice(-self.shift_size[0], None))
|
| 355 |
+
w_slices = (slice(0, -self.window_size[1]),
|
| 356 |
+
slice(-self.window_size[1], -self.shift_size[1]),
|
| 357 |
+
slice(-self.shift_size[1], None))
|
| 358 |
+
cnt = 0
|
| 359 |
+
for h in h_slices:
|
| 360 |
+
for w in w_slices:
|
| 361 |
+
img_mask[:, h, w, :] = cnt
|
| 362 |
+
cnt += 1
|
| 363 |
+
|
| 364 |
+
mask_windows = window_partition(img_mask, self.window_size) # nW, window_size, window_size, 1
|
| 365 |
+
mask_windows = mask_windows.view(-1, self.window_size[0] * self.window_size[1])
|
| 366 |
+
attn_mask = mask_windows.unsqueeze(1) - mask_windows.unsqueeze(2)
|
| 367 |
+
attn_mask = attn_mask.masked_fill(attn_mask != 0, float(-100.0)).masked_fill(attn_mask == 0, float(0.0))
|
| 368 |
+
else:
|
| 369 |
+
attn_mask = None
|
| 370 |
+
|
| 371 |
+
self.register_buffer("attn_mask", attn_mask)
|
| 372 |
+
self.fused_window_process = fused_window_process
|
| 373 |
+
|
| 374 |
+
def forward(self, x):
|
| 375 |
+
H, W = self.input_resolution
|
| 376 |
+
B, L, C = x.shape
|
| 377 |
+
assert L == H * W, "input feature has wrong size"
|
| 378 |
+
|
| 379 |
+
shortcut = x
|
| 380 |
+
x = self.norm1(x)
|
| 381 |
+
x = x.view(B, H, W, C)
|
| 382 |
+
|
| 383 |
+
# cyclic shift
|
| 384 |
+
if self.shift_size[0] > 0 or self.shift_size[1] > 0:
|
| 385 |
+
if not self.fused_window_process:
|
| 386 |
+
shifted_x = torch.roll(x, shifts=(-self.shift_size[0], -self.shift_size[1]), dims=(1, 2))
|
| 387 |
+
# partition windows
|
| 388 |
+
x_windows = window_partition(shifted_x, self.window_size) # nW*B, window_size, window_size, C
|
| 389 |
+
else:
|
| 390 |
+
x_windows = WindowProcess.apply(x, B, H, W, C, -self.shift_size[0], self.window_size[1])
|
| 391 |
+
else:
|
| 392 |
+
shifted_x = x
|
| 393 |
+
# partition windows
|
| 394 |
+
x_windows = window_partition(shifted_x, self.window_size) # nW*B, window_size, window_size, C
|
| 395 |
+
|
| 396 |
+
x_windows = x_windows.view(-1, self.window_size[0] * self.window_size[1], C) # nW*B, window_size*window_size, C
|
| 397 |
+
|
| 398 |
+
# W-MSA/SW-MSA
|
| 399 |
+
attn_windows = self.attn(x_windows, mask=self.attn_mask) # nW*B, window_size*window_size, C
|
| 400 |
+
|
| 401 |
+
# merge windows
|
| 402 |
+
attn_windows = attn_windows.view(-1, self.window_size[0], self.window_size[1], C)
|
| 403 |
+
|
| 404 |
+
# reverse cyclic shift
|
| 405 |
+
if self.shift_size[0] > 0 or self.shift_size[1] > 0:
|
| 406 |
+
if not self.fused_window_process:
|
| 407 |
+
shifted_x = window_reverse(attn_windows, self.window_size, H, W) # B H' W' C
|
| 408 |
+
x = torch.roll(shifted_x, shifts=(self.shift_size[0], self.shift_size[1]), dims=(1, 2))
|
| 409 |
+
else:
|
| 410 |
+
x = WindowProcessReverse.apply(attn_windows, B, H, W, C, self.shift_size, self.window_size)
|
| 411 |
+
else:
|
| 412 |
+
shifted_x = window_reverse(attn_windows, self.window_size, H, W) # B H' W' C
|
| 413 |
+
x = shifted_x
|
| 414 |
+
x = x.view(B, H * W, C)
|
| 415 |
+
x = shortcut + self.drop_path(x)
|
| 416 |
+
|
| 417 |
+
# FFN
|
| 418 |
+
x = x + self.drop_path(self.mlp(self.norm2(x)))
|
| 419 |
+
|
| 420 |
+
return x
|
| 421 |
+
|
| 422 |
+
def extra_repr(self) -> str:
|
| 423 |
+
return f"dim={self.dim}, input_resolution={self.input_resolution}, num_heads={self.num_heads}, " \
|
| 424 |
+
f"window_size={self.window_size}, shift_size={self.shift_size}, mlp_ratio={self.mlp_ratio}"
|
| 425 |
+
|
| 426 |
+
def flops(self):
|
| 427 |
+
flops = 0
|
| 428 |
+
H, W = self.input_resolution
|
| 429 |
+
# norm1
|
| 430 |
+
flops += self.dim * H * W
|
| 431 |
+
# W-MSA/SW-MSA
|
| 432 |
+
nW = H * W / self.window_size / self.window_size
|
| 433 |
+
flops += nW * self.attn.flops(self.window_size * self.window_size)
|
| 434 |
+
# mlp
|
| 435 |
+
flops += 2 * H * W * self.dim * self.dim * self.mlp_ratio
|
| 436 |
+
# norm2
|
| 437 |
+
flops += self.dim * H * W
|
| 438 |
+
return flops
|
| 439 |
+
|
| 440 |
+
|
| 441 |
+
class PatchMerging(nn.Module):
|
| 442 |
+
r""" Patch Merging Layer.
|
| 443 |
+
|
| 444 |
+
Args:
|
| 445 |
+
input_resolution (tuple[int]): Resolution of input feature.
|
| 446 |
+
dim (int): Number of input channels.
|
| 447 |
+
norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm
|
| 448 |
+
"""
|
| 449 |
+
|
| 450 |
+
def __init__(self, input_resolution, dim, norm_layer=nn.LayerNorm):
|
| 451 |
+
super().__init__()
|
| 452 |
+
self.input_resolution = input_resolution
|
| 453 |
+
self.dim = dim
|
| 454 |
+
self.reduction = nn.Linear(4 * dim, 2 * dim, bias=False)
|
| 455 |
+
self.norm = norm_layer(4 * dim)
|
| 456 |
+
|
| 457 |
+
def forward(self, x):
|
| 458 |
+
"""
|
| 459 |
+
x: B, H*W, C
|
| 460 |
+
"""
|
| 461 |
+
H, W = self.input_resolution
|
| 462 |
+
B, L, C = x.shape
|
| 463 |
+
assert L == H * W, "input feature has wrong size"
|
| 464 |
+
assert H % 2 == 0 and W % 2 == 0, f"x size ({H}*{W}) are not even."
|
| 465 |
+
|
| 466 |
+
x = x.view(B, H, W, C)
|
| 467 |
+
|
| 468 |
+
x0 = x[:, 0::2, 0::2, :] # B H/2 W/2 C
|
| 469 |
+
x1 = x[:, 1::2, 0::2, :] # B H/2 W/2 C
|
| 470 |
+
x2 = x[:, 0::2, 1::2, :] # B H/2 W/2 C
|
| 471 |
+
x3 = x[:, 1::2, 1::2, :] # B H/2 W/2 C
|
| 472 |
+
x = torch.cat([x0, x1, x2, x3], -1) # B H/2 W/2 4*C
|
| 473 |
+
x = x.view(B, -1, 4 * C) # B H/2*W/2 4*C
|
| 474 |
+
|
| 475 |
+
x = self.norm(x)
|
| 476 |
+
x = self.reduction(x)
|
| 477 |
+
|
| 478 |
+
return x
|
| 479 |
+
|
| 480 |
+
def extra_repr(self) -> str:
|
| 481 |
+
return f"input_resolution={self.input_resolution}, dim={self.dim}"
|
| 482 |
+
|
| 483 |
+
def flops(self):
|
| 484 |
+
H, W = self.input_resolution
|
| 485 |
+
flops = H * W * self.dim
|
| 486 |
+
flops += (H // 2) * (W // 2) * 4 * self.dim * 2 * self.dim
|
| 487 |
+
return flops
|
| 488 |
+
|
| 489 |
+
|
| 490 |
+
class BasicLayer(nn.Module):
|
| 491 |
+
""" A basic Swin Transformer layer for one stage.
|
| 492 |
+
|
| 493 |
+
Args:
|
| 494 |
+
dim (int): Number of input channels.
|
| 495 |
+
input_resolution (tuple[int]): Input resolution.
|
| 496 |
+
depth (int): Number of blocks.
|
| 497 |
+
num_heads (int): Number of attention heads.
|
| 498 |
+
window_size (int): Local window size.
|
| 499 |
+
mlp_ratio (float): Ratio of mlp hidden dim to embedding dim.
|
| 500 |
+
qkv_bias (bool, optional): If True, add a learnable bias to query, key, value. Default: True
|
| 501 |
+
qk_scale (float | None, optional): Override default qk scale of head_dim ** -0.5 if set.
|
| 502 |
+
drop (float, optional): Dropout rate. Default: 0.0
|
| 503 |
+
attn_drop (float, optional): Attention dropout rate. Default: 0.0
|
| 504 |
+
drop_path (float | tuple[float], optional): Stochastic depth rate. Default: 0.0
|
| 505 |
+
norm_layer (nn.Module, optional): Normalization layer. Default: nn.LayerNorm
|
| 506 |
+
downsample (nn.Module | None, optional): Downsample layer at the end of the layer. Default: None
|
| 507 |
+
use_checkpoint (bool): Whether to use checkpointing to save memory. Default: False.
|
| 508 |
+
fused_window_process (bool, optional): If True, use one kernel to fused window shift & window partition for acceleration, similar for the reversed part. Default: False
|
| 509 |
+
"""
|
| 510 |
+
|
| 511 |
+
def __init__(self, dim, input_resolution, depth, num_heads, window_size,
|
| 512 |
+
mlp_ratio=4., qkv_bias=True, qk_scale=None, drop=0., attn_drop=0.,
|
| 513 |
+
drop_path=0., norm_layer=nn.LayerNorm, downsample=None, use_checkpoint=False,
|
| 514 |
+
fused_window_process=False):
|
| 515 |
+
|
| 516 |
+
super().__init__()
|
| 517 |
+
self.dim = dim
|
| 518 |
+
self.input_resolution = input_resolution
|
| 519 |
+
self.depth = depth
|
| 520 |
+
self.use_checkpoint = use_checkpoint
|
| 521 |
+
|
| 522 |
+
# build blocks
|
| 523 |
+
self.blocks = nn.ModuleList([
|
| 524 |
+
SwinTransformerBlock(dim=dim, input_resolution=input_resolution,
|
| 525 |
+
num_heads=num_heads, window_size=window_size,
|
| 526 |
+
shift_size=0 if (i % 2 == 0) else window_size // 2,
|
| 527 |
+
mlp_ratio=mlp_ratio,
|
| 528 |
+
qkv_bias=qkv_bias, qk_scale=qk_scale,
|
| 529 |
+
drop=drop, attn_drop=attn_drop,
|
| 530 |
+
drop_path=drop_path[i] if isinstance(drop_path, list) else drop_path,
|
| 531 |
+
norm_layer=norm_layer,
|
| 532 |
+
fused_window_process=fused_window_process)
|
| 533 |
+
for i in range(depth)])
|
| 534 |
+
|
| 535 |
+
# patch merging layer
|
| 536 |
+
if downsample is not None:
|
| 537 |
+
self.downsample = downsample(input_resolution, dim=dim, norm_layer=norm_layer)
|
| 538 |
+
else:
|
| 539 |
+
self.downsample = None
|
| 540 |
+
|
| 541 |
+
def forward(self, x):
|
| 542 |
+
for blk in self.blocks:
|
| 543 |
+
if self.use_checkpoint:
|
| 544 |
+
x = checkpoint.checkpoint(blk, x)
|
| 545 |
+
else:
|
| 546 |
+
x = blk(x)
|
| 547 |
+
if self.downsample is not None:
|
| 548 |
+
x = self.downsample(x)
|
| 549 |
+
return x
|
| 550 |
+
|
| 551 |
+
def extra_repr(self) -> str:
|
| 552 |
+
return f"dim={self.dim}, input_resolution={self.input_resolution}, depth={self.depth}"
|
| 553 |
+
|
| 554 |
+
def flops(self):
|
| 555 |
+
flops = 0
|
| 556 |
+
for blk in self.blocks:
|
| 557 |
+
flops += blk.flops()
|
| 558 |
+
if self.downsample is not None:
|
| 559 |
+
flops += self.downsample.flops()
|
| 560 |
+
return flops
|
| 561 |
+
|
| 562 |
+
|
| 563 |
+
class PatchEmbed(nn.Module):
|
| 564 |
+
r""" Image to Patch Embedding
|
| 565 |
+
|
| 566 |
+
Args:
|
| 567 |
+
img_size (int): Image size. Default: 224.
|
| 568 |
+
patch_size (int): Patch token size. Default: 4.
|
| 569 |
+
in_chans (int): Number of input image channels. Default: 3.
|
| 570 |
+
embed_dim (int): Number of linear projection output channels. Default: 96.
|
| 571 |
+
norm_layer (nn.Module, optional): Normalization layer. Default: None
|
| 572 |
+
"""
|
| 573 |
+
|
| 574 |
+
def __init__(self, img_size=224, patch_size=4, in_chans=3, embed_dim=96, norm_layer=None):
|
| 575 |
+
super().__init__()
|
| 576 |
+
img_size = to_2tuple(img_size)
|
| 577 |
+
patch_size = to_2tuple(patch_size)
|
| 578 |
+
patches_resolution = [img_size[0] // patch_size[0], img_size[1] // patch_size[1]]
|
| 579 |
+
self.img_size = img_size
|
| 580 |
+
self.patch_size = patch_size
|
| 581 |
+
self.patches_resolution = patches_resolution
|
| 582 |
+
self.num_patches = patches_resolution[0] * patches_resolution[1]
|
| 583 |
+
|
| 584 |
+
self.in_chans = in_chans
|
| 585 |
+
self.embed_dim = embed_dim
|
| 586 |
+
|
| 587 |
+
self.proj = nn.Conv2d(in_chans, embed_dim, kernel_size=patch_size, stride=patch_size)
|
| 588 |
+
if norm_layer is not None:
|
| 589 |
+
self.norm = norm_layer(embed_dim)
|
| 590 |
+
else:
|
| 591 |
+
self.norm = None
|
| 592 |
+
|
| 593 |
+
def forward(self, x):
|
| 594 |
+
B, C, H, W = x.shape
|
| 595 |
+
# FIXME look at relaxing size constraints
|
| 596 |
+
assert H == self.img_size[0] and W == self.img_size[1], \
|
| 597 |
+
f"Input image size ({H}*{W}) doesn't match model ({self.img_size[0]}*{self.img_size[1]})."
|
| 598 |
+
x = self.proj(x).flatten(2).transpose(1, 2) # B Ph*Pw C
|
| 599 |
+
if self.norm is not None:
|
| 600 |
+
x = self.norm(x)
|
| 601 |
+
return x
|
| 602 |
+
|
| 603 |
+
def flops(self):
|
| 604 |
+
Ho, Wo = self.patches_resolution
|
| 605 |
+
flops = Ho * Wo * self.embed_dim * self.in_chans * (self.patch_size[0] * self.patch_size[1])
|
| 606 |
+
if self.norm is not None:
|
| 607 |
+
flops += Ho * Wo * self.embed_dim
|
| 608 |
+
return flops
|
| 609 |
+
|
| 610 |
+
|
| 611 |
+
class SwinTransformer(nn.Module):
|
| 612 |
+
r""" Swin Transformer
|
| 613 |
+
A PyTorch impl of : `Swin Transformer: Hierarchical Vision Transformer using Shifted Windows` -
|
| 614 |
+
https://arxiv.org/pdf/2103.14030
|
| 615 |
+
|
| 616 |
+
Args:
|
| 617 |
+
img_size (int | tuple(int)): Input image size. Default 224
|
| 618 |
+
patch_size (int | tuple(int)): Patch size. Default: 4
|
| 619 |
+
in_chans (int): Number of input image channels. Default: 3
|
| 620 |
+
num_classes (int): Number of classes for classification head. Default: 1000
|
| 621 |
+
embed_dim (int): Patch embedding dimension. Default: 96
|
| 622 |
+
depths (tuple(int)): Depth of each Swin Transformer layer.
|
| 623 |
+
num_heads (tuple(int)): Number of attention heads in different layers.
|
| 624 |
+
window_size (int): Window size. Default: 7
|
| 625 |
+
mlp_ratio (float): Ratio of mlp hidden dim to embedding dim. Default: 4
|
| 626 |
+
qkv_bias (bool): If True, add a learnable bias to query, key, value. Default: True
|
| 627 |
+
qk_scale (float): Override default qk scale of head_dim ** -0.5 if set. Default: None
|
| 628 |
+
drop_rate (float): Dropout rate. Default: 0
|
| 629 |
+
attn_drop_rate (float): Attention dropout rate. Default: 0
|
| 630 |
+
drop_path_rate (float): Stochastic depth rate. Default: 0.1
|
| 631 |
+
norm_layer (nn.Module): Normalization layer. Default: nn.LayerNorm.
|
| 632 |
+
ape (bool): If True, add absolute position embedding to the patch embedding. Default: False
|
| 633 |
+
patch_norm (bool): If True, add normalization after patch embedding. Default: True
|
| 634 |
+
use_checkpoint (bool): Whether to use checkpointing to save memory. Default: False
|
| 635 |
+
fused_window_process (bool, optional): If True, use one kernel to fused window shift & window partition for acceleration, similar for the reversed part. Default: False
|
| 636 |
+
"""
|
| 637 |
+
|
| 638 |
+
def __init__(self, img_size=224, patch_size=4, in_chans=3, num_classes=1000,
|
| 639 |
+
embed_dim=96, depths=[2, 2, 6, 2], num_heads=[3, 6, 12, 24],
|
| 640 |
+
window_size=7, mlp_ratio=4., qkv_bias=True, qk_scale=None,
|
| 641 |
+
drop_rate=0., attn_drop_rate=0., drop_path_rate=0.1,
|
| 642 |
+
norm_layer=nn.LayerNorm, ape=False, patch_norm=True,
|
| 643 |
+
use_checkpoint=False, fused_window_process=False, **kwargs):
|
| 644 |
+
super().__init__()
|
| 645 |
+
|
| 646 |
+
self.num_classes = num_classes
|
| 647 |
+
self.num_layers = len(depths)
|
| 648 |
+
self.embed_dim = embed_dim
|
| 649 |
+
self.ape = ape
|
| 650 |
+
self.patch_norm = patch_norm
|
| 651 |
+
self.num_features = int(embed_dim * 2 ** (self.num_layers - 1))
|
| 652 |
+
self.mlp_ratio = mlp_ratio
|
| 653 |
+
|
| 654 |
+
# split image into non-overlapping patches
|
| 655 |
+
self.patch_embed = PatchEmbed(
|
| 656 |
+
img_size=img_size, patch_size=patch_size, in_chans=in_chans, embed_dim=embed_dim,
|
| 657 |
+
norm_layer=norm_layer if self.patch_norm else None)
|
| 658 |
+
num_patches = self.patch_embed.num_patches
|
| 659 |
+
patches_resolution = self.patch_embed.patches_resolution
|
| 660 |
+
self.patches_resolution = patches_resolution
|
| 661 |
+
|
| 662 |
+
# absolute position embedding
|
| 663 |
+
if self.ape:
|
| 664 |
+
self.absolute_pos_embed = nn.Parameter(torch.zeros(1, num_patches, embed_dim))
|
| 665 |
+
trunc_normal_(self.absolute_pos_embed, std=.02)
|
| 666 |
+
|
| 667 |
+
self.pos_drop = nn.Dropout(p=drop_rate)
|
| 668 |
+
|
| 669 |
+
# stochastic depth
|
| 670 |
+
dpr = [x.item() for x in torch.linspace(0, drop_path_rate, sum(depths))] # stochastic depth decay rule
|
| 671 |
+
|
| 672 |
+
# build layers
|
| 673 |
+
self.layers = nn.ModuleList()
|
| 674 |
+
for i_layer in range(self.num_layers):
|
| 675 |
+
layer = BasicLayer(dim=int(embed_dim * 2 ** i_layer),
|
| 676 |
+
input_resolution=(patches_resolution[0] // (2 ** i_layer),
|
| 677 |
+
patches_resolution[1] // (2 ** i_layer)),
|
| 678 |
+
depth=depths[i_layer],
|
| 679 |
+
num_heads=num_heads[i_layer],
|
| 680 |
+
window_size=window_size,
|
| 681 |
+
mlp_ratio=self.mlp_ratio,
|
| 682 |
+
qkv_bias=qkv_bias, qk_scale=qk_scale,
|
| 683 |
+
drop=drop_rate, attn_drop=attn_drop_rate,
|
| 684 |
+
drop_path=dpr[sum(depths[:i_layer]):sum(depths[:i_layer + 1])],
|
| 685 |
+
norm_layer=norm_layer,
|
| 686 |
+
downsample=PatchMerging if (i_layer < self.num_layers - 1) else None,
|
| 687 |
+
use_checkpoint=use_checkpoint,
|
| 688 |
+
fused_window_process=fused_window_process)
|
| 689 |
+
self.layers.append(layer)
|
| 690 |
+
|
| 691 |
+
self.norm = norm_layer(self.num_features)
|
| 692 |
+
self.avgpool = nn.AdaptiveAvgPool1d(1)
|
| 693 |
+
self.head = nn.Linear(self.num_features, num_classes) if num_classes > 0 else nn.Identity()
|
| 694 |
+
|
| 695 |
+
self.apply(self._init_weights)
|
| 696 |
+
|
| 697 |
+
def _init_weights(self, m):
|
| 698 |
+
if isinstance(m, nn.Linear):
|
| 699 |
+
trunc_normal_(m.weight, std=.02)
|
| 700 |
+
if isinstance(m, nn.Linear) and m.bias is not None:
|
| 701 |
+
nn.init.constant_(m.bias, 0)
|
| 702 |
+
elif isinstance(m, nn.LayerNorm):
|
| 703 |
+
nn.init.constant_(m.bias, 0)
|
| 704 |
+
nn.init.constant_(m.weight, 1.0)
|
| 705 |
+
|
| 706 |
+
@torch.jit.ignore
|
| 707 |
+
def no_weight_decay(self):
|
| 708 |
+
return {'absolute_pos_embed'}
|
| 709 |
+
|
| 710 |
+
@torch.jit.ignore
|
| 711 |
+
def no_weight_decay_keywords(self):
|
| 712 |
+
return {'relative_position_bias_table'}
|
| 713 |
+
|
| 714 |
+
def forward_features(self, x):
|
| 715 |
+
x = self.patch_embed(x)
|
| 716 |
+
if self.ape:
|
| 717 |
+
x = x + self.absolute_pos_embed
|
| 718 |
+
x = self.pos_drop(x)
|
| 719 |
+
|
| 720 |
+
for layer in self.layers:
|
| 721 |
+
x = layer(x)
|
| 722 |
+
|
| 723 |
+
x = self.norm(x) # B L C
|
| 724 |
+
x = self.avgpool(x.transpose(1, 2)) # B C 1
|
| 725 |
+
x = torch.flatten(x, 1)
|
| 726 |
+
return x
|
| 727 |
+
|
| 728 |
+
def forward(self, x):
|
| 729 |
+
x = self.forward_features(x)
|
| 730 |
+
x = self.head(x)
|
| 731 |
+
return x
|
| 732 |
+
|
| 733 |
+
def flops(self):
|
| 734 |
+
flops = 0
|
| 735 |
+
flops += self.patch_embed.flops()
|
| 736 |
+
for i, layer in enumerate(self.layers):
|
| 737 |
+
flops += layer.flops()
|
| 738 |
+
flops += self.num_features * self.patches_resolution[0] * self.patches_resolution[1] // (2 ** self.num_layers)
|
| 739 |
+
flops += self.num_features * self.num_classes
|
| 740 |
+
return flops
|
|
@@ -0,0 +1,852 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
# pytorch_diffusion + derived encoder decoder
|
| 2 |
+
import math
|
| 3 |
+
from typing import Tuple, Union
|
| 4 |
+
|
| 5 |
+
import numpy as np
|
| 6 |
+
import torch
|
| 7 |
+
import torch.nn as nn
|
| 8 |
+
|
| 9 |
+
from einops import rearrange
|
| 10 |
+
from einops.layers.torch import Rearrange
|
| 11 |
+
from omegaconf import ListConfig
|
| 12 |
+
|
| 13 |
+
|
| 14 |
+
def get_timestep_embedding(timesteps, embedding_dim):
|
| 15 |
+
"""
|
| 16 |
+
This matches the implementation in Denoising Diffusion Probabilistic Models:
|
| 17 |
+
From Fairseq.
|
| 18 |
+
Build sinusoidal embeddings.
|
| 19 |
+
This matches the implementation in tensor2tensor, but differs slightly
|
| 20 |
+
from the description in Section 3.5 of "Attention Is All You Need".
|
| 21 |
+
"""
|
| 22 |
+
assert len(timesteps.shape) == 1
|
| 23 |
+
|
| 24 |
+
half_dim = embedding_dim // 2
|
| 25 |
+
emb = math.log(10000) / (half_dim - 1)
|
| 26 |
+
emb = torch.exp(torch.arange(half_dim, dtype=torch.float32) * -emb)
|
| 27 |
+
emb = emb.to(device=timesteps.device)
|
| 28 |
+
emb = timesteps.float()[:, None] * emb[None, :]
|
| 29 |
+
emb = torch.cat([torch.sin(emb), torch.cos(emb)], dim=1)
|
| 30 |
+
if embedding_dim % 2 == 1: # zero pad
|
| 31 |
+
emb = torch.nn.functional.pad(emb, (0,1,0,0))
|
| 32 |
+
return emb
|
| 33 |
+
|
| 34 |
+
|
| 35 |
+
def nonlinearity(x):
|
| 36 |
+
# swish
|
| 37 |
+
return x*torch.sigmoid(x)
|
| 38 |
+
|
| 39 |
+
|
| 40 |
+
class SpatialNorm(nn.Module):
|
| 41 |
+
def __init__(self, f_channels, zq_channels, norm_layer=nn.GroupNorm, freeze_norm_layer=False, add_conv=False, **norm_layer_params):
|
| 42 |
+
super().__init__()
|
| 43 |
+
self.norm_layer = norm_layer(num_channels=f_channels, **norm_layer_params)
|
| 44 |
+
if freeze_norm_layer:
|
| 45 |
+
for p in self.norm_layer.parameters: # for p in self.norm_layer.parameters():
|
| 46 |
+
p.requires_grad = False
|
| 47 |
+
self.add_conv = add_conv
|
| 48 |
+
if self.add_conv:
|
| 49 |
+
self.conv = nn.Conv2d(zq_channels, zq_channels, kernel_size=3, stride=1, padding=1)
|
| 50 |
+
self.conv_y = nn.Conv2d(zq_channels, f_channels, kernel_size=1, stride=1, padding=0)
|
| 51 |
+
self.conv_b = nn.Conv2d(zq_channels, f_channels, kernel_size=1, stride=1, padding=0)
|
| 52 |
+
def forward(self, f, zq):
|
| 53 |
+
#print(f'f shape: {f.shape}', f'zq shape: {zq.shape}')
|
| 54 |
+
f_size = f.shape[-2:]
|
| 55 |
+
zq = torch.nn.functional.interpolate(zq, size=f_size, mode="nearest")
|
| 56 |
+
if self.add_conv:
|
| 57 |
+
zq = self.conv(zq)
|
| 58 |
+
norm_f = self.norm_layer(f)
|
| 59 |
+
new_f = norm_f * self.conv_y(zq) + self.conv_b(zq)
|
| 60 |
+
return new_f
|
| 61 |
+
|
| 62 |
+
def SpatialNormalize(in_channels, zq_ch, add_conv):
|
| 63 |
+
return SpatialNorm(in_channels, zq_ch, norm_layer=nn.GroupNorm, freeze_norm_layer=False, add_conv=add_conv, num_groups=32, eps=1e-6, affine=True)
|
| 64 |
+
|
| 65 |
+
|
| 66 |
+
def Normalize(in_channels):
|
| 67 |
+
return torch.nn.GroupNorm(num_groups=32, num_channels=in_channels, eps=1e-6, affine=True)
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
class Upsample(nn.Module):
|
| 71 |
+
def __init__(self, in_channels, with_conv):
|
| 72 |
+
super().__init__()
|
| 73 |
+
self.with_conv = with_conv
|
| 74 |
+
if self.with_conv:
|
| 75 |
+
self.conv = torch.nn.Conv2d(in_channels,
|
| 76 |
+
in_channels,
|
| 77 |
+
kernel_size=3,
|
| 78 |
+
stride=1,
|
| 79 |
+
padding=1)
|
| 80 |
+
|
| 81 |
+
def forward(self, x):
|
| 82 |
+
x = torch.nn.functional.interpolate(x, scale_factor=2.0, mode="nearest")
|
| 83 |
+
if self.with_conv:
|
| 84 |
+
x = self.conv(x)
|
| 85 |
+
return x
|
| 86 |
+
|
| 87 |
+
|
| 88 |
+
class Downsample(nn.Module):
|
| 89 |
+
def __init__(self, in_channels, with_conv):
|
| 90 |
+
super().__init__()
|
| 91 |
+
self.with_conv = with_conv
|
| 92 |
+
if self.with_conv:
|
| 93 |
+
# no asymmetric padding in torch conv, must do it ourselves
|
| 94 |
+
self.conv = torch.nn.Conv2d(in_channels,
|
| 95 |
+
in_channels,
|
| 96 |
+
kernel_size=3,
|
| 97 |
+
stride=2,
|
| 98 |
+
padding=0)
|
| 99 |
+
|
| 100 |
+
def forward(self, x):
|
| 101 |
+
if self.with_conv:
|
| 102 |
+
pad = (0,1,0,1)
|
| 103 |
+
x = torch.nn.functional.pad(x, pad, mode="constant", value=0)
|
| 104 |
+
x = self.conv(x)
|
| 105 |
+
else:
|
| 106 |
+
x = torch.nn.functional.avg_pool2d(x, kernel_size=2, stride=2)
|
| 107 |
+
return x
|
| 108 |
+
|
| 109 |
+
|
| 110 |
+
class ResnetBlock(nn.Module):
|
| 111 |
+
def __init__(self, *, in_channels, out_channels=None, conv_shortcut=False,
|
| 112 |
+
dropout, temb_channels=512):
|
| 113 |
+
super().__init__()
|
| 114 |
+
self.in_channels = in_channels
|
| 115 |
+
out_channels = in_channels if out_channels is None else out_channels
|
| 116 |
+
self.out_channels = out_channels
|
| 117 |
+
self.use_conv_shortcut = conv_shortcut
|
| 118 |
+
|
| 119 |
+
self.norm1 = Normalize(in_channels)
|
| 120 |
+
self.conv1 = torch.nn.Conv2d(in_channels,
|
| 121 |
+
out_channels,
|
| 122 |
+
kernel_size=3,
|
| 123 |
+
stride=1,
|
| 124 |
+
padding=1)
|
| 125 |
+
if temb_channels > 0:
|
| 126 |
+
self.temb_proj = torch.nn.Linear(temb_channels,
|
| 127 |
+
out_channels)
|
| 128 |
+
self.norm2 = Normalize(out_channels)
|
| 129 |
+
self.dropout = torch.nn.Dropout(dropout)
|
| 130 |
+
self.conv2 = torch.nn.Conv2d(out_channels,
|
| 131 |
+
out_channels,
|
| 132 |
+
kernel_size=3,
|
| 133 |
+
stride=1,
|
| 134 |
+
padding=1)
|
| 135 |
+
if self.in_channels != self.out_channels:
|
| 136 |
+
if self.use_conv_shortcut:
|
| 137 |
+
self.conv_shortcut = torch.nn.Conv2d(in_channels,
|
| 138 |
+
out_channels,
|
| 139 |
+
kernel_size=3,
|
| 140 |
+
stride=1,
|
| 141 |
+
padding=1)
|
| 142 |
+
else:
|
| 143 |
+
self.nin_shortcut = torch.nn.Conv2d(in_channels,
|
| 144 |
+
out_channels,
|
| 145 |
+
kernel_size=1,
|
| 146 |
+
stride=1,
|
| 147 |
+
padding=0)
|
| 148 |
+
|
| 149 |
+
def forward(self, x, temb):
|
| 150 |
+
h = x
|
| 151 |
+
h = self.norm1(h)
|
| 152 |
+
h = nonlinearity(h)
|
| 153 |
+
h = self.conv1(h)
|
| 154 |
+
|
| 155 |
+
if temb is not None:
|
| 156 |
+
h = h + self.temb_proj(nonlinearity(temb))[:,:,None,None]
|
| 157 |
+
|
| 158 |
+
h = self.norm2(h)
|
| 159 |
+
h = nonlinearity(h)
|
| 160 |
+
h = self.dropout(h)
|
| 161 |
+
h = self.conv2(h)
|
| 162 |
+
|
| 163 |
+
if self.in_channels != self.out_channels:
|
| 164 |
+
if self.use_conv_shortcut:
|
| 165 |
+
x = self.conv_shortcut(x)
|
| 166 |
+
else:
|
| 167 |
+
x = self.nin_shortcut(x)
|
| 168 |
+
|
| 169 |
+
return x+h
|
| 170 |
+
|
| 171 |
+
class SpatialResnetBlock(nn.Module):
|
| 172 |
+
def __init__(self, *, in_channels, out_channels=None, conv_shortcut=False,
|
| 173 |
+
dropout, temb_channels=512, zq_ch=None, add_conv=False):
|
| 174 |
+
super().__init__()
|
| 175 |
+
self.in_channels = in_channels
|
| 176 |
+
out_channels = in_channels if out_channels is None else out_channels
|
| 177 |
+
self.out_channels = out_channels
|
| 178 |
+
self.use_conv_shortcut = conv_shortcut
|
| 179 |
+
|
| 180 |
+
self.norm1 = SpatialNormalize(in_channels, zq_ch, add_conv)
|
| 181 |
+
self.conv1 = torch.nn.Conv2d(in_channels,
|
| 182 |
+
out_channels,
|
| 183 |
+
kernel_size=3,
|
| 184 |
+
stride=1,
|
| 185 |
+
padding=1)
|
| 186 |
+
if temb_channels > 0:
|
| 187 |
+
self.temb_proj = torch.nn.Linear(temb_channels,
|
| 188 |
+
out_channels)
|
| 189 |
+
self.norm2 = SpatialNormalize(out_channels, zq_ch, add_conv)
|
| 190 |
+
self.dropout = torch.nn.Dropout(dropout)
|
| 191 |
+
self.conv2 = torch.nn.Conv2d(out_channels,
|
| 192 |
+
out_channels,
|
| 193 |
+
kernel_size=3,
|
| 194 |
+
stride=1,
|
| 195 |
+
padding=1)
|
| 196 |
+
if self.in_channels != self.out_channels:
|
| 197 |
+
if self.use_conv_shortcut:
|
| 198 |
+
self.conv_shortcut = torch.nn.Conv2d(in_channels,
|
| 199 |
+
out_channels,
|
| 200 |
+
kernel_size=3,
|
| 201 |
+
stride=1,
|
| 202 |
+
padding=1)
|
| 203 |
+
else:
|
| 204 |
+
self.nin_shortcut = torch.nn.Conv2d(in_channels,
|
| 205 |
+
out_channels,
|
| 206 |
+
kernel_size=1,
|
| 207 |
+
stride=1,
|
| 208 |
+
padding=0)
|
| 209 |
+
|
| 210 |
+
def forward(self, x, temb, zq):
|
| 211 |
+
h = x
|
| 212 |
+
h = self.norm1(h, zq)
|
| 213 |
+
h = nonlinearity(h)
|
| 214 |
+
h = self.conv1(h)
|
| 215 |
+
|
| 216 |
+
if temb is not None:
|
| 217 |
+
h = h + self.temb_proj(nonlinearity(temb))[:,:,None,None]
|
| 218 |
+
|
| 219 |
+
h = self.norm2(h, zq)
|
| 220 |
+
h = nonlinearity(h)
|
| 221 |
+
h = self.dropout(h)
|
| 222 |
+
h = self.conv2(h)
|
| 223 |
+
|
| 224 |
+
if self.in_channels != self.out_channels:
|
| 225 |
+
if self.use_conv_shortcut:
|
| 226 |
+
x = self.conv_shortcut(x)
|
| 227 |
+
else:
|
| 228 |
+
x = self.nin_shortcut(x)
|
| 229 |
+
|
| 230 |
+
return x+h
|
| 231 |
+
|
| 232 |
+
|
| 233 |
+
class AttnBlock(nn.Module):
|
| 234 |
+
def __init__(self, in_channels):
|
| 235 |
+
super().__init__()
|
| 236 |
+
self.in_channels = in_channels
|
| 237 |
+
|
| 238 |
+
self.norm = Normalize(in_channels)
|
| 239 |
+
self.q = torch.nn.Conv2d(in_channels,
|
| 240 |
+
in_channels,
|
| 241 |
+
kernel_size=1,
|
| 242 |
+
stride=1,
|
| 243 |
+
padding=0)
|
| 244 |
+
self.k = torch.nn.Conv2d(in_channels,
|
| 245 |
+
in_channels,
|
| 246 |
+
kernel_size=1,
|
| 247 |
+
stride=1,
|
| 248 |
+
padding=0)
|
| 249 |
+
self.v = torch.nn.Conv2d(in_channels,
|
| 250 |
+
in_channels,
|
| 251 |
+
kernel_size=1,
|
| 252 |
+
stride=1,
|
| 253 |
+
padding=0)
|
| 254 |
+
self.proj_out = torch.nn.Conv2d(in_channels,
|
| 255 |
+
in_channels,
|
| 256 |
+
kernel_size=1,
|
| 257 |
+
stride=1,
|
| 258 |
+
padding=0)
|
| 259 |
+
|
| 260 |
+
|
| 261 |
+
def forward(self, x):
|
| 262 |
+
h_ = x
|
| 263 |
+
h_ = self.norm(h_)
|
| 264 |
+
q = self.q(h_)
|
| 265 |
+
k = self.k(h_)
|
| 266 |
+
v = self.v(h_)
|
| 267 |
+
|
| 268 |
+
# compute attention
|
| 269 |
+
b,c,h,w = q.shape
|
| 270 |
+
q = q.reshape(b,c,h*w)
|
| 271 |
+
q = q.permute(0,2,1) # b,hw,c
|
| 272 |
+
k = k.reshape(b,c,h*w) # b,c,hw
|
| 273 |
+
w_ = torch.bmm(q,k) # b,hw,hw w[b,i,j]=sum_c q[b,i,c]k[b,c,j]
|
| 274 |
+
w_ = w_ * (int(c)**(-0.5))
|
| 275 |
+
w_ = torch.nn.functional.softmax(w_, dim=2)
|
| 276 |
+
|
| 277 |
+
# attend to values
|
| 278 |
+
v = v.reshape(b,c,h*w)
|
| 279 |
+
w_ = w_.permute(0,2,1) # b,hw,hw (first hw of k, second of q)
|
| 280 |
+
h_ = torch.bmm(v,w_) # b, c,hw (hw of q) h_[b,c,j] = sum_i v[b,c,i] w_[b,i,j]
|
| 281 |
+
h_ = h_.reshape(b,c,h,w)
|
| 282 |
+
|
| 283 |
+
h_ = self.proj_out(h_)
|
| 284 |
+
|
| 285 |
+
return x+h_
|
| 286 |
+
|
| 287 |
+
class SpatialAttnBlock(nn.Module):
|
| 288 |
+
def __init__(self, in_channels, zq_ch=None, add_conv=False):
|
| 289 |
+
super().__init__()
|
| 290 |
+
self.in_channels = in_channels
|
| 291 |
+
|
| 292 |
+
self.norm = SpatialNormalize(in_channels, zq_ch, add_conv)
|
| 293 |
+
self.q = torch.nn.Conv2d(in_channels,
|
| 294 |
+
in_channels,
|
| 295 |
+
kernel_size=1,
|
| 296 |
+
stride=1,
|
| 297 |
+
padding=0)
|
| 298 |
+
self.k = torch.nn.Conv2d(in_channels,
|
| 299 |
+
in_channels,
|
| 300 |
+
kernel_size=1,
|
| 301 |
+
stride=1,
|
| 302 |
+
padding=0)
|
| 303 |
+
self.v = torch.nn.Conv2d(in_channels,
|
| 304 |
+
in_channels,
|
| 305 |
+
kernel_size=1,
|
| 306 |
+
stride=1,
|
| 307 |
+
padding=0)
|
| 308 |
+
self.proj_out = torch.nn.Conv2d(in_channels,
|
| 309 |
+
in_channels,
|
| 310 |
+
kernel_size=1,
|
| 311 |
+
stride=1,
|
| 312 |
+
padding=0)
|
| 313 |
+
|
| 314 |
+
|
| 315 |
+
def forward(self, x, zq):
|
| 316 |
+
h_ = x
|
| 317 |
+
h_ = self.norm(h_, zq)
|
| 318 |
+
q = self.q(h_)
|
| 319 |
+
k = self.k(h_)
|
| 320 |
+
v = self.v(h_)
|
| 321 |
+
|
| 322 |
+
# compute attention
|
| 323 |
+
b,c,h,w = q.shape
|
| 324 |
+
q = q.reshape(b,c,h*w)
|
| 325 |
+
q = q.permute(0,2,1) # b,hw,c
|
| 326 |
+
k = k.reshape(b,c,h*w) # b,c,hw
|
| 327 |
+
w_ = torch.bmm(q,k) # b,hw,hw w[b,i,j]=sum_c q[b,i,c]k[b,c,j]
|
| 328 |
+
w_ = w_ * (int(c)**(-0.5))
|
| 329 |
+
w_ = torch.nn.functional.softmax(w_, dim=2)
|
| 330 |
+
|
| 331 |
+
# attend to values
|
| 332 |
+
v = v.reshape(b,c,h*w)
|
| 333 |
+
w_ = w_.permute(0,2,1) # b,hw,hw (first hw of k, second of q)
|
| 334 |
+
h_ = torch.bmm(v,w_) # b, c,hw (hw of q) h_[b,c,j] = sum_i v[b,c,i] w_[b,i,j]
|
| 335 |
+
h_ = h_.reshape(b,c,h,w)
|
| 336 |
+
|
| 337 |
+
h_ = self.proj_out(h_)
|
| 338 |
+
|
| 339 |
+
return x+h_
|
| 340 |
+
|
| 341 |
+
|
| 342 |
+
class Model(nn.Module):
|
| 343 |
+
def __init__(self, *, ch, out_ch, ch_mult=(1,2,4,8), num_res_blocks,
|
| 344 |
+
attn_resolutions, dropout=0.0, resamp_with_conv=True, in_channels,
|
| 345 |
+
resolution, use_timestep=True):
|
| 346 |
+
super().__init__()
|
| 347 |
+
self.ch = ch
|
| 348 |
+
self.temb_ch = self.ch*4
|
| 349 |
+
self.num_resolutions = len(ch_mult)
|
| 350 |
+
self.num_res_blocks = num_res_blocks
|
| 351 |
+
self.resolution = resolution
|
| 352 |
+
self.in_channels = in_channels
|
| 353 |
+
|
| 354 |
+
self.use_timestep = use_timestep
|
| 355 |
+
if self.use_timestep:
|
| 356 |
+
# timestep embedding
|
| 357 |
+
self.temb = nn.Module()
|
| 358 |
+
self.temb.dense = nn.ModuleList([
|
| 359 |
+
torch.nn.Linear(self.ch,
|
| 360 |
+
self.temb_ch),
|
| 361 |
+
torch.nn.Linear(self.temb_ch,
|
| 362 |
+
self.temb_ch),
|
| 363 |
+
])
|
| 364 |
+
|
| 365 |
+
# downsampling
|
| 366 |
+
self.conv_in = torch.nn.Conv2d(in_channels,
|
| 367 |
+
self.ch,
|
| 368 |
+
kernel_size=3,
|
| 369 |
+
stride=1,
|
| 370 |
+
padding=1)
|
| 371 |
+
|
| 372 |
+
curr_res = resolution
|
| 373 |
+
in_ch_mult = (1,)+tuple(ch_mult)
|
| 374 |
+
self.down = nn.ModuleList()
|
| 375 |
+
for i_level in range(self.num_resolutions):
|
| 376 |
+
block = nn.ModuleList()
|
| 377 |
+
attn = nn.ModuleList()
|
| 378 |
+
block_in = ch*in_ch_mult[i_level]
|
| 379 |
+
block_out = ch*ch_mult[i_level]
|
| 380 |
+
for i_block in range(self.num_res_blocks):
|
| 381 |
+
block.append(ResnetBlock(in_channels=block_in,
|
| 382 |
+
out_channels=block_out,
|
| 383 |
+
temb_channels=self.temb_ch,
|
| 384 |
+
dropout=dropout))
|
| 385 |
+
block_in = block_out
|
| 386 |
+
if curr_res in attn_resolutions:
|
| 387 |
+
attn.append(AttnBlock(block_in))
|
| 388 |
+
down = nn.Module()
|
| 389 |
+
down.block = block
|
| 390 |
+
down.attn = attn
|
| 391 |
+
if i_level != self.num_resolutions-1:
|
| 392 |
+
down.downsample = Downsample(block_in, resamp_with_conv)
|
| 393 |
+
curr_res = curr_res // 2
|
| 394 |
+
self.down.append(down)
|
| 395 |
+
|
| 396 |
+
# middle
|
| 397 |
+
self.mid = nn.Module()
|
| 398 |
+
self.mid.block_1 = ResnetBlock(in_channels=block_in,
|
| 399 |
+
out_channels=block_in,
|
| 400 |
+
temb_channels=self.temb_ch,
|
| 401 |
+
dropout=dropout)
|
| 402 |
+
self.mid.attn_1 = AttnBlock(block_in)
|
| 403 |
+
self.mid.block_2 = ResnetBlock(in_channels=block_in,
|
| 404 |
+
out_channels=block_in,
|
| 405 |
+
temb_channels=self.temb_ch,
|
| 406 |
+
dropout=dropout)
|
| 407 |
+
|
| 408 |
+
# upsampling
|
| 409 |
+
self.up = nn.ModuleList()
|
| 410 |
+
for i_level in reversed(range(self.num_resolutions)):
|
| 411 |
+
block = nn.ModuleList()
|
| 412 |
+
attn = nn.ModuleList()
|
| 413 |
+
block_out = ch*ch_mult[i_level]
|
| 414 |
+
skip_in = ch*ch_mult[i_level]
|
| 415 |
+
for i_block in range(self.num_res_blocks+1):
|
| 416 |
+
if i_block == self.num_res_blocks:
|
| 417 |
+
skip_in = ch*in_ch_mult[i_level]
|
| 418 |
+
block.append(ResnetBlock(in_channels=block_in+skip_in,
|
| 419 |
+
out_channels=block_out,
|
| 420 |
+
temb_channels=self.temb_ch,
|
| 421 |
+
dropout=dropout))
|
| 422 |
+
block_in = block_out
|
| 423 |
+
if curr_res in attn_resolutions:
|
| 424 |
+
attn.append(AttnBlock(block_in))
|
| 425 |
+
up = nn.Module()
|
| 426 |
+
up.block = block
|
| 427 |
+
up.attn = attn
|
| 428 |
+
if i_level != 0:
|
| 429 |
+
up.upsample = Upsample(block_in, resamp_with_conv)
|
| 430 |
+
curr_res = curr_res * 2
|
| 431 |
+
self.up.insert(0, up) # prepend to get consistent order
|
| 432 |
+
|
| 433 |
+
# end
|
| 434 |
+
self.norm_out = Normalize(block_in)
|
| 435 |
+
self.conv_out = torch.nn.Conv2d(block_in,
|
| 436 |
+
out_ch,
|
| 437 |
+
kernel_size=3,
|
| 438 |
+
stride=1,
|
| 439 |
+
padding=1)
|
| 440 |
+
|
| 441 |
+
|
| 442 |
+
def forward(self, x, t=None):
|
| 443 |
+
#assert x.shape[2] == x.shape[3] == self.resolution
|
| 444 |
+
|
| 445 |
+
if self.use_timestep:
|
| 446 |
+
# timestep embedding
|
| 447 |
+
assert t is not None
|
| 448 |
+
temb = get_timestep_embedding(t, self.ch)
|
| 449 |
+
temb = self.temb.dense[0](temb)
|
| 450 |
+
temb = nonlinearity(temb)
|
| 451 |
+
temb = self.temb.dense[1](temb)
|
| 452 |
+
else:
|
| 453 |
+
temb = None
|
| 454 |
+
|
| 455 |
+
# downsampling
|
| 456 |
+
hs = [self.conv_in(x)]
|
| 457 |
+
for i_level in range(self.num_resolutions):
|
| 458 |
+
for i_block in range(self.num_res_blocks):
|
| 459 |
+
h = self.down[i_level].block[i_block](hs[-1], temb)
|
| 460 |
+
if len(self.down[i_level].attn) > 0:
|
| 461 |
+
h = self.down[i_level].attn[i_block](h)
|
| 462 |
+
hs.append(h)
|
| 463 |
+
if i_level != self.num_resolutions-1:
|
| 464 |
+
hs.append(self.down[i_level].downsample(hs[-1]))
|
| 465 |
+
|
| 466 |
+
# middle
|
| 467 |
+
h = hs[-1]
|
| 468 |
+
h = self.mid.block_1(h, temb)
|
| 469 |
+
h = self.mid.attn_1(h)
|
| 470 |
+
h = self.mid.block_2(h, temb)
|
| 471 |
+
|
| 472 |
+
# upsampling
|
| 473 |
+
for i_level in reversed(range(self.num_resolutions)):
|
| 474 |
+
for i_block in range(self.num_res_blocks+1):
|
| 475 |
+
h = self.up[i_level].block[i_block](
|
| 476 |
+
torch.cat([h, hs.pop()], dim=1), temb)
|
| 477 |
+
if len(self.up[i_level].attn) > 0:
|
| 478 |
+
h = self.up[i_level].attn[i_block](h)
|
| 479 |
+
if i_level != 0:
|
| 480 |
+
h = self.up[i_level].upsample(h)
|
| 481 |
+
|
| 482 |
+
# end
|
| 483 |
+
h = self.norm_out(h)
|
| 484 |
+
h = nonlinearity(h)
|
| 485 |
+
h = self.conv_out(h)
|
| 486 |
+
return h
|
| 487 |
+
|
| 488 |
+
def get_1d_sincos_pos_embed_from_grid(embed_dim, pos):
|
| 489 |
+
"""
|
| 490 |
+
embed_dim: output dimension for each position
|
| 491 |
+
pos: a list of positions to be encoded: size (M,)
|
| 492 |
+
out: (M, D)
|
| 493 |
+
"""
|
| 494 |
+
assert embed_dim % 2 == 0
|
| 495 |
+
omega = np.arange(embed_dim // 2, dtype=float)
|
| 496 |
+
omega /= embed_dim / 2.
|
| 497 |
+
omega = 1. / 10000**omega # (D/2,)
|
| 498 |
+
|
| 499 |
+
pos = pos.reshape(-1) # (M,)
|
| 500 |
+
out = np.einsum('m,d->md', pos, omega) # (M, D/2), outer product
|
| 501 |
+
|
| 502 |
+
emb_sin = np.sin(out) # (M, D/2)
|
| 503 |
+
emb_cos = np.cos(out) # (M, D/2)
|
| 504 |
+
|
| 505 |
+
emb = np.concatenate([emb_sin, emb_cos], axis=1) # (M, D)
|
| 506 |
+
return emb
|
| 507 |
+
|
| 508 |
+
def get_2d_sincos_pos_embed_from_grid(embed_dim, grid):
|
| 509 |
+
assert embed_dim % 2 == 0
|
| 510 |
+
|
| 511 |
+
# use half of dimensions to encode grid_h
|
| 512 |
+
emb_h = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[0]) # (H*W, D/2)
|
| 513 |
+
emb_w = get_1d_sincos_pos_embed_from_grid(embed_dim // 2, grid[1]) # (H*W, D/2)
|
| 514 |
+
|
| 515 |
+
emb = np.concatenate([emb_h, emb_w], axis=1) # (H*W, D)
|
| 516 |
+
return emb
|
| 517 |
+
|
| 518 |
+
def get_2d_sincos_pos_embed(embed_dim, grid_size):
|
| 519 |
+
"""
|
| 520 |
+
grid_size: int or (int, int) of the grid height and width
|
| 521 |
+
return:
|
| 522 |
+
pos_embed: [grid_size*grid_size, embed_dim] or [1+grid_size*grid_size, embed_dim] (w/ or w/o cls_token)
|
| 523 |
+
"""
|
| 524 |
+
grid_size = (grid_size, grid_size) if type(grid_size) != tuple else grid_size
|
| 525 |
+
grid_h = np.arange(grid_size[0], dtype=np.float32)
|
| 526 |
+
grid_w = np.arange(grid_size[1], dtype=np.float32)
|
| 527 |
+
grid = np.meshgrid(grid_w, grid_h) # here w goes first
|
| 528 |
+
grid = np.stack(grid, axis=0)
|
| 529 |
+
|
| 530 |
+
grid = grid.reshape([2, 1, grid_size[0], grid_size[1]])
|
| 531 |
+
pos_embed = get_2d_sincos_pos_embed_from_grid(embed_dim, grid)
|
| 532 |
+
|
| 533 |
+
return pos_embed
|
| 534 |
+
|
| 535 |
+
def init_weights(m):
|
| 536 |
+
if isinstance(m, nn.Linear):
|
| 537 |
+
# we use xavier_uniform following official JAX ViT:
|
| 538 |
+
torch.nn.init.xavier_uniform_(m.weight)
|
| 539 |
+
if m.bias is not None:
|
| 540 |
+
nn.init.constant_(m.bias, 0)
|
| 541 |
+
elif isinstance(m, nn.LayerNorm):
|
| 542 |
+
nn.init.constant_(m.bias, 0)
|
| 543 |
+
nn.init.constant_(m.weight, 1.0)
|
| 544 |
+
elif isinstance(m, nn.Conv2d) or isinstance(m, nn.ConvTranspose2d):
|
| 545 |
+
with torch.no_grad():
|
| 546 |
+
w = m.weight
|
| 547 |
+
torch.nn.init.xavier_uniform_(w.view([w.shape[0], -1]))
|
| 548 |
+
|
| 549 |
+
class PreNorm(nn.Module):
|
| 550 |
+
def __init__(self, dim: int, fn: nn.Module) -> None:
|
| 551 |
+
super().__init__()
|
| 552 |
+
self.norm = nn.LayerNorm(dim)
|
| 553 |
+
self.fn = fn
|
| 554 |
+
|
| 555 |
+
def forward(self, x: torch.FloatTensor, **kwargs) -> torch.FloatTensor:
|
| 556 |
+
return self.fn(self.norm(x), **kwargs)
|
| 557 |
+
|
| 558 |
+
class FeedForward(nn.Module):
|
| 559 |
+
def __init__(self, dim: int, hidden_dim: int) -> None:
|
| 560 |
+
super().__init__()
|
| 561 |
+
self.net = nn.Sequential(
|
| 562 |
+
nn.Linear(dim, hidden_dim),
|
| 563 |
+
nn.Tanh(),
|
| 564 |
+
nn.Linear(hidden_dim, dim)
|
| 565 |
+
)
|
| 566 |
+
|
| 567 |
+
def forward(self, x: torch.FloatTensor) -> torch.FloatTensor:
|
| 568 |
+
return self.net(x)
|
| 569 |
+
|
| 570 |
+
class Attention(nn.Module):
|
| 571 |
+
def __init__(self, dim: int, heads: int = 8, dim_head: int = 64) -> None:
|
| 572 |
+
super().__init__()
|
| 573 |
+
inner_dim = dim_head * heads
|
| 574 |
+
project_out = not (heads == 1 and dim_head == dim)
|
| 575 |
+
|
| 576 |
+
self.heads = heads
|
| 577 |
+
self.scale = dim_head ** -0.5
|
| 578 |
+
|
| 579 |
+
self.attend = nn.Softmax(dim = -1)
|
| 580 |
+
self.to_qkv = nn.Linear(dim, inner_dim * 3, bias = False)
|
| 581 |
+
|
| 582 |
+
self.to_out = nn.Linear(inner_dim, dim) if project_out else nn.Identity()
|
| 583 |
+
|
| 584 |
+
def forward(self, x: torch.FloatTensor) -> torch.FloatTensor:
|
| 585 |
+
qkv = self.to_qkv(x).chunk(3, dim = -1)
|
| 586 |
+
q, k, v = map(lambda t: rearrange(t, 'b n (h d) -> b h n d', h = self.heads), qkv)
|
| 587 |
+
|
| 588 |
+
attn = torch.matmul(q, k.transpose(-1, -2)) * self.scale
|
| 589 |
+
attn = self.attend(attn)
|
| 590 |
+
|
| 591 |
+
out = torch.matmul(attn, v)
|
| 592 |
+
out = rearrange(out, 'b h n d -> b n (h d)')
|
| 593 |
+
|
| 594 |
+
return self.to_out(out)
|
| 595 |
+
|
| 596 |
+
class Transformer(nn.Module):
|
| 597 |
+
def __init__(self, dim: int, depth: int, heads: int, dim_head: int, mlp_dim: int) -> None:
|
| 598 |
+
super().__init__()
|
| 599 |
+
self.layers = nn.ModuleList([])
|
| 600 |
+
for idx in range(depth):
|
| 601 |
+
layer = nn.ModuleList([PreNorm(dim, Attention(dim, heads=heads, dim_head=dim_head)),
|
| 602 |
+
PreNorm(dim, FeedForward(dim, mlp_dim))])
|
| 603 |
+
self.layers.append(layer)
|
| 604 |
+
self.norm = nn.LayerNorm(dim)
|
| 605 |
+
|
| 606 |
+
def forward(self, x: torch.FloatTensor) -> torch.FloatTensor:
|
| 607 |
+
for attn, ff in self.layers:
|
| 608 |
+
x = attn(x) + x
|
| 609 |
+
x = ff(x) + x
|
| 610 |
+
|
| 611 |
+
return self.norm(x)
|
| 612 |
+
|
| 613 |
+
class EncoderVIT(nn.Module):
|
| 614 |
+
def __init__(self, image_size: Union[Tuple[int, int], int], patch_size: Union[Tuple[int, int], int],
|
| 615 |
+
dim: int, depth: int, heads: int, mlp_dim: int, channels: int = 3, dim_head: int = 64) -> None:
|
| 616 |
+
super().__init__()
|
| 617 |
+
|
| 618 |
+
image_height, image_width = image_size if isinstance(image_size, tuple) \
|
| 619 |
+
else (image_size, image_size)
|
| 620 |
+
patch_height, patch_width = patch_size if isinstance(patch_size, tuple) \
|
| 621 |
+
else (patch_size, patch_size)
|
| 622 |
+
self.image_height = image_height
|
| 623 |
+
self.image_width = image_width
|
| 624 |
+
self.patch_height = patch_height
|
| 625 |
+
self.patch_width = patch_width
|
| 626 |
+
assert image_height % patch_height == 0 and image_width % patch_width == 0, 'Image dimensions must be divisible by the patch size.'
|
| 627 |
+
en_pos_embedding = get_2d_sincos_pos_embed(dim, (image_height // patch_height, image_width // patch_width))
|
| 628 |
+
|
| 629 |
+
self.num_patches = (image_height // patch_height) * (image_width // patch_width)
|
| 630 |
+
self.patch_dim = channels * patch_height * patch_width
|
| 631 |
+
|
| 632 |
+
self.to_patch_embedding = nn.Sequential(
|
| 633 |
+
nn.Conv2d(channels, dim, kernel_size=patch_size, stride=patch_size),
|
| 634 |
+
Rearrange('b c h w -> b (h w) c'),
|
| 635 |
+
)
|
| 636 |
+
self.en_pos_embedding = nn.Parameter(torch.from_numpy(en_pos_embedding).float().unsqueeze(0), requires_grad=False)
|
| 637 |
+
self.transformer = Transformer(dim, depth, heads, dim_head, mlp_dim)
|
| 638 |
+
|
| 639 |
+
self.apply(init_weights)
|
| 640 |
+
|
| 641 |
+
def forward(self, img: torch.FloatTensor) -> torch.FloatTensor:
|
| 642 |
+
x = self.to_patch_embedding(img)
|
| 643 |
+
x = x + self.en_pos_embedding
|
| 644 |
+
x = self.transformer(x) # (B, N, D)
|
| 645 |
+
#import pdb; pdb.set_trace()
|
| 646 |
+
x = x.reshape(x.shape[0], x.shape[1]//(self.image_height // self.patch_height), x.shape[1]//(self.image_width // self.patch_width), -1)
|
| 647 |
+
x = x.permute(0, 3, 1, 2).contiguous()
|
| 648 |
+
#import pdb; pdb.set_trace()
|
| 649 |
+
return x
|
| 650 |
+
|
| 651 |
+
class Encoder(nn.Module):
|
| 652 |
+
def __init__(self, *, ch, out_ch, ch_mult=(1,2,4,8), num_res_blocks,
|
| 653 |
+
attn_resolutions, dropout=0.0, resamp_with_conv=True, in_channels,
|
| 654 |
+
resolution, z_channels, double_z=True, **ignore_kwargs):
|
| 655 |
+
super().__init__()
|
| 656 |
+
self.ch = ch
|
| 657 |
+
self.temb_ch = 0
|
| 658 |
+
self.num_resolutions = len(ch_mult)
|
| 659 |
+
self.num_res_blocks = num_res_blocks
|
| 660 |
+
self.resolution = resolution
|
| 661 |
+
self.in_channels = in_channels
|
| 662 |
+
|
| 663 |
+
# downsampling
|
| 664 |
+
self.conv_in = torch.nn.Conv2d(in_channels,
|
| 665 |
+
self.ch,
|
| 666 |
+
kernel_size=3,
|
| 667 |
+
stride=1,
|
| 668 |
+
padding=1)
|
| 669 |
+
|
| 670 |
+
curr_res = resolution
|
| 671 |
+
in_ch_mult = (1,)+tuple(ch_mult)
|
| 672 |
+
self.down = nn.ModuleList()
|
| 673 |
+
for i_level in range(self.num_resolutions):
|
| 674 |
+
block = nn.ModuleList()
|
| 675 |
+
attn = nn.ModuleList()
|
| 676 |
+
block_in = ch*in_ch_mult[i_level]
|
| 677 |
+
block_out = ch*ch_mult[i_level]
|
| 678 |
+
for i_block in range(self.num_res_blocks):
|
| 679 |
+
block.append(ResnetBlock(in_channels=block_in,
|
| 680 |
+
out_channels=block_out,
|
| 681 |
+
temb_channels=self.temb_ch,
|
| 682 |
+
dropout=dropout))
|
| 683 |
+
block_in = block_out
|
| 684 |
+
if curr_res in attn_resolutions:
|
| 685 |
+
attn.append(AttnBlock(block_in))
|
| 686 |
+
down = nn.Module()
|
| 687 |
+
down.block = block
|
| 688 |
+
down.attn = attn
|
| 689 |
+
if i_level != self.num_resolutions-1:
|
| 690 |
+
down.downsample = Downsample(block_in, resamp_with_conv)
|
| 691 |
+
if isinstance(curr_res, int):
|
| 692 |
+
curr_res = curr_res // 2
|
| 693 |
+
elif isinstance(curr_res, (list, ListConfig)):
|
| 694 |
+
curr_res = [r // 2 for r in curr_res]
|
| 695 |
+
else:
|
| 696 |
+
raise TypeError(f"Unsupported type for curr_res: {type(curr_res)}")
|
| 697 |
+
|
| 698 |
+
self.down.append(down)
|
| 699 |
+
|
| 700 |
+
# middle
|
| 701 |
+
self.mid = nn.Module()
|
| 702 |
+
self.mid.block_1 = ResnetBlock(in_channels=block_in,
|
| 703 |
+
out_channels=block_in,
|
| 704 |
+
temb_channels=self.temb_ch,
|
| 705 |
+
dropout=dropout)
|
| 706 |
+
self.mid.attn_1 = AttnBlock(block_in)
|
| 707 |
+
self.mid.block_2 = ResnetBlock(in_channels=block_in,
|
| 708 |
+
out_channels=block_in,
|
| 709 |
+
temb_channels=self.temb_ch,
|
| 710 |
+
dropout=dropout)
|
| 711 |
+
|
| 712 |
+
# end
|
| 713 |
+
self.norm_out = Normalize(block_in)
|
| 714 |
+
self.conv_out = torch.nn.Conv2d(block_in,
|
| 715 |
+
2*z_channels if double_z else z_channels,
|
| 716 |
+
kernel_size=3,
|
| 717 |
+
stride=1,
|
| 718 |
+
padding=1)
|
| 719 |
+
|
| 720 |
+
|
| 721 |
+
def forward(self, x):
|
| 722 |
+
# timestep embedding
|
| 723 |
+
temb = None
|
| 724 |
+
# downsampling
|
| 725 |
+
hs = [self.conv_in(x)]
|
| 726 |
+
for i_level in range(self.num_resolutions):
|
| 727 |
+
for i_block in range(self.num_res_blocks):
|
| 728 |
+
h = self.down[i_level].block[i_block](hs[-1], temb)
|
| 729 |
+
if len(self.down[i_level].attn) > 0:
|
| 730 |
+
h = self.down[i_level].attn[i_block](h)
|
| 731 |
+
hs.append(h)
|
| 732 |
+
if i_level != self.num_resolutions-1:
|
| 733 |
+
hs.append(self.down[i_level].downsample(hs[-1]))
|
| 734 |
+
|
| 735 |
+
# middle
|
| 736 |
+
h = hs[-1]
|
| 737 |
+
h = self.mid.block_1(h, temb)
|
| 738 |
+
h = self.mid.attn_1(h)
|
| 739 |
+
h = self.mid.block_2(h, temb)
|
| 740 |
+
|
| 741 |
+
# end
|
| 742 |
+
h = self.norm_out(h)
|
| 743 |
+
h = nonlinearity(h)
|
| 744 |
+
h = self.conv_out(h)
|
| 745 |
+
return h
|
| 746 |
+
|
| 747 |
+
|
| 748 |
+
class Decoder(nn.Module):
|
| 749 |
+
def __init__(self, *, ch, out_ch, ch_mult=(1,2,4,8), num_res_blocks,
|
| 750 |
+
attn_resolutions, dropout=0.0, resamp_with_conv=True, in_channels,
|
| 751 |
+
resolution, z_channels, give_pre_end=False, **ignorekwargs):
|
| 752 |
+
super().__init__()
|
| 753 |
+
self.ch = ch
|
| 754 |
+
self.temb_ch = 0
|
| 755 |
+
self.num_resolutions = len(ch_mult)
|
| 756 |
+
self.num_res_blocks = num_res_blocks
|
| 757 |
+
self.resolution = resolution
|
| 758 |
+
self.in_channels = in_channels
|
| 759 |
+
self.give_pre_end = give_pre_end
|
| 760 |
+
|
| 761 |
+
# compute in_ch_mult, block_in and curr_res at lowest res
|
| 762 |
+
in_ch_mult = (1,)+tuple(ch_mult)
|
| 763 |
+
block_in = ch*ch_mult[self.num_resolutions-1]
|
| 764 |
+
|
| 765 |
+
curr_res_h, curr_res_w = (resolution[0] // 2**(self.num_resolutions-1), resolution[1] // 2**(self.num_resolutions-1)) if isinstance(resolution, (list, tuple, ListConfig)) else (resolution// 2**(self.num_resolutions-1) ,resolution// 2**(self.num_resolutions-1))
|
| 766 |
+
self.z_shape = (1,z_channels,curr_res_h,curr_res_w)
|
| 767 |
+
|
| 768 |
+
# z to block_in
|
| 769 |
+
self.conv_in = torch.nn.Conv2d(z_channels,
|
| 770 |
+
block_in,
|
| 771 |
+
kernel_size=3,
|
| 772 |
+
stride=1,
|
| 773 |
+
padding=1)
|
| 774 |
+
|
| 775 |
+
# middle
|
| 776 |
+
self.mid = nn.Module()
|
| 777 |
+
self.mid.block_1 = ResnetBlock(in_channels=block_in,
|
| 778 |
+
out_channels=block_in,
|
| 779 |
+
temb_channels=self.temb_ch,
|
| 780 |
+
dropout=dropout)
|
| 781 |
+
self.mid.attn_1 = AttnBlock(block_in)
|
| 782 |
+
self.mid.block_2 = ResnetBlock(in_channels=block_in,
|
| 783 |
+
out_channels=block_in,
|
| 784 |
+
temb_channels=self.temb_ch,
|
| 785 |
+
dropout=dropout)
|
| 786 |
+
|
| 787 |
+
#self.decoder_dino_conv = nn.Conv2d(block_in, 768, kernel_size=1, stride=1, padding=0) # remove hard-coded emb_dim=768
|
| 788 |
+
|
| 789 |
+
# upsampling
|
| 790 |
+
self.up = nn.ModuleList()
|
| 791 |
+
for i_level in reversed(range(self.num_resolutions)):
|
| 792 |
+
block = nn.ModuleList()
|
| 793 |
+
attn = nn.ModuleList()
|
| 794 |
+
block_out = ch*ch_mult[i_level]
|
| 795 |
+
for i_block in range(self.num_res_blocks+1):
|
| 796 |
+
block.append(ResnetBlock(in_channels=block_in,
|
| 797 |
+
out_channels=block_out,
|
| 798 |
+
temb_channels=self.temb_ch,
|
| 799 |
+
dropout=dropout))
|
| 800 |
+
block_in = block_out
|
| 801 |
+
if curr_res_h in attn_resolutions:
|
| 802 |
+
attn.append(AttnBlock(block_in))
|
| 803 |
+
up = nn.Module()
|
| 804 |
+
up.block = block
|
| 805 |
+
up.attn = attn
|
| 806 |
+
if i_level != 0:
|
| 807 |
+
up.upsample = Upsample(block_in, resamp_with_conv)
|
| 808 |
+
curr_res_h = curr_res_h * 2
|
| 809 |
+
self.up.insert(0, up) # prepend to get consistent order
|
| 810 |
+
|
| 811 |
+
# end
|
| 812 |
+
self.norm_out = Normalize(block_in)
|
| 813 |
+
self.conv_out = torch.nn.Conv2d(block_in,
|
| 814 |
+
out_ch,
|
| 815 |
+
kernel_size=3,
|
| 816 |
+
stride=1,
|
| 817 |
+
padding=1)
|
| 818 |
+
|
| 819 |
+
def forward(self, z):
|
| 820 |
+
#assert z.shape[1:] == self.z_shape[1:]
|
| 821 |
+
self.last_z_shape = z.shape
|
| 822 |
+
|
| 823 |
+
# timestep embedding
|
| 824 |
+
temb = None
|
| 825 |
+
|
| 826 |
+
# z to block_in
|
| 827 |
+
h = self.conv_in(z)
|
| 828 |
+
|
| 829 |
+
# middle
|
| 830 |
+
h = self.mid.block_1(h, temb)
|
| 831 |
+
h = self.mid.attn_1(h)
|
| 832 |
+
h = self.mid.block_2(h, temb)
|
| 833 |
+
|
| 834 |
+
# upsampling
|
| 835 |
+
for i_level in reversed(range(self.num_resolutions)):
|
| 836 |
+
for i_block in range(self.num_res_blocks+1):
|
| 837 |
+
h = self.up[i_level].block[i_block](h, temb)
|
| 838 |
+
if len(self.up[i_level].attn) > 0:
|
| 839 |
+
h = self.up[i_level].attn[i_block](h)
|
| 840 |
+
if i_level != 0:
|
| 841 |
+
h = self.up[i_level].upsample(h)
|
| 842 |
+
|
| 843 |
+
# end
|
| 844 |
+
if self.give_pre_end:
|
| 845 |
+
return h
|
| 846 |
+
|
| 847 |
+
h = self.norm_out(h)
|
| 848 |
+
h = nonlinearity(h)
|
| 849 |
+
h = self.conv_out(h)
|
| 850 |
+
|
| 851 |
+
return h
|
| 852 |
+
|
|
@@ -0,0 +1,158 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import torch
|
| 2 |
+
import timm
|
| 3 |
+
from torch import nn
|
| 4 |
+
from omegaconf import ListConfig
|
| 5 |
+
from einops import rearrange
|
| 6 |
+
|
| 7 |
+
|
| 8 |
+
from typing import Tuple, Union
|
| 9 |
+
|
| 10 |
+
class Encoder(nn.Module):
|
| 11 |
+
def __init__(
|
| 12 |
+
self,
|
| 13 |
+
resolution: Union[Tuple[int, int], int],
|
| 14 |
+
channels: int = 3,
|
| 15 |
+
pretrained_encoder = 'MAE',
|
| 16 |
+
patch_size: int = 16,
|
| 17 |
+
z_channels: int = 768,
|
| 18 |
+
e_dim: int = 8,
|
| 19 |
+
normalize_embedding: bool = True,
|
| 20 |
+
use_pretrained_weights = True,
|
| 21 |
+
# **ignore_kwargs
|
| 22 |
+
) -> None:
|
| 23 |
+
# Initialize parent class with the first patch size
|
| 24 |
+
super().__init__()
|
| 25 |
+
self.image_size = resolution
|
| 26 |
+
self.patch_size = patch_size
|
| 27 |
+
self.channels = channels
|
| 28 |
+
self.normalize_embedding = normalize_embedding
|
| 29 |
+
self.z_channels = z_channels
|
| 30 |
+
self.e_dim = e_dim
|
| 31 |
+
self.pretrained_encoder = pretrained_encoder
|
| 32 |
+
|
| 33 |
+
self.init_transformer(pretrained_encoder, use_pretrained_weights)
|
| 34 |
+
|
| 35 |
+
def init_transformer(self, pretrained_encoder, use_pretrained_weights):
|
| 36 |
+
if pretrained_encoder == 'VIT_DINO':
|
| 37 |
+
pretrained_encoder_model = 'timm/vit_base_patch16_224.dino'
|
| 38 |
+
elif pretrained_encoder == 'VIT_DINOv2':
|
| 39 |
+
pretrained_encoder_model = 'timm/vit_base_patch14_dinov2.lvd142m'
|
| 40 |
+
elif pretrained_encoder == 'MAE':
|
| 41 |
+
pretrained_encoder_model = 'timm/vit_base_patch16_224.mae'
|
| 42 |
+
elif pretrained_encoder == 'MAE_VIT_L':
|
| 43 |
+
pretrained_encoder_model = 'timm/vit_large_patch16_224.mae'
|
| 44 |
+
elif pretrained_encoder == 'VIT':
|
| 45 |
+
pretrained_encoder_model = 'timm/vit_large_patch32_224.orig_in21k'
|
| 46 |
+
elif pretrained_encoder == 'CLIP32':
|
| 47 |
+
pretrained_encoder_model = 'timm/vit_base_patch32_clip_224.openai'
|
| 48 |
+
elif pretrained_encoder == 'CLIP':
|
| 49 |
+
pretrained_encoder_model = 'timm/vit_base_patch16_clip_224.openai'
|
| 50 |
+
elif pretrained_encoder == 'base':
|
| 51 |
+
pretrained_encoder_model = 'timm/vit_base_patch16_224'
|
| 52 |
+
elif pretrained_encoder == 'large':
|
| 53 |
+
pretrained_encoder_model = 'timm/vit_large_patch16_224'
|
| 54 |
+
|
| 55 |
+
if pretrained_encoder == "VIT_DINOv3":
|
| 56 |
+
self.encoder = torch.hub.load('../dinov3', 'dinov3_vitb16', source='local', weights='./pretrained_models/dinov3_vitb16_pretrain_lvd1689m-73cec8be.pth').train()
|
| 57 |
+
else:
|
| 58 |
+
self.encoder = timm.create_model(pretrained_encoder_model, img_size=self.image_size, patch_size=self.patch_size, pretrained=False, dynamic_img_size=True).train()
|
| 59 |
+
if use_pretrained_weights:
|
| 60 |
+
pretrained_model = timm.create_model(pretrained_encoder_model, img_size=self.image_size, patch_size=self.patch_size, pretrained=True)
|
| 61 |
+
"""Initialize weights of target_model with weights from source_model."""
|
| 62 |
+
with torch.no_grad():
|
| 63 |
+
for target_param, source_param in zip(self.encoder.parameters(), pretrained_model.parameters()):
|
| 64 |
+
target_param.data.copy_(source_param.data)
|
| 65 |
+
|
| 66 |
+
# Clean up
|
| 67 |
+
del pretrained_model
|
| 68 |
+
|
| 69 |
+
def forward(self, img: torch.FloatTensor) -> torch.FloatTensor:
|
| 70 |
+
if self.pretrained_encoder == "VIT_DINOv3":
|
| 71 |
+
h = self.encoder.forward_features(img)['x_norm_patchtokens']
|
| 72 |
+
else:
|
| 73 |
+
h = self.encoder.forward_features(img)[:,1:]
|
| 74 |
+
h = h.permute(0, 2, 1).contiguous()
|
| 75 |
+
h = h.reshape(h.shape[0], -1, img.size(2)//self.patch_size, img.size(3)//self.patch_size)
|
| 76 |
+
return h
|
| 77 |
+
|
| 78 |
+
|
| 79 |
+
class MaskedEncoder(Encoder):
|
| 80 |
+
def __init__(
|
| 81 |
+
self,
|
| 82 |
+
*,
|
| 83 |
+
mask_ratio_min: float = 0.0,
|
| 84 |
+
mask_ratio_max: float = 0.5,
|
| 85 |
+
mask_during_eval: bool = False,
|
| 86 |
+
mask_token_init_std: float = 0.02,
|
| 87 |
+
**kwargs,
|
| 88 |
+
) -> None:
|
| 89 |
+
super().__init__(**kwargs)
|
| 90 |
+
if not 0.0 <= mask_ratio_min <= mask_ratio_max <= 1.0:
|
| 91 |
+
raise ValueError(
|
| 92 |
+
f'Expected 0 <= mask_ratio_min <= mask_ratio_max <= 1, got '
|
| 93 |
+
f'{mask_ratio_min}, {mask_ratio_max}.'
|
| 94 |
+
)
|
| 95 |
+
|
| 96 |
+
self.mask_ratio_min = float(mask_ratio_min)
|
| 97 |
+
self.mask_ratio_max = float(mask_ratio_max)
|
| 98 |
+
self.mask_during_eval = bool(mask_during_eval)
|
| 99 |
+
embed_dim = getattr(self.encoder, 'embed_dim', self.z_channels)
|
| 100 |
+
self.mask_token = nn.Parameter(torch.zeros(1, 1, embed_dim))
|
| 101 |
+
nn.init.normal_(self.mask_token, std=mask_token_init_std)
|
| 102 |
+
|
| 103 |
+
def _sample_patch_mask(self, batch_size: int, num_patches: int, device: torch.device):
|
| 104 |
+
if self.mask_ratio_max <= 0.0:
|
| 105 |
+
return None
|
| 106 |
+
if not self.training and not self.mask_during_eval:
|
| 107 |
+
return None
|
| 108 |
+
|
| 109 |
+
ratios = torch.empty(batch_size, device=device).uniform_(self.mask_ratio_min, self.mask_ratio_max)
|
| 110 |
+
return torch.rand(batch_size, num_patches, device=device) < ratios.unsqueeze(1)
|
| 111 |
+
|
| 112 |
+
def _apply_mask_after_patch_embed(self, x: torch.Tensor) -> torch.Tensor:
|
| 113 |
+
if x.dim() != 3:
|
| 114 |
+
raise ValueError(f'Expected patch embeddings with shape (B, N, C), got {tuple(x.shape)}.')
|
| 115 |
+
mask = self._sample_patch_mask(x.shape[0], x.shape[1], x.device)
|
| 116 |
+
if mask is None:
|
| 117 |
+
return x
|
| 118 |
+
mask_token = self.mask_token.to(dtype=x.dtype).expand(x.shape[0], x.shape[1], -1)
|
| 119 |
+
return torch.where(mask.unsqueeze(-1), mask_token, x)
|
| 120 |
+
|
| 121 |
+
def _forward_timm_features(self, img: torch.FloatTensor) -> torch.Tensor:
|
| 122 |
+
x = self.encoder.patch_embed(img)
|
| 123 |
+
if x.dim() == 4:
|
| 124 |
+
embed_dim = getattr(self.encoder, 'embed_dim', x.shape[-1])
|
| 125 |
+
if x.shape[-1] == embed_dim:
|
| 126 |
+
b, h, w, c = x.shape
|
| 127 |
+
x = x.reshape(b, h * w, c)
|
| 128 |
+
x = self._apply_mask_after_patch_embed(x)
|
| 129 |
+
x = x.reshape(b, h, w, c)
|
| 130 |
+
else:
|
| 131 |
+
x = x.flatten(2).transpose(1, 2)
|
| 132 |
+
x = self._apply_mask_after_patch_embed(x)
|
| 133 |
+
else:
|
| 134 |
+
x = self._apply_mask_after_patch_embed(x)
|
| 135 |
+
x = self.encoder._pos_embed(x)
|
| 136 |
+
x = self.encoder.patch_drop(x)
|
| 137 |
+
x = self.encoder.norm_pre(x)
|
| 138 |
+
|
| 139 |
+
blocks = self.encoder.blocks
|
| 140 |
+
if isinstance(blocks, nn.ModuleList):
|
| 141 |
+
for blk in blocks:
|
| 142 |
+
x = blk(x)
|
| 143 |
+
else:
|
| 144 |
+
x = blocks(x)
|
| 145 |
+
x = self.encoder.norm(x)
|
| 146 |
+
return x
|
| 147 |
+
|
| 148 |
+
def forward(self, img: torch.FloatTensor) -> torch.FloatTensor:
|
| 149 |
+
if self.pretrained_encoder == 'VIT_DINOv3':
|
| 150 |
+
raise NotImplementedError('MaskedEncoder currently supports timm-based encoders only.')
|
| 151 |
+
|
| 152 |
+
h = self._forward_timm_features(img)
|
| 153 |
+
num_prefix_tokens = getattr(self.encoder, 'num_prefix_tokens', 1)
|
| 154 |
+
if num_prefix_tokens > 0:
|
| 155 |
+
h = h[:, num_prefix_tokens:]
|
| 156 |
+
h = h.permute(0, 2, 1).contiguous()
|
| 157 |
+
h = h.reshape(h.shape[0], -1, img.size(2) // self.patch_size, img.size(3) // self.patch_size)
|
| 158 |
+
return h
|
|
@@ -0,0 +1,214 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
+
import argparse
|
| 2 |
+
import hashlib
|
| 3 |
+
import importlib
|
| 4 |
+
import logging
|
| 5 |
+
import os
|
| 6 |
+
import requests
|
| 7 |
+
|
| 8 |
+
from tqdm import tqdm
|
| 9 |
+
from pytorch_lightning.trainer import Trainer
|
| 10 |
+
|
| 11 |
+
|
| 12 |
+
def maybe_raise_nofile_limit(target_soft=32768, logger=None):
|
| 13 |
+
"""
|
| 14 |
+
Raise RLIMIT_NOFILE soft limit for current process when possible.
|
| 15 |
+
|
| 16 |
+
Returns True if the limit was raised, False otherwise.
|
| 17 |
+
"""
|
| 18 |
+
if logger is None:
|
| 19 |
+
logger = logging.getLogger(__name__)
|
| 20 |
+
try:
|
| 21 |
+
import resource
|
| 22 |
+
except ImportError:
|
| 23 |
+
logger.warning("resource module unavailable; cannot set RLIMIT_NOFILE")
|
| 24 |
+
return False
|
| 25 |
+
try:
|
| 26 |
+
soft, hard = resource.getrlimit(resource.RLIMIT_NOFILE)
|
| 27 |
+
desired = target_soft
|
| 28 |
+
if hard not in (resource.RLIM_INFINITY, -1):
|
| 29 |
+
desired = min(desired, hard)
|
| 30 |
+
if soft >= desired:
|
| 31 |
+
return False
|
| 32 |
+
resource.setrlimit(resource.RLIMIT_NOFILE, (desired, hard))
|
| 33 |
+
logger.info("Raised RLIMIT_NOFILE from %s to %s", soft, desired)
|
| 34 |
+
return True
|
| 35 |
+
except (ValueError, PermissionError, OSError) as exc:
|
| 36 |
+
logger.warning("Unable to raise RLIMIT_NOFILE: %s", exc)
|
| 37 |
+
return False
|
| 38 |
+
|
| 39 |
+
URL_MAP = {
|
| 40 |
+
"vgg_lpips": "https://heibox.uni-heidelberg.de/f/607503859c864bc1b30b/?dl=1"
|
| 41 |
+
}
|
| 42 |
+
|
| 43 |
+
CKPT_MAP = {
|
| 44 |
+
"vgg_lpips": "vgg.pth"
|
| 45 |
+
}
|
| 46 |
+
|
| 47 |
+
MD5_MAP = {
|
| 48 |
+
"vgg_lpips": "d507d7349b931f0638a25a48a722f98a"
|
| 49 |
+
}
|
| 50 |
+
|
| 51 |
+
|
| 52 |
+
def download(url, local_path, chunk_size=1024):
|
| 53 |
+
os.makedirs(os.path.split(local_path)[0], exist_ok=True)
|
| 54 |
+
with requests.get(url, stream=True) as r:
|
| 55 |
+
total_size = int(r.headers.get("content-length", 0))
|
| 56 |
+
with tqdm(total=total_size, unit="B", unit_scale=True) as pbar:
|
| 57 |
+
with open(local_path, "wb") as f:
|
| 58 |
+
for data in r.iter_content(chunk_size=chunk_size):
|
| 59 |
+
if data:
|
| 60 |
+
f.write(data)
|
| 61 |
+
pbar.update(chunk_size)
|
| 62 |
+
|
| 63 |
+
|
| 64 |
+
def md5_hash(path):
|
| 65 |
+
with open(path, "rb") as f:
|
| 66 |
+
content = f.read()
|
| 67 |
+
return hashlib.md5(content).hexdigest()
|
| 68 |
+
|
| 69 |
+
|
| 70 |
+
def get_ckpt_path(name, root, check=False):
|
| 71 |
+
assert name in URL_MAP
|
| 72 |
+
path = os.path.join(root, CKPT_MAP[name])
|
| 73 |
+
if not os.path.exists(path) or (check and not md5_hash(path) == MD5_MAP[name]):
|
| 74 |
+
print("Downloading {} model from {} to {}".format(name, URL_MAP[name], path))
|
| 75 |
+
download(URL_MAP[name], path)
|
| 76 |
+
md5 = md5_hash(path)
|
| 77 |
+
assert md5 == MD5_MAP[name], md5
|
| 78 |
+
return path
|
| 79 |
+
|
| 80 |
+
|
| 81 |
+
class KeyNotFoundError(Exception):
|
| 82 |
+
def __init__(self, cause, keys=None, visited=None):
|
| 83 |
+
self.cause = cause
|
| 84 |
+
self.keys = keys
|
| 85 |
+
self.visited = visited
|
| 86 |
+
messages = list()
|
| 87 |
+
if keys is not None:
|
| 88 |
+
messages.append("Key not found: {}".format(keys))
|
| 89 |
+
if visited is not None:
|
| 90 |
+
messages.append("Visited: {}".format(visited))
|
| 91 |
+
messages.append("Cause:\n{}".format(cause))
|
| 92 |
+
message = "\n".join(messages)
|
| 93 |
+
super().__init__(message)
|
| 94 |
+
|
| 95 |
+
|
| 96 |
+
def retrieve(
|
| 97 |
+
list_or_dict, key, splitval="/", default=None, expand=True, pass_success=False
|
| 98 |
+
):
|
| 99 |
+
"""Given a nested list or dict return the desired value at key expanding
|
| 100 |
+
callable nodes if necessary and :attr:`expand` is ``True``. The expansion
|
| 101 |
+
is done in-place.
|
| 102 |
+
|
| 103 |
+
Parameters
|
| 104 |
+
----------
|
| 105 |
+
list_or_dict : list or dict
|
| 106 |
+
Possibly nested list or dictionary.
|
| 107 |
+
key : str
|
| 108 |
+
key/to/value, path like string describing all keys necessary to
|
| 109 |
+
consider to get to the desired value. List indices can also be
|
| 110 |
+
passed here.
|
| 111 |
+
splitval : str
|
| 112 |
+
String that defines the delimiter between keys of the
|
| 113 |
+
different depth levels in `key`.
|
| 114 |
+
default : obj
|
| 115 |
+
Value returned if :attr:`key` is not found.
|
| 116 |
+
expand : bool
|
| 117 |
+
Whether to expand callable nodes on the path or not.
|
| 118 |
+
|
| 119 |
+
Returns
|
| 120 |
+
-------
|
| 121 |
+
The desired value or if :attr:`default` is not ``None`` and the
|
| 122 |
+
:attr:`key` is not found returns ``default``.
|
| 123 |
+
|
| 124 |
+
Raises
|
| 125 |
+
------
|
| 126 |
+
Exception if ``key`` not in ``list_or_dict`` and :attr:`default` is
|
| 127 |
+
``None``.
|
| 128 |
+
"""
|
| 129 |
+
|
| 130 |
+
keys = key.split(splitval)
|
| 131 |
+
|
| 132 |
+
success = True
|
| 133 |
+
try:
|
| 134 |
+
visited = []
|
| 135 |
+
parent = None
|
| 136 |
+
last_key = None
|
| 137 |
+
for key in keys:
|
| 138 |
+
if callable(list_or_dict):
|
| 139 |
+
if not expand:
|
| 140 |
+
raise KeyNotFoundError(
|
| 141 |
+
ValueError(
|
| 142 |
+
"Trying to get past callable node with expand=False."
|
| 143 |
+
),
|
| 144 |
+
keys=keys,
|
| 145 |
+
visited=visited,
|
| 146 |
+
)
|
| 147 |
+
list_or_dict = list_or_dict()
|
| 148 |
+
parent[last_key] = list_or_dict
|
| 149 |
+
|
| 150 |
+
last_key = key
|
| 151 |
+
parent = list_or_dict
|
| 152 |
+
|
| 153 |
+
try:
|
| 154 |
+
if isinstance(list_or_dict, dict):
|
| 155 |
+
list_or_dict = list_or_dict[key]
|
| 156 |
+
else:
|
| 157 |
+
list_or_dict = list_or_dict[int(key)]
|
| 158 |
+
except (KeyError, IndexError, ValueError) as e:
|
| 159 |
+
raise KeyNotFoundError(e, keys=keys, visited=visited)
|
| 160 |
+
|
| 161 |
+
visited += [key]
|
| 162 |
+
# final expansion of retrieved value
|
| 163 |
+
if expand and callable(list_or_dict):
|
| 164 |
+
list_or_dict = list_or_dict()
|
| 165 |
+
parent[last_key] = list_or_dict
|
| 166 |
+
except KeyNotFoundError as e:
|
| 167 |
+
if default is None:
|
| 168 |
+
raise e
|
| 169 |
+
else:
|
| 170 |
+
list_or_dict = default
|
| 171 |
+
success = False
|
| 172 |
+
|
| 173 |
+
if not pass_success:
|
| 174 |
+
return list_or_dict
|
| 175 |
+
else:
|
| 176 |
+
return list_or_dict, success
|
| 177 |
+
|
| 178 |
+
|
| 179 |
+
def get_obj_from_str(string, reload=False):
|
| 180 |
+
module, cls = string.rsplit(".", 1)
|
| 181 |
+
if reload:
|
| 182 |
+
module_imp = importlib.import_module(module)
|
| 183 |
+
importlib.reload(module_imp)
|
| 184 |
+
return getattr(importlib.import_module(module, package=None), cls)
|
| 185 |
+
|
| 186 |
+
|
| 187 |
+
|
| 188 |
+
def get_jobid():
|
| 189 |
+
try:
|
| 190 |
+
jobid = os.environ["PBS_JOBID"].replace(".lmbtorque.informatik.uni-freiburg.de", "")
|
| 191 |
+
except KeyError:
|
| 192 |
+
try:
|
| 193 |
+
jobid = "DLC" + os.environ["SLURM_JOB_ID"]
|
| 194 |
+
except KeyError:
|
| 195 |
+
try:
|
| 196 |
+
jobid = os.uname()[1]
|
| 197 |
+
except KeyError:
|
| 198 |
+
jobid = "local"
|
| 199 |
+
return jobid
|
| 200 |
+
|
| 201 |
+
|
| 202 |
+
def nondefault_trainer_args(opt):
|
| 203 |
+
parser = argparse.ArgumentParser()
|
| 204 |
+
parser = Trainer.add_argparse_args(parser)
|
| 205 |
+
args = parser.parse_args([])
|
| 206 |
+
return sorted(k for k in vars(args) if getattr(opt, k) != getattr(args, k))
|
| 207 |
+
|
| 208 |
+
|
| 209 |
+
def instantiate_from_config(config):
|
| 210 |
+
if not "target" in config:
|
| 211 |
+
raise KeyError("Expected key `target` to instantiate.")
|
| 212 |
+
return get_obj_from_str(config["target"])(**config.get("params", dict()))
|
| 213 |
+
|
| 214 |
+
|
|
@@ -12,6 +12,11 @@ imageio
|
|
| 12 |
timm
|
| 13 |
pillow
|
| 14 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 15 |
# Space infrastructure
|
| 16 |
gradio
|
| 17 |
spaces
|
|
|
|
| 12 |
timm
|
| 13 |
pillow
|
| 14 |
|
| 15 |
+
# Orbis 2 (app2.py) extra deps — video-in rollout reads mp4s directly
|
| 16 |
+
decord
|
| 17 |
+
h5py
|
| 18 |
+
requests
|
| 19 |
+
|
| 20 |
# Space infrastructure
|
| 21 |
gradio
|
| 22 |
spaces
|