Reference code openai/gpt-2 @ 9b63575, huggingface/transformers @ 71c6f69, configuration openai-community/gpt2 @ 607a30d · checkpoint tensors not independently counted · How to read

GPT-2 model overview

  1. UTF-8 text passes through byte-level BPE, which is configured by fixed vocabulary and merge assets.
  2. Token IDs cross the preprocessing boundary and select rows from the learned token embedding table WTE.
  3. The token sequence shape determines absolute position IDs, independently of token values, which select rows from WPE on a lower positional branch.
  4. Token and position embeddings are added, then training-only embedding dropout produces x zero. Evaluation leaves the sum unchanged.
  5. Exactly 12 transformer blocks transform x zero into x_12.
  6. Final LayerNorm acts across the 768-dimensional feature axis with epsilon equal to 10 to the minus 5.
  7. The transpose view of the shared WTE matrix projects the final states to logits without adding parameters.

Tied vocabulary projection

×

reuses weightstf.matmul(h_flat, wte, transpose_b=True)

Project each normalized feature vector into vocabulary logits.

logits=hfinalWtokenT\mathrm{logits}={\htmlData{light=ln-f}{h_{\mathrm{final}}}}W_{\mathrm{token}}^{\mathsf T}
in
hfinalh_{\mathrm{final}} [B batch, T positions, 768 d]
out
logits\mathrm{logits} [B batch, T positions, 50257 vocabulary]
Parameters

Reuses Token embedding: weights are counted there, with no additional parameter ownership here.

Explanation

Each entry scores one vocabulary token as the next token for its position. The softmax and the choice of token happen outside the model.

Each vocabulary row of WTE serves both the input lookup and, transposed, the output scoring.

one tablein: pick a rowout: score againstevery row
Schematic of WTE’s input lookup and output scoring uses. No quantitative scale is encoded.
Codemodel.py 170–172 · modeling_gpt2.py 638–698
model.py 170–172
        h_flat = tf.reshape(h, [batch*sequence, hparams.n_embd])
        logits = tf.matmul(h_flat, wte, transpose_b=True)
        logits = tf.reshape(logits, [batch, sequence, hparams.n_vocab])
modeling_gpt2.py 638
    _tied_weights_keys = {"lm_head.weight": "transformer.wte.weight"}
modeling_gpt2.py 642–643
        self.transformer = GPT2Model(config)
        self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False)
modeling_gpt2.py 695
        hidden_states = transformer_outputs.last_hidden_state
modeling_gpt2.py 698
        logits = self.lm_head(hidden_states[:, slice_indices, :])
Sources2

Direct evidence

The original graph explicitly multiplies by wteᵀ.

openai · openai/gpt-2 / src/model.py
Pinned revision 9b63575ef427 ↗ · lines 169–172

Read code excerpt
        h_flat = tf.reshape(h, [batch*sequence, hparams.n_embd])
        logits = tf.matmul(h_flat, wte, transpose_b=True)
        logits = tf.reshape(logits, [batch, sequence, hparams.n_vocab])

Direct evidence

The maintained model declares lm_head.weight tied to transformer.wte.weight and applies the vocabulary projection without a bias.

transformers · huggingface/transformers / src/transformers/models/gpt2/modeling_gpt2.py
Pinned revision 71c6f699ac9b ↗ · lines 637–698

Read code excerpt
    _tied_weights_keys = {"lm_head.weight": "transformer.wte.weight"}

… lines 639–641 omitted …

        self.transformer = GPT2Model(config)
        self.lm_head = nn.Linear(config.n_embd, config.vocab_size, bias=False)

… lines 644–694 omitted …

        hidden_states = transformer_outputs.last_hidden_state

… lines 696–697 omitted …

        logits = self.lm_head(hidden_states[:, slice_indices, :])