The Encoder-Only Transformer

The encoder-only transformer is a neural network architecture that processes an entire input sequence simultaneously using bidirectional self-attention, producing a rich contextual representation for every token. It matters because it underpins BERT, RoBERTa, and nearly every state-of-the-art NLP system for classification, named entity recognition, and semantic search. The counterintuitive fact: despite never being trained to generate text, encoder-only models often understand language more deeply than generative models of similar size, precisely because bidirectional context forces every token to explain itself in light of the full sequence.

The core insight

The fundamental insight is that the meaning of a word is entirely determined by its context — and an encoder-only transformer captures all context simultaneously rather than left-to-right. Every token attends to every other token in the same forward pass, so the representation of "bank" in "river bank" and "bank account" diverges at layer 1 rather than after seeing subsequent tokens. This bidirectional awareness is the thing that makes encoder models exceptional at understanding tasks: the model never has to guess what comes next because it already knows.

How It Works

The encoder-only transformer takes a sequence of tokens, converts them to embeddings, adds positional information, then passes them through a stack of identical layers. Each layer has two sub-components: multi-head self-attention and a position-wise feed-forward network, with layer normalization and residual connections wrapping each.

Input Representation

Each input token xix_i is mapped to a dmodeld_{\text{model}}-dimensional embedding. Positional encodings are added to inject sequence order, since self-attention is inherently permutation-invariant:

1
PE(pos,2i)=sin⁡ ⁣(pos100002i/dmodel),PE(pos,2i+1)=cos⁡ ⁣(pos100002i/dmodel)\quad \text{PE}(pos, 2i) = \sin\!\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right), \quad \text{PE}(pos, 2i+1) = \cos\!\left(\frac{pos}{10000^{2i/d_{\text{model}}}}\right)

Explanation: Each position gets a unique fingerprint of interleaved sine and cosine waves at geometrically spaced frequencies, so the model can distinguish any two positions and even generalize to unseen sequence lengths. Here pospos is the token position (0-indexed), ii is the dimension index, and dmodeld_{\text{model}} is the total embedding dimension (e.g. 768 in BERT-base).

Scaled Dot-Product Attention

The core computation packs the sequence into three matrices — queries QQ, keys KK, and values VV — each produced by learned linear projections of the input:

2
Attention(Q,K,V)=softmax ⁣(QK⊤dk)V\quad \text{Attention}(Q, K, V) = \text{softmax}\!\left(\frac{QK^\top}{\sqrt{d_k}}\right)V

Explanation: Each token's query vector scores its compatibility with every key in the sequence, the scores are normalized into a probability distribution, and then that distribution is used to compute a weighted sum of value vectors.

  • Q∈Rn×dkQ \in \mathbb{R}^{n \times d_k}: query matrix, one row per token
  • K∈Rn×dkK \in \mathbb{R}^{n \times d_k}: key matrix, one row per token
  • V∈Rn×dvV \in \mathbb{R}^{n \times d_v}: value matrix, one row per token
  • dkd_k: key/query dimension — the dk\sqrt{d_k} scaling prevents dot products from growing so large that softmax gradients vanish

Multi-Head Attention

Rather than running one attention function, the model runs hh attention heads in parallel, each with its own projections, then concatenates and re-projects:

3
MultiHead(Q,K,V)=Concat(head1,…,headh) WO\quad \text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \ldots, \text{head}_h)\,W^O

Explanation: Each head learns to attend to a different type of relationship (syntax, coreference, semantics, etc.) and the output projection mixes them back into a single dmodeld_{\text{model}}-dimensional space. where headi=Attention(QWiQ, KWiK, VWiV)\text{head}_i = \text{Attention}(QW_i^Q,\, KW_i^K,\, VW_i^V) with WiQ,WiK∈Rdmodel×dkW_i^Q, W_i^K \in \mathbb{R}^{d_{\text{model}} \times d_k}, WiV∈Rdmodel×dvW_i^V \in \mathbb{R}^{d_{\text{model}} \times d_v}, and WO∈Rhdv×dmodelW^O \in \mathbb{R}^{hd_v \times d_{\text{model}}}. Typically dk=dv=dmodel/hd_k = d_v = d_{\text{model}}/h.

Position-Wise Feed-Forward Network

After attention, each token representation is passed independently through a two-layer MLP with a GELU or ReLU nonlinearity:

4
FFN(x)=GELU(xW1+b1) W2+b2\quad \text{FFN}(x) = \text{GELU}(xW_1 + b_1)\,W_2 + b_2

Explanation: This per-token MLP (with a large hidden dimension, typically 4×dmodel4 \times d_{\text{model}}) acts as the model's long-term memory and feature transformer, applying the same transformation to every position independently. W1∈Rdmodel×dffW_1 \in \mathbb{R}^{d_{\text{model}} \times d_{ff}} expands the representation; W2∈Rdff×dmodelW_2 \in \mathbb{R}^{d_{ff} \times d_{\text{model}}} projects it back. BERT-base uses dff=3072d_{ff} = 3072 with dmodel=768d_{\text{model}} = 768.

Full Layer with Residuals

Each encoder layer wraps both sub-components with Pre-LN or Post-LN residual connections:

