Download code/tt_streampetr/reference/model.py from changh95/streampetr-p150: direct link, hf CLI and curl.
- Browser
- Download file 30.2 kB
-
https://huggingface.co/changh95/streampetr-p150/resolve/main/code/tt_streampetr/reference/model.py
- Command line
-
hf download hf://changh95/streampetr-p150/code/tt_streampetr/reference/model.py
-
curl -L -o model.py https://huggingface.co/changh95/streampetr-p150/resolve/main/code/tt_streampetr/reference/model.py
30.2 kB
| # SPDX-License-Identifier: Apache-2.0 | |
| """Pure-PyTorch fp32 re-implementation of the three deployed StreamPETR graphs with per-module taps. | |
| Ported from the verified research reference (``research/streampetr/scripts/torch_reference.py``: PCC >= 0.9999999998 | |
| against ONNX Runtime on every output, SPEC section 6) onto this port's weight loader (``reference.weights``: every | |
| parameter and constant addressed by its consuming ONNX node). Module boundaries follow the training code (AWML | |
| ``projects/StreamPETR``: VoVNet-99-eSE + CPFPN, PETR position embedding, StreamPETRHead with the PETRTemporal | |
| decoder) so that each TT stage has a reference tap. No mmdet / mmcv / mmdet3d; plain ``torch.nn.functional``. | |
| ``Backbone(img)``: ``img`` [1, 5, 3, 480, 640] (normalised, ``host.preprocess``) -> ``img_feats`` [1, 5, 256, 30, 40]. | |
| ``PositionEmbedding(img_metas_pad, intrinsics, img2lidar)`` -> ``pos_embed`` [1, 6000, 256], ``cone`` [1, 6000, 8]. | |
| ``Head(x, pos_embed, cone, E, Einv, pre)``: the ONNX head (global-frame memory) -> its 14 outputs (ONNX names) plus | |
| ``topk_idx`` [1, 256, 1] and ``rec_score`` [1, 900, 1] (not ONNX outputs). The stages are separate methods | |
| (``tokens``, ``memory_pre_update``, ``temporal``, ``queries``, ``decoder``, ``predict``, ``post_update``) so that | |
| ``reference.port_form`` can run the TT port's re-parameterised memory through the same functions. | |
| Tap names (``ttaw.golden.TapRegistry``; the device PCC gates use the same names): | |
| ================================ ====================== =================================================== | |
| tap shape where | |
| ================================ ====================== =================================================== | |
| ``backbone.stem`` [5, 128, 120, 160] stem_1 (s2) .. stem_3 (s2), conv + ReLU | |
| ``backbone.stage2.osa1`` ... [5, C, h, w] every OSA block output (after eSE and the identity) | |
| ``backbone.stage2`` .. ``stage5`` [5, 256..1024, ...] stage outputs (stage4 = 30x40, stage5 = 15x20) | |
| ``neck.lateral0`` / ``lateral1`` [5, 256, 30, 40] / 15x20 CPFPN 1x1 laterals (no activation) | |
| ``neck.merged`` [5, 256, 30, 40] lateral0 + nearest x2(lateral1) | |
| ``img_feats`` [1, 5, 256, 30, 40] fpn_convs.0 3x3 (the backbone graph output) | |
| ``pe.coords`` [1, 6000, 192] normalised 3-D points of the 64 depth bins | |
| ``pe.hidden`` [1, 6000, 1024] ReLU(Linear 192 -> 1024) of inverse_sigmoid(coords) | |
| ``pos_embed`` / ``cone`` [1, 6000, 256] / 8 the PE graph outputs (once per calibration) | |
| ``head.tokens`` [1, 6000, 256] x permuted to camera-major tokens | |
| ``head.memory_embed`` [1, 6000, 256] memory_embed MLP | |
| ``head.sa_gamma`` / ``sa_beta`` [1, 6000, 256] spatial-alignment MLN coefficients (cone only) | |
| ``head.memory`` [1, 6000, 256] LN(memory) * gamma + beta: the cross-attention values | |
| ``head.pos`` [1, 6000, 256] pos_embed * sigmoid(featurized PE): + memory = keys | |
| ``head.mem_ego`` / ``mem_ref`` [1, 1024, 4, 4] / 3 Einv @ pre_memory_egopose / reference points | |
| ``head.temp_ref`` [1, 1024, 3] (mem_ref - pc_min) / pc_size | |
| ``head.motion`` [1, 1024, 180] nerf_pe([velo, ts, mem_ego[:3]]) | |
| ``head.temp_pos`` / ``temp_mem`` [1, 1024, 256] after the two MLNs and the time embedding | |
| ``head.tgt`` / ``query_pos`` [1, 900, 256] decoder inputs (644 learned + 256 propagated) | |
| ``head.reference_points`` [1, 900, 3] | |
| ``head.layer<l>.self_attn`` [1, 900, 256] LN(tgt + self-attention), l = 0..5 | |
| ``head.layer<l>.cross_attn`` [1, 900, 256] LN(. + cross-attention over the 6000 tokens) | |
| ``head.layer<l>.ffn`` [1, 900, 256] LN(. + FFN): the next layer's tgt | |
| ``head.outs_dec`` [6, 1, 900, 256] post_norm of every layer's tgt | |
| ``head.cls`` / ``head.reg`` [6, 1, 900, 5] / 10 shared branches; reg centres decoded to metres | |
| ``head.rec_score`` [1, 900, 1] max_c sigmoid(cls[5]) (TopK k=1) | |
| ``head.topk_idx`` [1, 256, 1] TopK k=256 (sorted, ties to the lower index) | |
| ``all_cls_scores`` ... ONNX outputs ``Head.__call__`` return values (ONNX names) | |
| ================================ ====================== =================================================== | |
| """ | |
| from __future__ import annotations | |
| from typing import Any, Dict, Optional, Tuple | |
| import numpy as np | |
| from ..ttaw.golden import NULL_TAPS, TapRegistry | |
| from .config import StreamPETRConfig, default_config | |
| from .weights import OSA_STAGES, StreamPETRWeights | |
| __all__ = ["Backbone", "PositionEmbedding", "Head", "StreamPETRNet", "inverse_sigmoid", "nerf_pe", "posemb", | |
| "HEAD_OUTPUTS", "MEMORY_KEYS"] | |
| HEAD_OUTPUTS = ("all_cls_scores", "all_bbox_preds", "post_memory_embedding", "post_memory_reference_point", | |
| "post_memory_timestamp", "post_memory_egopose", "post_memory_velo", "reference_points", "tgt", | |
| "temp_memory", "temp_pos", "query_pos", "query_pos_in", "outs_dec") | |
| MEMORY_KEYS = ("embedding", "reference_point", "timestamp", "egopose", "velo") | |
| def _torch(): | |
| import torch | |
| return torch | |
| def _t(a: Any): | |
| """numpy / torch -> float32 torch tensor (own copy for numpy input).""" | |
| torch = _torch() | |
| if isinstance(a, torch.Tensor): | |
| return a.float() | |
| return torch.from_numpy(np.array(a, dtype=np.float32)) | |
| def inverse_sigmoid(x: Any, eps: float = 1e-4): | |
| """``misc.py:7-25`` as the ONNX graphs compute it: clip(x, 0, 1); log(max(x, eps) / max(1 - x, eps)).""" | |
| x = x.clamp(0, 1) | |
| return (x.clamp(min=eps) / (1 - x).clamp(min=eps)).log() | |
| def nerf_pe(x: Any, n: int = 6): | |
| """[sin(x), cos(x), sin(2x), cos(2x), ..., sin(32x), cos(32x)] along the last axis (ONNX /Concat_20).""" | |
| torch = _torch() | |
| enc = [torch.sin(x), torch.cos(x)] | |
| for f in (2.0, 4.0, 8.0, 16.0, 32.0)[: n - 1]: | |
| xf = x * f | |
| enc += [torch.sin(xf), torch.cos(xf)] | |
| return torch.cat(enc, -1) | |
| def posemb(p: Any, dim_t: Any): | |
| """One axis of ``pos2posemb``: ``q = p / dim_t``, then (sin of the even, cos of the odd entries) interleaved.""" | |
| torch = _torch() | |
| q = p[..., None] / dim_t | |
| return torch.stack((q[..., 0::2].sin(), q[..., 1::2].cos()), -1).flatten(-2) | |
| # ============================================================================================== backbone | |
| class Backbone: | |
| """VoVNet-99-eSE + CPFPN (``vovnet.py:80-88,174-239``, ``cp_fpn.py:160-204``), BN folded by the exporter.""" | |
| def __init__(self, weights: StreamPETRWeights, cfg: Optional[StreamPETRConfig] = None): | |
| self.cfg = cfg or default_config() | |
| self.params: Dict[str, Tuple[Any, Any, int, int]] = { | |
| n: (_t(c.weight), None if c.bias is None else _t(c.bias), c.stride, c.padding) | |
| for n, c in weights.iter_convs()} | |
| def conv(self, x: Any, node: str, relu: bool = True): | |
| import torch.nn.functional as F | |
| w, b, s, p = self.params[node] | |
| y = F.conv2d(x, w, b, stride=s, padding=p) | |
| return F.relu(y) if relu else y | |
| def osa(self, x: Any, stage: int, k: int, identity: bool): | |
| """One OSA block: 5 chained 3x3 convs, concat [x, 5 outputs], 1x1 concat conv, eSE, optional identity.""" | |
| torch = _torch() | |
| pre = f"/img_backbone/stage{stage}/OSA{stage}_{k}" | |
| outs, h = [x], x | |
| for i in range(5): | |
| h = self.conv(h, f"{pre}/layers.{i}/OSA{stage}_{k}_{i}/conv/Conv") | |
| outs.append(h) | |
| xt = self.conv(torch.cat(outs, 1), f"{pre}/concat/OSA{stage}_{k}_concat/conv/Conv") | |
| # eSE (vovnet.py:174-186): x * hsigmoid(fc(GAP(x))), hsigmoid = clip(v + 3, 0, 6) / 6 | |
| s = self.conv(xt.mean((2, 3), keepdim=True), f"{pre}/ese/fc/Conv", relu=False) | |
| xt = xt * (torch.clamp(s + 3.0, 0.0, 6.0) / 6.0) | |
| return xt + x if identity else xt | |
| def __call__(self, img: Any, taps: TapRegistry = NULL_TAPS): | |
| """``img`` [1, N, 3, H, W] (or [N, 3, H, W]) normalised -> ``img_feats`` [1, N, 256, H/16, W/16].""" | |
| import torch.nn.functional as F | |
| x = _t(img) | |
| if x.dim() == 4: | |
| x = x[None] | |
| b, n = x.shape[:2] | |
| x = x.reshape(b * n, *x.shape[2:]) | |
| for i in (1, 2, 3): | |
| x = self.conv(x, f"/img_backbone/stem/stem_{i}/conv/Conv") | |
| taps.tap("backbone.stem", x) | |
| feats = {} | |
| for st, (nb, _, _) in OSA_STAGES.items(): | |
| if st != 2: | |
| x = F.max_pool2d(x, 3, 2, ceil_mode=True) # MaxPool k3 s2 pads 0 ceil_mode 1 | |
| for k in range(1, nb + 1): | |
| x = taps.tap(f"backbone.stage{st}.osa{k}", self.osa(x, st, k, identity=k > 1)) | |
| feats[st] = taps.tap(f"backbone.stage{st}", x) | |
| lat0 = taps.tap("neck.lateral0", self.conv(feats[4], "/img_neck/lateral_convs.0/conv/Conv", relu=False)) | |
| lat1 = taps.tap("neck.lateral1", self.conv(feats[5], "/img_neck/lateral_convs.1/conv/Conv", relu=False)) | |
| # ONNX Resize nearest / asymmetric / floor to [5, 256, 30, 40] == F.interpolate(mode="nearest") | |
| merged = taps.tap("neck.merged", lat0 + F.interpolate(lat1, size=lat0.shape[-2:], mode="nearest")) | |
| out = self.conv(merged, "/img_neck/fpn_convs.0/conv/Conv", relu=False) | |
| return taps.tap("img_feats", out.reshape(b, n, *out.shape[1:])) | |
| # ============================================================================================== position embedding | |
| class PositionEmbedding: | |
| """PETR 3-D position embedding (``streampetr_head.py:415-463``, ``containers.py:11-24``), run once per | |
| calibration. Constants (pixel centres, LID depth bins, ``position_range``) are read from the ONNX graph.""" | |
| def __init__(self, weights: StreamPETRWeights, cfg: Optional[StreamPETRConfig] = None): | |
| self.cfg = cfg or default_config() | |
| pe = weights.pe | |
| self.l0 = tuple(_t(a) for a in weights.linear("/position_encoder/position_encoder.0", "position_embedding")) | |
| self.l2 = tuple(_t(a) for a in weights.linear("/position_encoder/position_encoder.2", "position_embedding")) | |
| self.u = _t(pe.params("/Div")[0]) # [40] pixel centres 16 x + 8 (divided by pad_w in-graph) | |
| self.v = _t(pe.params("/Div_1")[0]) # [30] | |
| depth = np.asarray(pe.array("/Tile_3_output_0"), np.float32) # [1, 6000, 64, 1], equal rows | |
| if not np.all(depth == depth[:, :1]): | |
| raise ValueError("the PE depth-bin constant differs between tokens") | |
| self.depth = _t(depth[0, 0, :, 0]) # [64] LID bins 1.0 .. 59.35 m | |
| self.depth_min = float(np.asarray(pe.array("/Constant_71_output_0")).reshape(-1)[0]) # 1e-5 | |
| self.pr_min = _t(pe.params("/Sub")[1]) # position_range[:3] | |
| self.pr_size = _t(pe.params("/Div_3")[1]) # position_range[3:] - [:3] | |
| self.focal_div = float(pe.params("/Div_2")[1]) # 1000.0 | |
| def __call__(self, img_metas_pad: Any, intrinsics: Any, img2lidar: Any, taps: TapRegistry = NULL_TAPS): | |
| """``img_metas_pad`` [3] = (pad_h, pad_w, 3); ``intrinsics`` / ``img2lidar`` [1, N, 4, 4] float32 -> | |
| ``pos_embed`` [1, N*H*W, 256], ``cone`` [1, N*H*W, 8].""" | |
| torch = _torch() | |
| metas, k4, i2l = _t(img_metas_pad), _t(intrinsics), _t(img2lidar) | |
| h, w = self.cfg.feat_hw | |
| n = k4.shape[1] | |
| pad_h, pad_w = metas[0], metas[1] | |
| xs = (self.u / pad_w) * pad_w # the graph's (u / pad_w) * pad_w, in float32 | |
| ys = (self.v / pad_h) * pad_h | |
| sy, sx = torch.meshgrid(ys, xs, indexing="ij") | |
| loc = torch.stack([sx, sy], -1) # [30, 40, 2] (u, v) | |
| centers = loc[None].repeat(n, 1, 1, 1).reshape(1, n * h * w, 1, 2) # camera-major tokens | |
| dn = self.depth.numel() | |
| d = self.depth.view(1, 1, dn, 1).expand(1, n * h * w, dn, 1) | |
| coords = torch.cat([centers.expand(1, n * h * w, dn, 2), d, torch.ones_like(d)], -1) | |
| coords = torch.cat([coords[..., :2] * torch.clamp(coords[..., 2:3], min=self.depth_min), coords[..., 2:]], -1) | |
| m = i2l.reshape(n, 1, 1, 4, 4).expand(n, h * w, dn, 4, 4).reshape(1, n * h * w, dn, 4, 4) | |
| c3d = (m @ coords.unsqueeze(-1)).squeeze(-1)[..., :3] | |
| c3d = ((c3d - self.pr_min) / self.pr_size).reshape(1, n * h * w, dn * 3) | |
| taps.tap("pe.coords", c3d) | |
| hid = taps.tap("pe.hidden", torch.relu(inverse_sigmoid(c3d) @ self.l0[0] + self.l0[1])) | |
| pos = hid @ self.l2[0] + self.l2[1] | |
| # upstream quirk (streampetr_head.py:419-421): the focal lengths are TILED, token t uses camera t % N | |
| f = torch.stack([k4[..., 0, 0], k4[..., 1, 1]], -1).abs() / self.focal_div | |
| f = f.repeat(1, h * w, 1) | |
| cone = torch.cat([f, c3d[..., -3:], c3d[..., -90:-87]], -1) | |
| return taps.tap("pos_embed", pos), taps.tap("cone", cone) | |
| # ============================================================================================== head | |
| class Head: | |
| """StreamPETRHead + PETRTemporalTransformer decoder exactly as ``simplify_pts_head_memory.onnx`` (S:4.3).""" | |
| def __init__(self, weights: StreamPETRWeights, cfg: Optional[StreamPETRConfig] = None): | |
| self.cfg = cfg or default_config() | |
| lin = lambda p: tuple(_t(a) for a in weights.linear(p)) # noqa: E731 | |
| ln = lambda n: (lambda g, b, e: (_t(g), _t(b), e))(*weights.layer_norm(n)) # noqa: E731 | |
| self.memory_embed = (lin("/memory_embed/memory_embed.0"), lin("/memory_embed/memory_embed.2")) | |
| self.sa = dict(ln=ln("/spatial_alignment/ln/LayerNormalization"), reduce=lin("/spatial_alignment/reduce/reduce.0"), | |
| gamma=lin("/spatial_alignment/gamma"), beta=lin("/spatial_alignment/beta")) | |
| self.fpe = (lin("/featurized_pe/conv_reduce"), lin("/featurized_pe/conv_expand")) | |
| self.qemb = (lin("/query_embedding/query_embedding.0_1"), lin("/query_embedding/query_embedding.2_1")) | |
| self.mln_pe = dict(ln=ln("/ego_pose_pe/ln_1/LayerNormalization"), reduce=lin("/ego_pose_pe/reduce/reduce.0_1"), | |
| gamma=lin("/ego_pose_pe/gamma_1"), beta=lin("/ego_pose_pe/beta_1")) | |
| self.mln_mem = dict(ln=ln("/ego_pose_memory/ln_1/LayerNormalization"), | |
| reduce=lin("/ego_pose_memory/reduce/reduce.0_1"), | |
| gamma=lin("/ego_pose_memory/gamma_1"), beta=lin("/ego_pose_memory/beta_1")) | |
| self.time = (lin("/time_embedding/time_embedding.0_1"), | |
| ln("/time_embedding/time_embedding.1_1/LayerNormalization")) | |
| self.scale = float(weights.attention_scale()) | |
| self.layers = [] | |
| for li in range(self.cfg.num_layers): | |
| p = f"/transformer/decoder/layers.{li}" | |
| attn = [] | |
| for a in range(2): | |
| q = f"{p}/attentions.{a}" | |
| attn.append({k: tuple(_t(v) for v in weights.attn_proj(q, k)) for k in ("q", "k", "v")} | |
| | {"o": tuple(_t(v) for v in weights.gemm(f"{q}/attn/Gemm"))}) | |
| self.layers.append(dict(attn=attn, norms=[ln(f"{p}/norms.{i}/LayerNormalization") for i in range(3)], | |
| ffn=(lin(f"{p}/ffns.0/layers/layers.0/layers.0.0"), | |
| lin(f"{p}/ffns.0/layers/layers.1")))) | |
| self.post_norm = ln("/transformer/decoder/post_norm/LayerNormalization") | |
| self.cls = (lin("/cls_branches.0/cls_branches.0.0"), ln("/cls_branches.0/cls_branches.0.1/LayerNormalization"), | |
| lin("/cls_branches.0/cls_branches.0.3"), ln("/cls_branches.0/cls_branches.0.4/LayerNormalization"), | |
| lin("/cls_branches.0/cls_branches.0.6")) | |
| self.reg = (lin("/reg_branches.0/reg_branches.0.0"), lin("/reg_branches.0/reg_branches.0.2"), | |
| lin("/reg_branches.0/reg_branches.0.4")) | |
| q = weights.learned_queries() | |
| self.ref644, self.tgt644 = _t(q["reference_points"]), _t(q["tgt"]) | |
| self.qpos644, self.query_pos_in = _t(q["query_pos"]), _t(q["query_pos_in"]) | |
| h = weights.head | |
| self.pc_min = _t(h.params("/Sub")[1]) # pc_range[:3] (temporal alignment) | |
| self.pc_size = _t(h.params("/Div_3")[1]) # pc_range[3:] - [:3] | |
| self.two_pi = float(h.params("/Mul_1")[1]) # float32(2 pi) | |
| self.dim_t3 = _t(h.params("/Div_4")[1]) # [128] pos2posemb3d temperatures | |
| self.dim_t1 = _t(h.params("/Div_7")[1]) # [256] pos2posemb1d | |
| self.box_scale = _t(h.params("/Mul_69")[1]) # pc_range size (box centre decode) | |
| self.box_min = _t(h.params("/Add_8")[1]) # pc_range[:3] | |
| # ---- primitives ------------------------------------------------------------------------------------- | |
| def lin(x: Any, wb: Tuple[Any, Any]): | |
| return x @ wb[0] + wb[1] | |
| def layer_norm(x: Any, p: Tuple[Any, Any, float]): | |
| import torch.nn.functional as F | |
| return F.layer_norm(x, (x.shape[-1],), p[0], p[1], p[2]) | |
| def mln(self, x: Any, c: Any, p: Dict[str, Any]): | |
| """MLN (``petr_transformer.py`` / ``misc.py:155-216``): gamma(c) * LN(x) + beta(c), c = ReLU(reduce(c)).""" | |
| torch = _torch() | |
| x = self.layer_norm(x, p["ln"]) | |
| c = torch.relu(self.lin(c, p["reduce"])) | |
| return self.lin(c, p["gamma"]) * x + self.lin(c, p["beta"]) | |
| def mha(self, q_in: Any, k_in: Any, v_in: Any, p: Dict[str, Any]): | |
| """nn.MultiheadAttention as exported: Q scaled after the projection, softmax over keys, Gemm out-proj.""" | |
| torch = _torch() | |
| lq, lk, c = q_in.shape[1], k_in.shape[1], q_in.shape[2] | |
| nh = self.cfg.num_heads | |
| hd = c // nh | |
| q = self.lin(q_in, p["q"]).view(lq, nh, hd).transpose(0, 1) * self.scale | |
| k = self.lin(k_in, p["k"]).view(lk, nh, hd).transpose(0, 1) | |
| v = self.lin(v_in, p["v"]).view(lk, nh, hd).transpose(0, 1) | |
| a = torch.softmax(q @ k.transpose(1, 2), -1) | |
| o = (a @ v).transpose(0, 1).reshape(1, lq, c) | |
| return self.lin(o, p["o"]) | |
| def posemb3d(self, p: Any): | |
| """``pos2posemb3d`` (``positional_encoding.py``): 128 features per axis, concatenated in (y, x, z) order.""" | |
| torch = _torch() | |
| q = p * self.two_pi | |
| x, y, z = (posemb(q[..., i], self.dim_t3) for i in range(3)) | |
| return torch.cat((y, x, z), -1) | |
| def posemb1d(self, p: Any): | |
| return posemb(p[..., 0] * self.two_pi, self.dim_t1) | |
| def cls_branch(self, x: Any): | |
| torch = _torch() | |
| x = torch.relu(self.layer_norm(self.lin(x, self.cls[0]), self.cls[1])) | |
| x = torch.relu(self.layer_norm(self.lin(x, self.cls[2]), self.cls[3])) | |
| return self.lin(x, self.cls[4]) | |
| def reg_branch(self, x: Any): | |
| torch = _torch() | |
| x = torch.relu(self.lin(x, self.reg[0])) | |
| x = torch.relu(self.lin(x, self.reg[1])) | |
| return self.lin(x, self.reg[2]) | |
| # ---- stages ----------------------------------------------------------------------------------------- | |
| def spatial_alignment_coeffs(self, cone: Any) -> Tuple[Any, Any]: | |
| """gamma / beta of the spatial-alignment MLN: functions of ``cone`` only (constant per calibration).""" | |
| torch = _torch() | |
| c = torch.relu(self.lin(_t(cone), self.sa["reduce"])) | |
| return self.lin(c, self.sa["gamma"]), self.lin(c, self.sa["beta"]) | |
| def tokens(self, x: Any, pos_embed: Any, cone: Any, taps: TapRegistry = NULL_TAPS, | |
| sa_coeffs: Optional[Tuple[Any, Any]] = None) -> Tuple[Any, Any]: | |
| """Image tokens (``containers.py:76-87``): memory = MLN_sa(memory_embed(x tokens), cone); pos = pos_embed * | |
| sigmoid(featurized PE(memory)). ``sa_coeffs`` = precomputed (gamma, beta) (the port's calibration cache).""" | |
| torch = _torch() | |
| x = _t(x) | |
| b, n, c, h, w = x.shape | |
| memory = taps.tap("head.tokens", x.permute(0, 1, 3, 4, 2).reshape(b, n * h * w, c)) | |
| memory = torch.relu(self.lin(memory, self.memory_embed[0])) | |
| memory = taps.tap("head.memory_embed", self.lin(memory, self.memory_embed[1])) | |
| gamma, beta = sa_coeffs if sa_coeffs is not None else self.spatial_alignment_coeffs(cone) | |
| taps.tap("head.sa_gamma", gamma) | |
| taps.tap("head.sa_beta", beta) | |
| memory = taps.tap("head.memory", gamma * self.layer_norm(memory, self.sa["ln"]) + beta) | |
| gate = torch.sigmoid(self.lin(torch.relu(self.lin(memory, self.fpe[0])), self.fpe[1])) | |
| return memory, taps.tap("head.pos", _t(pos_embed) * gate) | |
| def memory_pre_update(pre: Dict[str, Any], Einv: Any, taps: TapRegistry = NULL_TAPS) -> Tuple[Any, Any]: | |
| """``containers.py:154-162``: mem_ego = Einv @ pre_egopose; mem_ref = (Einv @ [pre_ref, 1])[:3].""" | |
| torch = _torch() | |
| einv = _t(Einv) | |
| mem_ego = einv[:, None] @ _t(pre["pre_memory_egopose"]) | |
| ref = _t(pre["pre_memory_reference_point"]) | |
| ref_h = torch.cat([ref, torch.ones_like(ref[..., :1])], -1) | |
| mem_ref = (einv[:, None] @ ref_h.unsqueeze(-1)).squeeze(-1)[..., :3] | |
| return taps.tap("head.mem_ego", mem_ego), taps.tap("head.mem_ref", mem_ref) | |
| def temporal(self, mem_ref: Any, mem_ego: Any, mem_ts: Any, mem_emb: Any, mem_velo: Any, | |
| taps: TapRegistry = NULL_TAPS) -> Dict[str, Any]: | |
| """Temporal alignment (``streampetr_head.py:465-507``) of the 1024 memory rows.""" | |
| torch = _torch() | |
| mem_ts, mem_emb, mem_velo = _t(mem_ts), _t(mem_emb), _t(mem_velo) | |
| temp_ref = taps.tap("head.temp_ref", (mem_ref - self.pc_min) / self.pc_size) | |
| temp_pos = self.lin(torch.relu(self.lin(self.posemb3d(temp_ref), self.qemb[0])), self.qemb[1]) | |
| motion = taps.tap("head.motion", nerf_pe(torch.cat([mem_velo, mem_ts, mem_ego[..., :3, :].flatten(-2)], -1))) | |
| temp_pos = self.mln(temp_pos, motion, self.mln_pe) | |
| temp_mem = taps.tap("head.temp_mem", self.mln(mem_emb, motion, self.mln_mem)) | |
| temp_pos = temp_pos + self.layer_norm(self.lin(self.posemb1d(mem_ts), self.time[0]), self.time[1]) | |
| return {"temp_ref": temp_ref, "temp_pos": taps.tap("head.temp_pos", temp_pos), "temp_mem": temp_mem} | |
| def queries(self, t: Dict[str, Any], taps: TapRegistry = NULL_TAPS) -> Dict[str, Any]: | |
| """644 learned + the first 256 memory rows; rows 256..1023 feed only the self-attention keys / values.""" | |
| torch = _torch() | |
| npr = self.cfg.num_propagated | |
| tgt = taps.tap("head.tgt", torch.cat([self.tgt644, t["temp_mem"][:, :npr]], 1)) | |
| query_pos = taps.tap("head.query_pos", torch.cat([self.qpos644, t["temp_pos"][:, :npr]], 1)) | |
| ref = taps.tap("head.reference_points", torch.cat([self.ref644, t["temp_ref"][:, :npr]], 1)) | |
| return {"tgt": tgt, "query_pos": query_pos, "reference_points": ref, | |
| "temp_memory": t["temp_mem"][:, npr:], "temp_pos": t["temp_pos"][:, npr:]} | |
| def decoder(self, tgt: Any, query_pos: Any, temp_mem: Any, temp_pos: Any, memory: Any, pos: Any, | |
| taps: TapRegistry = NULL_TAPS): | |
| """6 post-norm layers (``petr_transformer.py:646-753``): self-attention over [queries; memory rows 256..], | |
| cross-attention over the 6000 image tokens (keys memory + pos), FFN; returns outs_dec [6, 1, 900, 256].""" | |
| torch = _torch() | |
| outs = [] | |
| for li, lp in enumerate(self.layers): | |
| kv = torch.cat([tgt, temp_mem], 1) | |
| kpos = torch.cat([query_pos, temp_pos], 1) | |
| tgt = self.layer_norm(tgt + self.mha(tgt + query_pos, kv + kpos, kv, lp["attn"][0]), lp["norms"][0]) | |
| taps.tap(f"head.layer{li}.self_attn", tgt) | |
| tgt = self.layer_norm(tgt + self.mha(tgt + query_pos, memory + pos, memory, lp["attn"][1]), lp["norms"][1]) | |
| taps.tap(f"head.layer{li}.cross_attn", tgt) | |
| hidden = self.lin(torch.relu(self.lin(tgt, lp["ffn"][0])), lp["ffn"][1]) | |
| tgt = taps.tap(f"head.layer{li}.ffn", self.layer_norm(tgt + hidden, lp["norms"][2])) | |
| outs.append(self.layer_norm(tgt, self.post_norm)) | |
| return taps.tap("head.outs_dec", torch.stack(outs)) | |
| def predict(self, outs_dec: Any, reference_points: Any, taps: TapRegistry = NULL_TAPS) -> Tuple[Any, Any]: | |
| """Shared cls / reg branches on every layer; centres = sigmoid(reg[:3] + inverse_sigmoid(ref)) scaled to | |
| pc_range (``streampetr_head.py:626-714``). -> cls [6, 1, 900, 5] logits, reg [6, 1, 900, 10].""" | |
| torch = _torch() | |
| reference = inverse_sigmoid(reference_points) | |
| cls = torch.stack([self.cls_branch(o) for o in outs_dec]) | |
| reg = [] | |
| for o in outs_dec: | |
| r = self.reg_branch(o) | |
| reg.append(torch.cat([torch.sigmoid(r[..., :3] + reference), r[..., 3:]], -1)) | |
| reg = torch.stack(reg) | |
| reg = torch.cat([reg[..., :3] * self.box_scale + self.box_min, reg[..., 3:]], -1) | |
| return taps.tap("head.cls", cls), taps.tap("head.reg", reg) | |
| def select(self, cls: Any, taps: TapRegistry = NULL_TAPS) -> Tuple[Any, Any]: | |
| """``containers.py:42-71``: rec_score = max_c sigmoid(cls[last]) (TopK k=1), then TopK k=256 over the 900 | |
| queries (sorted descending, ties to the lower index as ONNX TopK).""" | |
| torch = _torch() | |
| rec_score = taps.tap("head.rec_score", torch.sigmoid(cls[-1]).max(-1, keepdim=True).values) | |
| # torch.topk on CPU is not stable for ties; a stable descending sort gives the ONNX order | |
| idx = torch.sort(rec_score[..., 0], dim=1, descending=True, stable=True).indices[:, : self.cfg.num_propagated] | |
| return rec_score, taps.tap("head.topk_idx", idx[..., None]) | |
| def post_update(self, cls: Any, reg: Any, outs_dec: Any, mem: Dict[str, Any], E: Any, | |
| taps: TapRegistry = NULL_TAPS, topk_idx: Any = None) -> Dict[str, Any]: | |
| """``containers.py:42-71``: the new 256 rows (outs_dec / reg of the last layer at the top-256), prepended to | |
| the 1024 memory rows; geometry back to the global frame with E. ``topk_idx`` teacher-forces the selection.""" | |
| torch = _torch() | |
| rec_score, idx = self.select(cls, taps) | |
| if topk_idx is not None: | |
| idx = torch.as_tensor(np.asarray(topk_idx)).long().reshape(1, -1, 1) | |
| g = lambda t: torch.gather(t, 1, idx.expand(-1, -1, t.shape[-1])) # noqa: E731 | |
| rec_ref, rec_mem, rec_velo = g(reg[-1][..., :3]), g(outs_dec[-1]), g(reg[-1][..., -2:]) | |
| k = idx.shape[1] | |
| rec_ego = torch.eye(4).expand(1, k, 4, 4) # upstream gathers rows of an identity: always I | |
| e = _t(E) | |
| post_ego = e[:, None] @ torch.cat([rec_ego, mem["mem_ego"]], 1) | |
| pr = torch.cat([rec_ref, mem["mem_ref"]], 1) | |
| post_ref = (e[:, None] @ torch.cat([pr, torch.ones_like(pr[..., :1])], -1).unsqueeze(-1)).squeeze(-1)[..., :3] | |
| return {"post_memory_embedding": torch.cat([rec_mem, _t(mem["mem_emb"])], 1), | |
| "post_memory_reference_point": post_ref, | |
| "post_memory_timestamp": torch.cat([torch.zeros(1, k, 1, dtype=torch.float64), | |
| _t(mem["mem_ts"]).double()], 1), | |
| "post_memory_egopose": post_ego, | |
| "post_memory_velo": torch.cat([rec_velo, _t(mem["mem_velo"])], 1), | |
| "topk_idx": idx, "rec_score": rec_score} | |
| def __call__(self, x: Any, pos_embed: Any, cone: Any, E: Any, Einv: Any, pre: Dict[str, Any], | |
| taps: TapRegistry = NULL_TAPS, topk_idx: Any = None) -> Dict[str, Any]: | |
| """The head graph: ``pre`` holds the five ``pre_memory_*`` inputs (ONNX names). Returns the 14 ONNX outputs | |
| (``post_memory_timestamp`` float64 as in the graph) + ``topk_idx`` / ``rec_score``.""" | |
| mem_ego, mem_ref = self.memory_pre_update(pre, Einv, taps) | |
| memory, pos = self.tokens(x, pos_embed, cone, taps) | |
| t = self.temporal(mem_ref, mem_ego, pre["pre_memory_timestamp"], pre["pre_memory_embedding"], | |
| pre["pre_memory_velo"], taps) | |
| q = self.queries(t, taps) | |
| outs_dec = self.decoder(q["tgt"], q["query_pos"], q["temp_memory"], q["temp_pos"], memory, pos, taps) | |
| cls, reg = self.predict(outs_dec, q["reference_points"], taps) | |
| mem = {"mem_ego": mem_ego, "mem_ref": mem_ref, "mem_emb": pre["pre_memory_embedding"], | |
| "mem_ts": pre["pre_memory_timestamp"], "mem_velo": pre["pre_memory_velo"]} | |
| post = self.post_update(cls, reg, outs_dec, mem, E, taps, topk_idx=topk_idx) | |
| out = {"all_cls_scores": cls.flatten(0, 2).unsqueeze(0).transpose(2, 1), | |
| "all_bbox_preds": reg.flatten(0, 2).unsqueeze(0).transpose(2, 1), | |
| "reference_points": q["reference_points"], "tgt": q["tgt"], "temp_memory": q["temp_memory"], | |
| "temp_pos": q["temp_pos"], "query_pos": q["query_pos"], "query_pos_in": self.query_pos_in, | |
| "outs_dec": outs_dec} | |
| out.update(post) | |
| taps.tap("all_cls_scores", out["all_cls_scores"]) | |
| taps.tap("all_bbox_preds", out["all_bbox_preds"]) | |
| return out | |
| # ============================================================================================== the three graphs | |
| class StreamPETRNet: | |
| """The three graphs over one set of weights (``weights_dir`` = the HF camera_streampetr v1.0 files).""" | |
| def __init__(self, weights_dir: Any, cfg: Optional[StreamPETRConfig] = None, *, threads: Optional[int] = None, | |
| check: bool = True): | |
| torch = _torch() | |
| if threads: | |
| torch.set_num_threads(int(threads)) | |
| self.cfg = cfg or default_config() | |
| self.weights = StreamPETRWeights(weights_dir, check=check) | |
| self.backbone = Backbone(self.weights, self.cfg) | |
| self.pe = PositionEmbedding(self.weights, self.cfg) | |
| self.head = Head(self.weights, self.cfg) | |
| def no_grad(): | |
| return _torch().no_grad() | |