File size: 12,327 Bytes
ad7c73d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
3817cd0
ad7c73d
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
"""Portable ARLoop inference: PyTorch SDPA and FP32 attention-residual routing.

Adapted from ARLoop's model and Parcae building blocks; see LICENSE-MIT.
No training repository, FLA, Triton, or flash-attn installation is needed.
"""
import torch
from torch import nn
from torch.nn import functional as F
from transformers import PreTrainedModel, GenerationMixin
from transformers.cache_utils import DynamicCache
from transformers.modeling_outputs import CausalLMOutputWithPast
from .configuration_arloop import ARLoopConfig


class RMSNorm(nn.Module):
    def __init__(self, size, eps):
        super().__init__()
        self.weight = nn.Parameter(torch.ones(size))
        self.eps = eps

    def forward(self, x):
        # Preserve the native final norm's rounding and weight promotion.
        y = x.float() * torch.rsqrt(x.float().square().mean(-1, keepdim=True) + self.eps)
        return y.to(x.dtype) * self.weight


class Attention(nn.Module):
    def __init__(self, config, layer):
        super().__init__()
        self.config = config
        dim = config.hidden_size
        kv_dim = config.num_key_value_heads * config.head_dim
        self.c_q = nn.Linear(dim, dim, bias=False)
        self.c_k = nn.Linear(dim, kv_dim, bias=False)
        self.c_v = nn.Linear(dim, kv_dim, bias=False)
        self.c_proj = nn.Linear(dim, dim, bias=False)
        self.ve_gate = (nn.Linear(32, config.num_key_value_heads, bias=False)
                        if layer % 2 == (config.physical_layers - 1) % 2 else None)

    def forward(self, x, positions, mask, value_embedding, cache, effective_layer):
        c = self.config
        batch, length, _ = x.shape
        q = self.c_q(x).view(batch, length, c.num_attention_heads, c.head_dim)
        k = self.c_k(x).view(batch, length, c.num_key_value_heads, c.head_dim)
        v = self.c_v(x).view_as(k)
        if self.ve_gate is not None:
            gate = 2 * torch.sigmoid(self.ve_gate(x[..., :32]))
            v = v + gate.unsqueeze(-1) * value_embedding.view_as(v).to(v.dtype)
        # Adjacent-pair rotary embedding, unlike Llama's split-half convention.
        freq = 1.0 / (c.rope_theta ** (torch.arange(0, c.head_dim, 2,
                    device=x.device, dtype=torch.float32) / c.head_dim))
        angles = positions.float().unsqueeze(-1) / c.rope_condense_ratio * freq
        cos, sin = angles.cos().unsqueeze(2), angles.sin().unsqueeze(2)
        def rotate(tensor):
            pairs = tensor.float().unflatten(-1, (-1, 2))
            return torch.stack((pairs[..., 0] * cos - pairs[..., 1] * sin,
                                pairs[..., 1] * cos + pairs[..., 0] * sin), -1).flatten(-2).to(tensor.dtype)
        q, k = rotate(q), rotate(k)
        if c.qk_norm:
            # Native Q/K normalization uses dtype-dependent default epsilon.
            q = F.rms_norm(q, (c.head_dim,))
            k = F.rms_norm(k, (c.head_dim,))
        q, k, v = (t.transpose(1, 2) for t in (q, k, v))
        if cache is not None:
            k, v = cache.update(k, v, effective_layer)
        causal = mask is None and q.shape[-2] == k.shape[-2]
        if mask is None and q.shape[-2] != k.shape[-2] and q.shape[-2] != 1:
            rows = torch.arange(k.shape[-2] - q.shape[-2], k.shape[-2], device=x.device)
            mask = torch.arange(k.shape[-2], device=x.device)[None, :] <= rows[:, None]
        y = F.scaled_dot_product_attention(q, k, v, attn_mask=mask,
                    is_causal=causal, enable_gqa=c.num_attention_heads != c.num_key_value_heads,
                    dropout_p=0.0)
        return self.c_proj(y.transpose(1, 2).contiguous().view(batch, length, c.hidden_size))


