File size: 12,674 Bytes
3c58630
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
from __future__ import annotations

from typing import Literal, Optional

import torch
import torch.nn as nn

from .blocks import Attention, RMSNorm, SwiGLUFFN, modulate

AttnAxis = Literal["spatial", "temporal", "object"]


def _reduce_replicated_register_tokens(reg_rep: torch.Tensor, group_keep: Optional[torch.Tensor]) -> torch.Tensor:
    """
    Reduces replicated register tokens produced by factorized attention.

    reg_rep: (B, G, N_reg, D) where G is the number of factorized groups.
    group_keep: Optional (B, G) bool/float indicating which groups contain any valid tokens.
    Returns: (B, N_reg, D)
    """
    if reg_rep.ndim != 4:
        raise ValueError(f"reg_rep must be (B,G,N_reg,D), got {tuple(reg_rep.shape)}")

    if group_keep is None:
        return reg_rep.mean(dim=1)

    if group_keep.shape != reg_rep.shape[:2]:
        raise ValueError(f"group_keep must be (B,G)={tuple(reg_rep.shape[:2])}, got {tuple(group_keep.shape)}")

    w = group_keep.to(device=reg_rep.device, dtype=torch.float32).unsqueeze(-1).unsqueeze(-1)
    denom = w.sum(dim=1).clamp_min(1.0)
    out = (reg_rep.to(dtype=torch.float32) * w).sum(dim=1) / denom
    return out.to(dtype=reg_rep.dtype)


