File size: 20,059 Bytes
12c3a55
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
# Copyright 2026 inclusionAI and The HuggingFace Team. All rights reserved.
#
# Licensed under the MIT License. See the LICENSE file in this repository.

import math

import torch
import torch.nn as nn
import torch.nn.functional as F

from diffusers.configuration_utils import ConfigMixin, register_to_config
from diffusers.models.modeling_utils import ModelMixin


class MingImageRMSNorm(nn.Module):
    def __init__(self, hidden_size: int, eps: float = 1e-6):
        super().__init__()
        self.weight = nn.Parameter(torch.ones(hidden_size))
        self.variance_epsilon = eps

    def forward(self, hidden_states: torch.Tensor) -> torch.Tensor:
        input_dtype = hidden_states.dtype
        hidden_states = hidden_states.to(torch.float32)
        variance = hidden_states.pow(2).mean(-1, keepdim=True)
        hidden_states = hidden_states * torch.rsqrt(variance + self.variance_epsilon)
        return self.weight * hidden_states.to(input_dtype)


def rotate_half(x: torch.Tensor) -> torch.Tensor:
    x1 = x[..., : x.shape[-1] // 2]
    x2 = x[..., x.shape[-1] // 2 :]
    return torch.cat((-x2, x1), dim=-1)


def video_rope_cos_sin(cos: torch.Tensor, sin: torch.Tensor, mrope_section: list[int]):
    """Interleave the (frame, height, width) rotary tables into the "video_rope" channel layout.

    `cos` / `sin` are `(3, batch, seq_len, rotary_dim)` for the frame, height and width axes. Height and width
    alternate channel by channel, and the frame axis takes the last channels of each half.
    """
    sections = [mrope_section[0], mrope_section[1] + mrope_section[2]] * 2
    index = 0
    result_cos, result_sin = [], []
    for i, section in enumerate(sections[::-1]):
        if i % 2 == 0:
            for j in range(section):
                row = 1 if j % 2 == 0 else 2
                result_cos.append(cos[row, ..., index : index + 1])
                result_sin.append(sin[row, ..., index : index + 1])
                index += 1
        else:
            result_cos.append(cos[0, ..., index : index + section])
            result_sin.append(sin[0, ..., index : index + section])
            index += section
    return torch.cat(result_cos, dim=-1), torch.cat(result_sin, dim=-1)


class MingImageRotaryEmbedding(nn.Module):
    def __init__(self, rotary_dim: int, theta: float):
        super().__init__()
        inv_freq = 1.0 / (theta ** (torch.arange(0, rotary_dim, 2, dtype=torch.int64).float() / rotary_dim))
        self.register_buffer("inv_freq", inv_freq, persistent=False)

    @torch.no_grad()
    def forward(self, x: torch.Tensor, position_ids: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
        # position_ids: (3, batch, seq_len) for the frame, height and width axes
        inv_freq = self.inv_freq[None, None, :, None].float().expand(3, position_ids.shape[1], -1, 1).to(x.device)
        position_ids = position_ids[:, :, None, :].float()
        with torch.autocast(device_type=x.device.type if x.device.type != "mps" else "cpu", enabled=False):
            freqs = (inv_freq @ position_ids).transpose(2, 3)
            emb = torch.cat((freqs, freqs), dim=-1)
        return emb.cos().to(x.dtype), emb.sin().to(x.dtype)


class MingImageMLP(nn.Module):
    def __init__(self, hidden_size: int, intermediate_size: int):
        super().__init__()
        self.gate_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
        self.up_proj = nn.Linear(hidden_size, intermediate_size, bias=False)
        self.down_proj = nn.Linear(intermediate_size, hidden_size, bias=False)

    def forward(self, x: torch.Tensor) -> torch.Tensor:
        return self.down_proj(F.silu(self.gate_proj(x)) * self.up_proj(x))


class MingImageGate(nn.Module):
    """Sigmoid router with expert bias and group-limited top-k selection."""

    def __init__(self, hidden_size, num_experts, top_k, n_group, topk_group, routed_scaling_factor):
        super().__init__()
        self.top_k = top_k
        self.num_experts = num_experts
        self.n_group = n_group
        self.topk_group = topk_group
        self.routed_scaling_factor = routed_scaling_factor
        self.weight = nn.Parameter(torch.zeros((num_experts, hidden_size)))
        self.expert_bias = nn.Parameter(torch.zeros(num_experts), requires_grad=False)

    def forward(self, hidden_states: torch.Tensor) -> tuple[torch.Tensor, torch.Tensor]:
        hidden_states = hidden_states.view(-1, hidden_states.shape[-1])
        logits = F.linear(hidden_states.float(), self.weight.float())
        scores = torch.sigmoid(logits)

        # pick the top `topk_group` groups by the sum of their two best (bias-corrected) scores, then the top-k
        # experts inside those groups
        scores_for_routing = scores + self.expert_bias
        num_tokens = scores_for_routing.shape[0]
        group_scores = scores_for_routing.view(num_tokens, self.n_group, -1).topk(2, dim=-1)[0].sum(dim=-1)
        group_idx = torch.topk(group_scores, k=self.topk_group, dim=-1, sorted=False)[1]
        group_mask = torch.zeros_like(group_scores).scatter_(1, group_idx, 1)
        score_mask = (
            group_mask.unsqueeze(-1)
            .expand(num_tokens, self.n_group, self.num_experts // self.n_group)
            .reshape(num_tokens, -1)
        )
        masked_scores = scores_for_routing.masked_fill(~score_mask.bool(), float("-inf"))
        topk_idx = torch.topk(masked_scores, k=self.top_k, dim=-1, sorted=False)[1]

        topk_weight = torch.gather(scores, dim=1, index=topk_idx).type_as(logits)
        topk_weight = topk_weight / (topk_weight.sum(dim=-1, keepdim=True) + 1e-20)
        return topk_idx, topk_weight * self.routed_scaling_factor


class MingImageSparseMoeBlock(nn.Module):
    """Routed experts plus a shared expert. Tokens at image positions are routed by `image_gate`."""

    def __init__(self, config):
        super().__init__()
        gate_kwargs = {
            "hidden_size": config.hidden_size,
            "num_experts": config.num_experts,
            "top_k": config.num_experts_per_tok,
            "n_group": config.n_group,
            "topk_group": config.topk_group,
            "routed_scaling_factor": config.routed_scaling_factor,
        }
        self.gate = MingImageGate(**gate_kwargs)
        self.image_gate = MingImageGate(**gate_kwargs)
        self.experts = nn.ModuleList(
            [MingImageMLP(config.hidden_size, config.moe_intermediate_size) for _ in range(config.num_experts)]
        )
        self.shared_experts = MingImageMLP(
            config.hidden_size, config.moe_intermediate_size * config.num_shared_experts
        )

    def forward(self, hidden_states: torch.Tensor, image_mask: torch.Tensor) -> torch.Tensor:
        batch_size, seq_len, hidden_size = hidden_states.shape
        topk_idx, topk_weight = self.gate(hidden_states)
        image_topk_idx, image_topk_weight = self.image_gate(hidden_states)
        image_mask = image_mask.view(-1, 1)
        topk_idx = torch.where(image_mask, image_topk_idx, topk_idx)
        topk_weight = torch.where(image_mask, image_topk_weight, topk_weight)

        routed = self.moe_infer(hidden_states.view(-1, hidden_size), topk_idx, topk_weight)
        return routed.view(batch_size, seq_len, hidden_size) + self.shared_experts(hidden_states)

    def moe_infer(self, x: torch.Tensor, topk_ids: torch.Tensor, topk_weight: torch.Tensor) -> torch.Tensor:
        tokens_per_expert = torch.bincount(topk_ids.view(-1), minlength=len(self.experts)).tolist()
        idxs = topk_ids.view(-1).argsort()
        sorted_tokens = x[idxs // topk_ids.shape[1]]
        outputs = []
        start_idx = 0
        for expert, num_tokens in zip(self.experts, tokens_per_expert):
            if num_tokens == 0:
                continue
            outputs.append(expert(sorted_tokens[start_idx : start_idx + num_tokens]))
            start_idx += num_tokens

        outs = torch.cat(outputs, dim=0)
        new_x = torch.empty_like(outs)
        new_x[idxs] = outs
        return (
            new_x.view(*topk_ids.shape, -1)
            .type(topk_weight.dtype)
            .mul_(topk_weight.unsqueeze(dim=-1))
            .sum(dim=1)
            .type(new_x.dtype)
        )


class MingImageTextAttention(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.num_heads = config.num_attention_heads
        self.num_kv_heads = config.num_key_value_heads
        self.head_dim = config.head_dim
        self.mrope_section = config.mrope_section

        self.query_key_value = nn.Linear(
            config.hidden_size, (self.num_heads + 2 * self.num_kv_heads) * self.head_dim, bias=False
        )
        self.q_norm = MingImageRMSNorm(self.head_dim, eps=config.rms_norm_eps)
        self.k_norm = MingImageRMSNorm(self.head_dim, eps=config.rms_norm_eps)
        self.dense = nn.Linear(self.num_heads * self.head_dim, config.hidden_size, bias=False)

    def forward(
        self,
        hidden_states: torch.Tensor,
        attention_mask: torch.Tensor,
        position_embeddings: tuple[torch.Tensor, torch.Tensor],
    ) -> torch.Tensor:
        batch_size, seq_len, _ = hidden_states.shape
        qkv = self.query_key_value(hidden_states).view(
            batch_size, seq_len, self.num_heads + 2 * self.num_kv_heads, self.head_dim
        )
        query, key, value = qkv.split([self.num_heads, self.num_kv_heads, self.num_kv_heads], dim=2)
        query = self.q_norm(query)
        key = self.k_norm(key)

        # partial rotary: only the first `rotary_dim` channels of each head are rotated
        cos, sin = video_rope_cos_sin(*position_embeddings, self.mrope_section)
        cos, sin = cos.unsqueeze(2), sin.unsqueeze(2)
        rotary_dim = cos.shape[-1]
        q_rot, q_pass = query[..., :rotary_dim], query[..., rotary_dim:]
        k_rot, k_pass = key[..., :rotary_dim], key[..., rotary_dim:]
        query = torch.cat([q_rot * cos + rotate_half(q_rot) * sin, q_pass], dim=-1)
        key = torch.cat([k_rot * cos + rotate_half(k_rot) * sin, k_pass], dim=-1)

        # (batch, heads, seq_len, head_dim), with the key/value heads repeated for grouped-query attention
        query = query.transpose(1, 2)
        key = key.repeat_interleave(self.num_heads // self.num_kv_heads, dim=2).transpose(1, 2)
        value = value.repeat_interleave(self.num_heads // self.num_kv_heads, dim=2).transpose(1, 2)

        # Explicit attention matching the original model's default ("eager") implementation operation for operation.
        # A fused kernel changes the rounding, which is enough to flip some near-tied expert choices in the MoE
        # layers; at the text encoder's sequence lengths the explicit form costs next to nothing.
        attn_weights = torch.matmul(query / math.sqrt(self.head_dim), key.transpose(2, 3)) + attention_mask
        attn_weights = torch.softmax(attn_weights, dim=-1, dtype=torch.float32).to(query.dtype)
        hidden_states = torch.matmul(attn_weights, value).transpose(1, 2)
        return self.dense(hidden_states.flatten(2, 3))


class MingImageTextDecoderLayer(nn.Module):
    def __init__(self, config, layer_idx: int):
        super().__init__()
        self.attention = MingImageTextAttention(config)
        self.is_moe = layer_idx >= config.first_k_dense_replace
        self.mlp = (
            MingImageSparseMoeBlock(config)
            if self.is_moe
            else MingImageMLP(config.hidden_size, config.intermediate_size)
        )
        self.input_layernorm = MingImageRMSNorm(config.hidden_size, eps=config.rms_norm_eps)
        self.post_attention_layernorm = MingImageRMSNorm(config.hidden_size, eps=config.rms_norm_eps)

    def forward(self, hidden_states, attention_mask, position_embeddings, image_mask) -> torch.Tensor:
        hidden_states = hidden_states + self.attention(
            self.input_layernorm(hidden_states), attention_mask, position_embeddings
        )
        residual = hidden_states
        hidden_states = self.post_attention_layernorm(hidden_states)
        hidden_states = self.mlp(hidden_states, image_mask) if self.is_moe else self.mlp(hidden_states)
        return residual + hidden_states


class MingImageTextEncoder(ModelMixin, ConfigMixin):
    r"""
    The Ming-Image text encoder: the language model of the Ming multimodal LLM (a BailingMoeV2 mixture-of-experts
    decoder with a separate router for image positions and 3D "video_rope" positions), together with the learnable
    image-generation query tokens it is prompted with.

    The query tokens are placed in `input_ids` as a block of `image_patch_token` ids between `image_start_token` and
    `image_end_token`. They are treated as a `1 x num_query_tokens` image: they are routed by the image router and get
    image positions.

    Args:
        vocab_size (`int`): Vocabulary size.
        hidden_size (`int`): Hidden size.
        intermediate_size (`int`): FFN width of the dense layers.
        moe_intermediate_size (`int`): FFN width of each expert.
        num_hidden_layers (`int`): Number of decoder layers.
        num_attention_heads (`int`): Number of query heads.
        num_key_value_heads (`int`): Number of key/value heads.
        head_dim (`int`): Head dimension.
        rms_norm_eps (`float`): RMSNorm epsilon.
        rope_theta (`float`): RoPE base.
        partial_rotary_factor (`float`): Fraction of each head's channels that are rotated.
        mrope_section (`list[int]`): Rotary channels of the frame, height and width axes.
        rope_time_scale (`float`): Frame-axis position step of image tokens.
        spatial_merge_size (`int`): Merge factor between an image grid and its tokens.
        num_experts (`int`): Number of routed experts.
        num_experts_per_tok (`int`): Experts per token.
        n_group (`int`): Number of expert groups for group-limited routing.
        topk_group (`int`): Number of groups a token may route to.
        routed_scaling_factor (`float`): Scale applied to the routing weights.
        num_shared_experts (`int`): Width multiplier of the shared expert.
        first_k_dense_replace (`int`): Number of leading layers with a dense FFN instead of experts.
        pad_token_id (`int`): Padding token id.
        image_patch_token (`int`): Placeholder id of image (and query) tokens.
        image_start_token (`int`): Id that opens an image block.
        image_end_token (`int`): Id that closes an image block.
        num_query_tokens (`int`): Number of image-generation query tokens.
    """

    _no_split_modules = ["MingImageTextDecoderLayer"]

    @register_to_config
    def __init__(
        self,
        vocab_size: int = 157184,
        hidden_size: int = 2048,
        intermediate_size: int = 5120,
        moe_intermediate_size: int = 512,
        num_hidden_layers: int = 20,
        num_attention_heads: int = 16,
        num_key_value_heads: int = 4,
        head_dim: int = 128,
        rms_norm_eps: float = 1e-6,
        rope_theta: float = 600000.0,
        partial_rotary_factor: float = 0.5,
        mrope_section: list[int] = [8, 12, 12],
        rope_time_scale: float = 2.0,
        spatial_merge_size: int = 2,
        num_experts: int = 256,
        num_experts_per_tok: int = 8,
        n_group: int = 8,
        topk_group: int = 4,
        routed_scaling_factor: float = 2.5,
        num_shared_experts: int = 1,
        first_k_dense_replace: int = 1,
        pad_token_id: int = 156892,
        image_patch_token: int = 157157,
        image_start_token: int = 157158,
        image_end_token: int = 157159,
        num_query_tokens: int = 256,
    ):
        super().__init__()
        self.word_embeddings = nn.Embedding(vocab_size, hidden_size, pad_token_id)
        self.query_tokens = nn.Parameter(torch.zeros(num_query_tokens, hidden_size))
        self.layers = nn.ModuleList(
            [MingImageTextDecoderLayer(self.config, layer_idx) for layer_idx in range(num_hidden_layers)]
        )
        self.norm = MingImageRMSNorm(hidden_size, eps=rms_norm_eps)
        self.rotary_emb = MingImageRotaryEmbedding(int(head_dim * partial_rotary_factor), rope_theta)

    def get_position_ids(self, input_ids: torch.Tensor, attention_mask: torch.Tensor) -> torch.Tensor:
        """3D (frame, height, width) positions: text tokens advance all three axes together; each query-token block
        is laid out as a `1 x num_query_tokens` image grid, centred and offset by the text before it."""
        position_ids = torch.ones(3, *input_ids.shape, dtype=input_ids.dtype, device=input_ids.device)
        query_grid_w = self.config.num_query_tokens
        for i in range(input_ids.shape[0]):
            tokens = input_ids[i][attention_mask[i] == 1].tolist()
            num_images = sum(
                1
                for idx, token in enumerate(tokens[:-1])
                if token == self.config.image_start_token and tokens[idx + 1] == self.config.image_patch_token
            )
            positions = []
            start = 0
            for _ in range(num_images):
                end = tokens.index(self.config.image_patch_token, start)
                text_len = end - start
                st_idx = positions[-1][0].max() + 1 if positions else 0
                positions.append(torch.arange(text_len).view(1, -1).expand(3, -1) + st_idx)

                t_index = torch.zeros(query_grid_w, dtype=torch.long) * self.config.rope_time_scale + text_len + st_idx
                h_index = torch.zeros(query_grid_w, dtype=torch.long) + t_index
                w_index = torch.arange(query_grid_w) - (query_grid_w - 1) // 2 + t_index
                positions.append(torch.stack([t_index, h_index, w_index]))
                start = end + query_grid_w

            if start < len(tokens):
                st_idx = positions[-1][0].max() + 1 if positions else 0
                positions.append(torch.arange(len(tokens) - start).view(1, -1).expand(3, -1) + st_idx)

            position_ids[..., i, attention_mask[i] == 1] = torch.cat(positions, dim=1).to(position_ids)
        return position_ids

    def forward(self, input_ids: torch.Tensor, attention_mask: torch.Tensor) -> tuple[torch.Tensor, ...]:
        r"""
        Args:
            input_ids (`torch.Tensor`):
                Token ids of shape `(batch_size, seq_len)`, with each item's query-token block already inserted.
            attention_mask (`torch.Tensor`):
                Padding mask of shape `(batch_size, seq_len)`, 1 for real tokens.

        Returns:
            `tuple[torch.Tensor, ...]`:
                `num_hidden_layers + 1` hidden states of shape `(batch_size, seq_len, hidden_size)`: the input
                embeddings, the outputs of layers 1 to `num_hidden_layers - 1`, and the final normalized output.
        """
        image_mask = input_ids == self.config.image_patch_token
        inputs_embeds = self.word_embeddings(input_ids.clip(0, self.config.vocab_size - 1))
        query_tokens = self.query_tokens.to(inputs_embeds.dtype).repeat(input_ids.shape[0], 1)
        inputs_embeds = inputs_embeds.masked_scatter(image_mask.unsqueeze(-1), query_tokens)

        position_ids = self.get_position_ids(input_ids, attention_mask)
        position_embeddings = self.rotary_emb(inputs_embeds, position_ids)

        # Additive causal mask that also excludes padded keys, (batch, 1, seq_len, seq_len)
        seq_len = input_ids.shape[1]
        attend = torch.ones(seq_len, seq_len, dtype=torch.bool, device=input_ids.device).tril()[None, None]
        attend = attend & attention_mask.bool()[:, None, None, :]
        mask = torch.zeros(attend.shape, dtype=inputs_embeds.dtype, device=input_ids.device)
        mask = mask.masked_fill(~attend, torch.finfo(inputs_embeds.dtype).min)

        hidden_states = inputs_embeds
        all_hidden_states = ()
        for layer in self.layers:
            all_hidden_states += (hidden_states,)
            hidden_states = layer(hidden_states, mask, position_embeddings, image_mask)
        all_hidden_states += (self.norm(hidden_states),)
        return all_hidden_states