sukritvemula commited on
Commit
81271b1
·
verified ·
1 Parent(s): 82896c2

HYDRA v1: 19M param non-transformer with selective gated recurrence

Browse files
Files changed (5) hide show
  1. README.md +57 -39
  2. config.json +1 -1
  3. model.pt +1 -1
  4. model.py +98 -419
  5. tokenizer_config.json +1 -1
README.md CHANGED
@@ -8,6 +8,7 @@ tags:
8
  - gated-recurrence
9
  - from-scratch
10
  - tinystories
 
11
  license: apache-2.0
12
  datasets:
13
  - roneneldan/TinyStories
@@ -15,27 +16,25 @@ datasets:
15
 
16
  # HYDRA: Hybrid Dynamic Recurrent Architecture
17
 
18
- **A novel non-transformer language model trained from scratch.**
19
 
20
  ## Architecture
21
 
22
- HYDRA is a completely original architecture that combines cutting-edge ideas from recent research:
23
-
24
- | Component | Inspired By | Paper |
25
  |---|---|---|
26
- | **Selective State Spaces** | Mamba | [arxiv:2312.00752](https://arxiv.org/abs/2312.00752) |
27
- | **Real-Gated Linear Recurrence (RG-LRU)** | Griffin | [arxiv:2402.19427](https://arxiv.org/abs/2402.19427) |
28
- | **Time/Channel Mixing Blocks** | RWKV | [arxiv:2305.13048](https://arxiv.org/abs/2305.13048) |
29
- | **Multi-Scale State Compression** | Novel | Parallel recurrences at different timescales |
30
 
31
  ### Key Properties
32
- - **NOT a transformer** — no attention mechanism at all
33
- - **O(n) time complexity** — linear in sequence length (vs O(n²) for transformers)
34
- - **Constant memory inference** no growing KV cache
35
- - **Content-aware state selection** addresses the classic RNN weakness
36
- - **Multi-scale temporal processing** captures both short and long-range patterns
37
 
38
- ### Architecture Details
39
  ```
40
  Token Embedding → N × HydraBlock → RMSNorm → LM Head
41
 
@@ -46,52 +45,71 @@ HydraBlock:
46
  SelectiveGatedRecurrence (per timescale):
47
  ├── Input projection (2 branches)
48
  ├── Branch 1: Separable Conv1D → SiLU → Selective B,C projection
49
- ├── Branch 2: GeLU gate
50
- ├── Multi-scale gated recurrence: h_t = a_t·h_{t-1} + √(1-a_t²)·(i_t·B_t)
51
- ├── Timescale fusion
52
  └── Gated merge + output projection
53
  ```
54
 
55
- ## Model Specs
56
- - **Parameters**: 19,274,816
57
  - **d_model**: 256
58
  - **Layers**: 6
59
- - **State dimension**: 16
60
  - **Timescales**: 2
61
- - **Context length**: 256
62
- - **Vocabulary**: GPT-2 tokenizer (50,257 tokens)
63
 
64
  ## Training
65
- - **Dataset**: [TinyStories](https://huggingface.co/datasets/roneneldan/TinyStories)
66
- - **From scratch**: All weights randomly initialized, no pretrained components
67
- - **Optimizer**: AdamW (β1=0.9, β2=0.95, from RWKV paper)
68
- - **Schedule**: Cosine decay with linear warmup
69
- - **Hardware**: CPU
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
70
 
71
  ## Usage
72
 
73
  ```python
74
- import torch
75
  from model import HydraModel, HydraConfig
76
  from transformers import AutoTokenizer
77
 
78
- # Load
79
  config = HydraConfig(**json.load(open("config.json")))
80
  model = HydraModel(config)
81
  model.load_state_dict(torch.load("model.pt", map_location="cpu"))
 
82
 
83
  tokenizer = AutoTokenizer.from_pretrained("gpt2")
84
-
85
- # Generate
86
  prompt = "Once upon a time"
87
- input_ids = torch.tensor([tokenizer.encode(prompt)])
88
- output = model.generate(input_ids, max_new_tokens=100, temperature=0.8)
89
- print(tokenizer.decode(output[0], skip_special_tokens=True))
 
90
  ```
91
 
92
- ## Research References
 
 
 
93
 
94
- 1. **Mamba**: Gu & Dao, "Mamba: Linear-Time Sequence Modeling with Selective State Spaces", 2023
95
- 2. **Griffin**: De et al., "Griffin: Mixing Gated Linear Recurrences with Local Attention", 2024
96
- 3. **RWKV**: Peng et al., "RWKV: Reinventing RNNs for the Transformer Era", 2023
97
- 4. **TinyStories**: Eldan & Li, "TinyStories: How Small Can Language Models Be and Still Speak Coherent English?", 2023
 
 
8
  - gated-recurrence
9
  - from-scratch
10
  - tinystories
11
+ - recurrent-neural-network
12
  license: apache-2.0
13
  datasets:
14
  - roneneldan/TinyStories
 
16
 
17
  # HYDRA: Hybrid Dynamic Recurrent Architecture
18
 
19
+ A novel **non-transformer** language model built from scratch. Trained on CPU using a custom architecture that combines Mamba's Selective State Spaces, Griffin's Real-Gated Linear Recurrence (RG-LRU), and RWKV's channel mixing — with **zero attention layers**.
20
 
21
  ## Architecture
22
 
23
+ | Component | Source | Paper |
 
 
24
  |---|---|---|
25
+ | Selective State Spaces | Mamba | arxiv:2312.00752 |
26
+ | Real-Gated Linear Recurrence | Griffin | arxiv:2402.19427 |
27
+ | Time/Channel Mixing | RWKV | arxiv:2305.13048 |
28
+ | Multi-Scale Compression | **Novel** | Parallel recurrences at different timescales |
29
 
30
  ### Key Properties
31
+ - **NOT a transformer** — no attention whatsoever
32
+ - **O(n) time** — linear in sequence length (vs O(n²) transformers)
33
+ - **Constant memory** at inference (no KV cache)
34
+ - **Content-aware** selective gating (Mamba + Griffin fusion)
35
+ - **Multi-scale** temporal processing
36
 
37
+ ### Architecture Diagram
38
  ```
39
  Token Embedding → N × HydraBlock → RMSNorm → LM Head
40
 
 
45
  SelectiveGatedRecurrence (per timescale):
46
  ├── Input projection (2 branches)
47
  ├── Branch 1: Separable Conv1D → SiLU → Selective B,C projection
48
+ ├── Input gate + Recurrence gate (from Griffin RG-LRU)
49
+ ├── Gated recurrence: h_t = a_t·h_{t-1} + √(1-a_t²)·(i_t·B_t)
 
50
  └── Gated merge + output projection
51
  ```
52
 
53
+ ## Specs
54
+ - **Parameters**: 19,274,816 (19.3M)
55
  - **d_model**: 256
56
  - **Layers**: 6
57
+ - **State dim**: 16
58
  - **Timescales**: 2
59
+ - **Context length**: 128
 
60
 
61
  ## Training
62
+ - **Dataset**: TinyStories (5,000 stories for quick training)
63
+ - **From scratch**: Random initialization, no pretrained components
64
+ - **Best val_loss**: 3.7988
65
+ - **Val_ppl**: 44.6
66
+ - **Hardware**: CPU only
67
+ - **Optimizer**: AdamW (β₁=0.9, β₂=0.95)
68
+
69
+ ## Generated Samples
70
+
71
+ **Once upon a time**: Once upon a time, there was a little girl named Lily. She loved to play outside. She had a big, feeling very excited! She couldn't wait for a big, but she said.
72
+
73
+ The little boy was so happy to make the bird who loved
74
+ **A little dog**: A little dog who ran to be happy. She was very excited that the park. She wanted to play with the window.
75
+
76
+ "I'm sorry, Lily. It is a voice?" She was so happy!
77
+
78
+ Ben did not give it and
79
+ **A girl named Lily**: A girl named Lily. She was very happy he was very tall. She saw what he could not want to the forest. She says, "I'm sorry, we can have to go to the water. She was very very excited and happy.
80
+
81
+ Lily
82
+ **One day a boy**: One day a boy named Lily liked to play with it. He said, "Don't be careful."
83
+
84
+ "We are happy, we have to go to the man that he decided to play with his friends. He was very happy that the dog had a man
85
 
86
  ## Usage
87
 
88
  ```python
89
+ import torch, json
90
  from model import HydraModel, HydraConfig
91
  from transformers import AutoTokenizer
92
 
 
93
  config = HydraConfig(**json.load(open("config.json")))
94
  model = HydraModel(config)
95
  model.load_state_dict(torch.load("model.pt", map_location="cpu"))
96
+ model.eval()
97
 
98
  tokenizer = AutoTokenizer.from_pretrained("gpt2")
 
 
99
  prompt = "Once upon a time"
100
+ ids = torch.tensor([tokenizer.encode(prompt)])
101
+ with torch.no_grad():
102
+ out = model.generate(ids, max_new_tokens=50, temperature=0.8, top_k=40)
103
+ print(tokenizer.decode(out[0], skip_special_tokens=True))
104
  ```
105
 
106
+ ## Model Files
107
+ - `model.pt` — trained weights
108
+ - `config.json` — model configuration
109
+ - `model.py` — full architecture source code
110
 
111
+ ## Research References
112
+ 1. Gu & Dao, "Mamba: Linear-Time Sequence Modeling with Selective State Spaces", NeurIPS 2023
113
+ 2. De et al., "Griffin: Mixing Gated Linear Recurrences with Local Attention", ICLR 2024
114
+ 3. Peng et al., "RWKV: Reinventing RNNs for the Transformer Era", 2023
115
+ 4. Eldan & Li, "TinyStories: How Small Can Language Models Be?", 2023
config.json CHANGED
@@ -8,7 +8,7 @@
8
  "mlp_expand": 3,
9
  "n_scales": 2,
10
  "dropout": 0.1,
11
- "max_seq_len": 256,
12
  "pad_token_id": 50256,
13
  "tie_weights": true
14
  }
 
8
  "mlp_expand": 3,
9
  "n_scales": 2,
10
  "dropout": 0.1,
11
+ "max_seq_len": 128,
12
  "pad_token_id": 50256,
13
  "tie_weights": true
14
  }
model.pt CHANGED
@@ -1,3 +1,3 @@
1
  version https://git-lfs.github.com/spec/v1
2
- oid sha256:bda4c00904bd3d69cfb22d2b106139693e92e2ed0e211c73a964ed53fabdc6e8
3
  size 77144619
 
1
  version https://git-lfs.github.com/spec/v1
2
+ oid sha256:8b4d19a13205d6f991c2a27001f06318aee6988971d276eaf0bc4c673d5667ed
3
  size 77144619
model.py CHANGED
@@ -1,455 +1,134 @@
1
  """
2
  HYDRA: Hybrid Dynamic Recurrent Architecture
3
- =============================================
4
- A novel non-transformer language model built from scratch.
5
-
6
- Architecture combines ideas from:
7
- - Mamba (arxiv:2312.00752): Selective State Spaces with input-dependent dynamics
8
- - Griffin (arxiv:2402.19427): Real-Gated Linear Recurrent Unit (RG-LRU)
9
- - RWKV (arxiv:2305.13048): Time-mixing + channel-mixing residual blocks
10
-
11
- Key innovations in this implementation:
12
- 1. Selective Gated Recurrence (SGR): Fuses Mamba's selective SSM with Griffin's RG-LRU gates
13
- 2. Multi-Scale State Compression: Parallel recurrences at different timescales
14
- 3. Gated Channel Mixing: RWKV-inspired channel mixing with GeGeLU
15
- 4. No attention mechanism - pure recurrent, O(n) in sequence length
16
-
17
- This gives us:
18
- - Linear time complexity (vs quadratic for transformers)
19
- - Constant memory at inference (vs growing KV cache)
20
- - Content-aware state selection (addresses RNN weakness)
21
- - Theoretically unlimited context (state carries forward indefinitely)
22
  """
23
-
24
- import math
25
- import torch
26
- import torch.nn as nn
27
- import torch.nn.functional as F
28
  from dataclasses import dataclass
29
- from typing import Optional, Tuple
30
-
31
 
32
  @dataclass
33
  class HydraConfig:
34
- """Configuration for the Hydra model."""
35
- vocab_size: int = 50257 # GPT-2 tokenizer vocab size
36
- d_model: int = 512 # Model dimension
37
- n_layers: int = 8 # Number of residual blocks
38
- d_state: int = 16 # SSM state dimension (N in Mamba)
39
- d_conv: int = 4 # Local convolution width (from Griffin/Mamba)
40
- d_inner: int = None # Inner dimension (defaults to 2 * d_model)
41
- mlp_expand: int = 3 # MLP expansion factor (from Griffin paper: M=3)
42
- n_scales: int = 3 # Number of timescales for multi-scale recurrence
43
- dropout: float = 0.1 # Dropout rate
44
- max_seq_len: int = 1024 # Maximum sequence length for training
45
- pad_token_id: int = 50256 # Padding token (GPT-2 <|endoftext|>)
46
- tie_weights: bool = True # Tie input/output embeddings (from Griffin)
47
-
48
  def __post_init__(self):
49
- if self.d_inner is None:
50
- self.d_inner = 2 * self.d_model
51
-
52
 
53
  class RMSNorm(nn.Module):
54
- """Root Mean Square Layer Normalization (from Griffin paper)."""
55
- def __init__(self, d_model: int, eps: float = 1e-8):
56
- super().__init__()
57
- self.eps = eps
58
- self.weight = nn.Parameter(torch.ones(d_model))
59
-
60
  def forward(self, x):
61
- rms = torch.sqrt(torch.mean(x ** 2, dim=-1, keepdim=True) + self.eps)
62
- return x / rms * self.weight
63
-
64
 
65
  class SelectiveGatedRecurrence(nn.Module):
66
- """
67
- Selective Gated Recurrence (SGR) — our novel temporal mixing block.
68
-
69
- Fuses ideas from:
70
- - Mamba's selective SSM: B, C, Δ are functions of input (content-aware)
71
- - Griffin's RG-LRU: Gated recurrence with input gate and recurrence gate
72
- - Multi-scale: Parallel recurrences at different timescales
73
-
74
- Core equations (per timescale s):
75
- r_t = sigmoid(W_r @ x_t) # recurrence gate (Griffin)
76
- i_t = sigmoid(W_i @ x_t) # input gate (Griffin)
77
- B_t = Linear(x_t) # selective input projection (Mamba)
78
- C_t = Linear(x_t) # selective output projection (Mamba)
79
- a_t = a_base^(c * r_t) # gated decay (Griffin: c=8)
80
- h_t = a_t * h_{t-1} + sqrt(1 - a_t^2) * (i_t * B_t * x_t) # state update
81
- y_t = C_t * h_t # readout
82
- """
83
-
84
- def __init__(self, config: HydraConfig):
85
  super().__init__()
86
- self.d_inner = config.d_inner
87
- self.d_state = config.d_state
88
- self.n_scales = config.n_scales
89
- self.gate_c = 8.0 # Scalar constant from Griffin paper
90
-
91
- # Input projection: x -> two branches (like Mamba/Griffin recurrent block)
92
- self.in_proj = nn.Linear(config.d_model, 2 * config.d_inner, bias=False)
93
-
94
- # Small causal conv1d on first branch (from Griffin Sec 2.3, filter size 4)
95
- self.conv1d = nn.Conv1d(
96
- config.d_inner, config.d_inner,
97
- kernel_size=config.d_conv,
98
- padding=config.d_conv - 1,
99
- groups=config.d_inner # depthwise/separable (from Griffin)
100
- )
101
-
102
- # Selective projections (from Mamba: B, C are input-dependent)
103
- # One set per timescale
104
- self.B_proj = nn.ModuleList([
105
- nn.Linear(config.d_inner, config.d_state, bias=False)
106
- for _ in range(config.n_scales)
107
- ])
108
- self.C_proj = nn.ModuleList([
109
- nn.Linear(config.d_inner, config.d_state, bias=False)
110
- for _ in range(config.n_scales)
111
- ])
112
-
113
- # Gates (from Griffin RG-LRU)
114
- self.recurrence_gate = nn.ModuleList([
115
- nn.Linear(config.d_inner, config.d_state, bias=True)
116
- for _ in range(config.n_scales)
117
- ])
118
- self.input_gate = nn.ModuleList([
119
- nn.Linear(config.d_inner, config.d_state, bias=True)
120
- for _ in range(config.n_scales)
121
- ])
122
-
123
- # Learnable base decay per timescale (parameterized as Lambda, a = sigmoid(Lambda))
124
- # Initialize so a^c is uniformly in [0.9, 0.999] per Griffin
125
- self.Lambda = nn.ParameterList()
126
  for s in range(config.n_scales):
127
- # Different timescales: short-term, medium-term, long-term
128
- # a^c in [0.9^(1/(s+1)), 0.999^(1/(s+1))]
129
- low = math.log(0.9 ** (1.0 / (s + 1)))
130
- high = math.log(0.999 ** (1.0 / (s + 1)))
131
- # sigmoid^{-1}(x) = log(x/(1-x))
132
- low_a = math.exp(low)
133
- high_a = math.exp(high)
134
- # Initialize Lambda so sigmoid(Lambda) gives us the right range
135
- init_val = torch.empty(config.d_state).uniform_(
136
- math.log(low_a / (1 - low_a + 1e-8)),
137
- math.log(high_a / (1 - high_a + 1e-8))
138
- )
139
  self.Lambda.append(nn.Parameter(init_val))
140
-
141
- # Multi-scale fusion
142
- self.scale_fusion = nn.Linear(config.n_scales * config.d_state, config.d_inner, bias=False)
143
-
144
- # Output projection
145
- self.out_proj = nn.Linear(config.d_inner, config.d_model, bias=False)
146
-
147
- self.dropout = nn.Dropout(config.dropout)
148
-
149
- def forward(self, x: torch.Tensor) -> torch.Tensor:
150
- """
151
- Args:
152
- x: (batch, seq_len, d_model)
153
- Returns:
154
- y: (batch, seq_len, d_model)
155
- """
156
- batch, seq_len, _ = x.shape
157
-
158
- # Project input into two branches
159
- xz = self.in_proj(x) # (B, L, 2*d_inner)
160
- x_branch, z_branch = xz.chunk(2, dim=-1) # each (B, L, d_inner)
161
-
162
- # Branch 1: Conv1D + recurrence
163
- # Conv1d expects (B, C, L)
164
- x_conv = self.conv1d(x_branch.transpose(1, 2))[:, :, :seq_len].transpose(1, 2)
165
- x_conv = F.silu(x_conv) # SiLU activation (from Mamba)
166
-
167
- # Branch 2: Gate (from Griffin/Mamba architecture)
168
- z_gate = F.gelu(z_branch)
169
-
170
- # Multi-scale selective recurrence
171
- scale_outputs = []
172
  for s in range(self.n_scales):
173
- # Selective projections (Mamba: B, C depend on input)
174
- B_t = self.B_proj[s](x_conv) # (B, L, d_state)
175
- C_t = self.C_proj[s](x_conv) # (B, L, d_state)
176
-
177
- # Gates (Griffin RG-LRU)
178
- r_t = torch.sigmoid(self.recurrence_gate[s](x_conv)) # (B, L, d_state)
179
- i_t = torch.sigmoid(self.input_gate[s](x_conv)) # (B, L, d_state)
180
-
181
- # Base decay: a = sigmoid(Lambda)
182
- a_base = torch.sigmoid(self.Lambda[s]) # (d_state,)
183
-
184
- # Gated decay: a_t = a_base^(c * r_t) (Griffin eq 3)
185
- # Compute in log space for stability: log(a_t) = c * r_t * log(a_base)
186
- log_a_base = torch.log(a_base.clamp(min=1e-8)) # (d_state,)
187
- log_a_t = self.gate_c * r_t * log_a_base.unsqueeze(0).unsqueeze(0) # (B, L, d_state)
188
- a_t = torch.exp(log_a_t) # (B, L, d_state)
189
-
190
- # Input contribution: sqrt(1 - a_t^2) * i_t * B_t * x_conv_projected
191
- # We use B_t as the projection of x into state space
192
- sqrt_term = torch.sqrt((1 - a_t ** 2).clamp(min=1e-8))
193
- input_contrib = sqrt_term * i_t * B_t # (B, L, d_state)
194
-
195
- # Sequential recurrence (numerically stable)
196
- # h_t = a_t * h_{t-1} + input_contrib_t
197
- # Compute all h states for this timescale
198
- h = torch.zeros(batch, seq_len, self.d_state, device=x.device, dtype=x.dtype)
199
- h_prev = torch.zeros(batch, self.d_state, device=x.device, dtype=x.dtype)
200
  for t in range(seq_len):
201
- h_prev = a_t[:, t] * h_prev + input_contrib[:, t]
202
- h[:, t] = h_prev
 
203
  scale_outputs.append(h)
204
-
205
- # Fuse multi-scale outputs
206
- multi_scale = torch.cat(scale_outputs, dim=-1) # (B, L, n_scales * d_state)
207
- fused = self.scale_fusion(multi_scale) # (B, L, d_inner)
208
-
209
- # Merge with gated branch (element-wise multiplication, like Griffin/Mamba)
210
- output = fused * z_gate
211
-
212
- # Output projection
213
- output = self.out_proj(output) # (B, L, d_model)
214
  return self.dropout(output)
215
 
216
-
217
  class GatedChannelMixing(nn.Module):
218
- """
219
- Channel mixing block inspired by RWKV and Griffin's gated MLP.
220
-
221
- Uses GeGeLU (Gated GeLU) from Griffin paper Sec 2.2:
222
- y = Linear(GeLU(Linear(x)) * Linear(x))
223
-
224
- With expansion factor M=3 (from Griffin paper).
225
- """
226
-
227
- def __init__(self, config: HydraConfig):
228
- super().__init__()
229
- d_ff = config.mlp_expand * config.d_model # M * D
230
-
231
- self.gate_proj = nn.Linear(config.d_model, d_ff, bias=False)
232
- self.up_proj = nn.Linear(config.d_model, d_ff, bias=False)
233
- self.down_proj = nn.Linear(d_ff, config.d_model, bias=False)
234
- self.dropout = nn.Dropout(config.dropout)
235
-
236
- def forward(self, x: torch.Tensor) -> torch.Tensor:
237
- # GeGeLU: GeLU(gate) * up, then project down
238
- gate = F.gelu(self.gate_proj(x))
239
- up = self.up_proj(x)
240
- return self.dropout(self.down_proj(gate * up))
241
-
242
 
243
  class HydraBlock(nn.Module):
244
- """
245
- Single Hydra residual block (from Griffin architecture Sec 2.1):
246
- x -> RMSNorm -> TemporalMix (SGR) -> + residual
247
- x -> RMSNorm -> ChannelMix (GeGeLU MLP) -> + residual
248
- """
249
-
250
- def __init__(self, config: HydraConfig):
251
  super().__init__()
252
- self.norm1 = RMSNorm(config.d_model)
253
- self.temporal_mix = SelectiveGatedRecurrence(config)
254
- self.norm2 = RMSNorm(config.d_model)
255
- self.channel_mix = GatedChannelMixing(config)
256
-
257
- def forward(self, x: torch.Tensor) -> torch.Tensor:
258
- # Temporal mixing with pre-norm and residual
259
- x = x + self.temporal_mix(self.norm1(x))
260
- # Channel mixing with pre-norm and residual
261
- x = x + self.channel_mix(self.norm2(x))
262
- return x
263
-
264
 
265
  class HydraModel(nn.Module):
266
- """
267
- HYDRA: Hybrid Dynamic Recurrent Architecture
268
-
269
- A novel non-transformer language model combining:
270
- - Selective State Spaces (Mamba)
271
- - Real-Gated Linear Recurrence (Griffin)
272
- - Channel mixing (RWKV/Griffin)
273
-
274
- Architecture:
275
- Token Embedding -> N x HydraBlock -> RMSNorm -> LM Head
276
-
277
- Each HydraBlock:
278
- -> RMSNorm -> SelectiveGatedRecurrence -> residual
279
- -> RMSNorm -> GatedChannelMixing -> residual
280
- """
281
-
282
- def __init__(self, config: HydraConfig):
283
- super().__init__()
284
- self.config = config
285
-
286
- # Token embedding
287
- self.embed = nn.Embedding(config.vocab_size, config.d_model)
288
- self.embed_dropout = nn.Dropout(config.dropout)
289
-
290
- # Stack of Hydra blocks
291
- self.blocks = nn.ModuleList([
292
- HydraBlock(config) for _ in range(config.n_layers)
293
- ])
294
-
295
- # Final norm (from Griffin: RMSNorm before LM head)
296
- self.final_norm = RMSNorm(config.d_model)
297
-
298
- # Language model head
299
- self.lm_head = nn.Linear(config.d_model, config.vocab_size, bias=False)
300
-
301
- # Weight tying (from Griffin paper: shared embedding weights)
302
- if config.tie_weights:
303
- self.lm_head.weight = self.embed.weight
304
-
305
- # Initialize weights
306
  self._init_weights()
307
-
308
  def _init_weights(self):
309
- """Initialize weights following Griffin/Mamba conventions."""
310
- for module in self.modules():
311
- if isinstance(module, nn.Linear):
312
- # LeCun init (from Griffin Sec 2.4)
313
- nn.init.normal_(module.weight, mean=0.0, std=1.0 / math.sqrt(module.weight.shape[1]))
314
- if module.bias is not None:
315
- nn.init.zeros_(module.bias)
316
- elif isinstance(module, nn.Embedding):
317
- nn.init.normal_(module.weight, mean=0.0, std=0.02)
318
- elif isinstance(module, nn.Conv1d):
319
- nn.init.normal_(module.weight, mean=0.0, std=0.02)
320
- if module.bias is not None:
321
- nn.init.zeros_(module.bias)
322
-
323
- def forward(
324
- self,
325
- input_ids: torch.Tensor,
326
- labels: Optional[torch.Tensor] = None
327
- ) -> dict:
328
- """
329
- Args:
330
- input_ids: (batch, seq_len) token indices
331
- labels: (batch, seq_len) target token indices for language modeling
332
- Returns:
333
- dict with 'logits' and optionally 'loss'
334
- """
335
- # Embed tokens
336
- x = self.embed(input_ids)
337
- x = self.embed_dropout(x)
338
-
339
- # Pass through Hydra blocks
340
- for block in self.blocks:
341
- x = block(x)
342
-
343
- # Final norm and LM head
344
- x = self.final_norm(x)
345
- logits = self.lm_head(x) # (batch, seq_len, vocab_size)
346
-
347
- result = {"logits": logits}
348
-
349
  if labels is not None:
350
- # Shift logits and labels for next-token prediction
351
- shift_logits = logits[:, :-1, :].contiguous()
352
- shift_labels = labels[:, 1:].contiguous()
353
- loss = F.cross_entropy(
354
- shift_logits.view(-1, self.config.vocab_size),
355
- shift_labels.view(-1),
356
- ignore_index=self.config.pad_token_id
357
- )
358
- result["loss"] = loss
359
-
360
  return result
361
-
362
- def generate(
363
- self,
364
- input_ids: torch.Tensor,
365
- max_new_tokens: int = 100,
366
- temperature: float = 0.8,
367
- top_k: int = 50,
368
- ) -> torch.Tensor:
369
- """Simple autoregressive generation."""
370
  self.eval()
371
  with torch.no_grad():
372
  for _ in range(max_new_tokens):
373
- # Use full context (or truncated if too long)
374
- context = input_ids[:, -self.config.max_seq_len:]
375
- output = self.forward(context)
376
- logits = output["logits"][:, -1, :] / temperature
377
-
378
- # Top-k filtering
379
- if top_k > 0:
380
- values, _ = torch.topk(logits, top_k)
381
- logits[logits < values[:, -1:]] = float('-inf')
382
-
383
- probs = F.softmax(logits, dim=-1)
384
- next_token = torch.multinomial(probs, num_samples=1)
385
- input_ids = torch.cat([input_ids, next_token], dim=-1)
386
-
387
  return input_ids
388
-
389
- @property
390
- def num_parameters(self):
391
- return sum(p.numel() for p in self.parameters())
392
-
393
  @property
394
- def num_trainable_parameters(self):
395
- return sum(p.numel() for p in self.parameters() if p.requires_grad)
396
-
397
-
398
- def create_hydra_small():
399
- """~10M parameter model for CPU training on TinyStories."""
400
- return HydraModel(HydraConfig(
401
- vocab_size=50257,
402
- d_model=256,
403
- n_layers=6,
404
- d_state=16,
405
- d_conv=4,
406
- mlp_expand=3,
407
- n_scales=2,
408
- dropout=0.1,
409
- max_seq_len=512,
410
- ))
411
-
412
-
413
- def create_hydra_medium():
414
- """~30M parameter model."""
415
- return HydraModel(HydraConfig(
416
- vocab_size=50257,
417
- d_model=512,
418
- n_layers=8,
419
- d_state=16,
420
- d_conv=4,
421
- mlp_expand=3,
422
- n_scales=3,
423
- dropout=0.1,
424
- max_seq_len=1024,
425
- ))
426
-
427
-
428
- if __name__ == "__main__":
429
- # Quick test
430
- config = HydraConfig(
431
- vocab_size=50257,
432
- d_model=256,
433
- n_layers=4,
434
- d_state=16,
435
- d_conv=4,
436
- mlp_expand=3,
437
- n_scales=2,
438
- dropout=0.1,
439
- max_seq_len=128,
440
- )
441
-
442
- model = HydraModel(config)
443
- print(f"Hydra Model")
444
- print(f" Parameters: {model.num_parameters:,}")
445
- print(f" Config: {config}")
446
-
447
- # Test forward pass
448
- batch_size, seq_len = 2, 64
449
- input_ids = torch.randint(0, config.vocab_size, (batch_size, seq_len))
450
- labels = torch.randint(0, config.vocab_size, (batch_size, seq_len))
451
-
452
- output = model(input_ids, labels=labels)
453
- print(f" Logits shape: {output['logits'].shape}")
454
- print(f" Loss: {output['loss'].item():.4f}")
455
- print(" ✓ Forward pass successful!")
 
1
  """
2
  HYDRA: Hybrid Dynamic Recurrent Architecture
3
+ Novel non-transformer combining Mamba SSM, Griffin RG-LRU, RWKV mixing.
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
4
  """
5
+ import math, torch, torch.nn as nn, torch.nn.functional as F
 
 
 
 
6
  from dataclasses import dataclass
7
+ from typing import Optional
 
8
 
9
  @dataclass
10
  class HydraConfig:
11
+ vocab_size: int = 50257
12
+ d_model: int = 512
13
+ n_layers: int = 8
14
+ d_state: int = 16
15
+ d_conv: int = 4
16
+ d_inner: int = None
17
+ mlp_expand: int = 3
18
+ n_scales: int = 3
19
+ dropout: float = 0.1
20
+ max_seq_len: int = 1024
21
+ pad_token_id: int = 50256
22
+ tie_weights: bool = True
 
 
23
  def __post_init__(self):
24
+ if self.d_inner is None: self.d_inner = 2 * self.d_model
 
 
25
 
26
  class RMSNorm(nn.Module):
27
+ def __init__(self, d_model, eps=1e-8):
28
+ super().__init__(); self.eps=eps; self.weight=nn.Parameter(torch.ones(d_model))
 
 
 
 
29
  def forward(self, x):
30
+ return x / torch.sqrt(torch.mean(x**2,dim=-1,keepdim=True)+self.eps) * self.weight
 
 
31
 
32
  class SelectiveGatedRecurrence(nn.Module):
33
+ def __init__(self, config):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
34
  super().__init__()
35
+ self.d_inner=config.d_inner; self.d_state=config.d_state; self.n_scales=config.n_scales; self.gate_c=8.0
36
+ self.in_proj=nn.Linear(config.d_model, 2*config.d_inner, bias=False)
37
+ self.conv1d=nn.Conv1d(config.d_inner,config.d_inner,config.d_conv,padding=config.d_conv-1,groups=config.d_inner)
38
+ self.B_proj=nn.ModuleList([nn.Linear(config.d_inner,config.d_state,bias=False) for _ in range(config.n_scales)])
39
+ self.C_proj=nn.ModuleList([nn.Linear(config.d_inner,config.d_state,bias=False) for _ in range(config.n_scales)])
40
+ self.recurrence_gate=nn.ModuleList([nn.Linear(config.d_inner,config.d_state,bias=True) for _ in range(config.n_scales)])
41
+ self.input_gate=nn.ModuleList([nn.Linear(config.d_inner,config.d_state,bias=True) for _ in range(config.n_scales)])
42
+ self.Lambda=nn.ParameterList()
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
43
  for s in range(config.n_scales):
44
+ low_a=0.9**(1.0/(s+1)); high_a=0.999**(1.0/(s+1))
45
+ init_val=torch.empty(config.d_state).uniform_(math.log(low_a/(1-low_a+1e-8)),math.log(high_a/(1-high_a+1e-8)))
 
 
 
 
 
 
 
 
 
 
46
  self.Lambda.append(nn.Parameter(init_val))
47
+ self.scale_fusion=nn.Linear(config.n_scales*config.d_state,config.d_inner,bias=False)
48
+ self.out_proj=nn.Linear(config.d_inner,config.d_model,bias=False)
49
+ self.dropout=nn.Dropout(config.dropout)
50
+
51
+ def forward(self, x):
52
+ batch,seq_len,_=x.shape
53
+ xz=self.in_proj(x); x_branch,z_branch=xz.chunk(2,dim=-1)
54
+ x_conv=self.conv1d(x_branch.transpose(1,2))[:,:,:seq_len].transpose(1,2)
55
+ x_conv=F.silu(x_conv); z_gate=F.gelu(z_branch)
56
+ scale_outputs=[]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
57
  for s in range(self.n_scales):
58
+ B_t=self.B_proj[s](x_conv); C_t=self.C_proj[s](x_conv)
59
+ r_t=torch.sigmoid(self.recurrence_gate[s](x_conv))
60
+ i_t=torch.sigmoid(self.input_gate[s](x_conv))
61
+ a_base=torch.sigmoid(self.Lambda[s])
62
+ log_a_base=torch.log(a_base.clamp(min=1e-8))
63
+ log_a_t=self.gate_c*r_t*log_a_base.unsqueeze(0).unsqueeze(0)
64
+ a_t=torch.exp(log_a_t)
65
+ sqrt_term=torch.sqrt((1-a_t**2).clamp(min=1e-8))
66
+ input_contrib=sqrt_term*i_t*B_t
67
+ h_prev=torch.zeros(batch,self.d_state,device=x.device,dtype=x.dtype)
68
+ h_states=[]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
69
  for t in range(seq_len):
70
+ h_prev=a_t[:,t]*h_prev+input_contrib[:,t]
71
+ h_states.append(h_prev.unsqueeze(1))
72
+ h=torch.cat(h_states,dim=1)
73
  scale_outputs.append(h)
74
+ multi_scale=torch.cat(scale_outputs,dim=-1)
75
+ fused=self.scale_fusion(multi_scale)
76
+ output=self.out_proj(fused*z_gate)
 
 
 
 
 
 
 
77
  return self.dropout(output)
78
 
 
79
  class GatedChannelMixing(nn.Module):
80
+ def __init__(self, config):
81
+ super().__init__(); d_ff=config.mlp_expand*config.d_model
82
+ self.gate_proj=nn.Linear(config.d_model,d_ff,bias=False)
83
+ self.up_proj=nn.Linear(config.d_model,d_ff,bias=False)
84
+ self.down_proj=nn.Linear(d_ff,config.d_model,bias=False)
85
+ self.dropout=nn.Dropout(config.dropout)
86
+ def forward(self, x):
87
+ return self.dropout(self.down_proj(F.gelu(self.gate_proj(x))*self.up_proj(x)))
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
88
 
89
  class HydraBlock(nn.Module):
90
+ def __init__(self, config):
 
 
 
 
 
 
91
  super().__init__()
92
+ self.norm1=RMSNorm(config.d_model); self.temporal_mix=SelectiveGatedRecurrence(config)
93
+ self.norm2=RMSNorm(config.d_model); self.channel_mix=GatedChannelMixing(config)
94
+ def forward(self, x):
95
+ return x+self.channel_mix(self.norm2(x+self.temporal_mix(self.norm1(x))))
 
 
 
 
 
 
 
 
96
 
97
  class HydraModel(nn.Module):
98
+ def __init__(self, config):
99
+ super().__init__(); self.config=config
100
+ self.embed=nn.Embedding(config.vocab_size,config.d_model)
101
+ self.embed_dropout=nn.Dropout(config.dropout)
102
+ self.blocks=nn.ModuleList([HydraBlock(config) for _ in range(config.n_layers)])
103
+ self.final_norm=RMSNorm(config.d_model)
104
+ self.lm_head=nn.Linear(config.d_model,config.vocab_size,bias=False)
105
+ if config.tie_weights: self.lm_head.weight=self.embed.weight
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
106
  self._init_weights()
 
107
  def _init_weights(self):
108
+ for m in self.modules():
109
+ if isinstance(m, nn.Linear): nn.init.normal_(m.weight,0,1.0/math.sqrt(m.weight.shape[1])); hasattr(m,'bias') and m.bias is not None and nn.init.zeros_(m.bias)
110
+ elif isinstance(m, nn.Embedding): nn.init.normal_(m.weight,0,0.02)
111
+ elif isinstance(m, nn.Conv1d): nn.init.normal_(m.weight,0,0.02); hasattr(m,'bias') and m.bias is not None and nn.init.zeros_(m.bias)
112
+ def forward(self, input_ids, labels=None):
113
+ x=self.embed_dropout(self.embed(input_ids))
114
+ for b in self.blocks: x=b(x)
115
+ x=self.final_norm(x); logits=self.lm_head(x)
116
+ result={"logits":logits}
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
117
  if labels is not None:
118
+ loss=F.cross_entropy(logits[:,:-1,:].contiguous().view(-1,self.config.vocab_size),labels[:,1:].contiguous().view(-1),ignore_index=self.config.pad_token_id)
119
+ result["loss"]=loss
 
 
 
 
 
 
 
 
120
  return result
121
+ def generate(self, input_ids, max_new_tokens=100, temperature=0.8, top_k=50):
 
 
 
 
 
 
 
 
122
  self.eval()
123
  with torch.no_grad():
124
  for _ in range(max_new_tokens):
125
+ ctx=input_ids[:,-self.config.max_seq_len:]
126
+ logits=self.forward(ctx)["logits"][:,-1,:]/temperature
127
+ if top_k>0:
128
+ v,_=torch.topk(logits,top_k); logits[logits<v[:,-1:]]=float('-inf')
129
+ probs=F.softmax(logits,dim=-1)
130
+ next_token=torch.multinomial(probs,1)
131
+ input_ids=torch.cat([input_ids,next_token],dim=-1)
 
 
 
 
 
 
 
132
  return input_ids
 
 
 
 
 
133
  @property
134
+ def num_parameters(self): return sum(p.numel() for p in self.parameters())
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
tokenizer_config.json CHANGED
@@ -7,7 +7,7 @@
7
  "is_local": false,
8
  "local_files_only": false,
9
  "model_max_length": 1024,
10
- "pad_token": "<|endoftext|>",
11
  "tokenizer_class": "GPT2Tokenizer",
12
  "unk_token": "<|endoftext|>"
13
  }
 
7
  "is_local": false,
8
  "local_files_only": false,
9
  "model_max_length": 1024,
10
+ "pad_token": null,
11
  "tokenizer_class": "GPT2Tokenizer",
12
  "unk_token": "<|endoftext|>"
13
  }