class MLP(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.fc = nn.Linear(config.hidden_size, 2 * config.intermediate_size, bias=False)
        self.proj = nn.Linear(config.intermediate_size, config.hidden_size, bias=False)

    def forward(self, x):
        gate, value = self.fc(x).chunk(2, dim=-1)
        return self.proj(F.silu(gate) * value)


class ARLoopBlock(nn.Module):
    def __init__(self, config, layer):
        super().__init__()
        self.attn_norm = RMSNorm(config.hidden_size, config.rms_norm_eps)
        self.attn = Attention(config, layer)
        self.mlp_norm = RMSNorm(config.hidden_size, config.rms_norm_eps)
        self.mlp = MLP(config)


class ARLoopForCausalLM(PreTrainedModel, GenerationMixin):
    config_class = ARLoopConfig
    base_model_prefix = ""
    main_input_name = "input_ids"
    _no_split_modules = ["ARLoopBlock"]
    _supports_cache_class = True

    def __init__(self, config):
        super().__init__(config)
        self.transformer = nn.ModuleDict({
            "wte": nn.Embedding(config.vocab_size, config.hidden_size),
            "h": nn.ModuleList(ARLoopBlock(config, i) for i in range(config.physical_layers)),
            "ln_f": RMSNorm(config.hidden_size, config.rms_norm_eps),
        })
        self.shared_value_embedding = nn.Embedding(config.vocab_size,
                                  config.num_key_value_heads * config.head_dim)
        self.attnres_queries = nn.Parameter(torch.zeros(2 * config.num_hidden_layers, config.hidden_size))
        self.attnres_norms = nn.ModuleList(RMSNorm(config.hidden_size, config.rms_norm_eps)
                                         for _ in range(2 * config.num_hidden_layers))
        self.final_attnres_query = nn.Parameter(torch.zeros(config.hidden_size))
        self.final_attnres_norm = RMSNorm(config.hidden_size, config.rms_norm_eps)
        self.post_init()

    def _init_weights(self, module):
        if isinstance(module, (nn.Linear, nn.Embedding)):
            nn.init.normal_(module.weight, std=0.02)
        elif isinstance(module, RMSNorm):
            nn.init.ones_(module.weight)

    def get_input_embeddings(self):
        return self.transformer.wte

    def set_input_embeddings(self, value):
        self.transformer.wte = value

    def _route(self, query, norm, blocks, partial, output_norm=None):
        values = blocks + [partial]
        if len(values) > self.config.sw_blocks + 1:
            values = [values[0], *values[-self.config.sw_blocks:]]
        # FLA accumulates routing scores, softmax, and the fused output norm in
        # FP32, rounding only the final result back to the residual dtype.
        with torch.autocast(device_type=partial.device.type, enabled=False):
            stacked = torch.stack(values, dim=-2).float()
            scores = (stacked * (query.float() * norm.weight.float())).sum(-1)
            scores = scores * torch.rsqrt(stacked.square().mean(-1) + norm.eps)
            weights = scores.softmax(dim=-1)
            y = torch.zeros_like(partial, dtype=torch.float32)
            for i, value in enumerate(values):
                y = y + weights[..., i, None] * value.float()
            if output_norm is not None:
                y = y * torch.rsqrt(y.square().mean(-1, keepdim=True) + norm.eps)
                y = y * output_norm.weight.float()
        return y.to(partial.dtype)

    def forward(self, input_ids=None, attention_mask=None, position_ids=None,
                past_key_values=None, inputs_embeds=None, labels=None, use_cache=None,
                return_dict=None, output_hidden_states=False, output_attentions=False,
                cache_position=None, logits_to_keep=0, **kwargs):
        if input_ids is None or inputs_embeds is not None:
            raise ValueError("ARLoop requires input_ids for token-dependent value embeddings")
        if output_attentions:
            raise ValueError("output_attentions is not implemented")
        use_cache = self.config.use_cache if use_cache is None else use_cache
        if labels is not None:
            use_cache = False
        if past_key_values is not None and not use_cache:
            raise ValueError("past_key_values requires use_cache=True")
        cache = past_key_values if use_cache else None
        if use_cache and cache is None:
            cache = DynamicCache()
        past_length = cache.get_seq_length() if cache is not None else 0
        batch, length = input_ids.shape
        total = past_length + length
        if total > self.config.max_position_embeddings:
            raise ValueError("Input and cache exceed max_position_embeddings")
        if position_ids is None:
            if attention_mask is None:
                position_ids = torch.arange(past_length, total, device=input_ids.device)[None, :]
            else:
                position_ids = (attention_mask.long().cumsum(-1) - 1).clamp_min(0)[:, -length:]
        key_indices = torch.arange(total, device=input_ids.device)
        query_indices = torch.arange(past_length, total, device=input_ids.device)
        mask = None
        if attention_mask is not None:
            if attention_mask.shape != (batch, total):
                raise ValueError("attention_mask must cover both cached and new tokens")
            if not bool(attention_mask.all()):
                mask = (key_indices[None, :] <= query_indices[:, None])[None, None, :, :]
                mask = mask & attention_mask[:, None, None, :].bool()
        # Use BF16 activations; parameters follow the requested loading dtype.
        with torch.autocast(device_type=input_ids.device.type, dtype=torch.bfloat16):
            embedded = (self.transformer.wte(input_ids) * self.config.embedding_scale).to(torch.bfloat16)
            ve = self.shared_value_embedding(input_ids)
            blocks, partial = [embedded], embedded
            states = [embedded] if output_hidden_states else None
            for loop in range(self.config.loop_count):
                for i, block in enumerate(self.transformer.h):
                    effective = loop * self.config.physical_layers + i
                    h = self._route(self.attnres_queries[2 * effective],
                                    self.attnres_norms[2 * effective], blocks, partial, block.attn_norm)
                    if effective > 0 and effective % (self.config.attnres_block_size // 2) == 0:
                        blocks.append(partial)
                        partial = None
                    a = block.attn(h, position_ids, mask, ve, cache, effective)
                    partial = a if partial is None else partial + a
                    h = self._route(self.attnres_queries[2 * effective + 1],
                                    self.attnres_norms[2 * effective + 1], blocks, partial, block.mlp_norm)
                    partial = partial + block.mlp(h)
                    if states is not None:
                        states.append(partial)
            h = self._route(self.final_attnres_query, self.final_attnres_norm, blocks, partial)
            # Native RMSNorm disables autocast around the final normalization.
            with torch.autocast(device_type=input_ids.device.type, enabled=False):
                h = self.transformer.ln_f(h)
            if logits_to_keep and labels is None:
                h = h[:, -logits_to_keep:, :]
            logits = F.linear(h, self.transformer.wte.weight).float()
            if self.config.logit_softcap is not None:
                cap = self.config.logit_softcap
                logits = cap * torch.tanh(logits / cap)
        loss = None
        if labels is not None:
            loss = F.cross_entropy(logits[:, :-1].contiguous().view(-1, logits.size(-1)),
                                   labels[:, 1:].contiguous().view(-1), ignore_index=-100)
        result = CausalLMOutputWithPast(loss=loss, logits=logits, past_key_values=cache,
                                       hidden_states=tuple(states) if states is not None else None)
        return result if return_dict is not False else result.to_tuple()

    def prepare_inputs_for_generation(self, input_ids, past_key_values=None,
                                      attention_mask=None, **kwargs):
        if past_key_values is not None:
            seen = past_key_values.get_seq_length()
            if seen:
                input_ids = input_ids[:, seen:] if input_ids.shape[1] > seen else input_ids[:, -1:]
        return dict(input_ids=input_ids.contiguous(), past_key_values=past_key_values,
                    attention_mask=attention_mask, use_cache=kwargs.get("use_cache", True),
                    logits_to_keep=1)