| """ |
| FutureInteractionGraphV2 β mode-specific edge features. |
| |
| Difference from V1 (graph_interaction_nba.py): |
| V1 averages the K predicted trajectories into one representative trajectory |
| per scene, computes edge features from that average, and then replicates them |
| across all K modes. Every mode therefore sees identical inter-agent geometry. |
| |
| V2 computes edge features *separately for each mode*, so agent i's message |
| from agent j in mode k reflects that mode's specific predicted positions, |
| not the cross-mode mean. |
| |
| Architecture and GNN code (FutureGeomAttnLayer) are unchanged β only the |
| edge-feature-construction step in forward() is different. |
| |
| Memory note: |
| V1 computed edges at B scale (27.5 K edges) then replicated. |
| V2 computes edges directly at B*K scale (550 K edges) in one step. |
| The GNN forward pass size is identical in both versions (55 K nodes, |
| 550 K edges); only the edge-feature tensor construction is larger in V2 |
| (B*K*E0*T*2 β 88 MB extra intermediate before projection β negligible |
| on an A6000). |
| """ |
|
|
| import torch |
| from models.graph_interaction_nba import FutureInteractionGraph |
|
|
|
|
| class FutureInteractionGraphV2(FutureInteractionGraph): |
| """FutureInteractionGraph with per-mode (mode-specific) edge features. |
| |
| Constructor arguments are identical to FutureInteractionGraph. |
| Only forward() is overridden. |
| """ |
|
|
| def forward( |
| self, |
| y_emb: torch.Tensor, |
| y_abs: torch.Tensor, |
| t_emb: torch.Tensor, |
| tau: torch.Tensor, |
| sigma_agent: torch.Tensor = None, |
| ) -> torch.Tensor: |
| B, K, A, D = y_emb.shape |
| T = y_abs.shape[3] |
| E0 = self._E0 |
|
|
| |
| |
| |
| pos_bk = y_abs.reshape(B * K * A, T, 2) |
| edge_index_bk = self._make_batched_edge_index(B * K) |
|
|
| |
| rel_pos_bk = (pos_bk[edge_index_bk[0]] - |
| pos_bk[edge_index_bk[1]]).mean(dim=1) |
| edge_attr_bk = self.rel_pos_proj(rel_pos_bk) |
|
|
| |
| temb_bka = (t_emb |
| .unsqueeze(1).unsqueeze(2) |
| .expand(-1, K, A, -1) |
| .reshape(B * K * A, D)) |
| if sigma_agent is not None: |
| s = sigma_agent.mean(dim=-1) if sigma_agent.dim() == 4 else sigma_agent |
| tau_bka = s.reshape(B * K * A) |
| else: |
| tau_bka = (tau |
| .unsqueeze(1).unsqueeze(2) |
| .expand(-1, K, A) |
| .reshape(B * K * A)) |
|
|
| |
| nodes = y_emb.reshape(B * K * A, D) |
| for layer in self.gnn_layers: |
| nodes = layer(nodes, edge_index_bk, edge_attr_bk, |
| temb_agent=temb_bka, tau=tau_bka) |
|
|
| |
| orig = y_emb.reshape(B * K * A, D) |
| gate = self.gate_proj(torch.cat([orig, nodes], dim=-1)) |
| out = orig + gate * self.out_proj(nodes) |
|
|
| return out.view(B, K, A, D) |
|
|