File size: 13,575 Bytes
ae9d09b
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
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
# Copyright (c) Microsoft Corporation. Licensed under the MIT license.
# Copyright Lightning AI. Licensed under the Apache License 2.0.
# Modified for LatentLoop: MoE architectures, loop visits, and latent sequence updates.
"""MoE Base, MoE Loop, and LatentLoop with separate caches for each layer visit."""
import math
from functools import lru_cache
from typing import Optional, Tuple

import torch
from torch import Tensor, nn
from torch.nn import functional as F

from model.config import Config
from model.latent import ScaledDecoder, latent_correction
from model.fused_rotary_embedding import apply_rotary_emb_func
from model.moe import SparseMoE, load_balancing_loss

RoPECache = Tuple[Tensor, Tensor]
KVCache = Tuple[Tensor, Tensor]


@lru_cache(maxsize=1)
def flash_attention():
    try:
        from flash_attn import flash_attn_func
        return flash_attn_func
    except (ImportError, OSError):
        return None


class GPT(nn.Module):
    def __init__(self, config: Config):
        super().__init__()
        self.config = config
        self.lm_head = nn.Linear(config.n_embd, config.padded_vocab_size, bias=False)
        self.transformer = nn.ModuleDict(dict(
            wte=nn.Embedding(config.padded_vocab_size, config.n_embd),
            h=nn.ModuleList(Block(config) for _ in range(config.n_layer)),
            ln_f=config.norm_class(config.n_embd, eps=config.norm_eps),
        ))
        self.layer_order = config.layer_order
        self.coarse_execution_span = config.coarse_execution_span
        self.register_buffer("rope_cos", None, persistent=False)
        self.register_buffer("rope_sin", None, persistent=False)
        self.kv_caches = []
        self._cache_length = self._cache_capacity = 0
        self._coarse_pending = self._coarse_update = None
        self.apply(self._init_weights)
        self.coarse_decoder = ScaledDecoder(config.n_embd) if config.architecture == "latentloop" else None
        if config.architecture == "latentloop":
            # One physical owner for the shared MoE and both attention blocks.
            for i in range(config.loop_start, config.loop_end):
                block = self.transformer.h[i]
                block.norm_1_repeat = config.norm_class(config.n_embd, eps=config.norm_eps)
                block.norm_2_repeat = config.norm_class(config.n_embd, eps=config.norm_eps)
                block.attn_repeat = CausalSelfAttention(config)
                block.attn_repeat.apply(self._init_weights)

    def _init_weights(self, module):
        if isinstance(module, (nn.Linear, nn.Embedding)):
            nn.init.normal_(module.weight, std=self.config.initializer_range)
            if getattr(module, "bias", None) is not None:
                nn.init.zeros_(module.bias)

    def reset_cache(self):
        self.kv_caches = []
        self._cache_length = self._cache_capacity = 0
        self._coarse_pending = self._coarse_update = None

    def _apply(self, fn, recurse=True):
        self.reset_cache()
        self.rope_cos = self.rope_sin = None
        return super()._apply(fn, recurse=recurse)
    def forward(self, idx: Tensor, max_seq_length: Optional[int] = None,
                input_pos: Optional[Tensor] = None, *, return_aux_loss: bool = False):
        if idx.ndim != 2 or idx.size(1) == 0:
            raise ValueError("idx must have shape [batch, nonempty sequence]")
        batch, length = idx.shape
        capacity = self.config.block_size if max_seq_length is None else max_seq_length
        if not 0 < capacity <= self.config.block_size or length > capacity:
            raise ValueError("sequence/cache length must be within config.block_size")
        mask = None
        if input_pos is not None:
            if torch.is_grad_enabled():
                raise RuntimeError("KV caching is inference-only; use torch.no_grad() or inference_mode()")
            if input_pos.ndim != 1 or input_pos.numel() != length or input_pos.dtype != torch.long:
                raise ValueError("input_pos must be a 1-D int64 tensor with one position per token")
            input_pos = input_pos.to(idx.device)
            expected = torch.arange(self._cache_length, self._cache_length + length, device=idx.device)
            if not torch.equal(input_pos, expected) or self._cache_length + length > capacity:
                raise ValueError("KV cache positions must be contiguous from zero and fit the cache; reset_cache() first")
            if not self.kv_caches:
                # Shared weights do not imply shared activations: each visit needs its own KV.
                self.kv_caches = [None] * len(self.layer_order)
                self._cache_capacity = capacity
            elif capacity != self._cache_capacity:
                raise ValueError("cache capacity changed; call reset_cache() first")
            mask = torch.arange(capacity, device=idx.device)[None, :] <= input_pos[:, None]
        if self.config.rotary_dim:
            rope_dtype = (torch.get_autocast_dtype(idx.device.type) if torch.is_autocast_enabled(idx.device.type)
                          else self.transformer.wte.weight.dtype)
            if (self.rope_cos is None or self.rope_cos.device != idx.device
                    or self.rope_cos.size(0) < capacity or self.rope_cos.dtype != rope_dtype):
                self.rope_cos, self.rope_sin = build_rope_cache(
                    capacity, self.config.rotary_dim, rope_dtype, idx.device,
                    self.config.rope_base, 1,
                )
            rope = (self.rope_cos[:length], self.rope_sin[:length]) if input_pos is None else (
                self.rope_cos.index_select(0, input_pos), self.rope_sin.index_select(0, input_pos)
            )
        else:
            rope = None
        x = self.transformer.wte(idx)
        router_stats = []
        visits = [0] * self.config.n_layer
        for i, layer_index in enumerate(self.layer_order):
            first_visit = visits[layer_index] == 0
            visits[layer_index] += 1
            if self.coarse_execution_span is not None and (
                    self.coarse_execution_span[0] <= i < self.coarse_execution_span[1]):
                if i == self.coarse_execution_span[0]:
                    x, stats = self._coarse_pass(x, input_pos, capacity, return_aux_loss)
                    router_stats.extend(stats)
                continue
            block = self.transformer.h[layer_index]
            cache = None if input_pos is None else self.kv_caches[i]
            if return_aux_loss:
                x, cache, stats = block(x, rope, mask, input_pos, cache, capacity,
                                       return_router_stats=True, first_visit=first_visit)
                router_stats.append(stats)  # Average aux over all executions, including repeat visits.
            else:
                x, cache = block(x, rope, mask, input_pos, cache, capacity, first_visit=first_visit)
            if input_pos is not None:
                self.kv_caches[i] = cache
        if input_pos is not None:
            self._cache_length += length
        logits = self.lm_head(self.transformer.ln_f(x))
        if return_aux_loss:
            aux = load_balancing_loss(router_stats) if router_stats else logits.new_zeros((), dtype=torch.float32)
            return logits, aux
        return logits

    def _coarse_pass(self, x, input_pos, capacity, return_aux_loss):
        cached = input_pos is not None
        coarse_capacity = capacity // 2
        stats = []

        def run_core(z, positions):
            ends = positions * 2 + 1
            rope = (self.rope_cos.index_select(0, ends), self.rope_sin.index_select(0, ends))
            mask = (torch.arange(coarse_capacity, device=x.device)[None, :] <= positions[:, None]
                    if cached else None)
            for execution in range(*self.coarse_execution_span):
                block = self.transformer.h[self.layer_order[execution]]
                cache = self.kv_caches[execution] if cached else None
                args = (z, rope, mask, positions if cached else None, cache, coarse_capacity)
                if return_aux_loss:
                    z, cache, entry = block(*args, return_router_stats=True, first_visit=True)
                    stats.append(entry)
                else:
                    z, cache = block(*args, first_visit=True)
                if cached:
                    self.kv_caches[execution] = cache
            return z

        result, pending, update = latent_correction(
            x, run_core, self.coarse_decoder,
            offset=self._cache_length if cached else 0,
            pending=self._coarse_pending if cached else None,
            last_update=self._coarse_update if cached else None,
        )
        if cached:
            self._coarse_pending, self._coarse_update = pending, update
        return result, stats