class DiTBlockSpaceTempAltObj(nn.Module):
    """
    DiT-style transformer block with AdaLN and three attention axes:

      - Spatial: full attention across all vertices within each frame.
      - Temporal: full attention across frames for each vertex.
      - Object: attention within each object's vertices inside each frame, then scatter back.

    Register tokens follow the divided-attention convention for all factorized axes:
      - Replicate across groups before attention.
      - Reduce back via (weighted) mean after attention.
    """

    def __init__(
        self,
        *,
        hidden_size: int,
        num_heads: int,
        attn_axis: AttnAxis,
        mlp_ratio: float = 4.0,
        attn_drop: float = 0.0,
        proj_drop: float = 0.0,
    ) -> None:
        super().__init__()
        if attn_axis not in ("spatial", "temporal", "object"):
            raise ValueError(f"attn_axis must be 'spatial', 'temporal', or 'object', got {attn_axis}")
        self.attn_axis: AttnAxis = attn_axis

        self.norm1 = RMSNorm(hidden_size, eps=1e-6)
        self.attn = Attention(
            hidden_size,
            num_heads=num_heads,
            attn_drop=attn_drop,
            proj_drop=proj_drop,
            qkv_bias=True,
            qk_norm=True,
        )
        self.norm2 = RMSNorm(hidden_size, eps=1e-6)
        mlp_hidden = int(hidden_size * mlp_ratio)
        self.mlp = SwiGLUFFN(hidden_size, mlp_hidden, drop=proj_drop)
        self.adaLN_modulation = nn.Sequential(
            nn.SiLU(),
            nn.Linear(hidden_size, 6 * hidden_size, bias=True),
        )

    def _spatial_attn(
        self,
        x: torch.Tensor,
        *,
        num_frames: int,
        num_vertices: int,
        num_register_tokens: int,
        rope=None,
        src_key_padding_mask: Optional[torch.Tensor] = None,
        group_keep: Optional[torch.Tensor] = None,
    ) -> torch.Tensor:
        bsz, _, dim = x.shape
        tokens = x[:, num_register_tokens:, :].reshape(bsz, num_frames, num_vertices, dim)
        tok_sp = tokens.reshape(bsz * num_frames, num_vertices, dim)

        if num_register_tokens > 0:
            reg = x[:, :num_register_tokens, :]
            reg_rep = reg[:, None, :, :].expand(bsz, num_frames, num_register_tokens, dim).reshape(
                bsz * num_frames, num_register_tokens, dim
            )
            x_sp = torch.cat([reg_rep, tok_sp], dim=1)
        else:
            x_sp = tok_sp

        y_sp = self.attn(x_sp, rope=rope, src_key_padding_mask=src_key_padding_mask)

        if num_register_tokens > 0:
            reg_rep_out = y_sp[:, :num_register_tokens, :].reshape(bsz, num_frames, num_register_tokens, dim)
            reg_out = _reduce_replicated_register_tokens(reg_rep_out, group_keep)
            tok_out = y_sp[:, num_register_tokens:, :].reshape(bsz, num_frames, num_vertices, dim)
            tok_out = tok_out.reshape(bsz, num_frames * num_vertices, dim)
            return torch.cat([reg_out, tok_out], dim=1)

        return y_sp.reshape(bsz, num_frames * num_vertices, dim)

    def _temporal_attn(
        self,
        x: torch.Tensor,
        *,
        num_frames: int,
        num_vertices: int,
        num_register_tokens: int,
        rope=None,
        src_key_padding_mask: Optional[torch.Tensor] = None,
        group_keep: Optional[torch.Tensor] = None,
    ) -> torch.Tensor:
        bsz, _, dim = x.shape
        tokens = x[:, num_register_tokens:, :].reshape(bsz, num_frames, num_vertices, dim)
        tok_t = tokens.permute(0, 2, 1, 3).reshape(bsz * num_vertices, num_frames, dim)

        if num_register_tokens > 0:
            reg = x[:, :num_register_tokens, :]
            reg_rep = reg[:, None, :, :].expand(bsz, num_vertices, num_register_tokens, dim).reshape(
                bsz * num_vertices, num_register_tokens, dim
            )
            x_t = torch.cat([reg_rep, tok_t], dim=1)
        else:
            x_t = tok_t

        y_t = self.attn(x_t, rope=rope, src_key_padding_mask=src_key_padding_mask)

        if num_register_tokens > 0:
            reg_rep_out = y_t[:, :num_register_tokens, :].reshape(bsz, num_vertices, num_register_tokens, dim)
            reg_out = _reduce_replicated_register_tokens(reg_rep_out, group_keep)
            tok_out = y_t[:, num_register_tokens:, :].reshape(bsz, num_vertices, num_frames, dim)
            tok_out = tok_out.permute(0, 2, 1, 3).reshape(bsz, num_frames * num_vertices, dim)
            return torch.cat([reg_out, tok_out], dim=1)

        tok_out = y_t.reshape(bsz, num_vertices, num_frames, dim).permute(0, 2, 1, 3).reshape(
            bsz, num_frames * num_vertices, dim
        )
        return tok_out

    def _object_attn(
        self,
        x: torch.Tensor,
        *,
        num_frames: int,
        num_vertices: int,
        num_register_tokens: int,
        num_objects: int,
        object_nmax: int,
        object_vertex_index: torch.Tensor,
        object_token_keep: torch.Tensor,
        rope=None,
        src_key_padding_mask: Optional[torch.Tensor] = None,
        group_keep: Optional[torch.Tensor] = None,
    ) -> torch.Tensor:
        if num_objects <= 0:
            raise ValueError(f"num_objects must be > 0, got {num_objects}")
        if object_nmax <= 0:
            raise ValueError(f"object_nmax must be > 0, got {object_nmax}")

        bsz, _, dim = x.shape
        bf = bsz * num_frames

        if object_vertex_index.shape != (bf, num_objects * object_nmax):
            raise ValueError(
                "object_vertex_index must be "
                f"(B*F,O*Nmax)={(bf, num_objects * object_nmax)}, got {tuple(object_vertex_index.shape)}"
            )
        if object_token_keep.shape != (bf, num_objects, object_nmax):
            raise ValueError(
                "object_token_keep must be "
                f"(B*F,O,Nmax)={(bf, num_objects, object_nmax)}, got {tuple(object_token_keep.shape)}"
            )

        tokens = x[:, num_register_tokens:, :].reshape(bsz, num_frames, num_vertices, dim)
        tok_flat = tokens.reshape(bf, num_vertices, dim)

        gather_idx = object_vertex_index.unsqueeze(-1).expand(-1, -1, dim)
        tok_obj = tok_flat.gather(1, gather_idx).reshape(bf, num_objects, object_nmax, dim)
        tok_obj = tok_obj.reshape(bf * num_objects, object_nmax, dim)

        if num_register_tokens > 0:
            reg = x[:, :num_register_tokens, :]
            reg_rep = reg[:, None, None, :, :].expand(bsz, num_frames, num_objects, num_register_tokens, dim).reshape(
                bf * num_objects, num_register_tokens, dim
            )
            x_obj = torch.cat([reg_rep, tok_obj], dim=1)
        else:
            x_obj = tok_obj

        y_obj = self.attn(x_obj, rope=rope, src_key_padding_mask=src_key_padding_mask)
        if y_obj.dtype != x.dtype:
            y_obj = y_obj.to(dtype=x.dtype)

        if num_register_tokens > 0:
            reg_rep_out = y_obj[:, :num_register_tokens, :].reshape(bsz, num_frames * num_objects, num_register_tokens, dim)
            reg_out = _reduce_replicated_register_tokens(reg_rep_out, group_keep)
            tok_out = y_obj[:, num_register_tokens:, :].reshape(bf, num_objects, object_nmax, dim)
        else:
            reg_out = None
            tok_out = y_obj.reshape(bf, num_objects, object_nmax, dim)

        tok_out = tok_out * object_token_keep.unsqueeze(-1).to(dtype=tok_out.dtype)
        tok_src = tok_out.reshape(bf, num_objects * object_nmax, dim).to(dtype=x.dtype)
        tok_full = x.new_zeros((bf, num_vertices, dim), dtype=x.dtype)
        tok_full.scatter_add_(
            1,
            object_vertex_index.unsqueeze(-1).expand(-1, -1, dim),
            tok_src,
        )
        tok_full = tok_full.reshape(bsz, num_frames * num_vertices, dim)

        if reg_out is not None:
            return torch.cat([reg_out, tok_full], dim=1)
        return tok_full

    def forward(
        self,
        x: torch.Tensor,
        c: torch.Tensor,
        *,
        num_frames: int,
        num_vertices: int,
        rope_spatial=None,
        rope_temporal=None,
        rope_object=None,
        src_key_padding_mask_spatial: Optional[torch.Tensor] = None,
        src_key_padding_mask_temporal: Optional[torch.Tensor] = None,
        src_key_padding_mask_object: Optional[torch.Tensor] = None,
        spatial_group_keep: Optional[torch.Tensor] = None,
        temporal_group_keep: Optional[torch.Tensor] = None,
        object_group_keep: Optional[torch.Tensor] = None,
        object_vertex_index: Optional[torch.Tensor] = None,
        object_token_keep: Optional[torch.Tensor] = None,
        num_objects: int = 0,
        object_nmax: int = 0,
    ) -> torch.Tensor:
        if x.ndim != 3:
            raise ValueError(f"x must be (B,S,D), got {tuple(x.shape)}")
        if c.ndim != 2:
            raise ValueError(f"c must be (B,D), got {tuple(c.shape)}")
        if x.shape[0] != c.shape[0]:
            raise ValueError(f"Batch mismatch: x has B={x.shape[0]}, c has B={c.shape[0]}")

        bsz, seq_len, _ = x.shape
        num_frames = int(num_frames)
        num_vertices = int(num_vertices)
        if num_frames <= 0 or num_vertices <= 0:
            raise ValueError(f"num_frames and num_vertices must be >0, got {num_frames}, {num_vertices}")

        num_token = num_frames * num_vertices
        if seq_len < num_token:
            raise ValueError(f"seq_len={seq_len} must be >= num_frames*num_vertices={num_token}")
        num_register_tokens = seq_len - num_token

        shift_msa, scale_msa, gate_msa, shift_mlp, scale_mlp, gate_mlp = self.adaLN_modulation(c).chunk(6, dim=-1)

        x_attn_in = modulate(self.norm1(x), shift_msa, scale_msa)
        if self.attn_axis == "spatial":
            attn_out = self._spatial_attn(
                x_attn_in,
                num_frames=num_frames,
                num_vertices=num_vertices,
                num_register_tokens=num_register_tokens,
                rope=rope_spatial,
                src_key_padding_mask=src_key_padding_mask_spatial,
                group_keep=spatial_group_keep,
            )
        elif self.attn_axis == "temporal":
            attn_out = self._temporal_attn(
                x_attn_in,
                num_frames=num_frames,
                num_vertices=num_vertices,
                num_register_tokens=num_register_tokens,
                rope=rope_temporal,
                src_key_padding_mask=src_key_padding_mask_temporal,
                group_keep=temporal_group_keep,
            )
        else:
            if object_vertex_index is None or object_token_keep is None:
                raise ValueError("object attention requires object_vertex_index and object_token_keep")
            attn_out = self._object_attn(
                x_attn_in,
                num_frames=num_frames,
                num_vertices=num_vertices,
                num_register_tokens=num_register_tokens,
                num_objects=int(num_objects),
                object_nmax=int(object_nmax),
                object_vertex_index=object_vertex_index,
                object_token_keep=object_token_keep,
                rope=rope_object,
                src_key_padding_mask=src_key_padding_mask_object,
                group_keep=object_group_keep,
            )

        x = x + gate_msa.unsqueeze(1) * attn_out
        x = x + gate_mlp.unsqueeze(1) * self.mlp(modulate(self.norm2(x), shift_mlp, scale_mlp))
        return x