5
h=LayerNorm(x+MultiHead(x,x,x)),z=LayerNorm(h+FFN(h))\quad h = \text{LayerNorm}(x + \text{MultiHead}(x,x,x)), \quad z = \text{LayerNorm}(h + \text{FFN}(h))

Explanation: The residual stream xx is never destroyed — attention and FFN layers only add corrections to it, which is why gradient flow remains healthy across 12–24 layers and why probing the residual stream at intermediate layers reveals progressively richer linguistic structure.

Masked Language Modeling Objective

Encoder-only models are typically pre-trained with Masked Language Modeling (MLM): 15% of input tokens are replaced by `[MASK]`, and the model must predict the original token from bidirectional context. Unlike causal language modeling, the loss is computed only over masked positions: ```python import torch import torch.nn as nn from transformers import BertTokenizer, BertForMaskedLM tokenizer = BertTokenizer.from_pretrained("bert-base-uncased") model = BertForMaskedLM.from_pretrained("bert-base-uncased")

# Mask a token manually text = "The [MASK] sat on the mat." inputs = tokenizer(text, return_tensors="pt") with torch.no_grad(): outputs = model(**inputs) logits = outputs.logits # shape: (1, seq_len, vocab_size)

# Get prediction at the [MASK] position mask_idx = (inputs.input_ids == tokenizer.mask_token_id).nonzero(as_tuple=True)[1] predicted_token_id = logits[0, mask_idx].argmax(-1) print(tokenizer.decode(predicted_token_id)) # → "cat" ```

A Minimal Encoder-Only Block in PyTorch

```python import torch import torch.nn as nn import torch.nn.functional as F import math class EncoderBlock(nn.Module): def __init__(self, d_model=768, n_heads=12, d_ff=3072, dropout=0.1): super().__init__() assert d_model % n_heads == 0 self.n_heads = n_heads self.d_k = d_model // n_heads

# Attention projections (Q, K, V packed together for efficiency) self.qkv_proj = nn.Linear(d_model, 3 * d_model) self.out_proj = nn.Linear(d_model, d_model)

# Feed-forward self.ff1 = nn.Linear(d_model, d_ff) self.ff2 = nn.Linear(d_ff, d_model) self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) self.drop = nn.Dropout(dropout) def forward(self, x, attention_mask=None): B, T, C = x.shape

# --- Multi-head self-attention --- qkv = self.qkv_proj(x).reshape(B, T, 3, self.n_heads, self.d_k) qkv = qkv.permute(2, 0, 3, 1, 4) # (3, B, heads, T, d_k) q, k, v = qkv[0], qkv[1], qkv[2] scale = math.sqrt(self.d_k) attn = (q @ k.transpose(-2, -1)) / scale # (B, heads, T, T) if attention_mask is not None:

# attention_mask: (B, T), 1=keep, 0=pad mask = attention_mask[:, None, None, :] # broadcast attn = attn.masked_fill(mask == 0, float('-inf')) attn = F.softmax(attn, dim=-1) attn = self.drop(attn) out = (attn @ v).transpose(1, 2).reshape(B, T, C) out = self.out_proj(out)

# Residual + LayerNorm x = self.norm1(x + self.drop(out))

# --- Feed-forward --- ff = self.ff2(self.drop(F.gelu(self.ff1(x)))) x = self.norm2(x + self.drop(ff)) return x ```

researcher · Ashish Vaswani | b. 1983 | Invented the Transformer Architecture

Vaswani led the Google Brain team that published "Attention Is All You Need" (2017), eliminating recurrence and convolutions in favor of pure attention — the architectural shift that made modern LLMs possible. His specific contribution relevant to encoders is the multi-head self-attention mechanism and the positional encoding scheme that allowed fully parallel sequence processing. Surprising fact: the original transformer paper was written over just a few months, and several co-authors have since noted that the encoder-only and decoder-only variants they inspired were not explicitly anticipated — BERT's success two years later genuinely surprised the original team.

researcher · Jacob Devlin | b. 1980 | Created BERT

Devlin is a research scientist at Google who designed BERT (Bidirectional Encoder Representations from Transformers), the first large-scale demonstration that an encoder-only transformer pre-trained with masked language modeling could achieve state-of-the-art results across 11 NLP benchmarks simultaneously. His key insight was that removing the causal mask and training on both left and right context simultaneously unlocked a qualitatively different kind of language understanding. Surprising fact: Devlin originally considered BERT an internal research experiment unlikely to displace existing NLP pipelines — within 18 months it had replaced hand-tuned feature engineering across essentially the entire NLP industry.

Figure
Encoder-Only Transformer Architecture
Encoder-Only Transformer Input: [CLS] bank river [SEP] Token Embeddings + Positional Encoding Encoder Layer × N Multi-Head Self-Attention Q · Kᵀ / √dₖ → softmax → · V + Residual LayerNorm Feed-Forward Network GELU(x W₁ + b₁) W₂ + b₂ + Residual LayerNorm Output: h[CLS] h[bank] h[river] h[SEP] classify NER NER
Figure
Attention Pattern: Bidirectional vs. Causal

Encoder (Bidirectional) Decoder (Causal) The cat sat here The cat sat here ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ ✓ Can attend (full matrix) The cat sat here The cat sat here