The whole of GPT-2 fits in 174 lines of TensorFlow. Reading the primary source, line by line, against everything you have learned.
repos/We cloned the primary sources. They are not for running — they are for reading. After twelve chapters, you have the vocabulary to understand every line, and reading the original has a value that no second-hand explanation replaces.
| Repository | What it contains | Runs here? |
|---|---|---|
gpt-2/ | the original GPT-2, in TensorFlow 1.x. 558 lines in total, of which 174 are the model. | no — requires TF 1.15 and Python 3.7 |
GPT-3-Encoder/ | the BPE tokenizer in JavaScript, with the complete encoder.json and vocab.bpe | yes — Node 24 is installed |
gpt-3/ | has no code: it has data. Samples from the 175 B model, corpus statistics, contamination analysis. | yes — it is JSON |
It uses tensorflow.contrib, a module removed in TensorFlow 2.0 (2019). And TF 1.15, the last version that has it, does not support Python above 3.7 — we are on 3.14. Making it run would require a separate Python 3.7 environment and a 2019 version of TF.
It is not worth it, and the reason is a good one: the weights are the same. The gpt2 you load with transformers is exactly the model in this repository, converted from a TensorFlow checkpoint to safetensors. You are already running the original GPT-2 — just in an implementation that works on your Python.
The value of this repository is the reading. And for that it is irreplaceable.
Here is the block function from gpt-2/src/model.py, not a line altered:
def block(x, scope, *, past, hparams): with tf.variable_scope(scope): nx = x.shape[-1].value a, present = attn(norm(x, 'ln_1'), 'attn', nx, past=past, hparams=hparams) x = x + a m = mlp(norm(x, 'ln_2'), 'mlp', nx*4, hparams=hparams) x = x + m return x, present
Read slowly and recognize everything:
x = x + a and x = x + m — the residual connections (chapter 05). The x is never destroyed, only added to. The blackboard.norm(x, 'ln_1') inside the call to attn, and the sum outside — this is Pre-LN (chapter 05, section 4). The difference that allowed 48 layers is in this exact order.nx*4 — the MLP's 4× expansion factor, right there, literal.present — this layer's Seven lines. It is the entire Transformer block.
def attention_mask(nd, ns, *, dtype): """1's in the lower triangle, counting from the lower right corner. Same as tf.matrix_band_part(tf.ones([nd, ns]), -1, ns-nd), but doesn't produce garbage on TPUs.""" i = tf.range(nd)[:,None] j = tf.range(ns) m = i >= j - ns + nd return tf.cast(m, dtype) def mask_attn_weights(w): _, _, nd, ns = shape_list(w) b = attention_mask(nd, ns, dtype=w.dtype) b = tf.reshape(b, [1, 1, nd, ns]) w = w*b - tf.cast(1e10, w.dtype)*(1-b) return w
w = w*b - 1e10*(1-b)
Where b=1 (allowed), the weight passes intact. Where b=0 (future), 10¹⁰ is subtracted — which makes exp() return zero in the following softmax.
Note that they use −1e10, not −∞, exactly as chapter 04 describes: a true infinity produces NaN in floating-point arithmetic when multiplied by zero. This is the kind of detail only the primary source teaches.
And notice their comment: "doesn't produce garbage on TPUs". There is an entire hardware-debugging story compressed into that remark.
def gelu(x): return 0.5*x*(1+tf.tanh(np.sqrt(2/np.pi)*(x+0.044715*tf.pow(x, 3))))
The exact GELU uses the cumulative distribution function of the normal, which is expensive. This is the hyperbolic-tangent approximation — and that 0.044715 is an empirically tuned constant. It is in the original code, written by hand. If you use PyTorch's nn.GELU() with no arguments, you get the exact version, slightly different from this one.
c = conv1d(x, 'c_attn', n_state*3) q, k, v = map(split_heads, tf.split(c, 3, axis=2))
A single projection to 3 × 768 = 2304, then a cut into three. Exactly what chapter 03 described as an implementation detail — and here is the evidence.
The name conv1d is misleading: read the implementation and you will see it is an ordinary matmul. It is a kernel-1 convolution, which is mathematically identical to a dense layer. A convention inherited from the era.
present = tf.stack([k, v], axis=1) if past is not None: pk, pv = tf.unstack(past, axis=1) k = tf.concat([pk, k], axis=-2) v = tf.concat([pv, v], axis=-2)
Stack the new
And it is only possible because the model is causal (chapter 11). In an encoder, there would be nothing to cache.
GPT-3-EncoderDespite the name, it is the GPT-2 tokenizer — GPT-3 reused the same one. And it is useful for two reasons.
First, the vocabulary files are there, in plain text:
encoder.json — the token → ID map, all 50,257 of them.vocab.bpe — the 50,000 merges, in the order they were learned. The first line is the first merge of the BPE training.You can open these files in an editor. It is the merges.txt of chapter 01, and seeing the first lines is seeing the statistics of English.
Second, it runs — in Node, which you have. It is useful when you need to count tokens in the browser or in a JavaScript service, without loading Python.
gpt-3 repository: data, not codeThere is no implementation — GPT-3 was never open-sourced. But there are valuable things:
175b_samples.jsonl — raw outputs from the 175-billion model. Compare with what gpt2-xl produces and you see, with your own eyes, what 112× more parameters buy.overlap_frequency.md — contamination analysis: how much of the evaluation sets leaked into the training corpus. It is an honest and rare document, and a good reminder that LLM benchmarks are more fragile than they seem.dataset_statistics/ — composition of the corpus..venv\Scripts\python.exe lab\10_original_code.py
model.py side by side with the equivalent PyTorch implementation, so you can see the line-by-line correspondence.vocab.bpe and shows the first 20 merges the BPE learned. The first ones are the most frequent in English.Encoder.js via Node and compares.175b_samples.jsonl and shows them next to a gpt2-xl generation on the same theme.repos/gpt-2/src/model.py. Find the norm function and confirm, from the formula, that it is the LayerNorm of chapter 05. What is epsilon=1e-5?g and b. Which letters did chapter 05 use for them?repos/GPT-3-Encoder/vocab.bpe and look at the first 10 lines. Which character pairs did the BPE find most frequent in English? Does that surprise you?model.py, find where the logits are computed at the end. Can you identify the weight tying of chapter 02?attention_mask function uses i >= j - ns + nd instead of simply i >= j. Why this complication? (Hint: think about what happens when past is not null and nd ≠ ns.)The primary sources for the concepts in this chapter. The numbers in the text were measured in the lab; the ideas come from here.
Technical terms here carry their machine-learning meaning. The course glossary shows that meaning next to the others the same words have in IT.
You have read the source. Chapter 14 closes the lab with the last practical piece:
how to serve these models over HTTP — and what TGI and TEI do that our server does not.