Spaces:
Running on Zero
Running on Zero
| # model.py | |
| import math | |
| from dataclasses import dataclass | |
| from enum import IntEnum | |
| from typing import Optional, Tuple, Dict, List | |
| import torch | |
| import torch._dynamo | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| class Modality(IntEnum): | |
| LATENT = -1 | |
| IMAGE = 0 | |
| ACTION = 1 | |
| PROPRIO = 2 | |
| REGISTER = 3 | |
| SPATIAL = 4 | |
| SHORTCUT_SIGNAL = 5 | |
| SHORTCUT_STEP = 6 | |
| AGENT = 7 | |
| class TokenLayout: | |
| n_latents: int | |
| segments: Tuple[Tuple[Modality, int], ...] | |
| def S(self) -> int: | |
| return self.n_latents + sum(n for _, n in self.segments) | |
| def modality_ids(self) -> torch.Tensor: | |
| parts = [] | |
| if self.n_latents > 0: | |
| parts.append(torch.full((self.n_latents,), int(Modality.LATENT), dtype=torch.int32)) | |
| for m, n in self.segments: | |
| if n > 0: | |
| parts.append(torch.full((n,), int(m), dtype=torch.int32)) | |
| return torch.cat(parts, dim=0) if parts else torch.zeros((0,), dtype=torch.int32) | |
| def slices(self) -> Dict[Modality, slice]: | |
| idx = 0 | |
| out: Dict[Modality, slice] = {} | |
| if self.n_latents > 0: | |
| out[Modality.LATENT] = slice(idx, idx + self.n_latents) | |
| idx += self.n_latents | |
| for m, n in self.segments: | |
| if n > 0 and m not in out: | |
| out[m] = slice(idx, idx + n) | |
| idx += n | |
| return out | |
| def temporal_patchify(videos_btchw: torch.Tensor, patch: int) -> torch.Tensor: | |
| """ | |
| videos: (B,T,C,H,W) float in [0,1] | |
| returns: (B,T,Np,Dp) where Dp = patch*patch*C and Np = (H/patch)*(W/patch) | |
| """ | |
| assert videos_btchw.dim() == 5 | |
| B, T, C, H, W = videos_btchw.shape | |
| assert H % patch == 0 and W % patch == 0 | |
| x = videos_btchw.reshape(B * T, C, H, W) | |
| cols = F.unfold(x, kernel_size=patch, stride=patch) # (BT, C*pp, Np) | |
| cols = cols.transpose(1, 2).contiguous() # (BT, Np, Dp) | |
| Np, Dp = cols.shape[1], cols.shape[2] | |
| return cols.reshape(B, T, Np, Dp) | |
| def temporal_unpatchify(patches_btnd: torch.Tensor, H: int, W: int, C: int, patch: int) -> torch.Tensor: | |
| """ | |
| patches: (B,T,Np,Dp) -> (B,T,C,H,W) | |
| """ | |
| assert patches_btnd.dim() == 4 | |
| B, T, Np, Dp = patches_btnd.shape | |
| assert Dp == C * patch * patch | |
| x = patches_btnd.reshape(B * T, Np, Dp).transpose(1, 2).contiguous() # (BT, Dp, Np) | |
| out = F.fold(x, output_size=(H, W), kernel_size=patch, stride=patch) # (BT, C, H, W) | |
| return out.reshape(B, T, C, H, W) | |
| class EmaRms(nn.Module): | |
| """ | |
| Running root-mean-square normalizer using exponential moving average (EMA). | |
| Per the Dreamer 4 paper: "we normalize all loss terms by running estimates | |
| of their root-mean-square (RMS)." This makes loss coefficients interpretable | |
| as relative weights regardless of each term's absolute scale. | |
| """ | |
| def __init__(self, decay: float = 0.99): | |
| super().__init__() | |
| self.decay = float(decay) | |
| self.register_buffer("sq_ema", torch.tensor(1.0)) | |
| def update(self, x: torch.Tensor) -> None: | |
| v = float(x.detach().float().item()) | |
| self.sq_ema.mul_(self.decay).add_((1.0 - self.decay) * v * v) | |
| def normalize(self, x: torch.Tensor) -> torch.Tensor: | |
| return x / self.sq_ema.sqrt().clamp_min(1e-8) | |
| def sync(self, world_size: int) -> None: | |
| """Average sq_ema across all DDP ranks so normalization is consistent.""" | |
| import torch.distributed as dist | |
| if world_size > 1 and dist.is_initialized(): | |
| dist.all_reduce(self.sq_ema, op=dist.ReduceOp.AVG) | |
| def rms_val(self) -> float: | |
| return float(self.sq_ema.sqrt().item()) | |
| class MAEReplacer(nn.Module): | |
| def __init__(self, d_model: int, p_min: float = 0.0, p_max: float = 0.9): | |
| super().__init__() | |
| self.p_min = float(p_min) | |
| self.p_max = float(p_max) | |
| self.mask_token = nn.Parameter(torch.empty(d_model)) | |
| nn.init.normal_(self.mask_token, std=0.02) | |
| def forward(self, patches_btnd: torch.Tensor) -> Tuple[torch.Tensor, torch.Tensor, torch.Tensor]: | |
| """ | |
| patches: (B,T,Np,D) | |
| returns: | |
| replaced: (B,T,Np,D) | |
| mae_mask: (B,T,Np,1) bool, True where masked (must reconstruct) | |
| keep_prob:(B,T,1) float | |
| """ | |
| B, T, Np, D = patches_btnd.shape | |
| device = patches_btnd.device | |
| # fast path: deterministic "no MAE" | |
| if self.p_min == 0.0 and self.p_max == 0.0: | |
| keep_prob = torch.ones((B, T, 1), device=device, dtype=patches_btnd.dtype) | |
| mae_mask = torch.zeros((B, T, Np, 1), device=device, dtype=torch.bool) | |
| return patches_btnd, mae_mask, keep_prob | |
| p_bt = torch.empty((B, T), device=device).uniform_(self.p_min, self.p_max) | |
| keep_prob = (1.0 - p_bt).unsqueeze(-1) # (B,T,1) | |
| keep = (torch.rand((B, T, Np), device=device) < keep_prob) # (B,T,Np) | |
| keep_ = keep.unsqueeze(-1) | |
| mask_tok = self.mask_token.to(dtype=patches_btnd.dtype) | |
| replaced = torch.where(keep_, patches_btnd, mask_tok.view(1, 1, 1, D)) | |
| mae_mask = (~keep_).to(torch.bool) | |
| return replaced, mae_mask, keep_prob | |
| class RMSNorm(nn.Module): | |
| def __init__(self, d: int, eps: float = 1e-6): | |
| super().__init__() | |
| self.eps = eps | |
| self.scale = nn.Parameter(torch.ones(d)) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| var = x.pow(2).mean(dim=-1, keepdim=True) | |
| return x * (self.scale / torch.sqrt(var + self.eps)) | |
| class RotaryEmbedding(nn.Module): | |
| def __init__(self, dim: int, base: float = 10000.0): | |
| super().__init__() | |
| assert dim % 2 == 0, "RoPE dim must be even" | |
| inv_freq = 1.0 / (base ** (torch.arange(0, dim, 2, dtype=torch.float32) / dim)) | |
| self.register_buffer("inv_freq", inv_freq, persistent=False) | |
| self._seq_len_cached = 0 | |
| self._cos_cached = None | |
| self._sin_cached = None | |
| self._cache_device = None # torch.device | |
| def get_cos_sin(self, seq_len: int, *, device, dtype, offset: int = 0): | |
| needed = seq_len + offset | |
| need_new = ( | |
| self._cos_cached is None or | |
| self._sin_cached is None or | |
| self._seq_len_cached < needed or | |
| self._cache_device != device | |
| ) | |
| if need_new: | |
| # Build in fp32 and cache in fp32 | |
| t = torch.arange(needed, device=device, dtype=torch.float32) # (needed,) | |
| inv = self.inv_freq.to(device=device) # (dim/2,) | |
| freqs = torch.einsum("i,j->ij", t, inv) # (needed, dim/2) | |
| emb = torch.cat([freqs, freqs], dim=-1) # (needed, dim) | |
| self._cos_cached = emb.cos() # fp32 | |
| self._sin_cached = emb.sin() # fp32 | |
| self._seq_len_cached = needed | |
| self._cache_device = device | |
| # Slice then cast to requested dtype (bf16/fp16/etc) | |
| cos = self._cos_cached[offset:offset + seq_len].to(dtype=dtype) | |
| sin = self._sin_cached[offset:offset + seq_len].to(dtype=dtype) | |
| return cos, sin | |
| def _rotate_half(x: torch.Tensor) -> torch.Tensor: | |
| # x: (..., D) with D even, interleaved pairs | |
| x1 = x[..., ::2] | |
| x2 = x[..., 1::2] | |
| return torch.stack((-x2, x1), dim=-1).flatten(-2) | |
| def apply_rope(x: torch.Tensor, cos: torch.Tensor, sin: torch.Tensor) -> torch.Tensor: | |
| # x: (N, H, L, D), cos/sin: (L, D) | |
| cos = cos[None, None, :, :] | |
| sin = sin[None, None, :, :] | |
| return (x * cos) + (_rotate_half(x) * sin) | |
| class MLP(nn.Module): | |
| def __init__(self, d_model: int, mlp_ratio: float = 4.0, dropout: float = 0.0): | |
| super().__init__() | |
| hidden = int(d_model * mlp_ratio * 2 / 3) | |
| self.fc_in = nn.Linear(d_model, 2 * hidden) | |
| self.fc_out = nn.Linear(hidden, d_model) | |
| self.drop = nn.Dropout(dropout) | |
| def forward(self, x: torch.Tensor) -> torch.Tensor: | |
| u, v = self.fc_in(x).chunk(2, dim=-1) | |
| h = u * F.silu(v) | |
| h = self.drop(h) | |
| y = self.fc_out(h) | |
| y = self.drop(y) | |
| return y | |
| class MultiheadSelfAttention(nn.Module): | |
| def __init__(self, d_model: int, n_heads: int, dropout: float = 0.0): | |
| super().__init__() | |
| assert d_model % n_heads == 0 | |
| self.d_model = d_model | |
| self.n_heads = n_heads | |
| self.head_dim = d_model // n_heads | |
| self.dropout_p = float(dropout) | |
| self.qkv = nn.Linear(d_model, 3 * d_model, bias=True) | |
| self.out = nn.Linear(d_model, d_model, bias=True) | |
| # RoPE (always on) | |
| self.rope = RotaryEmbedding(self.head_dim) | |
| # QKNorm (always on): per-head temperature, positive by construction. | |
| # Init g ~= sqrt(head_dim) so initial logit scale matches typical 1/sqrt(d) behavior. | |
| init = math.sqrt(self.head_dim) | |
| self.log_qk_scale = nn.Parameter(torch.full((n_heads,), math.log(init), dtype=torch.float32)) | |
| # numeric stability for normalize | |
| self.qk_eps = 1e-6 | |
| def forward( | |
| self, | |
| x_nld: torch.Tensor, | |
| *, | |
| attn_mask: Optional[torch.Tensor] = None, | |
| is_causal: bool = False, | |
| rope_offset: int = 0, | |
| kv_cache: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, | |
| return_kv_cache: bool = False, | |
| ): | |
| N, L, D = x_nld.shape | |
| q, k, v = self.qkv(x_nld).chunk(3, dim=-1) | |
| q = q.view(N, L, self.n_heads, self.head_dim).transpose(1, 2) # (N,H,L,hd) | |
| k = k.view(N, L, self.n_heads, self.head_dim).transpose(1, 2) | |
| v = v.view(N, L, self.n_heads, self.head_dim).transpose(1, 2) | |
| # RoPE — when using KV cache, offset by the cached sequence length | |
| if kv_cache is not None: | |
| rope_offset = kv_cache[0].shape[2] | |
| cos, sin = self.rope.get_cos_sin(L, device=x_nld.device, dtype=q.dtype, offset=rope_offset) | |
| q = apply_rope(q, cos, sin) | |
| k = apply_rope(k, cos, sin) | |
| # QKNorm: normalize along head_dim (cosine attention) | |
| q = F.normalize(q, p=2, dim=-1, eps=self.qk_eps) | |
| k = F.normalize(k, p=2, dim=-1, eps=self.qk_eps) | |
| # Save cache (normalized, RoPE'd K,V) before prepending cached entries | |
| new_cache = (k, v) if return_kv_cache else None | |
| # Prepend cached K,V for decode mode | |
| if kv_cache is not None: | |
| k = torch.cat([kv_cache[0], k], dim=2) | |
| v = torch.cat([kv_cache[1], v], dim=2) | |
| is_causal = False | |
| attn_mask = None | |
| # Fold learnable per-head temperature into q so SDPA yields logits = g * (q·k) | |
| # SDPA internally multiplies by 1/sqrt(head_dim). We multiply q by g*sqrt(head_dim). | |
| g = self.log_qk_scale.exp().to(device=q.device, dtype=q.dtype) # (H,) | |
| # Safety clamp to prevent runaway attention temperatures. | |
| g = g.clamp(0.0, 100.0) | |
| q = q * (g.view(1, self.n_heads, 1, 1) * math.sqrt(self.head_dim)) | |
| drop = self.dropout_p if self.training else 0.0 | |
| y = F.scaled_dot_product_attention( | |
| q, k, v, | |
| attn_mask=attn_mask, | |
| dropout_p=drop, | |
| is_causal=is_causal, | |
| ) | |
| y = y.transpose(1, 2).contiguous().view(N, L, D) | |
| if return_kv_cache: | |
| return self.out(y), new_cache | |
| return self.out(y) | |
| class SpaceSelfAttentionModality(nn.Module): | |
| def __init__(self, d_model: int, n_heads: int, modality_ids: torch.Tensor, n_latents: int, mode: str, dropout: float): | |
| super().__init__() | |
| self.n_latents = int(n_latents) | |
| self.mode = mode | |
| self.register_buffer("modality_ids", modality_ids.to(torch.int32), persistent=False) | |
| S = int(self.modality_ids.numel()) | |
| allow = self._build_allow(S) # (S,S) True=allowed | |
| attn_mask = torch.zeros(1, 1, S, S, dtype=torch.float32) | |
| attn_mask.masked_fill_(~allow.unsqueeze(0).unsqueeze(0), float("-inf")) | |
| self.register_buffer("attn_mask", attn_mask, persistent=False) | |
| self.attn = MultiheadSelfAttention(d_model, n_heads, dropout=dropout) | |
| def _build_allow(self, S: int) -> torch.Tensor: | |
| device = self.modality_ids.device | |
| q_idx = torch.arange(S, device=device).unsqueeze(1) # (S,1) | |
| k_idx = torch.arange(S, device=device).unsqueeze(0) # (1,S) | |
| is_q_lat = q_idx < self.n_latents | |
| is_k_lat = k_idx < self.n_latents | |
| q_mod = self.modality_ids[q_idx] | |
| k_mod = self.modality_ids[k_idx] | |
| same_mod = (q_mod == k_mod) | |
| if self.mode == "encoder": | |
| allow_lat_q = torch.ones((S, S), dtype=torch.bool, device=device) | |
| allow_nonlat_q = same_mod | |
| return torch.where(is_q_lat, allow_lat_q, allow_nonlat_q) | |
| if self.mode == "decoder": | |
| allow_lat_q = is_k_lat | |
| allow_nonlat_q = same_mod | is_k_lat | |
| return torch.where(is_q_lat, allow_lat_q, allow_nonlat_q) | |
| if self.mode == "wm_agent": | |
| # - Non-agent q (Action, Obs) -> all non-agent k (full mixing) | |
| # - Agent q -> all keys | |
| # - Non-agent q never sees Agent k | |
| # Action and obs mix bidirectionally; agent tokens stay isolated from the rest. | |
| is_q_agent = (q_mod == int(Modality.AGENT)) | |
| is_k_agent = (k_mod == int(Modality.AGENT)) | |
| allow_for_agent_q = torch.ones((S, S), dtype=torch.bool, device=device) | |
| allow_nonagent = ~is_k_agent # non-agent queries see all non-agent keys | |
| return torch.where(is_q_agent, allow_for_agent_q, allow_nonagent) | |
| raise ValueError(f"Unsupported mode for tokenizer/wm: {self.mode}") | |
| def forward(self, x_btSd: torch.Tensor) -> torch.Tensor: | |
| B, T, S, D = x_btSd.shape | |
| x = x_btSd.reshape(B * T, S, D) | |
| mask = self.attn_mask.expand(B * T, 1, S, S) | |
| y = self.attn(x, attn_mask=mask, is_causal=False) | |
| return y.reshape(B, T, S, D) | |
| class TimeSelfAttention(nn.Module): | |
| def __init__(self, d_model: int, n_heads: int, dropout: float, latents_only: bool, n_latents: int): | |
| super().__init__() | |
| self.latents_only = bool(latents_only) | |
| self.n_latents = int(n_latents) | |
| self.attn = MultiheadSelfAttention(d_model, n_heads, dropout=dropout) | |
| def forward( | |
| self, | |
| x_btSd: torch.Tensor, | |
| kv_cache: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, | |
| return_kv_cache: bool = False, | |
| ) -> torch.Tensor: | |
| B, T, S, D = x_btSd.shape | |
| if self.latents_only: | |
| L = self.n_latents | |
| lat = x_btSd[:, :, :L, :] # (B,T,L,D) | |
| lat_nld = lat.permute(0, 2, 1, 3).contiguous().view(B * L, T, D) | |
| out = self.attn(lat_nld, is_causal=True) | |
| out = out.view(B, L, T, D).permute(0, 2, 1, 3).contiguous() | |
| return torch.cat([out, x_btSd[:, :, L:, :]], dim=2) | |
| else: | |
| x_nld = x_btSd.permute(0, 2, 1, 3).contiguous().view(B * S, T, D) | |
| if return_kv_cache: | |
| out, cache = self.attn(x_nld, is_causal=True, return_kv_cache=True) | |
| out = out.view(B, S, T, D).permute(0, 2, 1, 3).contiguous() | |
| return out, cache | |
| elif kv_cache is not None: | |
| out = self.attn(x_nld, is_causal=True, kv_cache=kv_cache) | |
| out = out.view(B, S, T, D).permute(0, 2, 1, 3).contiguous() | |
| return out | |
| else: | |
| out = self.attn(x_nld, is_causal=True) | |
| return out.view(B, S, T, D).permute(0, 2, 1, 3).contiguous() | |
| class BlockCausalLayer(nn.Module): | |
| def __init__( | |
| self, | |
| d_model: int, | |
| n_heads: int, | |
| n_latents: int, | |
| modality_ids: torch.Tensor, | |
| space_mode: str, | |
| dropout: float, | |
| mlp_ratio: float, | |
| layer_index: int, | |
| time_every: int, | |
| latents_only_time: bool, | |
| ): | |
| super().__init__() | |
| self.do_time = ((layer_index + 1) % time_every == 0) | |
| self.norm1 = RMSNorm(d_model) | |
| self.space = SpaceSelfAttentionModality(d_model, n_heads, modality_ids, n_latents, space_mode, dropout) | |
| self.drop1 = nn.Dropout(dropout) | |
| if self.do_time: | |
| self.norm2 = RMSNorm(d_model) | |
| self.time = TimeSelfAttention(d_model, n_heads, dropout, latents_only_time, n_latents) | |
| self.drop2 = nn.Dropout(dropout) | |
| self.norm3 = RMSNorm(d_model) | |
| self.mlp = MLP(d_model, mlp_ratio=mlp_ratio, dropout=dropout) | |
| def forward( | |
| self, | |
| x: torch.Tensor, | |
| kv_cache: Optional[Tuple[torch.Tensor, torch.Tensor]] = None, | |
| return_kv_cache: bool = False, | |
| ) -> torch.Tensor: | |
| x = x + self.drop1(self.space(self.norm1(x))) | |
| new_cache = None | |
| if self.do_time: | |
| if return_kv_cache: | |
| time_out, new_cache = self.time(self.norm2(x), return_kv_cache=True) | |
| x = x + self.drop2(time_out) | |
| elif kv_cache is not None: | |
| x = x + self.drop2(self.time(self.norm2(x), kv_cache=kv_cache)) | |
| else: | |
| x = x + self.drop2(self.time(self.norm2(x))) | |
| x = x + self.mlp(self.norm3(x)) | |
| if return_kv_cache: | |
| return x, new_cache | |
| return x | |
| class BlockCausalTransformer(nn.Module): | |
| def __init__( | |
| self, | |
| d_model: int, | |
| n_heads: int, | |
| depth: int, | |
| n_latents: int, | |
| modality_ids: torch.Tensor, | |
| space_mode: str, | |
| dropout: float, | |
| mlp_ratio: float, | |
| time_every: int, | |
| latents_only_time: bool, | |
| ): | |
| super().__init__() | |
| self.layers = nn.ModuleList([ | |
| BlockCausalLayer( | |
| d_model=d_model, n_heads=n_heads, n_latents=n_latents, | |
| modality_ids=modality_ids, space_mode=space_mode, | |
| dropout=dropout, mlp_ratio=mlp_ratio, | |
| layer_index=i, time_every=time_every, | |
| latents_only_time=latents_only_time, | |
| ) | |
| for i in range(depth) | |
| ]) | |
| def forward( | |
| self, | |
| x: torch.Tensor, | |
| kv_cache: Optional[List[Optional[Tuple[torch.Tensor, torch.Tensor]]]] = None, | |
| return_kv_cache: bool = False, | |
| ) -> torch.Tensor: | |
| new_caches: Optional[List[Optional[Tuple[torch.Tensor, torch.Tensor]]]] = [] if return_kv_cache else None | |
| for i, layer in enumerate(self.layers): | |
| layer_cache = kv_cache[i] if kv_cache is not None else None | |
| if return_kv_cache: | |
| x, cache = layer(x, return_kv_cache=True) | |
| new_caches.append(cache) | |
| elif layer_cache is not None: | |
| x = layer(x, kv_cache=layer_cache) | |
| else: | |
| x = layer(x) | |
| if return_kv_cache: | |
| return x, new_caches | |
| return x | |
| class Encoder(nn.Module): | |
| def __init__( | |
| self, | |
| *, | |
| patch_dim: int, | |
| d_model: int, | |
| n_latents: int, | |
| n_patches: int, | |
| n_heads: int, | |
| depth: int, | |
| d_bottleneck: int, | |
| dropout: float = 0.0, | |
| mlp_ratio: float = 4.0, | |
| time_every: int = 4, | |
| latents_only_time: bool = True, | |
| mae_p_min: float = 0.0, | |
| mae_p_max: float = 0.9, | |
| ): | |
| super().__init__() | |
| self.d_model = d_model | |
| self.n_latents = n_latents | |
| self.n_patches = n_patches | |
| self.patch_proj = nn.Linear(patch_dim, d_model) | |
| self.bottleneck_proj = nn.Linear(d_model, d_bottleneck) | |
| self.layout = TokenLayout(n_latents=n_latents, segments=((Modality.IMAGE, n_patches),)) | |
| modality_ids = self.layout.modality_ids() # CPU buffer, moves with .to(device) | |
| self.transformer = BlockCausalTransformer( | |
| d_model=d_model, n_heads=n_heads, depth=depth, | |
| n_latents=n_latents, modality_ids=modality_ids, | |
| space_mode="encoder", | |
| dropout=dropout, mlp_ratio=mlp_ratio, | |
| time_every=time_every, latents_only_time=latents_only_time, | |
| ) | |
| self.mae = MAEReplacer(d_model=d_model, p_min=mae_p_min, p_max=mae_p_max) | |
| self.latents = nn.Parameter(torch.empty(n_latents, d_model)) | |
| nn.init.normal_(self.latents, std=0.02) | |
| def forward(self, patch_tokens_btnd: torch.Tensor): | |
| B, T, Np, Dp = patch_tokens_btnd.shape | |
| assert Np == self.n_patches | |
| proj = self.patch_proj(patch_tokens_btnd) # (B,T,Np,D) | |
| proj_masked, mae_mask, keep_prob = self.mae(proj) # (B,T,Np,D), (B,T,Np,1), (B,T,1) | |
| lat = self.latents.view(1, 1, self.n_latents, -1).expand(B, T, -1, -1) | |
| tokens = torch.cat([lat, proj_masked], dim=2) # (B,T,S,D) | |
| enc = self.transformer(tokens) | |
| z = torch.tanh(self.bottleneck_proj(enc[:, :, :self.n_latents, :])) | |
| return z, (mae_mask, keep_prob) | |
| class Decoder(nn.Module): | |
| def __init__( | |
| self, | |
| *, | |
| d_bottleneck: int, | |
| d_model: int, | |
| n_heads: int, | |
| depth: int, | |
| n_latents: int, | |
| n_patches: int, | |
| d_patch: int, | |
| dropout: float = 0.0, | |
| mlp_ratio: float = 4.0, | |
| time_every: int = 4, | |
| latents_only_time: bool = True, | |
| ): | |
| super().__init__() | |
| self.n_latents = n_latents | |
| self.n_patches = n_patches | |
| self.up_proj = nn.Linear(d_bottleneck, d_model) | |
| self.patch_queries = nn.Parameter(torch.empty(n_patches, d_model)) | |
| nn.init.normal_(self.patch_queries, std=0.02) | |
| self.patch_head = nn.Linear(d_model, d_patch) | |
| self.layout = TokenLayout(n_latents=n_latents, segments=((Modality.IMAGE, n_patches),)) | |
| modality_ids = self.layout.modality_ids() | |
| self.transformer = BlockCausalTransformer( | |
| d_model=d_model, n_heads=n_heads, depth=depth, | |
| n_latents=n_latents, modality_ids=modality_ids, | |
| space_mode="decoder", | |
| dropout=dropout, mlp_ratio=mlp_ratio, | |
| time_every=time_every, latents_only_time=latents_only_time, | |
| ) | |
| def forward(self, z_btLd: torch.Tensor) -> torch.Tensor: | |
| B, T, L, _ = z_btLd.shape | |
| assert L == self.n_latents | |
| lat = self.up_proj(z_btLd) # (B,T,L,D) | |
| qry = self.patch_queries.view(1, 1, self.n_patches, -1).expand(B, T, -1, -1) | |
| tokens = torch.cat([lat, qry], dim=2) # (B,T,S,D) | |
| x = self.transformer(tokens) | |
| x_p = x[:, :, L:, :] | |
| return torch.sigmoid(self.patch_head(x_p)) # (B,T,Np,Dp) | |
| class Tokenizer(nn.Module): | |
| def __init__(self, encoder: Encoder, decoder: Decoder): | |
| super().__init__() | |
| self.encoder = encoder | |
| self.decoder = decoder | |
| def forward(self, patches_btnd: torch.Tensor): | |
| z, (mae_mask, keep_prob) = self.encoder(patches_btnd) | |
| pred = self.decoder(z) | |
| return pred, mae_mask, keep_prob | |
| def pack_bottleneck_to_spatial(z_btLd: torch.Tensor, *, n_spatial: int, k: int) -> torch.Tensor: | |
| """ | |
| z: (B,T,L,D_b) where L == n_spatial * k | |
| -> (B,T,n_spatial,D_b*k) | |
| """ | |
| B, T, L, D = z_btLd.shape | |
| assert L == n_spatial * k, f"L={L} must equal n_spatial*k={n_spatial*k}" | |
| return z_btLd.view(B, T, n_spatial, k * D) | |
| def unpack_spatial_to_bottleneck(z_btSd: torch.Tensor, *, k: int) -> torch.Tensor: | |
| """ | |
| z: (B,T,n_spatial,D_b*k) -> (B,T,n_spatial*k,D_b) | |
| """ | |
| B, T, S, DK = z_btSd.shape | |
| assert DK % k == 0, f"D={DK} must be divisible by k={k}" | |
| D = DK // k | |
| return z_btSd.view(B, T, S * k, D) | |
| class ActionEncoder(nn.Module): | |
| """ | |
| Continuous actions in [-1,1], shape (B,T,A) -> token (B,T,1,D). | |
| If actions is None (unlabeled pretrain), emits a learned base token. | |
| """ | |
| def __init__(self, d_model: int, action_dim: int = 16, hidden_mult: float = 2.0): | |
| super().__init__() | |
| self.d_model = int(d_model) | |
| self.action_dim = int(action_dim) | |
| hidden = int(self.d_model * hidden_mult) | |
| self.base = nn.Parameter(torch.empty(self.d_model)) | |
| nn.init.normal_(self.base, std=0.02) | |
| self.fc1 = nn.Linear(self.action_dim, hidden) | |
| self.fc2 = nn.Linear(hidden, self.d_model) | |
| nn.init.normal_(self.fc2.weight, std=1e-3) | |
| nn.init.zeros_(self.fc2.bias) | |
| def forward( | |
| self, | |
| actions: Optional[torch.Tensor], # (B,T,A) or None | |
| *, | |
| batch_time_shape: Optional[Tuple[int,int]] = None, | |
| act_mask: Optional[torch.Tensor] = None, # (B,T,A) or (A,) | |
| ) -> torch.Tensor: | |
| if actions is None: | |
| assert batch_time_shape is not None | |
| B, T = batch_time_shape | |
| out = self.base.view(1, 1, -1).expand(B, T, -1) | |
| else: | |
| x = actions | |
| if act_mask is not None: | |
| x = x * act_mask | |
| x = x.clamp(-1, 1) | |
| out = self.fc2(F.silu(self.fc1(x))) + self.base.view(1, 1, -1) | |
| return out[:, :, None, :] | |
| def symlog(x: torch.Tensor) -> torch.Tensor: | |
| return torch.sign(x) * torch.log1p(x.abs()) | |
| def symexp(y: torch.Tensor) -> torch.Tensor: | |
| return torch.sign(y) * (torch.expm1(y.abs())) | |
| def twohot_from_symlog(y: torch.Tensor, centers_log: torch.Tensor, eps: float = 1e-8) -> torch.Tensor: | |
| """ | |
| y: (...,) in symlog-space | |
| centers_log: (K,) monotonically increasing in symlog-space | |
| returns: (..., K) two-hot distribution (linear interp between neighbors) | |
| """ | |
| K = centers_log.numel() | |
| y = y.clamp(centers_log[0], centers_log[-1]) | |
| y1 = y.unsqueeze(-1) # (...,1) | |
| idx = torch.searchsorted(centers_log, y1, right=False).clamp(1, K - 1) # (...,1) | |
| lo = centers_log.gather(0, (idx - 1).view(-1)).view_as(idx).to(y.dtype) | |
| hi = centers_log.gather(0, idx.view(-1)).view_as(idx).to(y.dtype) | |
| w_hi = (y1 - lo) / (hi - lo).clamp_min(eps) | |
| w_lo = 1.0 - w_hi | |
| out = torch.zeros((*y.shape, K), device=y.device, dtype=y.dtype) | |
| out.scatter_(-1, idx, w_hi) | |
| out.scatter_(-1, idx - 1, w_lo) | |
| return out | |
| def dist_cross_entropy_from_symlog( | |
| logits: torch.Tensor, # (..., K) | |
| target_symlog: torch.Tensor, # (...,) | |
| centers_log: torch.Tensor, # (K,) | |
| mask: Optional[torch.Tensor] = None, # (...) bool/float | |
| ) -> torch.Tensor: | |
| with torch.no_grad(): | |
| tgt = twohot_from_symlog(target_symlog, centers_log).to(dtype=logits.dtype) | |
| logp = logits.log_softmax(dim=-1) | |
| ce = -(tgt * logp).sum(dim=-1) # (...) | |
| if mask is None: | |
| return ce.mean() | |
| m = mask.to(dtype=ce.dtype) | |
| return (ce * m).sum() / m.sum().clamp_min(1.0) | |
| class RewardHeadMTP(nn.Module): | |
| """ | |
| Plain MLP reward head over already task-conditioned agent tokens h_t. | |
| Per the paper, language/task conditioning enters via the agent tokens inside | |
| the dynamics transformer; the reward head simply reads off h_t. | |
| Input: h_t: (B,T,n_agent,D) or (B,T,D) | |
| Output: logits: (B,T,L,K), centers_log: (K,) | |
| """ | |
| def __init__( | |
| self, | |
| *, | |
| d_model: int, | |
| L: int = 8, | |
| num_bins: int = 101, | |
| mlp_ratio: float = 2.0, | |
| dropout: float = 0.0, | |
| log_low: float = -8.0, | |
| log_high: float = 8.0, | |
| pool_agent: str = "attn", # "attn" | "mean" | "first" | |
| ): | |
| super().__init__() | |
| self.L = int(L) | |
| self.num_bins = int(num_bins) | |
| self.d_model = int(d_model) | |
| self.pool_agent = pool_agent | |
| if pool_agent not in ("attn", "mean", "first"): | |
| raise ValueError(f"pool_agent must be one of attn|mean|first, got {pool_agent}") | |
| # Attention pool: a single learnable query reads over the n_agent tokens, | |
| # letting the head put task-dependent weights on each agent slot. | |
| if pool_agent == "attn": | |
| self.pool_query = nn.Parameter(torch.randn(self.d_model) * 0.02) | |
| self.pool_kv = nn.Linear(self.d_model, 2 * self.d_model, bias=False) | |
| self.projector = MLP(d_model=d_model, mlp_ratio=mlp_ratio, dropout=dropout) | |
| self.out = nn.Linear(d_model, self.L * self.num_bins) | |
| centers = torch.linspace(log_low, log_high, self.num_bins, dtype=torch.float32) | |
| self.register_buffer("centers_log", centers, persistent=True) | |
| # Bias-init each MTP head toward the bin closest to symlog(0)=0 to match the | |
| # sparse-reward marginal, so initial CE is small. Weight init is left at | |
| # default so reward gradients still flow into the backbone from step 1. | |
| with torch.no_grad(): | |
| zero_bin = int(torch.argmin(centers.abs()).item()) | |
| bias = torch.full((self.L, self.num_bins), -5.0, dtype=self.out.bias.dtype) | |
| bias[:, zero_bin] = 5.0 | |
| self.out.bias.copy_(bias.view(-1)) | |
| def forward(self, h_t: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]: | |
| # pool agent tokens if present | |
| if h_t.dim() == 4: | |
| if self.pool_agent == "first": | |
| h = h_t[:, :, 0, :] | |
| elif self.pool_agent == "mean": | |
| h = h_t.mean(dim=2) | |
| else: # attn | |
| B, T, N, D = h_t.shape | |
| kv = self.pool_kv(h_t) # (B,T,N,2D) | |
| k, v = kv.chunk(2, dim=-1) # (B,T,N,D) each | |
| q = self.pool_query.to(dtype=k.dtype) # (D,) | |
| scores = (k * q).sum(dim=-1) / math.sqrt(D) # (B,T,N) | |
| attn = scores.softmax(dim=-1) | |
| h = (attn.unsqueeze(-1) * v).sum(dim=2) # (B,T,D) | |
| else: | |
| h = h_t # (B,T,D) | |
| B, T, _ = h.shape | |
| x = self.projector(h) | |
| logits = self.out(x).view(B, T, self.L, self.num_bins) | |
| return logits, self.centers_log | |
| class PolicyHeadMTP(nn.Module): | |
| """ | |
| Deterministic-MSE BC policy head over task-conditioned agent tokens h_t. | |
| Mirrors RewardHeadMTP's attn-pool + projector + MTP slicing, but outputs | |
| L x act_dim_max real-valued action means (tanh-squashed to [-1, 1]) rather | |
| than logits over bins — matches our continuous action space. | |
| Task conditioning arrives through the agent tokens (initialized from | |
| task_proj(lang_emb) inside the dynamics transformer); the head itself is | |
| task-agnostic. | |
| **Gradient isolation**: callers are expected to pass `h_t.detach()` so that | |
| BC gradients update only this head's parameters, not the dynamics / | |
| agent-token init. This is a deliberate deviation from the Dreamer-4 paper, | |
| which backprops BC gradients into the transformer. | |
| Input: h_t: (B,T,n_agent,D) or (B,T,D) | |
| Output: action_means: (B,T,L,A) in [-1, 1] | |
| """ | |
| def __init__( | |
| self, | |
| *, | |
| d_model: int, | |
| L: int = 8, | |
| act_dim_max: int = 16, | |
| mlp_ratio: float = 2.0, | |
| dropout: float = 0.0, | |
| pool_agent: str = "attn", # "attn" | "mean" | "first" | |
| ): | |
| super().__init__() | |
| self.L = int(L) | |
| self.act_dim_max = int(act_dim_max) | |
| self.d_model = int(d_model) | |
| self.pool_agent = pool_agent | |
| if pool_agent not in ("attn", "mean", "first"): | |
| raise ValueError(f"pool_agent must be one of attn|mean|first, got {pool_agent}") | |
| if pool_agent == "attn": | |
| self.pool_query = nn.Parameter(torch.randn(self.d_model) * 0.02) | |
| self.pool_kv = nn.Linear(self.d_model, 2 * self.d_model, bias=False) | |
| self.projector = MLP(d_model=d_model, mlp_ratio=mlp_ratio, dropout=dropout) | |
| self.out = nn.Linear(d_model, self.L * self.act_dim_max) | |
| # Small-normal weight init + zero bias: predicted means start near 0 (small | |
| # initial BC loss) while still letting gradient flow through `out` into the | |
| # projector and attn-pool from step 1 (a zero-weight init would block that | |
| # gradient until Adam moves the weights off zero). | |
| nn.init.normal_(self.out.weight, std=0.01) | |
| nn.init.zeros_(self.out.bias) | |
| def forward(self, h_t: torch.Tensor) -> torch.Tensor: | |
| # pool agent tokens if present | |
| if h_t.dim() == 4: | |
| if self.pool_agent == "first": | |
| h = h_t[:, :, 0, :] | |
| elif self.pool_agent == "mean": | |
| h = h_t.mean(dim=2) | |
| else: # attn | |
| B, T, N, D = h_t.shape | |
| kv = self.pool_kv(h_t) # (B,T,N,2D) | |
| k, v = kv.chunk(2, dim=-1) # (B,T,N,D) each | |
| q = self.pool_query.to(dtype=k.dtype) # (D,) | |
| scores = (k * q).sum(dim=-1) / math.sqrt(D) # (B,T,N) | |
| attn = scores.softmax(dim=-1) | |
| h = (attn.unsqueeze(-1) * v).sum(dim=2) # (B,T,D) | |
| else: | |
| h = h_t # (B,T,D) | |
| B, T, _ = h.shape | |
| x = self.projector(h) | |
| means = self.out(x).view(B, T, self.L, self.act_dim_max) | |
| return torch.tanh(means) | |
| class Dynamics(nn.Module): | |
| def __init__( | |
| self, | |
| *, | |
| d_model: int, | |
| d_bottleneck: int, | |
| d_spatial: int, | |
| n_spatial: int, | |
| n_register: int, | |
| n_agent: int, | |
| n_heads: int, | |
| depth: int, | |
| k_max: Optional[int] = None, | |
| dropout: float = 0.0, | |
| mlp_ratio: float = 4.0, | |
| time_every: int = 4, | |
| lang_dim: int = 0, | |
| ): | |
| super().__init__() | |
| assert d_spatial % d_bottleneck == 0, "expected packing: d_spatial = d_bottleneck * packing_factor" | |
| self.d_model = int(d_model) | |
| self.d_spatial = int(d_spatial) | |
| self.n_spatial = int(n_spatial) | |
| self.n_register = int(n_register) | |
| self.n_agent = int(n_agent) | |
| self.lang_dim = int(lang_dim) | |
| assert k_max is not None, "k_max must be provided" | |
| self.k_max = int(k_max) | |
| # Task projection: maps lang_emb -> initial agent token values (finetuning phase). | |
| # Only created when lang_dim > 0 and n_agent > 0. | |
| if self.n_agent > 0 and self.lang_dim > 0: | |
| self.task_proj = nn.Linear(self.lang_dim, self.n_agent * self.d_model) | |
| nn.init.normal_(self.task_proj.weight, std=0.02) | |
| nn.init.zeros_(self.task_proj.bias) | |
| else: | |
| self.task_proj = None | |
| self.spatial_proj = nn.Linear(self.d_spatial, self.d_model) | |
| self.register_tokens = nn.Parameter(torch.empty(self.n_register, self.d_model)) | |
| nn.init.normal_(self.register_tokens, std=0.02) | |
| self.action_encoder = ActionEncoder(d_model=self.d_model, action_dim=16) | |
| # shortcut conditioning: σ and d embeddings are half-sized and concatenated | |
| # into a single token per the paper ("their channels are concatenated"). | |
| assert self.d_model % 2 == 0, "d_model must be even for shortcut token channel concatenation" | |
| self.num_step_bins = int(math.log2(self.k_max)) + 1 | |
| self.step_embed = nn.Embedding(self.num_step_bins, self.d_model // 2) | |
| self.signal_embed = nn.Embedding(self.k_max + 1, self.d_model // 2) | |
| segments = [ | |
| (Modality.ACTION, 1), | |
| (Modality.SHORTCUT_SIGNAL, 1), # combined σ+d token | |
| (Modality.SPATIAL, self.n_spatial), | |
| (Modality.REGISTER, self.n_register), | |
| ] | |
| if self.n_agent > 0: | |
| segments.append((Modality.AGENT, self.n_agent)) | |
| self.layout = TokenLayout(n_latents=0, segments=tuple(segments)) | |
| sl = self.layout.slices() | |
| self.spatial_slice = sl[Modality.SPATIAL] | |
| self.agent_slice = sl.get(Modality.AGENT, slice(0, 0)) | |
| modality_ids = self.layout.modality_ids() | |
| self.transformer = BlockCausalTransformer( | |
| d_model=self.d_model, | |
| n_heads=int(n_heads), | |
| depth=int(depth), | |
| n_latents=0, | |
| modality_ids=modality_ids, | |
| space_mode="wm_agent", | |
| dropout=float(dropout), | |
| mlp_ratio=float(mlp_ratio), | |
| time_every=int(time_every), | |
| latents_only_time=False, | |
| ) | |
| self.flow_x_head = nn.Linear(self.d_model, self.d_spatial) | |
| nn.init.zeros_(self.flow_x_head.weight) | |
| nn.init.zeros_(self.flow_x_head.bias) | |
| def forward( | |
| self, | |
| actions: Optional[torch.Tensor], # (B,T,16) or None | |
| step_idxs: Optional[torch.Tensor], # (B,T) | |
| signal_idxs: Optional[torch.Tensor], # (B,T) | |
| packed_enc_tokens: torch.Tensor, # (B,T,n_spatial,d_spatial) | |
| *, | |
| act_mask: Optional[torch.Tensor] = None, # (B,T,16) or (16,) or None | |
| agent_tokens: Optional[torch.Tensor] = None, | |
| lang_emb: Optional[torch.Tensor] = None, # (B,lang_dim) task embedding | |
| kv_cache: Optional[List[Optional[Tuple[torch.Tensor, torch.Tensor]]]] = None, | |
| return_kv_cache: bool = False, | |
| ): | |
| B, T = packed_enc_tokens.shape[:2] | |
| spatial_tokens = self.spatial_proj(packed_enc_tokens) # (B,T,n_spatial,d_model) | |
| action_tokens = self.action_encoder( | |
| actions, | |
| batch_time_shape=(B, T), | |
| act_mask=act_mask, | |
| ) # (B,T,1,d_model) | |
| reg = self.register_tokens.view(1, 1, self.n_register, self.d_model).expand(B, T, -1, -1) | |
| assert step_idxs is not None and signal_idxs is not None, \ | |
| "step_idxs/signal_idxs are required" | |
| sig_emb = self.signal_embed(signal_idxs.to(torch.long)) # (B,T,d_model//2) | |
| step_emb = self.step_embed(step_idxs.to(torch.long)) # (B,T,d_model//2) | |
| shortcut_tok = torch.cat([sig_emb, step_emb], dim=-1)[:, :, None, :] # (B,T,1,d_model) | |
| if self.n_agent > 0: | |
| if agent_tokens is None: | |
| if self.task_proj is not None and lang_emb is not None: | |
| # Project lang_emb to agent token initial values, broadcast over T. | |
| agent_tokens = self.task_proj(lang_emb.to(dtype=spatial_tokens.dtype)) # (B, n_agent*d_model) | |
| agent_tokens = agent_tokens.view(B, 1, self.n_agent, self.d_model).expand(B, T, -1, -1) | |
| else: | |
| agent_tokens = torch.zeros((B, T, self.n_agent, self.d_model), device=spatial_tokens.device, dtype=spatial_tokens.dtype) | |
| toks = [action_tokens, shortcut_tok, spatial_tokens, reg, agent_tokens] | |
| else: | |
| toks = [action_tokens, shortcut_tok, spatial_tokens, reg] | |
| tokens = torch.cat(toks, dim=2) # (B,T,S,D) | |
| if return_kv_cache: | |
| x, new_cache = self.transformer(tokens, return_kv_cache=True) | |
| elif kv_cache is not None: | |
| x = self.transformer(tokens, kv_cache=kv_cache) | |
| else: | |
| x = self.transformer(tokens) | |
| spatial_out = x[:, :, self.spatial_slice, :] | |
| x1_hat = self.flow_x_head(spatial_out) # (B,T,n_spatial,d_spatial) | |
| h_t = None | |
| if self.n_agent > 0: | |
| h_t = x[:, :, self.agent_slice, :] # (B,T,n_agent,d_model) | |
| if return_kv_cache: | |
| return x1_hat, h_t, new_cache | |
| return x1_hat, h_t | |
| def recon_loss_from_mae(pred_btnd: torch.Tensor, | |
| target_btnd: torch.Tensor, | |
| mae_mask_btNp1: torch.Tensor) -> torch.Tensor: | |
| # mask: (B,T,Np,1) bool, True where masked | |
| mask = mae_mask_btNp1.to(dtype=torch.float32) # (B,T,Np,1) | |
| # compute in fp32 to avoid fp16 overflow on reduction | |
| diff = (pred_btnd.float() - target_btnd.float()) # (B,T,Np,Dp) | |
| sq = diff.mul(diff) * mask # broadcast mask over Dp | |
| denom = mask.sum().clamp_min(1.0) * diff.shape[-1] # (#masked patches) * Dp | |
| return sq.sum() / denom | |
| def lpips_on_mae_recon( | |
| lpips_fn, | |
| pred_btnd: torch.Tensor, | |
| target_btnd: torch.Tensor, | |
| mae_mask_btNp1: torch.Tensor, | |
| *, | |
| H: int, W: int, C: int, patch: int, | |
| subsample_frac: float = 1.0, | |
| ) -> torch.Tensor: | |
| recon_masked_btnd = torch.where(mae_mask_btNp1, pred_btnd, target_btnd) | |
| recon = temporal_unpatchify(recon_masked_btnd.float(), H, W, C, patch) | |
| tgt = temporal_unpatchify(target_btnd.float(), H, W, C, patch) | |
| if subsample_frac < 1.0: | |
| B, T = recon.shape[:2] | |
| step = max(1, int(1.0 / subsample_frac)) | |
| recon = recon[:, ::step] | |
| tgt = tgt[:, ::step] | |
| recon = (recon.clamp(0, 1) * 2.0 - 1.0).float() | |
| tgt = (tgt.clamp(0, 1) * 2.0 - 1.0).float() | |
| B, T = recon.shape[:2] | |
| recon = recon.reshape(B * T, C, H, W) | |
| tgt = tgt.reshape(B * T, C, H, W) | |
| with torch.autocast(device_type="cuda", enabled=False): | |
| lp = lpips_fn(recon, tgt) | |
| return lp.mean() | |