← learning.n0tls.com
§ a field guide, part three

Inside the transformer

You've now run all-MiniLM-L6-v2 on your own GPU, watched it learn from your corpus, and seen it win and lose against bigger, pretrained encoders. This is what's actually happening inside it — from raw text to the 384 numbers it hands back — using MiniLM's own real shape as the running example throughout.

01 text isn't the input — tokens are

A transformer never sees the string "resetting file permission". Before anything else, a tokenizer — a separate, much simpler piece of software with a fixed vocabulary (30,522 entries for MiniLM's, inherited from BERT) — chops the string into subword pieces and looks each one up as an integer.

02 integers become vectors

Each token id indexes a row in an embedding table — a 30,522 × 384 matrix of learned numbers, one 384-dimensional row per vocabulary entry. Because a transformer has no built-in sense of word order (unlike an RNN, which reads one token at a time), a second vector — the positional embedding, one per position 0, 1, 2, … (learned, BERT-style, not the sinusoids of the original Transformer) — is added on top, so "permission file" and "file permission" don't look identical to the model.

Every token in the input ends up as one such 384-number vector. The four pieces above, plus the [CLS] and [SEP] markers the tokenizer wraps every input in, make a 6×384 matrix — this is what actually enters the first transformer block.

03 self-attention: tokens looking at each other

This is the mechanism that the Transformer paper introduced in place of recurrence: every token's vector gets updated by looking at every other token in the sequence at once, weighted by how relevant each one is. Each token produces three projections of itself — a query (what am I looking for), a key (what do I offer), and a value (what do I actually contribute) — all three are just learned matrix multiplications of the same input vector.

Attention(Q, K, V) = softmax( QKT / √d ) V

Read it as: score every token's key against my query (QKT), scale it down so the softmax doesn't saturate (/√d), turn the scores into weights that sum to 1 (softmax), then take a weighted average of everyone's value vectors. "Reset" ends up attending heavily to "file" and "permission" — the learned Q/K projections are what make those tokens matter for disambiguating what "reset" means here.

04 heads, blocks, and depth

Why 12 heads instead of 1

A single attention pass can only learn one notion of "relevant." Running 12 of them in parallel — each with its own learned Q/K/V projections, on a 32-dimensional slice of the 384 — lets different heads specialize: one might track subject-verb agreement, another might link a pronoun back to its noun, another might just look at adjacent tokens. Their 12 outputs are concatenated back to 384 dimensions and passed through one more learned projection.

The full block, and stacking six of them

Multi-head attention is one half of a transformer block. The other half is a small feedforward network (384 → 1536 → 384, with a nonlinearity in between) applied to every token independently — attention mixes information across tokens, the feedforward layer processes each token's mixed result on its own. Both halves get a residual connection (add the input back to the output) and a layer normalization; the residual path is what makes it possible to stack these blocks deeply without training collapsing.

05 from N vectors to the one you actually use

After 6 blocks, a 6-token query is still 6 separate 384-dim vectors — one per token, now richly contextualized. But the bi-encoder from the first field guide needs exactly one vector per query and one per document to take a dot product. Pooling is the step that collapses the many into the one, and it's the one step here that sentence-transformers added on top of BERT for retrieval — base BERT has a [CLS] pooler, but it was never trained to make that vector comparable across sentences.

06 why MiniLM isn't Claude

Everything above — tokens, embeddings, attention, blocks, pooling — is shared machinery. The defining structural difference between an embedding model like MiniLM and a GPT-style chat model (Claude's internals aren't published; the comparison here is to the published GPT-style decoders) is which tokens are allowed to attend to which.

07 what each one was actually trained to do
1. encoder pretraining

Masked language modeling. BERT saw sentences with 15% of tokens masked out (mostly replaced by [MASK], a few swapped or left as-is) and learned to predict the missing word from both directions of context. This is where it learned English — grammar, word sense, world facts — from ~3.3 billion words of BooksCorpus + Wikipedia.

2. distillation

MiniLM learned to imitate a bigger model. A 12-layer, 384-wide student was trained to match the attention patterns of a BERT-base-sized teacher (Microsoft's UniLMv2, trained on a larger web-plus-books corpus of the same kind — the UniLMv2 paper reports 160 GB of Wikipedia, BookCorpus, OpenWebText, CC-News and Stories, though the card doesn't say which checkpoint was used) — most of the teacher's knowledge in a fraction of the size; still a full pass over the same Wikipedia + BookCorpus text (400k steps), but learning by imitation instead of masked-word prediction from scratch. The 6-layer variant used here was then made by keeping every other layer of that 12-layer student.

3. sentence-pair pretraining

Contrastive training on ~1 billion sentence pairs (Reddit comments, Q&A pairs, captions) — mined from the open web, not written for this purpose. This is what turned a language model into an embedding model: the same InfoNCE loss from field guide §05, just at a scale of a billion pairs instead of 1,372.

4. your fine-tuning

The same loss again, on 1,372 pairs from one corpus. Not a new skill — a small nudge to an already-formed 384-dimensional space, aiming it at your specific vocabulary and documents.

08 why "from scratch on this corpus" would struggle

Steps 1–3 above are where a model actually learns language and general-purpose meaning. Step 4 — the only one you've run — assumes all of that already exists and just steers it. Training a model from nothing directly on the deno-docs corpus means skipping straight to step 4 with no steps 1–3 underneath it: there's no pretrained space to steer, because nothing has taught the model what a sentence is yet.

~3.3B
words BERT pretrained on
(BooksCorpus + Wikipedia)
vs.
~330K
words in the entire
457-doc deno-docs corpus

That's roughly a 10,000× gap. It isn't just "less data, worse model" — below a certain scale (roughly 10–100 million words, by one careful study), a transformer trained from random initial weights learns grammar, word relationships, and general semantics far less reliably, and leans on surface patterns in the 330K words it saw. That's why every field-guide result so far started from a pretrained checkpoint and only fine-tuned: the pretraining is what makes the space worth steering, and no static 2,000-document corpus is anywhere near enough text to grow one from nothing.

09 optional, 5 questions
Check your understanding

1. Why does a transformer operate on tokens instead of raw characters or whole words directly?

2. What does self-attention let a token's representation do that a plain word embedding alone can't?

3. Why do transformers use multiple attention heads instead of just one?

4. What does pooling do, and why does retrieval need it?

5. Why would training a transformer from scratch on this project's ~330K-word corpus almost certainly fail to learn general language understanding?