Rorical commited on
Commit
fd9507e
·
verified ·
1 Parent(s): e10cacc

Fix inference code: recursive.py

Browse files
Files changed (1) hide show
  1. recursive.py +336 -0
recursive.py ADDED
@@ -0,0 +1,336 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """Recursive (looped-depth) decoder-only transformer.
2
+
3
+ Three sections — entry / body / exit — where the body is a small stack of
4
+ shared weights applied ``num_loops`` times per forward. The loop update is
5
+ ``h_{t+1} = A * h_t + B * e + R(h_t + e)`` with per-channel injection
6
+ gates A, B initialised to zero (so the loop starts as a weight-shared
7
+ transformer stack on h+e). Optional cross-loop expert diversity for shared
8
+ MoE routers via ``moe_diversity_factor``.
9
+ """
10
+
11
+ from __future__ import annotations
12
+
13
+ from dataclasses import dataclass
14
+ from typing import List, Optional, Tuple, Dict, Any
15
+
16
+ import torch
17
+ import torch.nn as nn
18
+ import torch.nn.functional as F
19
+
20
+ from .lm_loss import (
21
+ lm_cross_entropy_from_logits,
22
+ token_superposition_attention_mask,
23
+ token_superposition_embeddings,
24
+ )
25
+ from .baseline import (
26
+ BaselineConfig,
27
+ RMSNorm,
28
+ TransformerBlock,
29
+ MoELayer,
30
+ combine_lm_and_aux_loss,
31
+ init_moe_router_weights,
32
+ count_parameters,
33
+ model_summary,
34
+ )
35
+
36
+
37
+ @dataclass
38
+ class RecursiveConfig(BaselineConfig):
39
+ # Auto-derived from entry + body + exit in __post_init__.
40
+ num_layers: int = 0
41
+
42
+ num_entry_layers: int = 2
43
+ num_body_layers: int = 4
44
+ num_exit_layers: int = 2
45
+ num_loops: int = 4
46
+
47
+ # Std of the random init for the per-channel A gate. 0 (default)
48
+ # leaves the loop's residual mixing inert at step 0; small positive
49
+ # values (e.g. 0.02) break that symmetry. B always starts at zero.
50
+ body_gate_init_std: float = 0.0
51
+
52
+ def __post_init__(self):
53
+ super().__post_init__()
54
+ if self.num_body_layers <= 0 or self.num_loops <= 0:
55
+ raise ValueError(
56
+ "num_body_layers and num_loops must both be > 0; set "
57
+ "num_entry_layers / num_exit_layers to 0 if you want a "
58
+ "purely-body model."
59
+ )
60
+ if self.body_gate_init_std < 0:
61
+ raise ValueError("body_gate_init_std must be >= 0")
62
+ self.num_layers = (
63
+ self.num_entry_layers
64
+ + self.num_body_layers
65
+ + self.num_exit_layers
66
+ )
67
+
68
+
69
+ class RecursiveBlock(nn.Module):
70
+ """One iteration of the body loop: ``h_{t+1} = A*h + B*e + R(h+e)``.
71
+
72
+ The body's transformer blocks are reused ``num_loops`` times, so MoE
73
+ layers carry per-loop bias rows and the cross-loop diversity term.
74
+ """
75
+
76
+ def __init__(self, config: RecursiveConfig):
77
+ super().__init__()
78
+ self.blocks = nn.ModuleList([
79
+ TransformerBlock(config, num_loops=config.num_loops)
80
+ for _ in range(config.num_body_layers)
81
+ ])
82
+ if config.body_gate_init_std > 0:
83
+ self.A = nn.Parameter(
84
+ torch.randn(config.d_model) * config.body_gate_init_std
85
+ )
86
+ else:
87
+ self.A = nn.Parameter(torch.zeros(config.d_model))
88
+ self.B = nn.Parameter(torch.zeros(config.d_model))
89
+
90
+ def forward(
91
+ self,
92
+ h: torch.Tensor,
93
+ e: torch.Tensor,
94
+ attention_mask: Optional[torch.Tensor] = None,
95
+ is_causal: bool = True,
96
+ loop_idx: int = 0,
97
+ ) -> Tuple[torch.Tensor, torch.Tensor, List[Optional[torch.Tensor]]]:
98
+ x = h + e
99
+ aux_loss = torch.zeros((), device=x.device, dtype=x.dtype)
100
+ topk_list: List[Optional[torch.Tensor]] = []
101
+ for block in self.blocks:
102
+ x, block_aux, block_topk = block(
103
+ x,
104
+ attention_mask=attention_mask,
105
+ is_causal=is_causal,
106
+ loop_idx=loop_idx,
107
+ )
108
+ aux_loss = aux_loss + block_aux
109
+ topk_list.append(block_topk)
110
+ h_next = self.A * h + self.B * e + x
111
+ return h_next, aux_loss, topk_list
112
+
113
+
114
+ class RecursiveTransformer(nn.Module):
115
+ def __init__(self, config: RecursiveConfig):
116
+ super().__init__()
117
+ self.config = config
118
+
119
+ self.token_emb = nn.Embedding(config.vocab_size, config.d_model)
120
+
121
+ self.entry = nn.ModuleList([
122
+ TransformerBlock(config) for _ in range(config.num_entry_layers)
123
+ ])
124
+ self.body = RecursiveBlock(config)
125
+ self.exit = nn.ModuleList([
126
+ TransformerBlock(config) for _ in range(config.num_exit_layers)
127
+ ])
128
+
129
+ self.final_norm = RMSNorm(config.d_model, eps=config.norm_eps)
130
+ self.lm_head = nn.Linear(config.d_model, config.vocab_size, bias=False)
131
+ self.lm_head.weight = self.token_emb.weight
132
+
133
+ self._init_weights()
134
+
135
+ def _init_weights(self):
136
+ # ``RecursiveBlock.A`` and ``.B`` stay at their zero init — they are
137
+ # nn.Parameter (not Linear/Embedding) and so are skipped by this pass.
138
+ for module in self.modules():
139
+ if isinstance(module, nn.Linear):
140
+ torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
141
+ if module.bias is not None:
142
+ torch.nn.init.zeros_(module.bias)
143
+ elif isinstance(module, nn.Embedding):
144
+ torch.nn.init.normal_(module.weight, mean=0.0, std=0.02)
145
+ init_moe_router_weights(self, self.config.router_init_std)
146
+
147
+ def forward(
148
+ self,
149
+ input_ids: torch.Tensor,
150
+ attention_mask: Optional[torch.Tensor] = None,
151
+ labels: Optional[torch.Tensor] = None,
152
+ is_causal: bool = True,
153
+ token_superposition_bag_size: int = 1,
154
+ ) -> Dict[str, Any]:
155
+ x = token_superposition_embeddings(
156
+ self.token_emb, input_ids, token_superposition_bag_size,
157
+ )
158
+ attention_mask = token_superposition_attention_mask(
159
+ attention_mask, token_superposition_bag_size,
160
+ )
161
+
162
+ aux_loss = torch.zeros((), device=input_ids.device, dtype=x.dtype)
163
+ topk_indices_list: List[Optional[torch.Tensor]] = []
164
+
165
+ for layer in self.entry:
166
+ x, layer_aux, layer_topk = layer(
167
+ x, attention_mask=attention_mask, is_causal=is_causal
168
+ )
169
+ aux_loss = aux_loss + layer_aux
170
+ topk_indices_list.append(layer_topk)
171
+ e = x
172
+
173
+ h = torch.zeros_like(e)
174
+ for loop_idx in range(self.config.num_loops):
175
+ h, block_aux, block_topks = self.body(
176
+ h,
177
+ e,
178
+ attention_mask=attention_mask,
179
+ is_causal=is_causal,
180
+ loop_idx=loop_idx,
181
+ )
182
+ aux_loss = aux_loss + block_aux
183
+ topk_indices_list.extend(block_topks)
184
+ x = h
185
+
186
+ for layer in self.exit:
187
+ x, layer_aux, layer_topk = layer(
188
+ x, attention_mask=attention_mask, is_causal=is_causal
189
+ )
190
+ aux_loss = aux_loss + layer_aux
191
+ topk_indices_list.append(layer_topk)
192
+
193
+ x = self.final_norm(x)
194
+ logits = self.lm_head(x)
195
+
196
+ lm_loss: Optional[torch.Tensor] = None
197
+ if labels is not None:
198
+ lm_loss = lm_cross_entropy_from_logits(
199
+ logits,
200
+ labels,
201
+ token_superposition_bag_size=token_superposition_bag_size,
202
+ ignore_index=-100,
203
+ )
204
+ loss = combine_lm_and_aux_loss(
205
+ lm_loss,
206
+ aux_loss if self.config.use_moe else None,
207
+ self.training,
208
+ )
209
+
210
+ return {
211
+ "logits": logits,
212
+ "loss": loss,
213
+ "lm_loss": lm_loss,
214
+ "aux_loss": aux_loss if self.config.use_moe else None,
215
+ "topk_indices": topk_indices_list if self.config.use_moe else None,
216
+ }
217
+
218
+ def update_router_biases(self, topk_indices_list: List[Optional[torch.Tensor]]) -> None:
219
+ """Apply DeepSeek-style bias updates. Index layout:
220
+
221
+ [entry_0..E-1,
222
+ loop_0.b_0..B-1, loop_1.b_0..B-1, ..., loop_{L-1}.b_0..B-1,
223
+ exit_0..X-1]
224
+
225
+ Each body block is updated once per parameter set with all its
226
+ loop iterations grouped, so the cross-loop diversity term sees
227
+ them together.
228
+ """
229
+ if not self.config.use_moe:
230
+ return
231
+
232
+ n_entry = self.config.num_entry_layers
233
+ n_body = self.config.num_body_layers
234
+ n_loops = self.config.num_loops
235
+
236
+ for i, layer in enumerate(self.entry):
237
+ topk = topk_indices_list[i]
238
+ if topk is not None and isinstance(layer.ffn, MoELayer):
239
+ layer.ffn.update_bias(topk, loop_idx=0)
240
+
241
+ body_offset = n_entry
242
+ for r, block in enumerate(self.body.blocks):
243
+ if not isinstance(block.ffn, MoELayer):
244
+ continue
245
+ topk_per_loop: List[torch.Tensor] = []
246
+ valid = True
247
+ for l in range(n_loops):
248
+ idx = body_offset + l * n_body + r
249
+ topk = topk_indices_list[idx]
250
+ if topk is None:
251
+ valid = False
252
+ break
253
+ topk_per_loop.append(topk)
254
+ if valid:
255
+ block.ffn.update_bias_per_loop(topk_per_loop)
256
+
257
+ exit_offset = n_entry + n_loops * n_body
258
+ for i, layer in enumerate(self.exit):
259
+ topk = topk_indices_list[exit_offset + i]
260
+ if topk is not None and isinstance(layer.ffn, MoELayer):
261
+ layer.ffn.update_bias(topk, loop_idx=0)
262
+
263
+ @torch.no_grad()
264
+ def get_balance_stats(self) -> Dict[str, float]:
265
+ """One entry per parameter set — body sub-blocks appear once each
266
+ (not ``num_loops`` times)."""
267
+ if not self.config.use_moe:
268
+ return {}
269
+
270
+ stats: Dict[str, float] = {}
271
+
272
+ def _record(name: str, ffn: nn.Module) -> None:
273
+ if hasattr(ffn, "bias"):
274
+ bias = ffn.bias
275
+ stats[f"{name}_bias_mean"] = bias.abs().mean().item()
276
+ stats[f"{name}_bias_max"] = bias.abs().max().item()
277
+
278
+ for idx, layer in enumerate(self.entry):
279
+ _record(f"entry{idx}", layer.ffn)
280
+ for idx, block in enumerate(self.body.blocks):
281
+ _record(f"body{idx}", block.ffn)
282
+ for idx, layer in enumerate(self.exit):
283
+ _record(f"exit{idx}", layer.ffn)
284
+ return stats
285
+
286
+ @torch.no_grad()
287
+ def generate(
288
+ self,
289
+ input_ids: torch.Tensor,
290
+ max_new_tokens: int = 100,
291
+ temperature: float = 1.0,
292
+ top_k: Optional[int] = None,
293
+ attention_mask: Optional[torch.Tensor] = None,
294
+ eos_token_id: Optional[int] = None,
295
+ ) -> torch.Tensor:
296
+ self.train(False)
297
+ batch_size = input_ids.size(0)
298
+
299
+ for _ in range(max_new_tokens):
300
+ outputs = self.forward(
301
+ input_ids, attention_mask=attention_mask, is_causal=True,
302
+ )
303
+ logits = outputs["logits"][:, -1, :] / temperature
304
+
305
+ if top_k is not None:
306
+ v, _ = torch.topk(logits, min(top_k, logits.size(-1)))
307
+ logits[logits < v[:, [-1]]] = -float("Inf")
308
+
309
+ probs = F.softmax(logits, dim=-1)
310
+ next_token = torch.multinomial(probs, num_samples=1)
311
+
312
+ input_ids = torch.cat([input_ids, next_token], dim=-1)
313
+
314
+ if attention_mask is not None:
315
+ attention_mask = torch.cat([
316
+ attention_mask,
317
+ torch.ones(
318
+ (batch_size, 1),
319
+ device=attention_mask.device,
320
+ dtype=attention_mask.dtype,
321
+ ),
322
+ ], dim=-1)
323
+
324
+ if eos_token_id is not None and (next_token == eos_token_id).all():
325
+ break
326
+
327
+ return input_ids
328
+
329
+
330
+ __all__ = [
331
+ "RecursiveConfig",
332
+ "RecursiveBlock",
333
+ "RecursiveTransformer",
334
+ "count_parameters",
335
+ "model_summary",
336
+ ]