LLaMA: Open and Efficient Foundation Language Models
Not a new idea. A recipe. Explained as a diff, because that is what it is.
Download llama_from_scratch.py — everything below runs from it. NumPy only.
I'm an AI Engineer. I've served LLaMA-family models in production, quantised them, put them behind a load balancer, and I still couldn't have told you what was in one. I assumed there was a new mechanism. There isn't. LLaMA is the 2017 transformer with four config changes and a lot more data. The interview question is never "how does LLaMA work" — it's "what's different from the original transformer, and why". This is that answer.
LLaMA is a pull request against the 2017 transformer
You know what a diff looks like. Here is the whole architecture as one, against the decoder-only transformer — the GPT shape, causal mask and all:
-norm: layernorm, after the residual add
+norm: rmsnorm, before each sublayer
-activation: relu, hidden = 4d
+activation: swiglu, hidden = 2/3 * 4d rounded up to a multiple of 256
-position: sinusoid, added to the embeddings once at the bottom
+position: rope, rotate Q and K inside every attention layer
-bias: true
+bias: false
tokens: 1T to 1.4T, public data only # not a hunk. The real change.
Four hunks. The attention itself is untouched — it's the lookup from the attention page. Three hunks are named in the paper's architecture section; the fourth (no bias) is only in the released code. And the line that isn't a hunk is the one that mattered.
| Term | Means | Programmer's analogy |
|---|---|---|
| RMSNorm | Rescale each token's vector so its root-mean-square is 1. No mean subtraction. | Lower-casing before a string compare. Hygiene, not logic. |
| Pre-norm | The norm sits on the branch, not the trunk: x + f(norm(x)) | Middleware that reads a normalised copy of the request and never rewrites the request. |
| SwiGLU | FFN with a multiplicative gate: one branch scales the other. | A feature flag that's a float, not a bool. |
| RoPE | Rotate Q and K by their position before the dot product. | Fold a timestamp into the hash so equal offsets compare equal. |
| 1T tokens | Train 7× past the compute-optimal point. | Precompute. Pay at build time so every request is cheaper. |
The one formula
y = h + Wdown(SiLU(Wgate RMSNorm(h)) ⊙ Wup RMSNorm(h))
One block. Stack 32 for 7B, 80 for 65B. Read it as pipeline stages:
| Step | Shape | What it does |
|---|---|---|
RMSNorm(x) | (n, d) | Every token onto the same scale. Reads the trunk, doesn't write it. |
Attn(·) | (n, d) | The lookup from attention, RoPE applied to Q and K inside |
x + … | (n, d) | The skip link. Adds. Never rescales. This is the pre-norm hunk. |
Wgatez, Wupz | (n, h), h = 11008 | Two branches off the same normalised input |
SiLU(·) ⊙ · | (n, h) | The gate. One branch scales the other, elementwise |
Wdown | (n, d) | Back to trunk width, added to the trunk. Shape in equals shape out, so it stacks |
| after block 32 | (n, 32000) | One final RMSNorm, then the output head. No bias anywhere |
RMSNorm: LayerNorm minus the mean
LayerNorm subtracts the mean, divides by the standard deviation, then a learned scale γ and shift β. RMSNorm (Zhang and Sennrich, 2019) skips the mean and the shift: divide by the root-mean-square, multiply by γ. Same job — every token onto the same scale before it hits a matmul — with fewer passes over the vector, and no quality loss they could measure.
Don't take my word for it. Here's the actual output:
x = [1. 2. 3. 4.] mean 2.5000
layer_norm = [-1.3416 -0.4472 0.4472 1.3416] mean 0.0000
rms_norm = [0.3651 0.7303 1.0954 1.4606] mean 0.9129
same numbers? False ← the mean is still in there
x centred = [-1.5 -0.5 0.5 1.5] mean 0.0000
layer_norm = [-1.3416 -0.4472 0.4472 1.3416]
rms_norm = [-1.3416 -0.4472 0.4472 1.3416]
same numbers? True ← identical once centred
On a centred vector they are the same function; real activations aren't centred, so the outputs differ by exactly the mean. What you buy, counted off my implementation — seven passes over the vector versus four:
elementwise ops per token per norm at d=4096:
layer_norm 28,672 rms_norm 16,384 saved 12,288
x 2 norms per layer x 32 layers = 786,432 ops saved per token
rms_norm(zeros, eps=0) = [nan nan nan nan]
rms_norm(zeros, eps=1e-5) = [0. 0. 0. 0.] ✓
Nothing next to a 4096 × 11008 matmul, but it's paid on every token, on
every layer, for free. And eps is not decoration: 0/0 is NaN,
and one NaN in the residual stream poisons every layer above it.
Pre-norm: the norm goes on the branch, not the trunk
2017: x = norm(x + f(x)). The norm sits on the trunk; every
block's output is renormalised before the next block sees it. LLaMA:
x = x + f(norm(x)). The norm sits on the branch; the trunk is
a straight wire from embedding to output, and every block only ever
adds to it. You will read that pre-norm "keeps the activations
bounded". Run demo 3. It doesn't — post-norm does. What pre-norm keeps
bounded is something else:
d=64, sqrt(d)=8.0. residual-stream norm per token:
layer 1 layer 5 layer 10 layer 20 bump at 0 -> out
pre-norm (LLaMA) 10.8 18.9 27.7 40.7 x 1.77
post-norm (2017) 8 8 8 8 x 9.71
no norm at all 12 6.23e+05 inf nan x nan
Post-norm pins the trunk at √d = 8 exactly. Pre-norm lets it grow, and
nobody cares, because no sublayer ever reads x, only
rms_norm(x). Delete the norm and the trunk overflows before
layer 10 — which is why there's a norm in there at all.
The last column is the one that matters. It's a finite-difference gradient: bump the input by a tiny δ, measure how far the output moves, divide — the trick you'd use to unit-test a hand-written backward pass. It's how much a change at layer 0 is amplified by the top, which is also how much a gradient at the top is amplified on its way down to layer 0. Now go deeper:
same bump at layer 0, deeper and deeper stacks:
5 deep 10 deep 20 deep 32 deep
pre-norm (LLaMA) x 1.47 x 1.58 x 1.77 x 1.68
post-norm (2017) x 1.69 x 2.74 x 9.71 x 40.6 ← compounds
SwiGLU: a gate in the FFN, paid for with a narrower hidden layer
The 2017 FFN is relu(x @ W1) @ W2, hidden width 4d. SwiGLU
(Shazeer, 2020) replaces it
with (silu(x @ W_gate) * (x @ W_up)) @ W_down. Two branches see
the same input; one is squashed through SiLU and multiplies the other,
elementwise. A gate. Three matrices instead of two, so to keep the parameter
count level the hidden width is cut to two-thirds, then rounded up to a
multiple of 256 because GPUs like that. Hence 11008, not 10923.
d = 4096
vanilla ReLU FFN: hidden = 4d = 16384 params = 2*d*h = 134,217,728
LLaMA SwiGLU FFN: hidden = 2/3*4d = 11008 params = 3*d*h = 135,266,304
ratio SwiGLU / ReLU = 1.008
BREAK: SwiGLU with hidden = 4d = 16384: params = 201,326,592 (+50.0%)
Within 1%. That's the paper's point: the comparison is parameter-matched, so any benchmark gain is the gate, not extra capacity. Leave the hidden at 4d and you've grown the FFN by half and proved nothing. Demo 2 also runs a toy forward pass: ReLU switches 49.0% of hidden units exactly off, SwiGLU 0.0% — a switch versus a dimmer. Why does the dimmer help? Nobody knows. Shazeer's paper says so: "we attribute their success, as all else, to divine benevolence". Don't let an interviewer tell you there's a derivation.
RoPE: rotate Q and K, leave V alone
2017 added a position vector to the embeddings once, at the bottom. LLaMA adds nothing there. Inside every attention layer it rotates each query and key by an angle proportional to its position (Su et al., 2021). Rotate both by the same rule and the dot product depends only on the gap:
q at 3 . k at 1 (offset 2) = 1.7986
q at 7 . k at 5 (offset 2) = 1.7986 ← same offset, same score
q at 3 . k at 0 (offset 3) = 1.7523 ← different offset
where it is applied, straight from attn_sublayer's source:
return attention(rope(Q, pos), rope(K, pos), V) @ p["Wo"] # rope: Q, K. Not V
It goes on Q and K because those are the two things being dotted. V is the content handed over once a match is made; rotate that and the content would depend on where the token sits. The pairs, the frequencies, why it extrapolates: the RoPE page. Here the point is where it's called.
6.7B parameters, 1T tokens: the arithmetic
Every LLaMA-1 size is one formula with four numbers from the paper's Table 2. Attention is 4d² (four d × d matrices — no GQA yet, that's LLaMA 2 at 70B), the FFN is 3dh, two norms per layer, an embedding table, an untied output head:
size dim layers ffn count_params paper
7B 4096 32 11008 6,738,415,616 6.7B ✓
13B 5120 40 13824 13,015,864,320 13.0B
33B 6656 60 17920 32,528,943,616 32.5B
65B 8192 80 22016 65,285,660,672 65.2B
BREAK: put every bias term back (bias=True):
6,740,037,632 vs 6,738,415,616: +1,622,016 = +0.024%
Within rounding of every row in the paper. The FFN is twice the attention — which is why mixture-of-experts goes after the FFN. Dropping the biases was a simplicity decision, not a savings one: 0.024%. The paper doesn't mention it. Now the tokens. Chinchilla (Hoffmann et al., 2022) answered one question: for a fixed training budget, what model size and how many tokens minimise the loss? About 20 tokens per parameter, so a 7B model "should" see 135B tokens. LLaMA-7B saw a trillion.
LLaMA-7B parameters 6.74e+09
Chinchilla-optimal tokens, 20/param 1.35e+11 (135B)
LLaMA-7B actually trained on 1e+12 (1T) 7.4x more
inference compute ~ 2 * N per token:
LLaMA-7B 1.35e+10 FLOPs/token
GPT-3 175B 3.5e+11 FLOPs/token 26x more
tokens per parameter:
GPT-3 175B 300B / 175.0B = 1.7
LLaMA-7B 1.0T / 6.7B = 148.4
LLaMA-13B 1.0T / 13.0B = 76.8
The argument is a cost model you already run in your head. Training is paid once. Inference is paid per token, forever, and scales with parameter count: 7B costs 26× less per token than 175B. Serving a billion requests, you'd rather overspend training a small model than underspend and serve a big one. The paper's training curves show the 7B loss still falling at a trillion tokens — slower than at 135B, but falling. And that, not the four hunks, is why LLaMA-13B beat GPT-3 175B on most benchmarks in the paper: GPT-3 saw 1.7 tokens per parameter, LLaMA-13B saw 77.
நான் ஒரு AI Engineer. LLaMA-family models-ஐ production-ல் serve செய்திருக்கிறேன், quantise செய்திருக்கிறேன், load balancer-க்குப் பின்னால் வைத்திருக்கிறேன் — இருந்தும் ஒன்றுக்கு உள்ளே என்ன இருக்கிறது என்று சொல்ல முடியாது. புதிதாக ஒரு mechanism இருக்கும் என்று நினைத்தேன். இல்லை. LLaMA என்பது 2017-ஆம் ஆண்டு transformer-தான் — நான்கு config மாற்றங்கள், மற்றும் நிறைய data. Interview கேள்வி ஒருபோதும் "LLaMA எப்படி வேலை செய்கிறது" அல்ல — "original transformer-லிருந்து என்ன வேறுபாடு, ஏன்" என்பதுதான். இது அந்த விடை.
LLaMA என்பது 2017 transformer-க்கு எதிரான ஒரு pull request
Diff எப்படி இருக்கும் என்று உங்களுக்குத் தெரியும். முழு architecture-ம் ஒரே diff-ஆக, decoder-only transformer-க்கு எதிராக — அதாவது GPT வடிவம், causal mask எல்லாம் சேர்த்து:
-norm: layernorm, after the residual add
+norm: rmsnorm, before each sublayer
-activation: relu, hidden = 4d
+activation: swiglu, hidden = 2/3 * 4d rounded up to a multiple of 256
-position: sinusoid, added to the embeddings once at the bottom
+position: rope, rotate Q and K inside every attention layer
-bias: true
+bias: false
tokens: 1T to 1.4T, public data only # hunk அல்ல. உண்மையான மாற்றம்.
நான்கு hunks. Attention-ஐ யாரும் தொடவில்லை — அது attention பக்கத்தில் உள்ள அதே lookup-தான். மூன்று hunks கட்டுரையின் architecture பகுதியில் பெயரிடப்பட்டுள்ளன; நான்காவது (bias இல்லை) வெளியிடப்பட்ட code-ல் மட்டுமே இருக்கிறது. Hunk அல்லாத அந்த ஒரு வரிதான் உண்மையில் முக்கியமானது.
| சொல் | அர்த்தம் | Programmer-க்கான உதாரணம் |
|---|---|---|
| RMSNorm | ஒவ்வொரு token-ன் vector-ஐ அதன் root-mean-square 1 ஆகும்படி அளவிடு. Mean கழிப்பது இல்லை. | String compare-க்கு முன் lower-case ஆக்குவது. சுத்தம், logic அல்ல. |
| Pre-norm | Norm branch-ல் இருக்கிறது, trunk-ல் அல்ல: x + f(norm(x)) | Request-ன் normalise செய்யப்பட்ட நகலைப் படிக்கும் middleware — request-ஐ ஒருபோதும் திருத்தாது. |
| SwiGLU | பெருக்கல் gate கொண்ட FFN: ஒரு branch மற்றொன்றை அளவிடுகிறது. | Bool அல்ல, float ஆக இருக்கும் feature flag. |
| RoPE | Dot product-க்கு முன் Q மற்றும் K-ஐ அவற்றின் position-ஐப் பொறுத்துச் சுழற்று. | Timestamp-ஐ hash-க்குள் மடித்து வைப்பது — சம offset-கள் சமமாகப் பொருந்தும். |
| 1T tokens | Compute-optimal புள்ளியைத் தாண்டி 7× பயிற்சி. | Precompute. Build நேரத்தில் செலவழி, ஒவ்வொரு request-ம் மலிவாகும். |
LLaMA-la puthu mechanism ondrum illa. 2017 transformer mela naalu config maatram: RMSNorm, pre-norm, SwiGLU, RoPE — plus 7 madangu data.
அந்த ஒரு சூத்திரம்
y = h + Wdown(SiLU(Wgate RMSNorm(h)) ⊙ Wup RMSNorm(h))
ஒரு block. 7B-க்கு 32, 65B-க்கு 80 அடுக்குங்கள். Pipeline stages-ஆகப் படியுங்கள்:
| படி | Shape | என்ன செய்கிறது |
|---|---|---|
RMSNorm(x) | (n, d) | ஒவ்வொரு token-ஐயும் ஒரே அளவுக்குக் கொண்டு வருகிறது. Trunk-ஐப் படிக்கிறது, எழுதுவதில்லை. |
Attn(·) | (n, d) | Attention-லிருந்து வரும் அதே lookup; உள்ளே Q, K-க்கு RoPE பயன்படுத்தப்படுகிறது |
x + … | (n, d) | Skip link. கூட்டுகிறது. ஒருபோதும் மறுஅளவீடு செய்வதில்லை. இதுதான் pre-norm hunk. |
Wgatez, Wupz | (n, h), h = 11008 | ஒரே normalise செய்யப்பட்ட input-லிருந்து இரண்டு branches |
SiLU(·) ⊙ · | (n, h) | Gate. ஒரு branch மற்றொன்றை elementwise ஆக அளவிடுகிறது |
Wdown | (n, d) | Trunk அகலத்துக்குத் திரும்பி, trunk-உடன் கூட்டப்படுகிறது. உள்ளே வரும் shape-ம் வெளியே வரும் shape-ம் ஒன்றே, எனவே அடுக்கலாம் |
| block 32-க்குப் பிறகு | (n, 32000) | இறுதியாக ஒரு RMSNorm, பிறகு output head. எங்கும் bias இல்லை |
RMSNorm: LayerNorm, mean இல்லாமல்
LayerNorm mean-ஐக் கழிக்கிறது, standard deviation-ஆல் வகுக்கிறது, பிறகு கற்றுக்கொள்ளப்பட்ட scale γ மற்றும் shift β. RMSNorm (Zhang and Sennrich, 2019) mean-ஐயும் shift-ஐயும் தவிர்க்கிறது: root-mean-square-ஆல் வகு, γ-ஆல் பெருக்கு. வேலை ஒன்றுதான் — matmul-ஐ அடையும் முன் ஒவ்வொரு token-ஐயும் ஒரே அளவுக்குக் கொண்டு வருவது — ஆனால் vector மீது குறைவான passes, மேலும் அவர்களால் அளக்க முடிந்த அளவுக்கு எந்தத் தரக் குறைவும் இல்லை.
என் வார்த்தையை நம்ப வேண்டாம். இதோ உண்மையான output:
x = [1. 2. 3. 4.] mean 2.5000
layer_norm = [-1.3416 -0.4472 0.4472 1.3416] mean 0.0000
rms_norm = [0.3651 0.7303 1.0954 1.4606] mean 0.9129
same numbers? False ← mean இன்னும் உள்ளே இருக்கிறது
x centred = [-1.5 -0.5 0.5 1.5] mean 0.0000
layer_norm = [-1.3416 -0.4472 0.4472 1.3416]
rms_norm = [-1.3416 -0.4472 0.4472 1.3416]
same numbers? True ← centre செய்தால் ஒரே மாதிரி
Centre செய்யப்பட்ட vector-ல் இரண்டும் ஒரே function; உண்மையான activations centre செய்யப்பட்டவை அல்ல, எனவே outputs சரியாக mean அளவுக்கு வேறுபடுகின்றன. நீங்கள் பெறுவது என்ன — என் implementation-லிருந்து எண்ணியது, vector மீது ஏழு passes-க்குப் பதிலாக நான்கு:
elementwise ops per token per norm at d=4096:
layer_norm 28,672 rms_norm 16,384 saved 12,288
x 2 norms per layer x 32 layers = 786,432 ops saved per token
rms_norm(zeros, eps=0) = [nan nan nan nan]
rms_norm(zeros, eps=1e-5) = [0. 0. 0. 0.] ✓
4096 × 11008 matmul-உடன் ஒப்பிட்டால் இது ஒன்றுமில்லை, ஆனால் இது ஒவ்வொரு
token-க்கும், ஒவ்வொரு layer-க்கும், இலவசமாகக் கிடைக்கிறது. மேலும்
eps அலங்காரம் அல்ல: 0/0 என்பது NaN, மேலும் residual stream-ல்
ஒரு NaN இருந்தால் அதற்கு மேலுள்ள ஒவ்வொரு layer-ஐயும் நச்சாக்கிவிடும்.
Pre-norm: norm branch-ல் போகிறது, trunk-ல் அல்ல
2017: x = norm(x + f(x)). Norm trunk-ல் அமர்ந்திருக்கிறது;
அடுத்த block பார்ப்பதற்கு முன் ஒவ்வொரு block-ன் output-ம் மீண்டும்
normalise செய்யப்படுகிறது. LLaMA: x = x + f(norm(x)). Norm
branch-ல் அமர்ந்திருக்கிறது; trunk என்பது embedding-லிருந்து output வரை
ஒரு நேரடிக் கம்பி, ஒவ்வொரு block-ம் அதனுடன் கூட்டுவது மட்டுமே
செய்கிறது. Pre-norm "activations-ஐ வரம்புக்குள் வைத்திருக்கிறது" என்று
நீங்கள் படிப்பீர்கள். Demo 3-ஐ ஓட்டுங்கள். அது வைப்பதில்லை — post-norm-தான்
வைக்கிறது. Pre-norm வரம்புக்குள் வைப்பது வேறொன்று:
d=64, sqrt(d)=8.0. residual-stream norm per token:
layer 1 layer 5 layer 10 layer 20 bump at 0 -> out
pre-norm (LLaMA) 10.8 18.9 27.7 40.7 x 1.77
post-norm (2017) 8 8 8 8 x 9.71
no norm at all 12 6.23e+05 inf nan x nan
Post-norm trunk-ஐ சரியாக √d = 8-ல் பிடித்து வைக்கிறது. Pre-norm அது
வளர அனுமதிக்கிறது, யாரும் கவலைப்படுவதில்லை, ஏனெனில் எந்த sublayer-ம்
x-ஐப் படிப்பதில்லை, rms_norm(x)-ஐ மட்டுமே
படிக்கிறது. Norm-ஐ நீக்கினால் trunk layer 10-க்கு முன்பே overflow ஆகிறது —
அதனால்தான் அங்கே ஒரு norm இருக்கிறது.
கடைசி column-தான் முக்கியமானது. அது ஒரு finite-difference gradient: input-ஐ ஒரு சிறிய δ அளவு அசைத்து, output எவ்வளவு நகர்கிறது என்று அளந்து, வகுக்கவும் — கையால் எழுதிய backward pass-ஐ unit-test செய்யப் பயன்படுத்தும் அதே தந்திரம். Layer 0-ல் நடக்கும் ஒரு மாற்றம் மேல் முனையால் எவ்வளவு பெருக்கப்படுகிறது என்பதை இது காட்டுகிறது; அதுவே மேல் முனையில் உள்ள ஒரு gradient layer 0-க்கு இறங்கும் வழியில் எவ்வளவு பெருக்கப்படுகிறது என்பதும் ஆகும். இப்போது ஆழமாகப் போகலாம்:
same bump at layer 0, deeper and deeper stacks:
5 deep 10 deep 20 deep 32 deep
pre-norm (LLaMA) x 1.47 x 1.58 x 1.77 x 1.68
post-norm (2017) x 1.69 x 2.74 x 9.71 x 40.6 ← கூட்டுப்பெருக்கம்
Pre-norm activations-ai bound panradhu illa — post-norm dhaan. Pre-norm bound panradhu gradient ratio: post-norm depth-oda perukkidhu, pre-norm koottudhu.
SwiGLU: FFN-ல் ஒரு gate, குறுகிய hidden layer-ஆல் விலை கொடுக்கப்படுகிறது
2017-ன் FFN என்பது relu(x @ W1) @ W2, hidden அகலம் 4d. SwiGLU
(Shazeer, 2020) அதை
(silu(x @ W_gate) * (x @ W_up)) @ W_down ஆக மாற்றுகிறது.
இரண்டு branches ஒரே input-ஐப் பார்க்கின்றன; ஒன்று SiLU வழியாக அழுத்தப்பட்டு
மற்றொன்றை elementwise ஆகப் பெருக்குகிறது. ஒரு gate. இரண்டுக்குப் பதிலாக
மூன்று matrices, எனவே parameter எண்ணிக்கையை ஒரே அளவில் வைக்க hidden அகலம்
மூன்றில் இரண்டாகக் குறைக்கப்பட்டு, பிறகு 256-ன் மடங்காக மேல்நோக்கி
வட்டமிடப்படுகிறது — GPU-க்கள் அதை விரும்புகின்றன. அதனால்தான் 11008, 10923 அல்ல.
d = 4096
vanilla ReLU FFN: hidden = 4d = 16384 params = 2*d*h = 134,217,728
LLaMA SwiGLU FFN: hidden = 2/3*4d = 11008 params = 3*d*h = 135,266,304
ratio SwiGLU / ReLU = 1.008
BREAK: SwiGLU with hidden = 4d = 16384: params = 201,326,592 (+50.0%)
1%-க்குள். அதுதான் கட்டுரையின் கருத்து: ஒப்பீடு parameter-matched, எனவே எந்த benchmark ஆதாயமும் gate-லிருந்து வருகிறது, கூடுதல் capacity-லிருந்து அல்ல. Hidden-ஐ 4d-ல் விட்டுவிட்டால் FFN-ஐ பாதி அளவு வளர்த்திருப்பீர்கள், எதையும் நிரூபிக்காமல். Demo 2 ஒரு சிறிய forward pass-ஐயும் ஓட்டுகிறது: ReLU hidden units-ல் 49.0%-ஐ முற்றிலும் அணைக்கிறது, SwiGLU 0.0% — ஒரு switch-க்கும் ஒரு dimmer-க்கும் உள்ள வேறுபாடு. Dimmer ஏன் உதவுகிறது? யாருக்கும் தெரியாது. Shazeer-ன் கட்டுரையே சொல்கிறது: "we attribute their success, as all else, to divine benevolence". ஏதோ ஒரு derivation இருக்கிறது என்று interviewer சொன்னால் நம்ப வேண்டாம்.
RoPE: Q மற்றும் K-ஐச் சுழற்று, V-ஐ விட்டுவிடு
2017 ஒரு position vector-ஐ embeddings-உடன் ஒரே ஒரு முறை, கீழே சேர்த்தது. LLaMA அங்கே எதையும் சேர்ப்பதில்லை. ஒவ்வொரு attention layer-க்கு உள்ளேயும் ஒவ்வொரு query மற்றும் key-ஐ அதன் position-க்கு விகிதாசாரமான கோணத்தில் சுழற்றுகிறது (Su et al., 2021). இரண்டையும் ஒரே விதிப்படிச் சுழற்றினால், dot product இடைவெளியை மட்டுமே சார்ந்திருக்கும்:
q at 3 . k at 1 (offset 2) = 1.7986
q at 7 . k at 5 (offset 2) = 1.7986 ← same offset, same score
q at 3 . k at 0 (offset 3) = 1.7523 ← different offset
where it is applied, straight from attn_sublayer's source:
return attention(rope(Q, pos), rope(K, pos), V) @ p["Wo"] # rope: Q, K. Not V
இது Q மற்றும் K மீது போகிறது, ஏனெனில் dot செய்யப்படுவது அந்த இரண்டுதான். V என்பது ஒரு பொருத்தம் கிடைத்தபிறகு ஒப்படைக்கப்படும் உள்ளடக்கம்; அதைச் சுழற்றினால் உள்ளடக்கம் token எங்கே உட்கார்ந்திருக்கிறது என்பதைச் சார்ந்திருக்கும். Pairs, frequencies, ஏன் இது extrapolate செய்கிறது: RoPE பக்கம். இங்கே முக்கியம் இது எங்கே அழைக்கப்படுகிறது என்பதுதான்.
6.7B parameters, 1T tokens: கணக்கு
ஒவ்வொரு LLaMA-1 அளவும், கட்டுரையின் Table 2-லிருந்து வரும் நான்கு எண்களைக் கொண்ட ஒரே சூத்திரம். Attention என்பது 4d² (நான்கு d × d matrices — இன்னும் GQA இல்லை, அது LLaMA 2-ல் 70B-ல் வருகிறது), FFN 3dh, ஒரு layer-க்கு இரண்டு norms, ஒரு embedding table, tie செய்யப்படாத ஒரு output head:
size dim layers ffn count_params paper
7B 4096 32 11008 6,738,415,616 6.7B ✓
13B 5120 40 13824 13,015,864,320 13.0B
33B 6656 60 17920 32,528,943,616 32.5B
65B 8192 80 22016 65,285,660,672 65.2B
BREAK: put every bias term back (bias=True):
6,740,037,632 vs 6,738,415,616: +1,622,016 = +0.024%
கட்டுரையின் ஒவ்வொரு வரிசையுடனும் வட்டமிடலுக்குள் பொருந்துகிறது. FFN என்பது attention-ஐ விட இரண்டு மடங்கு — அதனால்தான் mixture-of-experts FFN-க்குப் பிறகு வருகிறது. Bias-களை நீக்கியது ஒரு எளிமைத் தேர்வு, சேமிப்புக்காக அல்ல: 0.024%. கட்டுரை அதைக் குறிப்பிடவே இல்லை. இப்போது tokens. Chinchilla (Hoffmann et al., 2022) ஒரு கேள்விக்கு விடை சொன்னது: நிர்ணயிக்கப்பட்ட ஒரு பயிற்சி budget-க்கு, எந்த model அளவும் எத்தனை tokens-ம் loss-ஐக் குறைக்கும்? ஒரு parameter-க்கு சுமார் 20 tokens, எனவே ஒரு 7B model 135B tokens-ஐப் பார்க்க "வேண்டும்". LLaMA-7B ஒரு trillion பார்த்தது.
LLaMA-7B parameters 6.74e+09
Chinchilla-optimal tokens, 20/param 1.35e+11 (135B)
LLaMA-7B actually trained on 1e+12 (1T) 7.4x more
inference compute ~ 2 * N per token:
LLaMA-7B 1.35e+10 FLOPs/token
GPT-3 175B 3.5e+11 FLOPs/token 26x more
tokens per parameter:
GPT-3 175B 300B / 175.0B = 1.7
LLaMA-7B 1.0T / 6.7B = 148.4
LLaMA-13B 1.0T / 13.0B = 76.8
இந்த வாதம் நீங்கள் ஏற்கனவே மனதுக்குள் ஓட்டும் ஒரு cost model-தான். பயிற்சி ஒரு முறை மட்டும் செலுத்தப்படுகிறது. Inference ஒவ்வொரு token-க்கும், எப்போதும், செலுத்தப்படுகிறது, மேலும் அது parameter எண்ணிக்கையுடன் வளர்கிறது: 7B ஒரு token-க்கு 175B-ஐ விட 26× குறைவாகச் செலவாகிறது. நூறு கோடி requests-ஐ serve செய்யும்போது, ஒரு சிறிய model-ஐப் பயிற்றுவிக்க அதிகம் செலவழிப்பதே, குறைவாகச் செலவழித்து ஒரு பெரிய model-ஐ serve செய்வதை விட மேல். கட்டுரையின் பயிற்சி வளைவுகள் காட்டுகின்றன: ஒரு trillion tokens-லும் 7B-ன் loss இன்னும் குறைந்து கொண்டேதான் இருக்கிறது — 135B-ல் இருந்ததை விட மெதுவாக, ஆனால் குறைகிறது. அதுதான், அந்த நான்கு hunks அல்ல, கட்டுரையில் LLaMA-13B பெரும்பாலான benchmarks-ல் GPT-3 175B-ஐ வென்றதற்குக் காரணம்: GPT-3 ஒரு parameter-க்கு 1.7 tokens பார்த்தது, LLaMA-13B 77 பார்த்தது.
मैं एक AI Engineer हूँ। मैंने LLaMA-family models को production में serve किया है, उन्हें quantise किया है, load balancer के पीछे रखा है — फिर भी मैं आपको यह नहीं बता पाता कि उनके अंदर क्या है। मैंने मान लिया था कि कोई नया mechanism होगा। नहीं है। LLaMA वही 2017 वाला transformer है — चार config बदलाव और कहीं ज़्यादा data। Interview का सवाल कभी "LLaMA कैसे काम करता है" नहीं होता — होता है "original transformer से क्या अलग है, और क्यों"। यह उसी का जवाब है।
LLaMA 2017 वाले transformer के ख़िलाफ़ एक pull request है
Diff कैसा दिखता है, यह आप जानते हैं। पूरा architecture एक ही diff में, decoder-only transformer के मुक़ाबले — यानी GPT वाली शक्ल, causal mask समेत:
-norm: layernorm, after the residual add
+norm: rmsnorm, before each sublayer
-activation: relu, hidden = 4d
+activation: swiglu, hidden = 2/3 * 4d rounded up to a multiple of 256
-position: sinusoid, added to the embeddings once at the bottom
+position: rope, rotate Q and K inside every attention layer
-bias: true
+bias: false
tokens: 1T to 1.4T, public data only # hunk नहीं. असली बदलाव यही.
चार hunks. Attention को छुआ तक नहीं गया — वह वही lookup है जो attention वाले पन्ने पर है। तीन hunks paper के architecture वाले हिस्से में नाम से बताए गए हैं; चौथा (कोई bias नहीं) सिर्फ़ release किए गए code में है। और जो line hunk है ही नहीं, असल में वही सबसे ज़्यादा मायने रखती है।
| शब्द | मतलब | Programmer वाली मिसाल |
|---|---|---|
| RMSNorm | हर token के vector को इतना rescale करो कि उसका root-mean-square 1 हो जाए। Mean घटाना नहीं। | String compare से पहले lower-case करना। सफ़ाई है, logic नहीं। |
| Pre-norm | Norm branch पर बैठता है, trunk पर नहीं: x + f(norm(x)) | ऐसा middleware जो request की normalise की हुई नक़ल पढ़ता है और request को कभी नहीं बदलता। |
| SwiGLU | गुणा वाले gate के साथ FFN: एक branch दूसरी को scale करती है। | ऐसा feature flag जो bool नहीं, float है। |
| RoPE | Dot product से पहले Q और K को उनकी position के हिसाब से घुमाओ। | Timestamp को hash में मोड़ देना, ताकि बराबर offset बराबर मिलें। |
| 1T tokens | Compute-optimal बिंदु से 7× आगे तक training। | Precompute. Build के वक़्त ख़र्च करो, हर request सस्ती हो जाएगी। |
वह एक formula
y = h + Wdown(SiLU(Wgate RMSNorm(h)) ⊙ Wup RMSNorm(h))
एक block. 7B के लिए 32 और 65B के लिए 80 बार stack करो। इसे pipeline stages की तरह पढ़ें:
| क़दम | Shape | क्या करता है |
|---|---|---|
RMSNorm(x) | (n, d) | हर token को एक ही scale पर लाता है। Trunk को पढ़ता है, लिखता नहीं। |
Attn(·) | (n, d) | Attention वाला वही lookup, अंदर Q और K पर RoPE लगा हुआ |
x + … | (n, d) | Skip link. जोड़ता है। कभी rescale नहीं करता। यही pre-norm वाला hunk है। |
Wgatez, Wupz | (n, h), h = 11008 | एक ही normalise किए हुए input से निकलती दो branches |
SiLU(·) ⊙ · | (n, h) | Gate. एक branch दूसरी को elementwise scale करती है |
Wdown | (n, d) | वापस trunk की चौड़ाई पर, और trunk में जुड़ जाता है। अंदर का shape बाहर के shape के बराबर, इसलिए stack होता है |
| block 32 के बाद | (n, 32000) | आख़िर में एक RMSNorm, फिर output head. कहीं कोई bias नहीं |
RMSNorm: LayerNorm में से mean हटा दीजिए
LayerNorm mean घटाता है, standard deviation से भाग देता है, फिर एक सीखा हुआ scale γ और shift β लगाता है। RMSNorm (Zhang and Sennrich, 2019) mean और shift दोनों छोड़ देता है: root-mean-square से भाग दो, γ से गुणा करो। काम वही है — matmul तक पहुँचने से पहले हर token को एक ही scale पर लाना — बस vector पर कम passes लगते हैं, और गुणवत्ता में ऐसी कोई कमी नहीं जो वे नाप पाते।
मेरी बात पर मत जाइए। यह रहा असली output:
x = [1. 2. 3. 4.] mean 2.5000
layer_norm = [-1.3416 -0.4472 0.4472 1.3416] mean 0.0000
rms_norm = [0.3651 0.7303 1.0954 1.4606] mean 0.9129
same numbers? False ← mean अब भी अंदर है
x centred = [-1.5 -0.5 0.5 1.5] mean 0.0000
layer_norm = [-1.3416 -0.4472 0.4472 1.3416]
rms_norm = [-1.3416 -0.4472 0.4472 1.3416]
same numbers? True ← centre करते ही एक जैसे
Centre किए हुए vector पर दोनों एक ही function हैं; असली activations centre नहीं होतीं, इसलिए outputs में ठीक mean जितना फ़र्क़ आता है। बदले में क्या मिलता है, मेरे implementation से गिनकर — vector पर सात passes के बजाय चार:
elementwise ops per token per norm at d=4096:
layer_norm 28,672 rms_norm 16,384 saved 12,288
x 2 norms per layer x 32 layers = 786,432 ops saved per token
rms_norm(zeros, eps=0) = [nan nan nan nan]
rms_norm(zeros, eps=1e-5) = [0. 0. 0. 0.] ✓
4096 × 11008 के matmul के आगे यह कुछ भी नहीं, पर यह हर token पर, हर layer
पर, मुफ़्त में मिलता है। और eps सजावट नहीं है: 0/0 NaN होता
है, और residual stream में एक NaN उसके ऊपर की हर layer को ज़हरीला कर देता है।
Pre-norm: norm branch पर जाता है, trunk पर नहीं
2017: x = norm(x + f(x)). Norm trunk पर बैठा है; अगला block
देखे उससे पहले हर block का output दोबारा normalise होता है। LLaMA:
x = x + f(norm(x)). Norm branch पर बैठा है; trunk embedding से
output तक एक सीधा तार है, और हर block उसमें सिर्फ़ जोड़ता है। आप
पढ़ेंगे कि pre-norm "activations को बँधा हुआ रखता है"। Demo 3 चलाइए। वह
नहीं रखता — post-norm रखता है। Pre-norm जो बँधा रखता है वह कुछ और है:
d=64, sqrt(d)=8.0. residual-stream norm per token:
layer 1 layer 5 layer 10 layer 20 bump at 0 -> out
pre-norm (LLaMA) 10.8 18.9 27.7 40.7 x 1.77
post-norm (2017) 8 8 8 8 x 9.71
no norm at all 12 6.23e+05 inf nan x nan
Post-norm trunk को ठीक √d = 8 पर बाँध देता है। Pre-norm उसे बढ़ने देता है,
और किसी को फ़र्क़ नहीं पड़ता, क्योंकि कोई sublayer कभी x नहीं
पढ़ता, सिर्फ़ rms_norm(x) पढ़ता है। Norm हटा दीजिए तो trunk
layer 10 से पहले ही overflow कर जाता है — इसीलिए वहाँ norm है ही।
असली बात आख़िरी column है। वह एक finite-difference gradient है: input को ज़रा सा δ हिलाओ, output कितना खिसका यह नापो, और भाग दो — वही तरकीब जो आप हाथ से लिखे backward pass का unit-test करने में लगाते। यह बताता है कि layer 0 पर हुआ बदलाव ऊपर तक कितना बढ़ा दिया जाता है, और यही वह मात्रा भी है जितनी ऊपर का gradient layer 0 तक उतरते हुए बढ़ता है। अब और गहरे चलिए:
same bump at layer 0, deeper and deeper stacks:
5 deep 10 deep 20 deep 32 deep
pre-norm (LLaMA) x 1.47 x 1.58 x 1.77 x 1.68
post-norm (2017) x 1.69 x 2.74 x 9.71 x 40.6 ← गुणा होता है
SwiGLU: FFN में एक gate, जिसकी क़ीमत पतली hidden layer से चुकाई गई
2017 का FFN है relu(x @ W1) @ W2, hidden चौड़ाई 4d. SwiGLU
(Shazeer, 2020) उसकी जगह
(silu(x @ W_gate) * (x @ W_up)) @ W_down रखता है। दोनों
branches एक ही input देखती हैं; एक को SiLU से दबाया जाता है और वह दूसरी को
elementwise गुणा कर देती है। यही gate है। दो के बजाय तीन matrices, तो
parameter की गिनती बराबर रखने के लिए hidden चौड़ाई दो-तिहाई कर दी जाती है,
फिर 256 के गुणक तक ऊपर round कर दी जाती है क्योंकि GPUs को यह भाता है।
इसीलिए 11008, 10923 नहीं।
d = 4096
vanilla ReLU FFN: hidden = 4d = 16384 params = 2*d*h = 134,217,728
LLaMA SwiGLU FFN: hidden = 2/3*4d = 11008 params = 3*d*h = 135,266,304
ratio SwiGLU / ReLU = 1.008
BREAK: SwiGLU with hidden = 4d = 16384: params = 201,326,592 (+50.0%)
1% के अंदर। paper की बात यही है: तुलना parameter-matched है, इसलिए benchmark पर जो भी फ़ायदा दिखे वह gate का है, ज़्यादा capacity का नहीं। Hidden को 4d पर छोड़ दीजिए तो आपने FFN डेढ़ गुना कर दिया और साबित कुछ नहीं किया। Demo 2 एक छोटा forward pass भी चलाता है: ReLU 49.0% hidden units को पूरी तरह बंद कर देता है, SwiGLU 0.0% — switch बनाम dimmer। Dimmer से फ़ायदा क्यों होता है? किसी को नहीं पता। Shazeer का paper ख़ुद कहता है: "we attribute their success, as all else, to divine benevolence". कोई interviewer कहे कि इसकी derivation है, तो मत मानिए।
RoPE: Q और K को घुमाओ, V को छोड़ दो
2017 ने position vector को embeddings में एक ही बार, सबसे नीचे जोड़ा था। LLaMA वहाँ कुछ नहीं जोड़ता। हर attention layer के अंदर वह हर query और key को उसकी position के अनुपात में एक कोण से घुमाता है (Su et al., 2021). दोनों को एक ही नियम से घुमाइए और dot product सिर्फ़ फ़ासले पर निर्भर रह जाता है:
q at 3 . k at 1 (offset 2) = 1.7986
q at 7 . k at 5 (offset 2) = 1.7986 ← same offset, same score
q at 3 . k at 0 (offset 3) = 1.7523 ← different offset
where it is applied, straight from attn_sublayer's source:
return attention(rope(Q, pos), rope(K, pos), V) @ p["Wo"] # rope: Q, K. Not V
यह Q और K पर लगता है क्योंकि dot उन्हीं दोनों का होता है। V वह content है जो match हो जाने के बाद सौंपा जाता है; उसे घुमा दें तो content इस बात पर निर्भर हो जाएगा कि token कहाँ बैठा है। Pairs, frequencies, और यह extrapolate क्यों करता है: RoPE वाला पन्ना। यहाँ बात सिर्फ़ इतनी है कि इसे कहाँ call किया जाता है।
6.7B parameters, 1T tokens: गणित
LLaMA-1 का हर size एक ही formula है, जिसमें paper की Table 2 से आए चार नंबर लगते हैं। Attention 4d² है (चार d × d matrices — अभी GQA नहीं, वह LLaMA 2 में 70B पर आता है), FFN 3dh, हर layer पर दो norms, एक embedding table, और एक untied output head:
size dim layers ffn count_params paper
7B 4096 32 11008 6,738,415,616 6.7B ✓
13B 5120 40 13824 13,015,864,320 13.0B
33B 6656 60 17920 32,528,943,616 32.5B
65B 8192 80 22016 65,285,660,672 65.2B
BREAK: put every bias term back (bias=True):
6,740,037,632 vs 6,738,415,616: +1,622,016 = +0.024%
Paper की हर पंक्ति से rounding के भीतर मेल खाता है। FFN attention से दोगुना है — इसीलिए mixture-of-experts FFN के बाद आता है। Bias हटाना सादगी का फ़ैसला था, बचत का नहीं: 0.024%. Paper इसका ज़िक्र तक नहीं करता। अब tokens. Chinchilla (Hoffmann et al., 2022) ने एक सवाल का जवाब दिया: तय training budget के लिए कौन-सा model size और कितने tokens loss को सबसे कम करते हैं? क़रीब 20 tokens प्रति parameter, यानी एक 7B model को 135B tokens देखने "चाहिए"। LLaMA-7B ने एक trillion देखे।
LLaMA-7B parameters 6.74e+09
Chinchilla-optimal tokens, 20/param 1.35e+11 (135B)
LLaMA-7B actually trained on 1e+12 (1T) 7.4x more
inference compute ~ 2 * N per token:
LLaMA-7B 1.35e+10 FLOPs/token
GPT-3 175B 3.5e+11 FLOPs/token 26x more
tokens per parameter:
GPT-3 175B 300B / 175.0B = 1.7
LLaMA-7B 1.0T / 6.7B = 148.4
LLaMA-13B 1.0T / 13.0B = 76.8
यह तर्क वही cost model है जो आप पहले से दिमाग़ में चलाते हैं। Training एक बार चुकाई जाती है। Inference हर token पर, हमेशा के लिए चुकानी पड़ती है, और वह parameter की गिनती के साथ बढ़ती है: 7B हर token पर 175B से 26× सस्ता है। अरबों requests serve करनी हों तो बेहतर यही है कि छोटे model की training पर ज़्यादा ख़र्च कर दें, बजाय इसके कि कम ख़र्च करके बड़ा model serve करते रहें। Paper के training curves दिखाते हैं कि एक trillion tokens पर भी 7B का loss गिर ही रहा है — 135B के मुक़ाबले धीरे, पर गिर रहा है। और यही, वे चार hunks नहीं, वह वजह है जिससे paper में LLaMA-13B ज़्यादातर benchmarks पर GPT-3 175B से आगे निकला: GPT-3 ने 1.7 tokens प्रति parameter देखे, LLaMA-13B ने 77.
The code
Pure NumPy. No torch, no autograd, no training — forward passes and parameter
arithmetic, so you can watch the numbers move. Every function under 15 lines.
The four deltas below; count_params, the post-norm block and the
finite-difference probe are in the script.
Download the full script — 130 lines plus six runnable demos. Then:
python3 llama_from_scratch.py
import numpy as np
def rms_norm(x, eps=1e-5):
rms = np.sqrt((x ** 2).mean(axis=-1, keepdims=True) + eps)
return x / rms
def silu(x):
return x / (1.0 + np.exp(-x)) # x * sigmoid(x)
def llama_hidden(dim, multiple_of=256):
hidden = int(2 * (4 * dim) / 3) # 2/3 of the vanilla 4d
return multiple_of * ((hidden + multiple_of - 1) // multiple_of)
def swiglu_ffn(x, W_gate, W_up, W_down):
return (silu(x @ W_gate) * (x @ W_up)) @ W_down # (n,d)->(n,h)->(n,d)
def rope(x, positions, base=10000.0):
d = x.shape[-1]
theta = base ** (-np.arange(0, d, 2) / d) # (d/2,) one per pair
angle = positions[:, None] * theta[None, :] # (n, d/2)
cos, sin = np.cos(angle), np.sin(angle)
x1, x2 = x[..., 0::2], x[..., 1::2] # even dims, odd dims
out = np.empty_like(x)
out[..., 0::2] = x1 * cos - x2 * sin # a 2-D rotation per pair
out[..., 1::2] = x1 * sin + x2 * cos
return out
def pre_norm_block(x, p): # LLaMA
x = x + attn_sublayer(rms_norm(x), p)
x = x + ffn_sublayer(rms_norm(x), p)
return x
Now break it
This is the part that makes it stick. Reading won't do it. You debug for a living — reverse-engineering from broken behaviour is your native mode.
| Break this | Watch for |
|---|---|
In llama_hidden, return 4 * dim | The 7B FFN goes from 135,266,304 to 201,326,592 parameters, +50.0%. The "SwiGLU is better" claim stops being parameter-matched and means nothing. |
rms_norm(np.zeros(4), eps=0.0) | [nan nan nan nan]. 0/0. One NaN in the residual stream poisons every layer above it. With eps=1e-5: zeros. |
In demo_3, run the post-norm ordering on blocks[:32] instead of [:20] | The bump-at-layer-0 ratio goes 9.71× → 40.6×. Pre-norm on the same 32 blocks: 1.77× → 1.68×. One compounds with depth, the other doesn't. |
Delete both rms_norm calls in pre_norm_block (that's no_norm_block) | Trunk norm 12 → 6.23e+05 by layer 5 → inf by layer 10 → nan. That's why there is a norm at all. |
count_params(4096, 32, 11008, bias=True) | +1,622,016 parameters, +0.024%. Dropping bias was for a simpler model.py, not for savings. |
Set tokens=1.35e11 in LLAMA["7B"] (Chinchilla-optimal) | The "7.4x more" lines print 1.0x. Training cost drops 7.4×; nothing else in a forward pass changes. What you'd lose — the loss curve past 135B tokens — is in the paper's training-loss figure, not in any script. Cheaper to train, worse to serve for the same quality. |
On the maths anxiety
This paper needs less maths than the attention one. A mean, a root-mean-square, a 2-D rotation (sine and cosine from school), and multiplying four numbers to count parameters. The scaling-law part is one rule of thumb — training FLOPs ≈ 6 × parameters × tokens, inference ≈ 2 × parameters per token — a cost estimate, not a theorem. You don't need to derive why SwiGLU beats ReLU; nobody has. You don't need the proof that pre-norm gradients behave; you need the finite-difference trick above: "does this multiply or add down the stack".
What separates AI engineers on this paper is the arithmetic: count 6.7B on a napkin, say why 13B is cheaper to serve than 175B, say what 20 tokens per parameter means for your training budget. None of it is research maths.
Where to go next
This page gave you LLaMA as a diff, with the four hunks measured. It skipped the tokeniser, the data pipeline, the optimiser, and running real weights.
-
LLaMA: Open and Efficient Foundation Language Models
§2.1 for the data mix, §2.2 for the diff, §2.4 for the engineering — xformers attention, activation checkpointing, overlapping compute with communication — that made 1.4T tokens take 21 days. With the code in hand, the paper is short.
-
Converting a GPT to Llama 2
The same diff applied in the other direction: starts from working GPT code, swaps in RMSNorm, SiLU, RoPE and the gated FFN one cell at a time, then loads the real 7B weights and generates text. What I left out — actual weights doing actual work.
-
llama/model.py
Every function on this page has a twin here:
RMSNorm,apply_rotary_emb, theFeedForwardwith the 2/3 and multiple-of-256 arithmetic,TransformerBlock. Plus the two things a served model needs that I skipped: the KV cache and the tensor-parallel layers.
rmsnorm as a for loop.