RoPE, RMSNorm and SwiGLU written next to the 2019 pieces they replace — and the duel with the same corpus, the same budget and the same seed.
A new piece is presented under the conditions in which it was developed: another scale, other data, another budget. A gain measured there does not automatically carry over here. That is why this chapter redoes the comparison under our conditions — same data, same seed, same number of steps — knowing that a fair comparison is laborious and expensive.
There are three traps, and all of them were deliberately avoided here:
| Trap | What happens | Our lock |
|---|---|---|
| Different implementations | the new piece comes from an optimized repository, the old one from a sketch | both in the same file, same training loop, chosen by Config(moderno=…) |
| Different parameter counts | the new one wins because it is bigger | 17.1 M (faithful) against 16.9 M (modern) at the pequeno size |
| Different budget or data | the new one saw more | same treino.bin, same 700 steps, same seed 1337 |
A comparison is only worth anything if the only thing that differs is what is being tested. That is why the duel's data file was frozen in model/data/duelo_200mb/ and the packing script now refuses to overwrite without an explicit confirmation — a corpus swapped in the middle of a series would silently invalidate everything.
The interview (chapter 10) answers a question this duel raises: JEPA is not an alternative to the Transformer — it is a macro-architecture, and the Transformer is the encoder inside it. The two variants in this file, classic and modern, are the same module with different parts; the mundo predictor and the cost in the latent sit on top, the same for both. The duel below chooses the encoder; it does not choose the paradigm.
wpe → RoPEGPT-2 has a wpe table with exactly 1024 rows — one per possible position. It is a learned vector, added to the token embedding. It works, and it has two hard consequences.
The first is the wall. We measured it directly:
contexto 512 → 29,6 ms/token contexto 1000 → 34,6 ms/token contexto 1024 → PAREDE: posição 1024 não existe na wpe (ela tem 1024 linhas: 0 a 1023)
It is not a configuration limit. It is a matrix that runs out. To extend the context, new rows have to be created — rows that were never trained.
RoPE (Su et al., 2021) solves it by construction: instead of adding a position vector, it rotates the query and key vectors by an angle proportional to the position. Since the dot product between two rotated vectors depends only on the difference of the angles, attention comes to see relative distance — and there is no table to run out.
wpe is giving each person in the queue a badge with their number. If the queue goes past 1024, the badges run out.
RoPE is rotating each person by an angle proportional to their position. Two people three places apart always have the same angle difference, whether the pair is at the front or the back of the queue — and the queue can be as long as you like.
Cost in parameters: the wpe of mini has 1024 × 256 = 262,144 weights. RoPE has zero — it is a computed rotation, not a learned one.
LayerNorm does two things: it centers (subtracts the mean) and scales (divides by the standard deviation). RMSNorm (Zhang & Sennrich, 2019) observed that centering contributes almost nothing, and removed it:
Gone is the mean subtraction and gone is the shift parameter
GPT-2's block expands to 4
class SwiGLU(nn.Module): def forward(self, x): return self.w_down(F.silu(self.w_portao(x)) * self.w_cima(x))
That is three matrices instead of two, so the inner dimension shrinks (from 4
Llama removed the
mini size (7.4 M), 700 steps, 5.7 M tokens, same seed. Only the architecture changes.
| Architecture | Validation | PPL | at step |
|---|---|---|---|
| classic (faithful GPT-2) | 5.5391 | 254.45 | 649 |
| modern | 5.2616 | 192.79 | 549 |
−24% perplexity, and 100 fewer steps to reach the best point. On the four bits/character measurements:
| Architecture | Portuguese | English | Code | Mean |
|---|---|---|---|---|
| classic | 2.439 | 2.066 | 2.522 | 2.342 |
| modern | 2.406 | 2.038 | 2.405 | 2.283 |
One run per architecture, one seed, one size, 5.7 M tokens — 0.09 epoch of the corpus. This shows that the modern architecture is better at this scale, with this budget. It does not show which piece contributed: RoPE, RMSNorm, SwiGLU and the biases all changed together.
Isolating each one would require four extra runs. It is on the list, not in the result.
During the duel, the classic model's validation rose between steps 649 and 699 (5.539 → 5.577). The immediate reading was "it started overfitting". It was wrong.
With 5.7 M tokens out of a 60.3 M corpus, the model saw 0.09 of an epoch — no token was seen twice. Overfitting is impossible by construction. What rose was evaluation noise added to the closing of the learning-rate cosine.
| Piece | Classic | Modern |
|---|---|---|
| Position | wpe: 1024×384 = 393,216 | RoPE: 0 |
| Normalization | LayerNorm: gain + bias | RMSNorm: gain only |
| MLP | 2 matrices, GELU | 3 matrices, SwiGLU (dim ⅔) |
| Linears | with bias | without bias |
| Total | 17.1 M | 16.9 M |
The modern one is slightly smaller and still wins. It is the strongest argument in the table: it is not more capacity, it is better use of the same.
Everything in model/architecture.py, with each modern piece written immediately next to the classic one it replaces:
def normalizador(cfg, d): return RMSNorm(d) if cfg.moderno else nn.LayerNorm(d) # in the Transformer: if cfg.moderno: self.wpe = None # RoPE goes into attention rope = RoPE(cfg.d_k, cfg.n_ctx, cfg.rope_base) else: self.wpe = nn.Embedding(cfg.n_ctx, cfg.d_model) rope = None
Reading that file top to bottom is seeing the difference between 2019 and today in ~400 lines. That was the goal — more than having both options, having both legible side by side.
With the architecture settled, chapter 03 looks inside the embedding table —
and finds a problem that none of these modern pieces fixes.