Kye Gomez commited on
Commit
18cca89
Β·
1 Parent(s): 537b116

[fix][rope Every decode token was stuck at position 0, so <q_decoded, k_cached> lost the (n - m) term entirely]

Browse files
open_mythos/main.py CHANGED
@@ -147,14 +147,19 @@ def apply_rope(x: torch.Tensor, freqs_cis: torch.Tensor) -> torch.Tensor:
147
 
148
  Args:
149
  x -- tensor of shape (B, T, H, head_dim); head_dim must be even
150
- freqs_cis -- precomputed complex frequencies of shape (max_len, head_dim//2)
 
 
151
 
152
  Returns:
153
  Rotated tensor of the same shape and dtype as x
154
  """
155
  xc = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2))
156
- freqs_cis = freqs_cis[: x.shape[1]].unsqueeze(0).unsqueeze(2)
157
- return torch.view_as_real(xc * freqs_cis).flatten(-2).to(x.dtype)
 
 
 
158
 
159
 
160
  # ---------------------------------------------------------------------------
@@ -936,6 +941,7 @@ class OpenMythos(nn.Module):
936
  input_ids: torch.Tensor,
937
  n_loops: Optional[int] = None,
938
  kv_cache: Optional[dict] = None,
 
939
  ) -> torch.Tensor:
940
  """
941
  Forward pass through Prelude β†’ Recurrent Block β†’ Coda.
@@ -946,17 +952,21 @@ class OpenMythos(nn.Module):
946
  Increase at inference to extrapolate to harder problems.
947
  kv_cache -- dict mutated in-place for autoregressive KV caching;
948
  pass an empty dict {} and reuse across decode steps
 
 
 
 
949
 
950
  Returns:
951
  Logits of shape (B, T, vocab_size)
952
  """
953
- B, T = input_ids.shape
954
  device = input_ids.device
955
 
956
  x = self.embed(input_ids)
957
  freqs_cis = (
958
  self.freqs_cis_mla if self.cfg.attn_type == "mla" else self.freqs_cis
959
- )[:T]
960
  mask = self._causal_mask(T, device) if T > 1 else None
961
 
962
  for i, layer in enumerate(self.prelude):
@@ -1001,9 +1011,17 @@ class OpenMythos(nn.Module):
1001
  Token indices of shape (B, T + max_new_tokens)
1002
  """
1003
  kv_cache: dict = {}
 
1004
  for step in range(max_new_tokens):
1005
- cur_ids = input_ids if step == 0 else input_ids[:, -1:]
1006
- logits = self.forward(cur_ids, n_loops=n_loops, kv_cache=kv_cache)
 
 
 
 
 
 
 
1007
  logits = logits[:, -1, :] / temperature
1008
  if top_k > 0:
1009
  v, _ = logits.topk(top_k)
 
147
 
148
  Args:
149
  x -- tensor of shape (B, T, H, head_dim); head_dim must be even
150
+ freqs_cis -- precomputed complex frequencies of shape (T, head_dim//2),
151
+ already sliced to exactly the positions being processed
152
+ (caller is responsible for correct start_pos offset)
153
 
154
  Returns:
155
  Rotated tensor of the same shape and dtype as x
156
  """
157
  xc = torch.view_as_complex(x.float().reshape(*x.shape[:-1], -1, 2))
158
+ return (
159
+ torch.view_as_real(xc * freqs_cis.unsqueeze(0).unsqueeze(2))
160
+ .flatten(-2)
161
+ .to(x.dtype)
162
+ )
163
 
164
 
165
  # ---------------------------------------------------------------------------
 
941
  input_ids: torch.Tensor,
942
  n_loops: Optional[int] = None,
943
  kv_cache: Optional[dict] = None,
944
+ start_pos: int = 0,
945
  ) -> torch.Tensor:
946
  """
947
  Forward pass through Prelude β†’ Recurrent Block β†’ Coda.
 
952
  Increase at inference to extrapolate to harder problems.
953
  kv_cache -- dict mutated in-place for autoregressive KV caching;
954
  pass an empty dict {} and reuse across decode steps
955
+ start_pos -- index of the first token in input_ids within the full
956
+ sequence; used to select the correct RoPE frequencies
957
+ during incremental decoding (0 for prefill, prompt_len
958
+ for each subsequent decode step)
959
 
960
  Returns:
961
  Logits of shape (B, T, vocab_size)
962
  """
963
+ T = input_ids.shape[1]
964
  device = input_ids.device
965
 
966
  x = self.embed(input_ids)
967
  freqs_cis = (
968
  self.freqs_cis_mla if self.cfg.attn_type == "mla" else self.freqs_cis
969
+ )[start_pos : start_pos + T]
970
  mask = self._causal_mask(T, device) if T > 1 else None
971
 
972
  for i, layer in enumerate(self.prelude):
 
1011
  Token indices of shape (B, T + max_new_tokens)
1012
  """
1013
  kv_cache: dict = {}
1014
+ prompt_len = input_ids.shape[1]
1015
  for step in range(max_new_tokens):
1016
+ if step == 0:
1017
+ cur_ids = input_ids
1018
+ start_pos = 0
1019
+ else:
1020
+ cur_ids = input_ids[:, -1:]
1021
+ start_pos = prompt_len + step - 1
1022
+ logits = self.forward(
1023
+ cur_ids, n_loops=n_loops, kv_cache=kv_cache, start_pos=start_pos
1024
+ )
1025
  logits = logits[:, -1, :] / temperature
1026
  if top_k > 0:
1027
  v, _ = logits.topk(top_k)
test_main.py CHANGED
@@ -96,20 +96,20 @@ class TestRoPE:
96
  def test_apply_rope_shape(self):
97
  freqs = precompute_rope_freqs(dim=16, max_len=32)
98
  x = torch.randn(B, T, 4, 16)
99
- out = apply_rope(x, freqs)
100
  assert out.shape == x.shape
101
 
102
  def test_apply_rope_preserves_norm(self):
103
  # rotation is an isometry β€” norms must be unchanged
104
  freqs = precompute_rope_freqs(dim=16, max_len=32)
105
  x = torch.randn(B, T, 4, 16)
106
- out = apply_rope(x, freqs)
107
  assert torch.allclose(x.norm(dim=-1), out.norm(dim=-1), atol=1e-5)
108
 
109
  def test_different_positions_differ(self):
110
  freqs = precompute_rope_freqs(dim=16, max_len=32)
111
  x = torch.ones(1, 2, 1, 16)
112
- out = apply_rope(x, freqs)
113
  # position 0 and position 1 should produce different rotations
114
  assert not torch.allclose(out[0, 0], out[0, 1])
115
 
@@ -168,27 +168,27 @@ class TestRoPEExtended:
168
  """T=1 input uses only freqs[0] = 1+0j, so output must equal input."""
169
  freqs = precompute_rope_freqs(dim=16, max_len=8)
170
  x = torch.randn(2, 1, 4, 16)
171
- out = apply_rope(x, freqs)
172
  assert torch.allclose(x, out, atol=1e-6)
173
 
174
  def test_dtype_float32_preserved(self):
175
  freqs = precompute_rope_freqs(dim=16, max_len=16)
176
  x = torch.randn(1, 4, 2, 16).float()
177
- assert apply_rope(x, freqs).dtype == torch.float32
178
 
179
  def test_dtype_float16_preserved(self):
180
  freqs = precompute_rope_freqs(dim=16, max_len=16)
181
  x = torch.randn(1, 4, 2, 16).half()
182
- assert apply_rope(x, freqs).dtype == torch.float16
183
 
184
  def test_inverse_rotation_recovers_input(self):
185
  """Rotating by freqs then by conj(freqs) (inverse) must recover the original."""
186
  dim = 16
187
  freqs = precompute_rope_freqs(dim=dim, max_len=8)
188
  x = torch.randn(2, 4, 3, dim)
189
- rotated = apply_rope(x, freqs)
190
  xc = torch.view_as_complex(rotated.float().reshape(*rotated.shape[:-1], -1, 2))
191
- inv = freqs.conj()[: rotated.shape[1]].unsqueeze(0).unsqueeze(2)
192
  recovered = torch.view_as_real(xc * inv).flatten(-2).to(x.dtype)
193
  assert torch.allclose(x, recovered, atol=1e-5)
194
 
@@ -199,8 +199,8 @@ class TestRoPEExtended:
199
  torch.manual_seed(7)
200
  x_a = torch.randn(1, 4, 2, dim)
201
  x_b = torch.randn(1, 4, 2, dim)
202
- solo = apply_rope(x_a, freqs)
203
- batched = apply_rope(torch.cat([x_a, x_b], dim=0), freqs)[:1]
204
  assert torch.allclose(solo, batched, atol=1e-6)
205
 
206
  def test_head_independence(self):
@@ -208,7 +208,7 @@ class TestRoPEExtended:
208
  dim = 16
209
  freqs = precompute_rope_freqs(dim=dim, max_len=8)
210
  x = torch.randn(1, 4, 1, dim).expand(1, 4, 3, dim).contiguous()
211
- out = apply_rope(x, freqs)
212
  assert torch.allclose(out[:, :, 0], out[:, :, 1], atol=1e-6)
213
  assert torch.allclose(out[:, :, 1], out[:, :, 2], atol=1e-6)
214
 
@@ -227,7 +227,7 @@ class TestRoPEExtended:
227
  """Rotate tensor at a specific position by embedding it in a zero sequence."""
228
  seq = torch.zeros(1, pos + 1, 1, dim)
229
  seq[0, pos] = tensor[0, 0]
230
- return apply_rope(seq, freqs)[:, pos : pos + 1]
231
 
232
  # Both pairs have relative offset n - m = 6
233
  dot_3_9 = (rope_at(q, 3) * rope_at(k, 9)).sum()
 
96
  def test_apply_rope_shape(self):
97
  freqs = precompute_rope_freqs(dim=16, max_len=32)
98
  x = torch.randn(B, T, 4, 16)
99
+ out = apply_rope(x, freqs[:T])
100
  assert out.shape == x.shape
101
 
102
  def test_apply_rope_preserves_norm(self):
103
  # rotation is an isometry β€” norms must be unchanged
104
  freqs = precompute_rope_freqs(dim=16, max_len=32)
105
  x = torch.randn(B, T, 4, 16)
106
+ out = apply_rope(x, freqs[:T])
107
  assert torch.allclose(x.norm(dim=-1), out.norm(dim=-1), atol=1e-5)
108
 
109
  def test_different_positions_differ(self):
110
  freqs = precompute_rope_freqs(dim=16, max_len=32)
111
  x = torch.ones(1, 2, 1, 16)
112
+ out = apply_rope(x, freqs[:2])
113
  # position 0 and position 1 should produce different rotations
114
  assert not torch.allclose(out[0, 0], out[0, 1])
115
 
 
168
  """T=1 input uses only freqs[0] = 1+0j, so output must equal input."""
169
  freqs = precompute_rope_freqs(dim=16, max_len=8)
170
  x = torch.randn(2, 1, 4, 16)
171
+ out = apply_rope(x, freqs[:1])
172
  assert torch.allclose(x, out, atol=1e-6)
173
 
174
  def test_dtype_float32_preserved(self):
175
  freqs = precompute_rope_freqs(dim=16, max_len=16)
176
  x = torch.randn(1, 4, 2, 16).float()
177
+ assert apply_rope(x, freqs[:4]).dtype == torch.float32
178
 
179
  def test_dtype_float16_preserved(self):
180
  freqs = precompute_rope_freqs(dim=16, max_len=16)
181
  x = torch.randn(1, 4, 2, 16).half()
182
+ assert apply_rope(x, freqs[:4]).dtype == torch.float16
183
 
184
  def test_inverse_rotation_recovers_input(self):
185
  """Rotating by freqs then by conj(freqs) (inverse) must recover the original."""
186
  dim = 16
187
  freqs = precompute_rope_freqs(dim=dim, max_len=8)
188
  x = torch.randn(2, 4, 3, dim)
189
+ rotated = apply_rope(x, freqs[:4])
190
  xc = torch.view_as_complex(rotated.float().reshape(*rotated.shape[:-1], -1, 2))
191
+ inv = freqs.conj()[:4].unsqueeze(0).unsqueeze(2)
192
  recovered = torch.view_as_real(xc * inv).flatten(-2).to(x.dtype)
193
  assert torch.allclose(x, recovered, atol=1e-5)
194
 
 
199
  torch.manual_seed(7)
200
  x_a = torch.randn(1, 4, 2, dim)
201
  x_b = torch.randn(1, 4, 2, dim)
202
+ solo = apply_rope(x_a, freqs[:4])
203
+ batched = apply_rope(torch.cat([x_a, x_b], dim=0), freqs[:4])[:1]
204
  assert torch.allclose(solo, batched, atol=1e-6)
205
 
206
  def test_head_independence(self):
 
208
  dim = 16
209
  freqs = precompute_rope_freqs(dim=dim, max_len=8)
210
  x = torch.randn(1, 4, 1, dim).expand(1, 4, 3, dim).contiguous()
211
+ out = apply_rope(x, freqs[:4])
212
  assert torch.allclose(out[:, :, 0], out[:, :, 1], atol=1e-6)
213
  assert torch.allclose(out[:, :, 1], out[:, :, 2], atol=1e-6)
214
 
 
227
  """Rotate tensor at a specific position by embedding it in a zero sequence."""
228
  seq = torch.zeros(1, pos + 1, 1, dim)
229
  seq[0, pos] = tensor[0, 0]
230
+ return apply_rope(seq, freqs[: pos + 1])[:, pos : pos + 1]
231
 
232
  # Both pairs have relative offset n - m = 6
233
  dot_3_9 = (rope_at(q, 3) * rope_at(k, 9)).sum()
tests/test_rope_debug.py ADDED
@@ -0,0 +1,195 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ """
2
+ Standalone RoPE debug test β€” logs tensor outputs and intermediate calculations
3
+ so you can visually verify correctness of precompute_rope_freqs and apply_rope.
4
+ """
5
+
6
+ import torch
7
+ from open_mythos.main import apply_rope, precompute_rope_freqs
8
+
9
+ DIM = 8
10
+ MAX_LEN = 6
11
+ THETA = 500000.0
12
+
13
+
14
+ def section(title: str) -> None:
15
+ print(f"\n{'=' * 60}")
16
+ print(f" {title}")
17
+ print("=" * 60)
18
+
19
+
20
+ def log(label: str, value) -> None:
21
+ print(f"\n[{label}]")
22
+ print(value)
23
+
24
+
25
+ # ---------------------------------------------------------------------------
26
+ # 1. precompute_rope_freqs β€” raw frequency table
27
+ # ---------------------------------------------------------------------------
28
+
29
+ section("1. precompute_rope_freqs")
30
+
31
+ freqs = precompute_rope_freqs(dim=DIM, max_len=MAX_LEN, theta=THETA)
32
+ log("freqs shape", freqs.shape)
33
+ log("freqs (complex)", freqs)
34
+ log("freqs.real", freqs.real)
35
+ log("freqs.imag", freqs.imag)
36
+ log("freqs magnitude (should be all 1.0)", freqs.abs())
37
+ log("freqs angle (radians)", freqs.angle())
38
+
39
+ print("\nExpected: base frequencies (1 per dim pair)")
40
+ base = 1.0 / (THETA ** (torch.arange(0, DIM, 2, dtype=torch.float32) / DIM))
41
+ log("base freqs", base)
42
+
43
+ print("\nExpected: freqs[t, k].angle() == t * base[k]")
44
+ for t in range(MAX_LEN):
45
+ expected_angles = t * base
46
+ actual_angles = freqs[t].angle()
47
+ match = torch.allclose(actual_angles, expected_angles, atol=1e-5)
48
+ print(f" t={t}: angles match = {match} actual={actual_angles.tolist()}")
49
+
50
+ # ---------------------------------------------------------------------------
51
+ # 2. Position 0 is identity (freqs[0] == 1+0j)
52
+ # ---------------------------------------------------------------------------
53
+
54
+ section("2. freqs[0] is identity phasor (1+0j)")
55
+ log("freqs[0]", freqs[0])
56
+ print(f" All magnitude=1: {torch.allclose(freqs[0].abs(), torch.ones(DIM // 2))}")
57
+ print(f" All angle=0: {torch.allclose(freqs[0].angle(), torch.zeros(DIM // 2))}")
58
+
59
+ # ---------------------------------------------------------------------------
60
+ # 3. apply_rope β€” shape and dtype
61
+ # ---------------------------------------------------------------------------
62
+
63
+ section("3. apply_rope β€” shape and dtype")
64
+
65
+ B, T, H = 2, MAX_LEN, 3
66
+ x = torch.randn(B, T, H, DIM)
67
+ log("input x shape", x.shape)
68
+
69
+ out = apply_rope(x, freqs)
70
+ log("output shape", out.shape)
71
+ print(f" Shape preserved: {out.shape == x.shape}")
72
+
73
+ # dtype float16
74
+ x_half = x.half()
75
+ out_half = apply_rope(x_half, freqs)
76
+ print(f" float16 dtype preserved: {out_half.dtype == torch.float16}")
77
+
78
+ # ---------------------------------------------------------------------------
79
+ # 4. apply_rope β€” isometry (norm preservation)
80
+ # ---------------------------------------------------------------------------
81
+
82
+ section("4. apply_rope β€” norm preservation (isometry)")
83
+
84
+ norms_in = x.norm(dim=-1)
85
+ norms_out = out.norm(dim=-1)
86
+ log("input norms (first batch item)", norms_in[0])
87
+ log("output norms (first batch item)", norms_out[0])
88
+ print(
89
+ f" Max absolute norm difference: {(norms_in - norms_out).abs().max().item():.2e}"
90
+ )
91
+ print(
92
+ f" Norms preserved (atol=1e-5): {torch.allclose(norms_in, norms_out, atol=1e-5)}"
93
+ )
94
+
95
+ # ---------------------------------------------------------------------------
96
+ # 5. Position 0 is the identity transformation
97
+ # ---------------------------------------------------------------------------
98
+
99
+ section("5. Position 0 is identity transformation")
100
+
101
+ x1 = torch.randn(1, 1, 2, DIM)
102
+ out1 = apply_rope(x1, freqs[:1])
103
+ log("input x[:,0]", x1[0, 0])
104
+ log("output x[:,0]", out1[0, 0])
105
+ log("diff (should be ~0)", (x1 - out1).abs())
106
+ print(f" Identity at pos 0: {torch.allclose(x1, out1, atol=1e-6)}")
107
+
108
+ # ---------------------------------------------------------------------------
109
+ # 6. Different positions produce different rotations
110
+ # ---------------------------------------------------------------------------
111
+
112
+ section("6. Different positions produce different rotations")
113
+
114
+ x2 = torch.ones(1, MAX_LEN, 1, DIM)
115
+ out2 = apply_rope(x2, freqs)
116
+ print("Per-position outputs (all input=1.0):")
117
+ for t in range(MAX_LEN):
118
+ print(f" pos={t}: {out2[0, t, 0].tolist()}")
119
+
120
+ # ---------------------------------------------------------------------------
121
+ # 7. Inverse rotation recovers original
122
+ # ---------------------------------------------------------------------------
123
+
124
+ section("7. Inverse rotation recovers original")
125
+
126
+ x3 = torch.randn(2, T, H, DIM)
127
+ rotated = apply_rope(x3, freqs)
128
+ xc = torch.view_as_complex(rotated.float().reshape(*rotated.shape[:-1], -1, 2))
129
+ inv_freqs = freqs.conj()
130
+ recovered = (
131
+ torch.view_as_real(xc * inv_freqs.unsqueeze(0).unsqueeze(2))
132
+ .flatten(-2)
133
+ .to(x3.dtype)
134
+ )
135
+ diff = (x3 - recovered).abs()
136
+ log("max recovery error", diff.max().item())
137
+ print(f" Recovery succeeded (atol=1e-5): {torch.allclose(x3, recovered, atol=1e-5)}")
138
+
139
+ # ---------------------------------------------------------------------------
140
+ # 8. start_pos correctness β€” the core generation bug fix
141
+ # ---------------------------------------------------------------------------
142
+
143
+ section("8. start_pos correctness (generation bug)")
144
+
145
+ print(
146
+ "Simulates: prompt of length 4, then decode step receives token at position 4.\n"
147
+ "Old buggy code: freqs[:1] β†’ position 0 always.\n"
148
+ "Fixed code: freqs[4:5] β†’ position 4."
149
+ )
150
+
151
+ prompt_len = 4
152
+ decode_token = torch.randn(1, 1, 1, DIM)
153
+
154
+ out_buggy = apply_rope(decode_token, freqs[:1]) # wrong: always pos 0
155
+ out_fixed = apply_rope(
156
+ decode_token, freqs[prompt_len : prompt_len + 1]
157
+ ) # correct: pos 4
158
+
159
+ log("freqs[0] (pos 0)", freqs[0])
160
+ log(f"freqs[{prompt_len}] (pos {prompt_len})", freqs[prompt_len])
161
+ log("buggy output (pos 0 encoding)", out_buggy[0, 0, 0])
162
+ log("fixed output (pos 4 encoding)", out_fixed[0, 0, 0])
163
+ print(f" Outputs differ (they should): {not torch.allclose(out_buggy, out_fixed)}")
164
+
165
+ # ---------------------------------------------------------------------------
166
+ # 9. Relative position property
167
+ # ---------------------------------------------------------------------------
168
+
169
+ section("9. Relative position property: <RoPE(q,m), RoPE(k,n)> depends only on (n-m)")
170
+
171
+ dim = 16
172
+ max_len = 32
173
+ freqs_big = precompute_rope_freqs(dim=dim, max_len=max_len, theta=THETA)
174
+ torch.manual_seed(42)
175
+ q = torch.randn(1, 1, 1, dim)
176
+ k = torch.randn(1, 1, 1, dim)
177
+
178
+
179
+ def rope_at(tensor, pos):
180
+ seq = torch.zeros(1, pos + 1, 1, dim)
181
+ seq[0, pos] = tensor[0, 0]
182
+ return apply_rope(seq, freqs_big[: pos + 1])[:, pos : pos + 1]
183
+
184
+
185
+ dot_3_9 = (rope_at(q, 3) * rope_at(k, 9)).sum()
186
+ dot_1_7 = (rope_at(q, 1) * rope_at(k, 7)).sum()
187
+ dot_0_6 = (rope_at(q, 0) * rope_at(k, 6)).sum()
188
+ print(f" <RoPE(q,3), RoPE(k,9)>: {dot_3_9.item():.6f}")
189
+ print(f" <RoPE(q,1), RoPE(k,7)>: {dot_1_7.item():.6f}")
190
+ print(f" <RoPE(q,0), RoPE(k,6)>: {dot_0_6.item():.6f}")
191
+ print(
192
+ f" All equal (offset=6): {torch.allclose(dot_3_9, dot_1_7, atol=1e-5) and torch.allclose(dot_1_7, dot_0_6, atol=1e-5)}"
193
+ )
194
+
195
+ section("DONE β€” all checks complete")
training/3b_fine_web_edu.py CHANGED
@@ -113,7 +113,9 @@ def main():
113
  master = rank == 0
114
 
115
  if master:
116
- print(f"GPUs: {torch.cuda.device_count()} | World size: {world_size} | Device: {device}")
 
 
117
 
118
  # ------------------------------------------------------------------
119
  # Tokenizer
 
113
  master = rank == 0
114
 
115
  if master:
116
+ print(
117
+ f"GPUs: {torch.cuda.device_count()} | World size: {world_size} | Device: {device}"
118
+ )
119
 
120
  # ------------------------------------------------------------------
121
  # Tokenizer