| """DA3 spatial-language branch for pi0.5 (JAX/Flax nnx port of the X-VLA addon). |
| |
| Faithful reimplementation of the TRAINABLE modules from |
| `DA3-XVLA-cache/models/spatial_language.py` (H=1024, GIANT C=1536, grid 18x24=432, |
| perceiver tokens 128/96/96, 7-ch scale-aware ray, ModernBERT language fusion). |
| |
| The FROZEN DA3 backbone + ModernBERT run offline (features precached); this module |
| consumes their outputs as arrays and produces per-view "banks" that are cross-attended |
| into the action-expert's late blocks (see gemma.py `SpatialActionInjection`). |
| |
| Only the bank BUILDER lives here (nnx, a submodule of Pi0). The injection layer lives |
| in gemma.py (linen, inside the action-expert scan). Both use identical X-VLA math. |
| |
| Reference math (verified by the understand-phase spec): |
| - ResidualCrossAttention: out = q_hidden + scale * MHA(LN_q(q_hidden), LN_kv(kv), LN_kv(kv)) |
| - MHA matches torch nn.MultiheadAttention: separate q/k/v/out Linears w/ bias, 1/sqrt(head_dim). |
| - GELU is the tanh approximation everywhere; LayerNorm eps=1e-5. |
| - Perceiver residual adds the RAW learned query (not the normalized one). |
| - View order everywhere: 0=main/countertop, 1=left wrist, 2=right wrist. |
| """ |
|
|
| import math as _math |
| import os as _os |
|
|
| import einops |
| import flax.nnx as nnx |
| import jax |
| import jax.numpy as jnp |
|
|
| import openpi.shared.array_typing as at |
|
|
| |
| |
| |
|
|
|
|
| def _gelu(x): |
| return nnx.gelu(x, approximate=True) |
|
|
|
|
| class MHACrossAttn(nnx.Module): |
| """Multi-head cross-attention matching torch nn.MultiheadAttention math (no residual, no norm).""" |
|
|
| def __init__(self, dim: int, num_heads: int, *, logit_gain: bool = False, |
| logit_gain_init: float = 32.0, logit_gain_max: float = 16.0, |
| qk_norm: bool = False, rngs: nnx.Rngs): |
| assert dim % num_heads == 0 |
| self.num_heads = num_heads |
| self.head_dim = dim // num_heads |
| self.q_proj = nnx.Linear(dim, dim, rngs=rngs) |
| self.k_proj = nnx.Linear(dim, dim, rngs=rngs) |
| self.v_proj = nnx.Linear(dim, dim, rngs=rngs) |
| self.out_proj = nnx.Linear(dim, dim, rngs=rngs) |
| |
| |
| |
| |
| |
| |
| |
| |
| self.qk_norm = bool(qk_norm) |
| if self.qk_norm: |
| self.q_ln = nnx.RMSNorm(self.head_dim, rngs=rngs) |
| self.k_ln = nnx.RMSNorm(self.head_dim, rngs=rngs) |
| |
| |
| |
| |
| |
| self.logit_gain = bool(logit_gain) |
| if self.logit_gain: |
| |
| |
| |
| |
| self.log_gain = nnx.Param(jnp.full((num_heads,), jnp.log(jnp.asarray(logit_gain_init, jnp.float32)))) |
| |
| |
| self.log_gain_max = _math.log(max(float(logit_gain_max), 1.0)) |
|
|
| def __call__(self, q, kv, key_pad_mask=None, kv_addr=None, attn_bias=None): |
| |
| |
| |
| |
| |
| |
| |
| h = self.num_heads |
| Q = einops.rearrange(self.q_proj(q), "b l (h d) -> b h l d", h=h) |
| k_in = kv if kv_addr is None else kv + kv_addr |
| K = einops.rearrange(self.k_proj(k_in), "b l (h d) -> b h l d", h=h) |
| V = einops.rearrange(self.v_proj(kv), "b l (h d) -> b h l d", h=h) |
| if self.qk_norm: |
| Q = self.q_ln(Q) |
| K = self.k_ln(K) |
| logits = jnp.einsum("bhqd,bhkd->bhqk", Q, K) * (self.head_dim**-0.5) |
| if self.logit_gain: |
| g = jnp.clip(self.log_gain.value, -self.log_gain_max, self.log_gain_max) |
| logits = logits * jnp.exp(g)[None, :, None, None].astype(logits.dtype) |
| if attn_bias is not None: |
| |
| ab = attn_bias[None] if attn_bias.ndim == 3 else attn_bias |
| logits = logits + ab.astype(logits.dtype) |
| if key_pad_mask is not None: |
| logits = jnp.where(key_pad_mask[:, None, None, :], jnp.asarray(-1e30, logits.dtype), logits) |
| probs = jax.nn.softmax(logits.astype(jnp.float32), axis=-1).astype(logits.dtype) |
| ctx = jnp.einsum("bhqk,bhkd->bhqd", probs, V) |
| ctx = einops.rearrange(ctx, "b h q d -> b q (h d)") |
| return self.out_proj(ctx) |
|
|
|
|
| class ResidualCrossAttn(nnx.Module): |
| """Pre-LN residual cross-attention: out = q_hidden + scale * MHA(LN_q(q_hidden), LN_kv(kv)).""" |
|
|
| def __init__(self, dim: int, num_heads: int, *, logit_gain: bool = False, |
| logit_gain_init: float = 32.0, logit_gain_max: float = 16.0, |
| norm_attn_out: bool = False, qk_norm: bool = False, rngs: nnx.Rngs): |
| self.q_norm = nnx.LayerNorm(dim, epsilon=1e-5, rngs=rngs) |
| self.kv_norm = nnx.LayerNorm(dim, epsilon=1e-5, rngs=rngs) |
| self.attn = MHACrossAttn(dim, num_heads, logit_gain=logit_gain, |
| logit_gain_init=logit_gain_init, logit_gain_max=logit_gain_max, |
| qk_norm=qk_norm, rngs=rngs) |
| |
| |
| |
| |
| self.out_norm = nnx.LayerNorm(dim, epsilon=1e-5, rngs=rngs) if norm_attn_out else None |
|
|
| def __call__(self, q_hidden, kv_hidden, key_pad_mask=None, residual_scale: float = 1.0, kv_addr=None, |
| attn_bias=None): |
| q = self.q_norm(q_hidden) |
| kv = self.kv_norm(kv_hidden) |
| |
| |
| out = self.attn(q, kv, key_pad_mask=key_pad_mask, kv_addr=kv_addr, attn_bias=attn_bias) |
| if self.out_norm is not None: |
| out = self.out_norm(out) |
| return q_hidden + residual_scale * out |
|
|
|
|
| class ResidualMlp(nnx.Module): |
| """Pre-LN residual MLP: x + Linear2(gelu(Linear1(LN(x)))).""" |
|
|
| def __init__(self, dim: int, mlp_ratio: float, *, rngs: nnx.Rngs): |
| hidden = int(dim * mlp_ratio) |
| self.norm = nnx.LayerNorm(dim, epsilon=1e-5, rngs=rngs) |
| self.fc1 = nnx.Linear(dim, hidden, rngs=rngs) |
| self.fc2 = nnx.Linear(hidden, dim, rngs=rngs) |
|
|
| def __call__(self, x): |
| return x + self.fc2(_gelu(self.fc1(self.norm(x)))) |
|
|
|
|
| class ProjLN(nnx.Module): |
| """Linear(in->H) -> gelu -> Linear(H->H) -> LayerNorm(H). Used for layer projectors & t5_projector.""" |
|
|
| def __init__(self, in_dim: int, dim: int, *, rngs: nnx.Rngs): |
| self.fc1 = nnx.Linear(in_dim, dim, rngs=rngs) |
| self.fc2 = nnx.Linear(dim, dim, rngs=rngs) |
| self.norm = nnx.LayerNorm(dim, epsilon=1e-5, rngs=rngs) |
|
|
| def __call__(self, x): |
| return self.norm(self.fc2(_gelu(self.fc1(x)))) |
|
|
|
|
| class Mlp2(nnx.Module): |
| """Linear(in->hidden) -> gelu -> Linear(hidden->out). Used for ray_mlp & pos2d_mlp (no LN).""" |
|
|
| def __init__(self, in_dim: int, hidden: int, out_dim: int, *, rngs: nnx.Rngs): |
| self.fc1 = nnx.Linear(in_dim, hidden, rngs=rngs) |
| self.fc2 = nnx.Linear(hidden, out_dim, rngs=rngs) |
|
|
| def __call__(self, x): |
| return self.fc2(_gelu(self.fc1(x))) |
|
|
|
|
| def fourier_encode(x, num_bands: int): |
| """[..., D] in [-1,1] -> [..., D*(1+2*num_bands)]: the raw value plus sin/cos at 2^k*pi. |
| |
| Raw low-dimensional coordinates through an MLP can only express smooth functions of |
| position (spectral bias), so neighbouring patches collapse to near-identical embeddings. |
| Lifting to a Fourier basis first makes nearby coordinates far apart in the high bands. |
| Band count is chosen against real precision, not maximum precision: with a ~1.3 m |
| half-range, 10 bands resolve ~5 mm, while GT depth is 1 mm-quantised and then |
| area-averaged over a 14x14 patch (~1-5 cm of surface), so more bands would encode noise. |
| """ |
| freqs = (2.0 ** jnp.arange(num_bands)) * jnp.pi |
| xb = x[..., None] * freqs |
| enc = jnp.concatenate([jnp.sin(xb), jnp.cos(xb)], axis=-1) |
| return jnp.concatenate([x, enc.reshape(*x.shape[:-1], -1)], axis=-1) |
|
|
|
|
| class SpatialConditioner(nnx.Module): |
| """token = LN( W [ s ; (1+gamma(s)) * fused + beta(s) ] ), s = MLP(spatial_vector).""" |
|
|
| def __init__(self, dim: int, in_dim: int, *, film: bool = True, use_da3: bool = True, |
| hidden: int = 256, rngs: nnx.Rngs): |
| self.fc1 = nnx.Linear(in_dim, hidden, rngs=rngs) |
| self.fc2 = nnx.Linear(hidden, dim, rngs=rngs) |
| self.film = bool(film) |
| self.use_da3 = bool(use_da3) |
| if self.film: |
| |
| self.gamma = nnx.Linear(dim, dim, rngs=rngs) |
| self.beta = nnx.Linear(dim, dim, rngs=rngs) |
| for lin in (self.gamma, self.beta): |
| lin.kernel.value = jnp.zeros_like(lin.kernel.value) |
| lin.bias.value = jnp.zeros_like(lin.bias.value) |
| self.out = nnx.Linear(2 * dim, dim, rngs=rngs) |
| self.norm = nnx.LayerNorm(dim, epsilon=1e-5, rngs=rngs) |
|
|
| def __call__(self, svec, fused): |
| s = self.fc2(_gelu(self.fc1(svec))) |
| if not self.use_da3: |
| f = jnp.zeros_like(s) |
| else: |
| f = fused |
| if self.film: |
| f = (1.0 + self.gamma(s)) * f + self.beta(s) |
| return self.norm(self.out(jnp.concatenate([s, f], axis=-1))) |
|
|
|
|
| def _locality_dist2(num_queries, h, w): |
| """Squared distance [K, h*w] between each query's tiled anchor and each patch position, both in |
| a normalized [0,1]^2 grid (patches row-major to match _fuse_layers 'b (h w)').""" |
| import numpy as _np |
| ys, xs = _np.meshgrid(_np.linspace(0.0, 1.0, h), _np.linspace(0.0, 1.0, w), indexing="ij") |
| patch = _np.stack([ys.ravel(), xs.ravel()], axis=-1) |
| ar = int(_np.ceil(_np.sqrt(num_queries))); ac = int(_np.ceil(num_queries / ar)) |
| ay, ax = _np.meshgrid(_np.linspace(0.0, 1.0, ar), _np.linspace(0.0, 1.0, ac), indexing="ij") |
| anch = _np.stack([ay.ravel(), ax.ravel()], axis=-1)[:num_queries] |
| return (((anch[:, None, :] - patch[None, :, :]) ** 2).sum(-1)).astype(_np.float32) |
|
|
|
|
| class PerceiverDownsampler(nnx.Module): |
| """432 grid tokens -> K learned-query tokens (single cross-attn + residual MLP).""" |
|
|
| def __init__(self, dim: int, num_queries: int, num_heads: int, *, query_std: float = 0.02, |
| logit_gain: bool = False, logit_gain_init: float = 32.0, logit_gain_max: float = 16.0, |
| norm_attn_out: bool = False, qk_norm: bool = False, norm_out: bool = False, |
| locality: bool = False, grid_hw: tuple = (18, 24), locality_gamma_init: float = 4.0, |
| ee_anchor: bool = False, ee_query_frac: float = 0.25, ee_gamma_init: float = 4.0, |
| rngs: nnx.Rngs): |
| key = rngs.params() |
| self.query = nnx.Param(jax.random.normal(key, (1, num_queries, dim)) * query_std) |
| self.xattn = ResidualCrossAttn(dim, num_heads, logit_gain=logit_gain, |
| logit_gain_init=logit_gain_init, logit_gain_max=logit_gain_max, |
| norm_attn_out=norm_attn_out, qk_norm=qk_norm, rngs=rngs) |
| self.mlp = ResidualMlp(dim, mlp_ratio=2.0, rngs=rngs) |
| |
| |
| |
| |
| self.locality = bool(locality) |
| if self.locality: |
| self._loc_nq = int(num_queries) |
| self._loc_gh = (int(grid_hw[0]), int(grid_hw[1])) |
| self.loc_log_gamma = nnx.Param(jnp.full((num_heads,), _math.log(max(locality_gamma_init, 1e-3)))) |
| |
| |
| |
| |
| |
| self.ee_anchor = bool(ee_anchor) |
| self._n_ee = min(int(round(num_queries * float(ee_query_frac))), num_queries) if ee_anchor else 0 |
| if self.ee_anchor and self._n_ee > 0: |
| self.ee_log_gamma = nnx.Param(jnp.full((num_heads,), _math.log(max(ee_gamma_init, 1e-3)))) |
| |
| |
| |
| |
| self.out_ln = nnx.LayerNorm(dim, epsilon=1e-5, rngs=rngs) if norm_out else None |
|
|
| def __call__(self, tokens, addr=None, token_embed=None, ee_dist2=None): |
| |
| |
| |
| |
| |
| b = tokens.shape[0] |
| q = jnp.broadcast_to(self.query.value, (b, *self.query.value.shape[1:])) |
| if token_embed is not None: |
| q = q + token_embed |
| bias = None |
| if self.locality: |
| gamma = jnp.exp(self.loc_log_gamma.value) |
| dist2 = jnp.asarray(_locality_dist2(self._loc_nq, *self._loc_gh)) |
| bias = -gamma[:, None, None] * dist2[None] |
| if self.ee_anchor and self._n_ee > 0 and ee_dist2 is not None: |
| |
| |
| n_ee, Lk = self._n_ee, tokens.shape[1] |
| h = self.xattn.attn.num_heads |
| side = jnp.asarray([j % 2 for j in range(n_ee)]) |
| g_ee = jnp.exp(self.ee_log_gamma.value) |
| sel = jnp.take(ee_dist2, side, axis=1) |
| ee_rows = -g_ee[None, :, None, None] * sel[:, None, :, :] |
| if bias is not None: |
| rest = jnp.broadcast_to(bias[None, :, n_ee:, :], (b, h, self._loc_nq - n_ee, Lk)) |
| else: |
| rest = jnp.zeros((b, h, q.shape[1] - n_ee, Lk), ee_rows.dtype) |
| bias = jnp.concatenate([ee_rows, rest.astype(ee_rows.dtype)], axis=2) |
| z = self.xattn(q, tokens, residual_scale=1.0, kv_addr=addr, attn_bias=bias) |
| out = self.mlp(z) |
| return self.out_ln(out) if self.out_ln is not None else out |
|
|
|
|
| class LanguageFusionStack(nnx.Module): |
| """N x [cross-attn(bank, lang) + residual-MLP], with language padding mask.""" |
|
|
| def __init__(self, dim: int, depth: int, num_heads: int, *, qk_norm: bool = False, |
| rngs: nnx.Rngs): |
| self.layers = [ |
| (ResidualCrossAttn(dim, num_heads, qk_norm=qk_norm, rngs=rngs), |
| ResidualMlp(dim, mlp_ratio=4.0, rngs=rngs)) |
| for _ in range(depth) |
| ] |
|
|
| def __call__(self, geo, lang_tokens, lang_pad_mask): |
| for xattn, mlp in self.layers: |
| geo = xattn(geo, lang_tokens, key_pad_mask=lang_pad_mask, residual_scale=1.0) |
| geo = mlp(geo) |
| return geo |
|
|
|
|
| def _cam_pose_feat(ext): |
| """Camera-pose feature [b,12] from a w2c extrinsic [b,4,4]: R_c2w flattened (9) + camera center (3). |
| Gives the cross-view fusion the RELATIVE viewpoints so it can reason across cameras geometrically.""" |
| R = ext[:, :3, :3] |
| t = ext[:, :3, 3] |
| Rc2w = jnp.swapaxes(R, -1, -2) |
| center = -jnp.einsum("bij,bj->bi", Rc2w, t) |
| return jnp.concatenate([Rc2w.reshape(ext.shape[0], 9), center], axis=-1).astype(jnp.float32) |
|
|
|
|
| class CrossViewFusion(nnx.Module): |
| """Self-attention over the CONCATENATED per-view tokens so the three views exchange 3D information |
| (grounded by per-view camera pose), turning three separate 2.5D banks into one integrated scene.""" |
|
|
| def __init__(self, dim: int, num_heads: int, depth: int, *, qk_norm: bool = False, rngs: nnx.Rngs): |
| self.blocks = [ |
| (ResidualCrossAttn(dim, num_heads, qk_norm=qk_norm, rngs=rngs), |
| ResidualMlp(dim, mlp_ratio=4.0, rngs=rngs)) |
| for _ in range(depth) |
| ] |
|
|
| def __call__(self, x): |
| for attn, mlp in self.blocks: |
| x = attn(x, x, residual_scale=1.0) |
| x = mlp(x) |
| return x |
|
|
|
|
| |
| |
| |
|
|
|
|
| def compute_world_ray_6d(ray_local, ext_w2c): |
| """ray_local [b,3,h,w] cam-local unit dir; ext_w2c [b,4,4] OpenCV world->cam. |
| |
| Returns [b,6,h,w] = concat([origin_world(camera center), dir_world]). |
| """ |
| R_w2c = ext_w2c[:, :3, :3] |
| t_w2c = ext_w2c[:, :3, 3] |
| R_c2w = jnp.swapaxes(R_w2c, -1, -2) |
| pos_world = -jnp.einsum("bij,bj->bi", R_c2w, t_w2c) |
| b, _, h, w = ray_local.shape |
| dir_world = jnp.einsum("bij,bjk->bik", R_c2w, ray_local.reshape(b, 3, h * w)).reshape(b, 3, h, w) |
| origin = jnp.broadcast_to(pos_world[:, :, None, None], (b, 3, h, w)) |
| return jnp.concatenate([origin, dir_world], axis=1) |
|
|
|
|
| def _cam_center(ext): |
| """Camera centre in the reference (robot) frame from a robot->cam extrinsic [b,4,4]: -R_c2w @ t. |
| For the WRIST-mounted realsense cameras this is the end-effector position.""" |
| R_c2w = jnp.swapaxes(ext[:, :3, :3], -1, -2) |
| return -jnp.einsum("bij,bj->bi", R_c2w, ext[:, :3, 3]).astype(jnp.float32) |
|
|
|
|
| def _info_nce(za, zb, temp: float): |
| """Symmetric (CLIP-style) InfoNCE between two L2-normalised embeddings [b,D]. The positive pair is |
| the diagonal (same sample); every other sample in the batch is a negative.""" |
| za = za / (jnp.linalg.norm(za, axis=-1, keepdims=True) + 1e-6) |
| zb = zb / (jnp.linalg.norm(zb, axis=-1, keepdims=True) + 1e-6) |
| logits = jnp.einsum("id,jd->ij", za, zb) / temp |
| labels = jnp.arange(logits.shape[0]) |
| ce = lambda lg: -jnp.mean(jax.nn.log_softmax(lg, axis=-1)[labels, labels]) |
| return 0.5 * (ce(logits) + ce(logits.T)) |
|
|
|
|
| def _grid_coords(h: int, w: int): |
| v = 2.0 * jnp.arange(h) / (h - 1) - 1.0 |
| u = 2.0 * jnp.arange(w) / (w - 1) - 1.0 |
| yy, xx = jnp.meshgrid(v, u, indexing="ij") |
| return jnp.stack([xx, yy], axis=-1).reshape(1, h * w, 2) |
|
|
|
|
| |
| |
| |
|
|
| |
| |
| |
| |
| _PERC_TOKEN_MULT = float(_os.environ.get("DA3_PERC_TOKEN_MULT", "1.0")) |
| def _perc_k(base: int) -> int: |
| return max(1, int(round(base * _PERC_TOKEN_MULT))) |
| _VIEWS = (("main", 0, _perc_k(128)), ("left", 1, _perc_k(96)), ("right", 2, _perc_k(96))) |
|
|
|
|
| class SpatialBankBuilder(nnx.Module): |
| """Cached DA3 (feats/ray/depth) + extrinsics + ModernBERT feats -> 3 per-view banks.""" |
|
|
| def __init__( |
| self, |
| *, |
| hidden_dim: int = 1024, |
| da3_channels: int = 1536, |
| num_layers: int = 4, |
| grid_hw: tuple[int, int] = (18, 24), |
| lang_dim: int = 1024, |
| num_heads: int = 8, |
| lang_fusion_depth: int = 2, |
| perceiver_query_std: float = 0.02, |
| qk_norm: bool = False, |
| perceiver_norm_out: bool = False, |
| pos_emb_scale: float = 1.0, |
| perceiver_logit_gain: bool = False, |
| perceiver_logit_gain_init: float = 32.0, |
| perceiver_logit_gain_max: float = 16.0, |
| perceiver_norm_attn_out: bool = False, |
| bank_token_embed: bool = False, |
| bank_center: bool = False, |
| aux_geom_head: bool = False, |
| depth_target_only: bool = False, |
| kv_split: bool = False, |
| depth_dropout: float = 0.0, |
| perc_locality: bool = False, |
| cross_view: bool = False, |
| cross_view_depth: int = 2, |
| bank_token_embed_query: bool = True, |
| use_depth_conf: bool = False, |
| use_pose_enc: bool = False, |
| use_cam_tokens: bool = False, |
| cam_token_dim: int = 2048, |
| pose_enc_dim: int = 9, |
| feat_input_norm: bool = False, |
| use_point_map: bool = False, |
| depth_aware_crossview: bool = False, |
| ee_anchor: bool = False, |
| ee_query_frac: float = 0.25, |
| ee_gamma_init: float = 4.0, |
| ee_max_dist2: float = 25.0, |
| infonce: bool = False, |
| infonce_temp: float = 0.07, |
| infonce_dim: int = 128, |
| infonce_pool_k: int = 16, |
| use_perceiver: bool = True, |
| spatial_vec: bool = False, |
| spatial_film: bool = True, |
| spatial_use_da3: bool = True, |
| fourier_bands: int = 10, |
| point_centre: tuple = (1.032, 0.527, 1.064), |
| point_centre_ee_l: tuple = (0.811, 0.278, 0.332), |
| point_centre_ee_r: tuple = (0.268, 0.895, 0.154), |
| point_scale: float = 1.30, |
| point_max_depth: float = 5.0, |
| rngs: nnx.Rngs, |
| ): |
| |
| |
| |
| self._bte_query = bool(bank_token_embed_query) |
| |
| |
| |
| |
| |
| |
| |
| |
| self.kv_split = bool(kv_split) |
| |
| |
| |
| self.depth_dropout = float(depth_dropout) |
| self.bank_center = bool(bank_center) |
| |
| |
| |
| |
| |
| |
| self.aux_geom_head = bool(aux_geom_head) |
| |
| |
| |
| |
| |
| |
| self.depth_target_only = bool(depth_target_only) |
| H = hidden_dim |
| self.hidden_dim = H |
| |
| |
| self.use_perceiver = bool(use_perceiver) |
| self._ntok = {name: (k if self.use_perceiver else grid_hw[0] * grid_hw[1]) |
| for name, _, k in _VIEWS} |
| self.num_layers = num_layers |
| self.grid_hw = grid_hw |
| |
| self.layer_projectors = [ProjLN(da3_channels, H, rngs=rngs) for _ in range(num_layers)] |
| self.layer_embed = nnx.Param(jax.random.normal(rngs.params(), (num_layers, H)) * 0.02) |
| self.layer_fuse = nnx.Linear(num_layers * H, H, rngs=rngs) |
| |
| |
| if self.kv_split: |
| self.ray_mlp = Mlp2(6, 256, H, rngs=rngs) |
| self.depth_mlp = Mlp2(1, 256, H, rngs=rngs) |
| else: |
| self.ray_mlp = Mlp2(7, 256, H, rngs=rngs) |
| |
| self.pos2d_mlp = Mlp2(2, 256, H, rngs=rngs) |
| self.view_embed = nnx.Embed(3, H, rngs=rngs) |
| |
| |
| self.lang_fusion_depth = int(lang_fusion_depth) |
| self.t5_projector = ProjLN(lang_dim, H, rngs=rngs) if self.lang_fusion_depth > 0 else None |
| |
| |
| |
| |
| self.pos_emb_scale = float(pos_emb_scale) |
| |
| self.perceivers = None if not self.use_perceiver else { |
| name: PerceiverDownsampler(H, k, num_heads, query_std=perceiver_query_std, |
| logit_gain=perceiver_logit_gain, |
| logit_gain_init=perceiver_logit_gain_init, |
| logit_gain_max=perceiver_logit_gain_max, |
| norm_attn_out=perceiver_norm_attn_out, |
| qk_norm=qk_norm, norm_out=perceiver_norm_out, |
| locality=perc_locality, grid_hw=grid_hw, |
| ee_anchor=ee_anchor, ee_query_frac=ee_query_frac, |
| ee_gamma_init=ee_gamma_init, rngs=rngs) |
| for name, _, k in _VIEWS |
| } |
| |
| self.ee_anchor = bool(ee_anchor) |
| self.ee_max_dist2 = float(ee_max_dist2) |
| |
| |
| self.infonce = bool(infonce) |
| self.infonce_temp = float(infonce_temp) |
| self.infonce_pool_k = int(infonce_pool_k) |
| if self.infonce: |
| self.nce_bank_proj = Mlp2(H * len(_VIEWS), 512, infonce_dim, rngs=rngs) |
| self.nce_geom_proj = Mlp2(3 * infonce_pool_k * len(_VIEWS), 512, infonce_dim, rngs=rngs) |
| self.lang_fusers = ({name: LanguageFusionStack(H, lang_fusion_depth, num_heads, |
| qk_norm=qk_norm, rngs=rngs) |
| for name, _, _ in _VIEWS} |
| if self.lang_fusion_depth > 0 else None) |
| |
| |
| self.cross_view = bool(cross_view) |
| if self.cross_view: |
| self.cam_pose_mlp = Mlp2(12, 256, H, rngs=rngs) |
| self.cross_view_fusion = CrossViewFusion(H, num_heads, cross_view_depth, qk_norm=qk_norm, rngs=rngs) |
| |
| |
| |
| self.use_depth_conf = bool(use_depth_conf) |
| if self.use_depth_conf: |
| self.conf_mlp = Mlp2(1, 256, H, rngs=rngs) |
| |
| |
| self.use_pose_enc = bool(use_pose_enc) |
| if self.use_pose_enc: |
| self.pose_enc_mlp = Mlp2(pose_enc_dim, 256, H, rngs=rngs) |
| |
| |
| |
| self.use_cam_tokens = bool(use_cam_tokens) |
| if self.use_cam_tokens: |
| |
| |
| self.cam_in_norm = nnx.LayerNorm(cam_token_dim, epsilon=1e-5, rngs=rngs) |
| self.cam_token_proj = ProjLN(cam_token_dim, H, rngs=rngs) |
| |
| |
| |
| self.feat_input_norm = bool(feat_input_norm) |
| if self.feat_input_norm: |
| self.feat_in_norm = nnx.LayerNorm(da3_channels, epsilon=1e-5, rngs=rngs) |
| |
| |
| |
| self.use_point_map = bool(use_point_map) |
| if self.use_point_map: |
| self.point_mlp = Mlp2(3, 256, H, rngs=rngs) |
| |
| |
| |
| self.depth_aware_crossview = bool(depth_aware_crossview) |
| if self.depth_aware_crossview: |
| self.pos3d_mlp = Mlp2(3, 256, H, rngs=rngs) |
| |
| |
| |
| |
| self.bank_token_embeds = ( |
| {name: nnx.Param(jax.random.normal(rngs.params(), (1, self._ntok[name], H)) * 0.05) |
| for name, _, _ in _VIEWS} |
| if bank_token_embed |
| else None |
| ) |
| |
| |
| |
| |
| |
| |
| |
| |
| self.spatial_vec = bool(spatial_vec) |
| self.fourier_bands = int(fourier_bands) |
| self.point_centre = tuple(float(v) for v in point_centre) |
| self.point_centre_ee_l = tuple(float(v) for v in point_centre_ee_l) |
| self.point_centre_ee_r = tuple(float(v) for v in point_centre_ee_r) |
| self.point_scale = float(point_scale) |
| self.point_max_depth = float(point_max_depth) |
| if self.spatial_vec: |
| _pd = 3 * (1 + 2 * self.fourier_bands) |
| _in = 3 * _pd + 3 + 1 |
| self.spatial_cond = SpatialConditioner( |
| H, _in, film=spatial_film, use_da3=spatial_use_da3, rngs=rngs) |
|
|
| |
| if self.aux_geom_head: |
| self.aux_q = nnx.Linear(H, H, rngs=rngs) |
| self.aux_k = nnx.Linear(H, H, rngs=rngs) |
| self.aux_v = nnx.Linear(H, H, rngs=rngs) |
| self.aux_out = nnx.Linear(H, 1, rngs=rngs) |
|
|
| def _fuse_layers(self, feats_v): |
| |
| b, L, C, h, w = feats_v.shape |
| parts = [] |
| for li in range(self.num_layers): |
| flat = einops.rearrange(feats_v[:, li], "b c h w -> b (h w) c") |
| if self.feat_input_norm: |
| flat = self.feat_in_norm(flat) |
| p = self.layer_projectors[li](flat) + self.layer_embed.value[li][None, None, :] |
| parts.append(p) |
| return self.layer_fuse(jnp.concatenate(parts, axis=-1)) |
|
|
| def _ray7(self, ray_v, depth_v, ext_v): |
| |
| ray6 = compute_world_ray_6d(ray_v, ext_v) |
| logd = jnp.log(jnp.clip(depth_v.astype(jnp.float32), a_min=1e-3)).astype(ray6.dtype) |
| ray7 = jnp.concatenate([ray6, logd], axis=1) |
| return einops.rearrange(ray7, "b c h w -> b (h w) c") |
|
|
| def _point_maps(self, ray_v, depth_v, ext_v): |
| """(unit cam ray, metric Z-depth, extrinsics) -> per-patch metric 3D points. |
| Z-depth d and unit cam dir r: range along ray = d / r_z; p_cam = range*r; p_world = origin+range*dir_world. |
| Returns (p_cam_flat [b,432,3], p_world_flat [b,432,3]).""" |
| ray6 = compute_world_ray_6d(ray_v, ext_v) |
| origin, dir_world = ray6[:, 0:3], ray6[:, 3:6] |
| r = ray_v.astype(jnp.float32) |
| rng = depth_v.astype(jnp.float32) / jnp.clip(r[:, 2:3], a_min=0.1) |
| p_cam = rng * r |
| p_world = origin + rng * dir_world |
| f = lambda t: einops.rearrange(t, "b c h w -> b (h w) c") |
| return f(p_cam), f(p_world) |
|
|
| def _norm_pt(self, p, centre): |
| c = jnp.asarray(centre, p.dtype) |
| return jnp.clip((p - c) / jnp.asarray(self.point_scale, p.dtype), -1.0, 1.0) |
|
|
| def _spatial_vector(self, p_world, dir_world, depth_flat, ee_pos): |
| """[b,P,3] robot-frame points (+ view dir, depth, EE positions) -> [b,P,in_dim]. |
| |
| Three Fourier-encoded position vectors: absolute, and relative to each hand. The |
| hand-relative pair is the per-sample quantity that survives a static scene -- the |
| gripper moves every frame even when nothing else does. dir_world is kept raw (already |
| a bounded unit vector) because it says which side of the surface we are looking at, |
| which `p` alone cannot express. Invalid depth zeroes the geometry but keeps the flag, |
| so the network can tell "no measurement" from "at the origin". |
| """ |
| valid = ((depth_flat > 0.05) & (depth_flat < self.point_max_depth)).astype(p_world.dtype) |
| K = self.fourier_bands |
| geom = jnp.concatenate([ |
| fourier_encode(self._norm_pt(p_world, self.point_centre), K), |
| fourier_encode(self._norm_pt(p_world - ee_pos[:, 0:1, :], self.point_centre_ee_l), K), |
| fourier_encode(self._norm_pt(p_world - ee_pos[:, 1:2, :], self.point_centre_ee_r), K), |
| dir_world, |
| ], axis=-1) * valid |
| return jnp.concatenate([geom, valid], axis=-1) |
|
|
| def _pool_pts(self, pw_flat, k): |
| """world points [b, h*w, 3] -> [b, k, 3] pooled to the perceiver's locality-anchor grid (row-major).""" |
| h, w = self.grid_hw |
| pmap = einops.rearrange(pw_flat, "b (h w) c -> b c h w", h=h, w=w) |
| ar = int(_math.ceil(_math.sqrt(k))); ac = int(_math.ceil(k / ar)) |
| pooled = jax.image.resize(pmap.astype(jnp.float32), (pmap.shape[0], 3, ar, ac), method="linear") |
| return einops.rearrange(pooled, "b c ar ac -> b (ar ac) c")[:, :k, :] |
|
|
| def __call__(self, feats, ray, depth, extrinsics, lang_feat, lang_mask, return_aux: bool = False, |
| depth_drop_rng=None, depth_conf=None, pose_enc=None, cam_tokens=None): |
| |
| |
| |
| |
| h, w = self.grid_hw |
| pos_emb = self.pos2d_mlp(_grid_coords(h, w).astype(feats.dtype)) |
| if self.pos_emb_scale != 1.0: |
| pos_emb = pos_emb * jnp.asarray(self.pos_emb_scale, pos_emb.dtype) |
| |
| |
| |
| lang_tokens = self.t5_projector(lang_feat) if self.t5_projector is not None else None |
| lang_pad = jnp.logical_not(lang_mask) |
| |
| |
| |
| |
| |
| ee_pos = None |
| if self.ee_anchor or self.infonce or self.spatial_vec: |
| ee_pos = jnp.stack([_cam_center(extrinsics[:, vi]) for vi in (1, 2)], axis=1) |
|
|
| geos = {} |
| world_pts = {} |
| aux_losses = [] |
| for name, vidx, _k in _VIEWS: |
| fused = self._fuse_layers(feats[:, :, vidx]) |
| ray_flat = self._ray7(ray[:, vidx], depth[:, vidx], extrinsics[:, vidx]) |
| p_cam_flat = None |
| need_pts = (self.use_point_map or self.depth_aware_crossview or self.ee_anchor |
| or self.infonce or self.spatial_vec) |
| if need_pts: |
| p_cam_flat, p_world_flat = self._point_maps(ray[:, vidx], depth[:, vidx], extrinsics[:, vidx]) |
| if self.depth_aware_crossview or self.infonce: |
| world_pts[name] = p_world_flat |
| ee_d2 = None |
| if self.ee_anchor and ee_pos is not None: |
| |
| |
| d = p_world_flat[:, None, :, :] - ee_pos[:, :, None, :] |
| ee_d2 = jnp.clip(jnp.sum(d * d, axis=-1), 0.0, self.ee_max_dist2) |
| _bte = self.bank_token_embeds[name].value if self.bank_token_embeds is not None else None |
| view_emb = self.view_embed(jnp.asarray(vidx))[None, None, :] |
| if self.spatial_vec: |
| |
| |
| |
| |
| _r6 = compute_world_ray_6d(ray[:, vidx], extrinsics[:, vidx]) |
| _dirw = einops.rearrange(_r6[:, 3:6], "b c h w -> b (h w) c") |
| _dfl = einops.rearrange(depth[:, vidx], "b c h w -> b (h w) c") |
| _sv = self._spatial_vector(p_world_flat.astype(jnp.float32), |
| _dirw.astype(jnp.float32), |
| _dfl.astype(jnp.float32), |
| ee_pos.astype(jnp.float32)) |
| geo = self.spatial_cond(_sv.astype(feats.dtype), fused) |
| if _bte is not None: |
| geo = geo + _bte |
| geos[name] = geo |
| continue |
| tok_emb = _bte if self._bte_query else None |
| if self.kv_split: |
| |
| ray_emb = self.ray_mlp(ray_flat[..., :6].astype(feats.dtype)) |
| depth_emb = self.depth_mlp(ray_flat[..., 6:7].astype(feats.dtype)) |
| if depth_drop_rng is not None and self.depth_dropout > 0.0: |
| |
| |
| keep = jax.random.bernoulli( |
| jax.random.fold_in(depth_drop_rng, vidx), |
| 1.0 - self.depth_dropout, (depth_emb.shape[0], 1, 1), |
| ) |
| depth_emb = depth_emb * keep.astype(depth_emb.dtype) |
| payload = fused + depth_emb |
| if self.use_point_map and p_cam_flat is not None: |
| payload = payload + self.point_mlp(p_cam_flat.astype(feats.dtype)) |
| if self.use_depth_conf and depth_conf is not None: |
| conf_flat = einops.rearrange(depth_conf[:, vidx], "b c h w -> b (h w) c") |
| conf_flat = jnp.log(jnp.clip(conf_flat.astype(feats.dtype), 1e-3)) |
| payload = payload + self.conf_mlp(conf_flat) |
| addr = view_emb + pos_emb + ray_emb |
| if self.use_perceiver: |
| geo = self.perceivers[name](payload, addr=addr, token_embed=tok_emb, ee_dist2=ee_d2) |
| else: |
| |
| |
| |
| geo = payload if tok_emb is None else payload + tok_emb |
| else: |
| if self.depth_target_only: |
| |
| |
| ray_in = ray_flat.at[..., 6].set(0.0) |
| else: |
| ray_in = ray_flat |
| ray_emb = self.ray_mlp(ray_in.astype(feats.dtype)) |
| spatial = fused + view_emb + pos_emb + ray_emb |
| if self.use_perceiver: |
| geo = self.perceivers[name](spatial, token_embed=tok_emb, ee_dist2=ee_d2) |
| else: |
| geo = spatial if tok_emb is None else spatial + tok_emb |
| if return_aux and self.aux_geom_head: |
| |
| |
| |
| qh = jnp.broadcast_to(self.aux_q(pos_emb), (geo.shape[0], h * w, self.hidden_dim)) |
| kh = self.aux_k(geo) |
| vh = self.aux_v(geo) |
| scale = jnp.sqrt(jnp.asarray(self.hidden_dim, qh.dtype)) |
| attn = jax.nn.softmax(jnp.einsum("bph,bkh->bpk", qh, kh) / scale, axis=-1) |
| pred_logd = self.aux_out(jnp.einsum("bpk,bkh->bph", attn, vh)) |
| true_logd = ray_flat[..., 6:7].astype(pred_logd.dtype) |
| aux_losses.append(jnp.mean(jnp.square(pred_logd - true_logd))) |
| geos[name] = geo |
|
|
| |
| if self.cross_view: |
| |
| |
| tok_xyz = None |
| if self.depth_aware_crossview and world_pts: |
| xyz_list = [self._pool_pts(world_pts[nm], self._ntok[nm]) for nm, _, _ in _VIEWS] |
| center = jnp.mean(jnp.concatenate(xyz_list, axis=1), axis=1, keepdims=True) |
| tok_xyz = [xz - center for xz in xyz_list] |
| parts = [] |
| for i, (name, vidx, _k) in enumerate(_VIEWS): |
| cam = self.cam_pose_mlp(_cam_pose_feat(extrinsics[:, vidx]).astype(feats.dtype)) |
| if self.use_pose_enc and pose_enc is not None: |
| cam = cam + self.pose_enc_mlp(pose_enc[:, vidx].astype(feats.dtype)) |
| tok = geos[name] + cam[:, None, :] |
| if tok_xyz is not None: |
| tok = tok + self.pos3d_mlp(tok_xyz[i].astype(feats.dtype)) |
| parts.append(tok) |
| x = self.cross_view_fusion(jnp.concatenate(parts, axis=1)) |
| off = 0 |
| for name, _vidx, _k in _VIEWS: |
| k = self._ntok[name] |
| geos[name] = x[:, off:off + k] |
| off += k |
|
|
| |
| banks = {} |
| for name, _vidx, _k in _VIEWS: |
| bank = (self.lang_fusers[name](geos[name], lang_tokens, lang_pad) |
| if self.lang_fusers is not None else geos[name]) |
| |
| |
| if self.bank_token_embeds is not None and not self._bte_query: |
| bank = bank + self.bank_token_embeds[name].value |
| if self.bank_center: |
| |
| |
| |
| bank = bank - jnp.mean(bank, axis=0, keepdims=True) |
| if self.use_cam_tokens and cam_tokens is not None: |
| |
| |
| ct = self.cam_token_proj(self.cam_in_norm(cam_tokens[:, _vidx].astype(feats.dtype))) |
| if self.bank_center: |
| ct = ct - jnp.mean(ct, axis=0, keepdims=True) |
| bank = jnp.concatenate([bank, ct], axis=1) |
| banks[name] = bank |
|
|
| |
| |
| |
| |
| nce = jnp.asarray(0.0, jnp.float32) |
| if return_aux and self.infonce and world_pts: |
| zb = jnp.concatenate([jnp.mean(banks[nm], axis=1) for nm, _, _ in _VIEWS], axis=-1) |
| zg = jnp.concatenate( |
| [self._pool_pts(world_pts[nm], self.infonce_pool_k).reshape(zb.shape[0], -1) |
| for nm, _, _ in _VIEWS], axis=-1) |
| nce = _info_nce(self.nce_bank_proj(zb.astype(jnp.float32)), |
| self.nce_geom_proj(zg.astype(jnp.float32)), self.infonce_temp) |
|
|
| if return_aux: |
| aux = jnp.mean(jnp.stack(aux_losses)) if aux_losses else jnp.asarray(0.0, jnp.float32) |
| return banks, aux, nce |
| return banks |
|
|