Spaces:
Running on Zero
Running on Zero
| """AQ3D — Adaptive Query Transformer for 3D Instance Segmentation. | |
| Vendored, dependency-trimmed port of the official implementation | |
| (https://github.com/kenomo/aq3d, MIT, Keno Moenck & Thorsten Schuppstuhl) so it | |
| runs on ZeroGPU. The Volt-B backbone comes from https://github.com/YilmazKadir/Volt. | |
| The only deviations from upstream are the compiled-extension replacements in | |
| ``nnutils`` (torch_scatter / torch_geometric.fps / flash_attn -> plain PyTorch); | |
| module names, layer order and every hyper-parameter follow | |
| ``configs/model/aqtd_volt_scannet200.yaml`` exactly so the released checkpoint | |
| loads with ``strict=True``. | |
| """ | |
| import math | |
| from functools import partial | |
| import torch | |
| import torch.nn as nn | |
| import torch.nn.functional as F | |
| from nnutils import fps, scatter_mean, scatter_softmax, scatter_sum, varlen_qkvpacked_attention | |
| # =========================================================================== # | |
| # src/models/components/attn.py | |
| # =========================================================================== # | |
| class MultiHeadAttention(nn.Module): | |
| def __init__(self, embed_dim=256, v_dim=None, num_heads=8, dropout=0.0, | |
| q_proj=True, k_proj=True, v_proj=True): | |
| super().__init__() | |
| self.num_heads = num_heads | |
| self.embed_dim = embed_dim | |
| self.v_dim = v_dim if v_dim is not None else embed_dim | |
| self.head_dim = embed_dim // num_heads | |
| self.v_head_dim = self.v_dim // num_heads | |
| assert self.head_dim * num_heads == embed_dim | |
| assert self.v_head_dim * num_heads == self.v_dim | |
| self.q_proj, self.k_proj, self.v_proj = q_proj, k_proj, v_proj | |
| if q_proj: | |
| self.q_proj_weight = nn.Parameter(torch.empty(embed_dim, embed_dim)) | |
| self.q_proj_bias = nn.Parameter(torch.empty(embed_dim)) | |
| if k_proj: | |
| self.k_proj_weight = nn.Parameter(torch.empty(embed_dim, embed_dim)) | |
| self.k_proj_bias = nn.Parameter(torch.empty(embed_dim)) | |
| if v_proj: | |
| self.v_proj_weight = nn.Parameter(torch.empty(self.v_dim, self.v_dim)) | |
| self.v_proj_bias = nn.Parameter(torch.empty(self.v_dim)) | |
| self.out_proj = nn.Linear(self.v_dim, self.v_dim, bias=True) | |
| self.dropout = nn.Dropout(dropout) | |
| def forward(self, query, key, value, key_padding_mask=None, attn_mask=None): | |
| B, q_len, _ = query.shape | |
| _, k_len, _ = key.shape | |
| v_len = k_len | |
| q = query.transpose(0, 1) | |
| k = key.transpose(0, 1) | |
| v = value.transpose(0, 1) | |
| if key_padding_mask is None: | |
| key_padding_mask = torch.zeros((B, k_len), dtype=torch.bool, device=q.device) | |
| if self.q_proj: | |
| q = F.linear(q, self.q_proj_weight, self.q_proj_bias) | |
| if self.k_proj: | |
| k = F.linear(k, self.k_proj_weight, self.k_proj_bias) | |
| if self.v_proj: | |
| v = F.linear(v, self.v_proj_weight, self.v_proj_bias) | |
| key_padding_mask = key_padding_mask.unsqueeze(1).repeat_interleave(q_len, dim=1) | |
| if attn_mask is None: | |
| attn_mask = key_padding_mask | |
| else: | |
| attn_mask = attn_mask.logical_or(key_padding_mask) | |
| attn_mask = attn_mask.repeat_interleave(self.num_heads, dim=0) | |
| attn_mask_float = torch.zeros_like(attn_mask, dtype=q.dtype, device=q.device) | |
| attn_mask_float = attn_mask_float.masked_fill(attn_mask, float("-inf")) | |
| q_sdpa = q.transpose(0, 1).view(B, q_len, self.num_heads, self.head_dim).transpose(1, 2) | |
| k_sdpa = k.transpose(0, 1).view(B, k_len, self.num_heads, self.head_dim).transpose(1, 2) | |
| v_sdpa = v.transpose(0, 1).view(B, v_len, self.num_heads, self.v_head_dim).transpose(1, 2) | |
| attn_mask_sdpa = attn_mask_float.view(B, self.num_heads, q_len, k_len) | |
| out = F.scaled_dot_product_attention(q_sdpa, k_sdpa, v_sdpa, | |
| attn_mask=attn_mask_sdpa, is_causal=False) | |
| out = out.transpose(1, 2).reshape(B, q_len, self.v_dim) | |
| return self.out_proj(out), None | |
| # =========================================================================== # | |
| # src/models/components/modules.py | |
| # =========================================================================== # | |
| class RoPE(nn.Module): | |
| """Axial rotary positional embedding over metric 3-D coordinates.""" | |
| def __init__(self, theta=100.0, head_split=(12, 12, 8), grid_size=0.1, | |
| max_grid_size=(1024, 1024, 512)): | |
| super().__init__() | |
| freqs = [1.0 / theta ** torch.linspace(0, 1, head_split[i] // 2) for i in range(3)] | |
| self.grid_size = grid_size | |
| self.head_split = head_split | |
| self.max_grid_size = max_grid_size | |
| for name, f, m in zip("xyz", freqs, max_grid_size): | |
| self.register_buffer(f"cis_cache_{name}", self._precompute(f, m), persistent=False) | |
| def _precompute(freqs, max_pos): | |
| freqs_pos = torch.outer(torch.arange(max_pos).float(), freqs) | |
| return torch.polar(torch.ones_like(freqs_pos), freqs_pos) | |
| def forward(self, x, coords): | |
| indices = torch.div(coords, self.grid_size, rounding_mode="floor").long() | |
| indices = indices.clamp(min=0) | |
| # upstream asserts here; clamping keeps out-of-domain (very large) scenes | |
| # running instead of hard-crashing the demo | |
| for a in range(3): | |
| indices[..., a] = indices[..., a].clamp(max=self.max_grid_size[a] - 1) | |
| cis = torch.cat([self.cis_cache_x[indices[..., 0]], | |
| self.cis_cache_y[indices[..., 1]], | |
| self.cis_cache_z[indices[..., 2]]], dim=-1).unsqueeze(2) | |
| x_ = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2)) | |
| return torch.view_as_real(x_ * cis).flatten(-2).to(x.dtype) | |
| class CosineClassifier(nn.Module): | |
| def __init__(self, in_features, out_features, scale=20.0): | |
| super().__init__() | |
| self.weight = nn.Parameter(torch.Tensor(out_features, in_features)) | |
| self.scale = scale | |
| nn.init.xavier_uniform_(self.weight) | |
| def forward(self, x): | |
| return F.linear(F.normalize(x, p=2, dim=-1), | |
| F.normalize(self.weight, p=2, dim=-1)) * self.scale | |
| class FFN(nn.Module): | |
| def __init__(self, d_model=256, output_dim=None, hidden_dim=1024, dropout=0.0, | |
| activation_fn=nn.GELU, use_residual=True, use_norm=True, num_layers=2): | |
| super().__init__() | |
| self.num_layers = num_layers | |
| output_dim = output_dim or d_model | |
| h = [hidden_dim] * (num_layers - 1) | |
| self.layers = nn.ModuleList(nn.Linear(n, k) for n, k in zip([d_model] + h, h + [output_dim])) | |
| self.use_residual = use_residual | |
| if use_residual: | |
| self.fast_path = nn.Linear(d_model, output_dim) if d_model != output_dim else nn.Identity() | |
| self.use_norm = use_norm | |
| self.activation_fn = activation_fn() | |
| self.norm = nn.LayerNorm(output_dim) | |
| self.dropout = nn.Dropout(dropout) | |
| def forward(self, x): | |
| input_x = x | |
| for i, layer in enumerate(self.layers): | |
| x = layer(x) | |
| if i < self.num_layers - 1: | |
| x = self.dropout(self.activation_fn(x)) | |
| x = self.dropout(x) | |
| if self.use_residual: | |
| x = x + self.fast_path(input_x) | |
| if self.use_norm: | |
| x = self.norm(x) | |
| return x | |
| # =========================================================================== # | |
| # src/models/components/aqtd/modules.py | |
| # =========================================================================== # | |
| class SelfAttentionLayer(nn.Module): | |
| def __init__(self, d_model=256, nhead=8, dropout=0.0, rope=None): | |
| super().__init__() | |
| self.qc_in_proj = nn.Linear(d_model, d_model) | |
| self.kc_in_proj = nn.Linear(d_model, d_model) | |
| self.attn = MultiHeadAttention(embed_dim=d_model, v_dim=d_model, num_heads=nhead, | |
| dropout=dropout, q_proj=False, k_proj=False) | |
| self.norm = nn.LayerNorm(d_model) | |
| self.dropout = nn.Dropout(dropout) | |
| self.nhead = nhead | |
| self.head_dim = d_model // nhead | |
| self.rope = rope | |
| def forward(self, q_c, q_coords, B, key_padding_mask=None, | |
| scene_ranges_min=None, **kwargs): | |
| B = q_c.shape[0] | |
| tgt_len = src_len = q_c.shape[1] | |
| coords = q_coords - scene_ranges_min | |
| qc = self.qc_in_proj(q_c).view(B, tgt_len, self.nhead, self.head_dim) | |
| kc = self.kc_in_proj(q_c).view(B, src_len, self.nhead, self.head_dim) | |
| q = (self.rope(qc, coords) if self.rope is not None else qc).flatten(2) | |
| k = (self.rope(kc, coords) if self.rope is not None else kc).flatten(2) | |
| out, _ = self.attn(q, k, q_c, key_padding_mask=key_padding_mask) | |
| return self.norm(self.dropout(out) + q_c) | |
| class CrossAttentionLayer(nn.Module): | |
| def __init__(self, d_model=256, nhead=8, dropout=0.0, attn_mask_thres=0.1, | |
| with_query_pos=False, rope=None): | |
| super().__init__() | |
| self.qc_in_proj = nn.Linear(d_model, d_model) | |
| self.kc_in_proj = nn.Linear(d_model, d_model) | |
| self.with_query_pos = with_query_pos | |
| if with_query_pos: | |
| self.qp_in_proj = nn.Linear(d_model, d_model) | |
| self.kp_in_proj = nn.Linear(d_model, d_model) | |
| self.attn = MultiHeadAttention(embed_dim=d_model * 2 if with_query_pos else d_model, | |
| v_dim=d_model, num_heads=nhead, dropout=dropout, | |
| q_proj=False, k_proj=False) | |
| self.nhead = nhead | |
| self.head_dim = d_model // nhead | |
| self.norm = nn.LayerNorm(d_model) | |
| self.dropout = nn.Dropout(dropout) | |
| self.attn_mask_thres = attn_mask_thres | |
| self.rope = rope | |
| def forward(self, q_c, q_p, k_c, k_p, v, key_padding_mask, q_coords, kv_coords, | |
| pred_masks, B, scene_ranges_min=None, **kwargs): | |
| device = q_c.device | |
| src_len = k_c.shape[1] | |
| tgt_len = q_c.shape[1] | |
| if pred_masks is not None: | |
| attn_mask = torch.ones((B, tgt_len, src_len), dtype=torch.bool, device=device) | |
| for i in range(B): | |
| inv = (pred_masks[i].sigmoid() < self.attn_mask_thres).bool() | |
| inv[torch.where(inv.sum(-1) == inv.shape[-1])] = False | |
| attn_mask[i, :inv.shape[0], :inv.shape[1]] = inv | |
| else: | |
| attn_mask = None | |
| q_coords_ = q_coords - scene_ranges_min | |
| kv_coords_ = kv_coords - scene_ranges_min | |
| qc = self.qc_in_proj(q_c).view(B, tgt_len, self.nhead, self.head_dim) | |
| qc_r = self.rope(qc, q_coords_) if self.rope is not None else qc | |
| if self.with_query_pos: | |
| qp = self.qp_in_proj(q_p).view(B, tgt_len, self.nhead, self.head_dim) | |
| q = torch.cat((qc_r, qp), dim=-1).flatten(2) | |
| else: | |
| q = qc_r.flatten(2) | |
| kc = self.kc_in_proj(k_c).view(B, src_len, self.nhead, self.head_dim) | |
| kc_r = self.rope(kc, kv_coords_) if self.rope is not None else kc | |
| if self.with_query_pos: | |
| kp = self.kp_in_proj(k_p).view(B, src_len, self.nhead, self.head_dim) | |
| k = torch.cat((kc_r, kp), dim=-1).flatten(2) | |
| else: | |
| k = kc_r.flatten(2) | |
| out, _ = self.attn(q, k, v, key_padding_mask=key_padding_mask, attn_mask=attn_mask) | |
| return self.norm(self.dropout(out) + q_c) | |
| # =========================================================================== # | |
| # src/models/components/aqtd/query_decoder.py | |
| # =========================================================================== # | |
| class QueryDecoder(nn.Module): | |
| def __init__(self, num_layer=6, num_query=100, num_query_ratio=0.6, max_query=True, | |
| num_class=198, in_channel=128, d_model=384, dropout_head=0.0, | |
| dropout_layer=0.0, query_init="feat", query_pos_init="adaptive", | |
| cosine_classifier=True, refinement_cross_attention=True, | |
| refinement_cross_attention_layer_indices=(1, 3, 5), | |
| refinement_cross_attention_layer=None, detach_query_pos=True, | |
| cross_attention_layer=None, self_attention_layer=None, ffn_layer=None, | |
| activation_fn=nn.ReLU): | |
| super().__init__() | |
| self.num_layer = num_layer | |
| self.d_model = d_model | |
| self.num_query = num_query | |
| self.num_query_ratio = num_query_ratio | |
| self.max_query = max_query | |
| self.dropout_layer = torch.linspace(0, dropout_layer, num_layer).tolist()[::-1] | |
| self.refinement_cross_attention = refinement_cross_attention | |
| self.detach_query_pos = detach_query_pos | |
| self.query_init = query_init | |
| self.query_pos_init = query_pos_init | |
| if query_init == "feat": | |
| self.query_feat_proj = nn.Sequential(nn.Linear(in_channel, d_model), | |
| nn.LayerNorm(d_model), activation_fn()) | |
| self.feat_proj = nn.Sequential(nn.Linear(in_channel, d_model), | |
| nn.LayerNorm(d_model), activation_fn()) | |
| self.mask_proj = nn.Sequential(nn.Linear(in_channel, d_model), activation_fn(), | |
| nn.Linear(d_model, d_model)) | |
| self.cross_attn_layers = nn.ModuleList() | |
| self.self_attn_layers = nn.ModuleList() | |
| self.ffn_layers = nn.ModuleList() | |
| for _ in range(num_layer): | |
| self.self_attn_layers.append(self_attention_layer(d_model=d_model)) | |
| self.cross_attn_layers.append(cross_attention_layer(d_model=d_model)) | |
| self.ffn_layers.append(ffn_layer(d_model=d_model)) | |
| self.refinement_cross_attention_layer_indices = list(refinement_cross_attention_layer_indices) | |
| if refinement_cross_attention: | |
| self.refinement_cross_attn_layers = nn.ModuleList() | |
| self.refinement_ffn_layers = nn.ModuleList() | |
| for _ in self.refinement_cross_attention_layer_indices: | |
| self.refinement_cross_attn_layers.append(refinement_cross_attention_layer(d_model=d_model)) | |
| self.refinement_ffn_layers.append(ffn_layer(d_model=d_model)) | |
| self.abs_pos_encoder = None | |
| self.abs_pos_encoder_proj = None | |
| self.query_pos_delta_head = nn.Sequential( | |
| nn.Linear(d_model, d_model), activation_fn(), | |
| nn.Linear(d_model, d_model), activation_fn(), | |
| nn.Dropout(dropout_head), nn.Linear(d_model, 3)) | |
| self.out_norm = nn.LayerNorm(d_model) | |
| self.out_cls = nn.Sequential( | |
| nn.Linear(d_model, d_model), activation_fn(), nn.Dropout(dropout_head), | |
| CosineClassifier(d_model, num_class + 1) if cosine_classifier | |
| else nn.Linear(d_model, num_class + 1)) | |
| self.out_score = nn.Sequential( | |
| nn.Linear(d_model, d_model), activation_fn(), nn.Dropout(dropout_head), | |
| nn.Linear(d_model, 1)) | |
| self.out_center = nn.Sequential( | |
| nn.Linear(d_model, d_model), activation_fn(), nn.Dropout(dropout_head), | |
| nn.Linear(d_model, 3)) | |
| def get_mask(query, mask_feats, batch_offsets): | |
| pred_masks = [] | |
| for i in range(len(batch_offsets) - 1): | |
| start_id, end_id = batch_offsets[i], batch_offsets[i + 1] | |
| pred_masks.append(torch.einsum("nd,md->nm", query[i], mask_feats[start_id:end_id])) | |
| return pred_masks | |
| def prediction_head(self, query, query_pos, mask_feats, batch_offsets, | |
| scene_ranges_max, scene_ranges_min): | |
| pred_masks = self.get_mask(query, mask_feats, batch_offsets) | |
| pred_labels = self.out_cls(query) | |
| pred_scores = self.out_score(query) | |
| pred_spatials = self.out_center(query) | |
| pred_spatials = query_pos * (scene_ranges_max - scene_ranges_min) + scene_ranges_min + pred_spatials | |
| return pred_labels, pred_scores, pred_masks, pred_spatials | |
| def get_query(self, B, batch_offsets, batch, device, dtype, kv_pos_xyz, query_feats=None): | |
| num_queris = (batch["superpoint_len"].to(device) * self.num_query_ratio).int() | |
| max_num_query = num_queris.max().item() | |
| query = torch.zeros(B, max_num_query, self.d_model, device=device, dtype=dtype) | |
| query_padding_mask = torch.ones(B, max_num_query, dtype=torch.bool, device=device) | |
| query_pos_norm = ((torch.randn(B, max_num_query, 3, device=device, dtype=dtype) + 0.5) * 0.5).clamp(0, 1) | |
| for b in range(B): | |
| start_id, end_id = batch_offsets[b], batch_offsets[b + 1] | |
| sp_xyz = kv_pos_xyz[start_id:end_id] | |
| ratio = torch.clamp(num_queris[b] / sp_xyz.size(0), max=0.99).item() | |
| fps_idx = fps(sp_xyz, ratio=ratio, random_start=True) | |
| query_pos_norm_b = ((sp_xyz[fps_idx] - sp_xyz.min(0).values) | |
| / (sp_xyz.max(0).values - sp_xyz.min(0).values)) | |
| len_b = min(num_queris[b].item(), query_pos_norm_b.size(0)) | |
| query_pos_norm_b = query_pos_norm_b[:len_b] | |
| query_padding_mask[b, :len_b] = False | |
| if self.query_init == "feat": | |
| query[b, :len_b] = query_feats[start_id:end_id][fps_idx][:len_b] | |
| query_pos_norm[b, :len_b] = query_pos_norm_b | |
| return query, query_pos_norm, query_padding_mask | |
| def forward(self, x, batch): | |
| dtype = x.dtype | |
| device = x.device | |
| batch_offsets = F.pad(batch["batched_superpoint_offset"], (1, 0)) | |
| B = len(batch_offsets) - 1 | |
| inst_feats = self.feat_proj(x) | |
| mask_feats = self.mask_proj(x) | |
| query_feats = self.query_feat_proj(x) if self.query_init == "feat" else None | |
| kv_pos_xyz = scatter_mean(batch["coord_full"], batch["batched_superpoint"], dim=0) | |
| query, query_pos_norm, query_padding_mask = self.get_query( | |
| B, batch_offsets, batch, device, dtype, kv_pos_xyz, query_feats) | |
| max_len = batch["superpoint_len"].max() | |
| key_padding_mask = torch.ones(B, max_len, dtype=torch.bool, device=device) | |
| for i in range(B): | |
| key_padding_mask[i, :batch["superpoint_len"][i]] = False | |
| kv_batched = torch.zeros(B, max_len, self.d_model, device=device, dtype=dtype) | |
| mask_feats_batched = torch.zeros(B, max_len, self.d_model, device=device, dtype=dtype) | |
| kv_pos_embedd_batched = torch.zeros(B, max_len, self.d_model, device=device, dtype=dtype) | |
| kv_pos_xyz_batched = torch.zeros(B, max_len, 3, device=device, dtype=dtype) | |
| scene_ranges_min, scene_ranges_max = [], [] | |
| for b in range(B): | |
| s, e = batch_offsets[b], batch_offsets[b + 1] | |
| kv_batched[b, :e - s] = inst_feats[s:e] | |
| mask_feats_batched[b, :e - s] = mask_feats[s:e] | |
| kv_pos_xyz_batched[b, :e - s] = kv_pos_xyz[s:e] | |
| scene_ranges_min.append(kv_pos_xyz[s:e].min(0).values) | |
| scene_ranges_max.append(kv_pos_xyz[s:e].max(0).values) | |
| scene_ranges_min = torch.stack(scene_ranges_min, 0).unsqueeze(0).permute(1, 0, 2) | |
| scene_ranges_max = torch.stack(scene_ranges_max, 0).unsqueeze(0).permute(1, 0, 2) | |
| pred_masks = None | |
| for layer_i in range(self.num_layer): | |
| query_pos_xyz = query_pos_norm * (scene_ranges_max - scene_ranges_min) + scene_ranges_min | |
| query = self.self_attn_layers[layer_i]( | |
| q_c=query, q_coords=query_pos_xyz, B=B, | |
| key_padding_mask=query_padding_mask, scene_ranges_min=scene_ranges_min) | |
| query = self.cross_attn_layers[layer_i]( | |
| q_c=query, q_p=None, k_c=kv_batched, k_p=kv_pos_embedd_batched, | |
| v=kv_batched, key_padding_mask=key_padding_mask, | |
| q_coords=query_pos_xyz, kv_coords=kv_pos_xyz_batched, | |
| pred_masks=pred_masks, B=B, scene_ranges_min=scene_ranges_min) | |
| query = self.ffn_layers[layer_i](query) | |
| if self.refinement_cross_attention and layer_i in self.refinement_cross_attention_layer_indices: | |
| ri = self.refinement_cross_attention_layer_indices.index(layer_i) | |
| mask_feats_batched = self.refinement_cross_attn_layers[ri]( | |
| q_c=mask_feats_batched, q_p=None, k_c=query, k_p=None, v=query, | |
| key_padding_mask=query_padding_mask, | |
| q_coords=kv_pos_xyz_batched, kv_coords=query_pos_xyz, | |
| pred_masks=None, B=B, scene_ranges_min=scene_ranges_min) | |
| mask_feats_batched = self.refinement_ffn_layers[ri](mask_feats_batched) | |
| query_norm = self.out_norm(query) | |
| if layer_i < self.num_layer - 1: | |
| if self.refinement_cross_attention and layer_i in self.refinement_cross_attention_layer_indices: | |
| mask_feats = torch.cat( | |
| [mask_feats_batched[b, :batch_offsets[b + 1] - batch_offsets[b]] | |
| for b in range(B)], dim=0) | |
| pred_masks = self.get_mask(query_norm, mask_feats, batch_offsets) | |
| query_pos_delta = self.query_pos_delta_head(query_norm) | |
| new_query_pos = (query_pos_norm * (scene_ranges_max - scene_ranges_min) | |
| + scene_ranges_min + query_pos_delta) | |
| new_query_pos_norm = (new_query_pos - scene_ranges_min) / (scene_ranges_max - scene_ranges_min) | |
| query_pos_norm = new_query_pos_norm.detach() if self.detach_query_pos else new_query_pos_norm | |
| # only the last layer is used at inference time | |
| if self.refinement_cross_attention: | |
| mask_feats = torch.cat( | |
| [mask_feats_batched[b, :batch_offsets[b + 1] - batch_offsets[b]] | |
| for b in range(B)], dim=0) | |
| pred_labels, pred_scores, pred_masks, pred_spatials = self.prediction_head( | |
| query_norm, query_pos_norm, mask_feats, batch_offsets, | |
| scene_ranges_max, scene_ranges_min) | |
| keep = [~query_padding_mask[b] for b in range(B)] | |
| return { | |
| "labels": [pred_labels[b][keep[b]] for b in range(B)], | |
| "scores": [pred_scores[b][keep[b]] for b in range(B)], | |
| "masks": [pred_masks[b][keep[b]] for b in range(B)], | |
| "spatials": [pred_spatials[b][keep[b]] for b in range(B)], | |
| } | |
| # =========================================================================== # | |
| # src/models/components/volt/{volt_base,decoder}.py | |
| # =========================================================================== # | |
| class Mlp(nn.Module): | |
| def __init__(self, in_features, hidden_features, act_layer=nn.GELU): | |
| super().__init__() | |
| self.fc1 = nn.Linear(in_features, hidden_features) | |
| self.act = act_layer() | |
| self.fc2 = nn.Linear(hidden_features, in_features) | |
| def forward(self, x): | |
| return self.fc2(self.act(self.fc1(x))) | |
| class Tokenizer(nn.Module): | |
| def __init__(self, in_channels, out_channels, kernel_size): | |
| super().__init__() | |
| self.kernel_size = kernel_size | |
| self.out_channels = out_channels | |
| self.proj = nn.Linear(kernel_size ** 3 * in_channels, out_channels) | |
| def forward(self, features, indices): | |
| K = self.kernel_size | |
| coarse_indices_per_voxel = indices // indices.new_tensor([1, K, K, K]) | |
| coarse_indices, inverse = torch.unique(coarse_indices_per_voxel, dim=0, | |
| sorted=True, return_inverse=True) | |
| offset = indices[:, 1:] % K | |
| offset_id = offset[:, 0] * K * K + offset[:, 1] * K + offset[:, 2] | |
| patches = features.new_zeros(coarse_indices.shape[0], K ** 3, features.shape[1]) | |
| patches[inverse, offset_id] = features | |
| return self.proj(patches.flatten(1)), coarse_indices, inverse, offset_id | |
| class VoltRoPE(nn.Module): | |
| def __init__(self, theta=100.0, freq_split=(12, 12, 8), max_grid_size=(1024, 1024, 512)): | |
| super().__init__() | |
| self.max_grid_size = max_grid_size | |
| for name, n, m in zip("xyz", freq_split, max_grid_size): | |
| freqs = 1.0 / theta ** torch.linspace(0, 1, n) | |
| self.register_buffer(f"cis_cache_{name}", | |
| self._precompute(freqs, m), persistent=False) | |
| def _precompute(freqs, max_pos): | |
| freqs_pos = torch.outer(torch.arange(max_pos).float(), freqs) | |
| return torch.polar(torch.ones_like(freqs_pos), freqs_pos) | |
| def compute_axial_cis_efficient(self, indices): | |
| idx = indices.clone() | |
| for a in range(3): | |
| idx[:, a] = idx[:, a].clamp(0, self.max_grid_size[a] - 1) | |
| return torch.cat([self.cis_cache_x[idx[:, 0]], | |
| self.cis_cache_y[idx[:, 1]], | |
| self.cis_cache_z[idx[:, 2]]], dim=-1).unsqueeze(0) | |
| class RoPE_Attention(nn.Module): | |
| def __init__(self, dim=768, num_heads=12, qk_norm=False): | |
| super().__init__() | |
| self.num_heads = num_heads | |
| self.h_dim = dim // num_heads | |
| self.qkv = nn.Linear(dim, 3 * dim) | |
| self.proj = nn.Linear(dim, dim) | |
| self.q_norm = nn.LayerNorm(self.h_dim) if qk_norm else nn.Identity() | |
| self.k_norm = nn.LayerNorm(self.h_dim) if qk_norm else nn.Identity() | |
| def apply_rotary_emb(q, k, freqs_cis): | |
| q_ = torch.view_as_complex(q.float().reshape(*q.shape[:-1], -1, 2)) | |
| k_ = torch.view_as_complex(k.float().reshape(*k.shape[:-1], -1, 2)) | |
| q_out = torch.view_as_real(q_ * freqs_cis).flatten(2) | |
| k_out = torch.view_as_real(k_ * freqs_cis).flatten(2) | |
| return q_out.type_as(q), k_out.type_as(k) | |
| def forward(self, x, freqs_cis, cu_seqlens, max_seqlen): | |
| N, C = x.shape | |
| qkv = self.qkv(x).view(N, 3, self.num_heads, self.h_dim).permute(1, 2, 0, 3) | |
| q, k, v = qkv.unbind(dim=0) | |
| q, k = self.q_norm(q).to(q.dtype), self.k_norm(k).to(k.dtype) | |
| q, k = self.apply_rotary_emb(q, k, freqs_cis) | |
| qkv = torch.stack([q, k, v], dim=0).permute(2, 0, 1, 3) | |
| qkv_dtype = qkv.dtype | |
| # upstream runs this through FlashAttention-2 in fp16 | |
| attn_dtype = torch.float16 if qkv.is_cuda else torch.float32 | |
| x = varlen_qkvpacked_attention(qkv.to(attn_dtype), cu_seqlens, max_seqlen) | |
| return self.proj(x.reshape(-1, C).to(qkv_dtype)) | |
| class Block(nn.Module): | |
| def __init__(self, dim=768, num_heads=12, mlp_ratio=4.0, qk_norm=False, | |
| act_layer=nn.GELU, norm_layer=nn.LayerNorm): | |
| super().__init__() | |
| self.norm1 = norm_layer(dim) | |
| self.attn = RoPE_Attention(dim=dim, num_heads=num_heads, qk_norm=qk_norm) | |
| self.ls1 = nn.Identity() | |
| self.drop_path1 = nn.Identity() | |
| self.norm2 = norm_layer(dim) | |
| self.mlp = Mlp(in_features=dim, hidden_features=int(dim * mlp_ratio), act_layer=act_layer) | |
| self.ls2 = nn.Identity() | |
| self.drop_path2 = nn.Identity() | |
| def forward(self, x, freqs_cis, cu_seq_lens, max_seqlen): | |
| x = x + self.drop_path1(self.ls1(self.attn(self.norm1(x), freqs_cis, cu_seq_lens, max_seqlen))) | |
| x = x + self.drop_path2(self.ls2(self.mlp(self.norm2(x)))) | |
| return x | |
| class Detokenizer(nn.Module): | |
| def __init__(self, in_channels, out_channels, kernel_size): | |
| super().__init__() | |
| self.kernel_size = kernel_size | |
| self.out_channels = out_channels | |
| self.proj = nn.Linear(in_channels, kernel_size ** 3 * out_channels, bias=False) | |
| self.bias = nn.Parameter(torch.zeros(out_channels)) | |
| def forward(self, coarse_features, inverse, offset_id): | |
| K = self.kernel_size | |
| all_offsets = self.proj(coarse_features).view(-1, K ** 3, self.out_channels) | |
| return all_offsets[inverse, offset_id] + self.bias | |
| class VoltDecoder(nn.Module): | |
| def __init__(self, in_channels, out_channels, kernel_size, | |
| norm_layer=partial(nn.BatchNorm1d, eps=1e-3, momentum=0.01)): | |
| super().__init__() | |
| act_layer = nn.GELU | |
| self.pre = nn.Sequential(norm_layer(in_channels), act_layer(), | |
| nn.Linear(in_channels, out_channels, bias=False), | |
| norm_layer(out_channels), act_layer()) | |
| self.unembed = Detokenizer(out_channels, out_channels, kernel_size=kernel_size) | |
| self.post = nn.Sequential(norm_layer(out_channels), act_layer()) | |
| def forward(self, x, inverse, offset_id): | |
| return self.post(self.unembed(self.pre(x), inverse, offset_id)) | |
| class Volt(nn.Module): | |
| def __init__(self, in_channels=6, embed_dim=768, depth=12, num_heads=12, mlp_ratio=4, | |
| qk_norm=True, stride=5, kernel_size=5, out_channels=128): | |
| super().__init__() | |
| assert stride == kernel_size | |
| self.tokenizer = Tokenizer(in_channels, embed_dim, kernel_size) | |
| self.blocks = nn.Sequential(*[ | |
| Block(dim=embed_dim, num_heads=num_heads, mlp_ratio=mlp_ratio, qk_norm=qk_norm) | |
| for _ in range(depth)]) | |
| self.pos_enc = VoltRoPE() | |
| self.decoder = VoltDecoder(in_channels=embed_dim, out_channels=out_channels, | |
| kernel_size=kernel_size) | |
| def compute_seqlens(batch_indices): | |
| points_per_batch = torch.bincount(batch_indices + 1) | |
| cu_seqlens = torch.cumsum(points_per_batch, dim=0, dtype=torch.int32) | |
| seq_lens = cu_seqlens[1:] - cu_seqlens[:-1] | |
| return cu_seqlens, seq_lens.max().item() | |
| def forward(self, data_dict): | |
| grid_coord = data_dict["coord_grid"] | |
| feat = data_dict["feat"] | |
| indices = torch.cat([data_dict["batch_indices"].unsqueeze(-1).int(), | |
| grid_coord.int()], dim=1).contiguous() | |
| features, indices, inverse, offset_id = self.tokenizer(feat, indices) | |
| cu_seqlens, max_seqlen = self.compute_seqlens(indices[:, 0]) | |
| freqs_cis = self.pos_enc.compute_axial_cis_efficient(indices[:, 1:]) | |
| for blk in self.blocks: | |
| features = blk(features, freqs_cis, cu_seqlens, max_seqlen) | |
| return self.decoder(features, inverse, offset_id) | |
| # =========================================================================== # | |
| # src/models/base_instance_former.py | |
| # =========================================================================== # | |
| class AQ3D(nn.Module): | |
| """AQ3D with the Volt-B backbone, configured for ScanNet200 (198 classes).""" | |
| def __init__(self, num_classes=198, in_features=6, mid_features=128): | |
| super().__init__() | |
| self.num_classes = num_classes | |
| rope = partial(RoPE, theta=100.0, head_split=[16, 16, 16], grid_size=0.05, | |
| max_grid_size=[512, 512, 256]) | |
| self.backbone = Volt(in_channels=in_features, embed_dim=768, depth=12, | |
| num_heads=12, mlp_ratio=4, qk_norm=True, stride=5, | |
| kernel_size=5, out_channels=mid_features) | |
| self.decoder = QueryDecoder( | |
| num_layer=6, max_query=True, num_query_ratio=0.6, query_init="feat", | |
| query_pos_init="adaptive", dropout_head=0.1, dropout_layer=0.2, | |
| cosine_classifier=True, refinement_cross_attention=True, | |
| refinement_cross_attention_layer_indices=[1, 3, 5], | |
| num_class=num_classes, in_channel=mid_features, d_model=384, | |
| activation_fn=nn.ReLU, | |
| self_attention_layer=partial(SelfAttentionLayer, nhead=8, dropout=0.0, rope=rope()), | |
| cross_attention_layer=partial(CrossAttentionLayer, nhead=8, dropout=0.0, | |
| attn_mask_thres=0.1, rope=rope()), | |
| refinement_cross_attention_layer=partial(CrossAttentionLayer, nhead=8, | |
| dropout=0.0, rope=rope()), | |
| ffn_layer=partial(FFN, hidden_dim=1024, dropout=0.0, activation_fn=nn.GELU), | |
| ) | |
| self.pool_attn = nn.Sequential( | |
| nn.Linear(mid_features, mid_features), nn.LayerNorm(mid_features), nn.ReLU(), | |
| nn.Linear(mid_features, mid_features), nn.LayerNorm(mid_features), nn.ReLU(), | |
| nn.Linear(mid_features, mid_features)) | |
| def forward(self, batch): | |
| feat = self.backbone(batch) | |
| feat = feat[batch["batched_inverse"]] | |
| scores = self.pool_attn(feat) | |
| weights = scatter_softmax(scores, batch["batched_superpoint"], dim=0) | |
| feat = scatter_sum(feat * weights, batch["batched_superpoint"], dim=0) | |
| return self.decoder(feat, batch) | |