class Block(nn.Module):
    def __init__(self, config):
        super().__init__()
        self.norm_1 = config.norm_class(config.n_embd, eps=config.norm_eps)
        self.attn = CausalSelfAttention(config)
        self.norm_2 = config.norm_class(config.n_embd, eps=config.norm_eps)
        self.mlp = SparseMoE(config)
        self.attn_repeat = self.norm_1_repeat = self.norm_2_repeat = None

    def forward(self, x, rope, mask=None, input_pos=None, kv_cache=None, max_seq_length=None,
                *, return_router_stats=False, first_visit=False):
        repeated = self.attn_repeat is not None and not first_visit
        attention = self.attn_repeat if repeated else self.attn
        norm_1 = self.norm_1_repeat if repeated else self.norm_1
        norm_2 = self.norm_2_repeat if repeated else self.norm_2
        h, kv_cache = attention(norm_1(x), rope, mask, input_pos, kv_cache, max_seq_length)
        x = torch.add(x, h, alpha=1.0)
        if return_router_stats:
            h, stats = self.mlp(norm_2(x), return_router_stats=True)
            return torch.add(x, h, alpha=1.0), kv_cache, stats
        return torch.add(x, self.mlp(norm_2(x)), alpha=1.0), kv_cache


class CausalSelfAttention(nn.Module):
    def __init__(self, config: Config):
        super().__init__()
        self.config = config
        self.attn = nn.Linear(config.n_embd, (config.n_head + 2 * config.n_query_groups) * config.head_size,
                              bias=False)
        self.proj = nn.Linear(config.n_head * config.head_size, config.n_embd, bias=False)
        self.q_norm = config.norm_class(config.head_size, eps=config.norm_eps)
        self.k_norm = config.norm_class(config.head_size, eps=config.norm_eps)

    def forward(self, x, rope, mask=None, input_pos=None, kv_cache=None, max_seq_length=None):
        batch, length, width = x.shape
        groups, heads, dim = self.config.n_query_groups, self.config.n_head, self.config.head_size
        projected = self.attn(x)
        # Checkpoint layout: [KV group, queries-per-group + key + value, head_dim].
        qkv = projected.view(batch, length, groups, heads // groups + 2, dim)
        q, k, v = qkv.split((heads // groups, 1, 1), dim=-2)
        q = q.reshape(batch, length, heads, dim)
        k, v = k.reshape(batch, length, groups, dim), v.reshape(batch, length, groups, dim)
        q, k = self.q_norm(q), self.k_norm(k)
        if rope is not None:
            cos, sin = (value.to(q.dtype) for value in rope)
            q = apply_rotary_emb_func(q, cos, sin, inplace=False)
            k = apply_rotary_emb_func(k, cos, sin, inplace=False)
        if input_pos is not None:
            shape = (batch, max_seq_length, groups, dim)
            if kv_cache is None:
                kv_cache = (k.new_zeros(shape), v.new_zeros(shape))
            elif kv_cache[0].shape != shape or kv_cache[0].device != k.device or kv_cache[0].dtype != k.dtype:
                raise ValueError("cache batch/device/dtype changed; call reset_cache() first")
            kv_cache[0].index_copy_(1, input_pos, k)
            kv_cache[1].index_copy_(1, input_pos, v)
            k, v = kv_cache
        y = self.scaled_dot_product_attention(q, k, v, mask, input_pos)
        y = y.reshape(batch, length, heads * dim)
        return self.proj(y), kv_cache

    def scaled_dot_product_attention(self, q, k, v, mask=None, input_pos=None):
        backend = self.config.attention_backend
        flash = flash_attention() if backend != "sdpa" and q.is_cuda else None
        eligible = q.is_cuda and q.dtype in (torch.float16, torch.bfloat16)
        if backend == "flash" and (flash is None or not eligible):
            raise RuntimeError("flash backend requires working flash-attn and CUDA fp16/bf16")
        # Cached decoding uses an explicit absolute-position mask; training takes the fast path.
        if flash is not None and eligible and mask is None:
            return flash(q, k, v, causal=True, softmax_scale=1 / math.sqrt(q.size(-1)))
        q, k, v = (value.transpose(1, 2) for value in (q, k, v))
        return F.scaled_dot_product_attention(
            q, k, v, attn_mask=mask, is_causal=mask is None, enable_gqa=q.size(1) != k.size(1),
        ).transpose(1, 2)


def build_rope_cache(seq_len: int, n_elem: int, dtype: torch.dtype, device: torch.device,
                     base: float = 10000, condense_ratio: float = 1) -> RoPECache:
    theta = 1.0 / (base ** (torch.arange(0, n_elem, 2, device=device, dtype=torch.float32) / n_elem))
    positions = torch.arange(seq_len, device=device, dtype=torch.float32) / condense_ratio
    angles = torch.outer(positions, theta)
    return angles.cos().to(dtype), angles.sin().to(dtype)