Transformers and Modern NLP for Engineers
Move the learner into current AI engineering territory with strong transformer fundamentals.
Every frontier AI model - GPT-4, Claude, Gemini, LLaMA, Mistral, Stable Diffusion, Whisper - is built on the Transformer architecture. Understanding Transformers is not optional for an ML engineer in 2025. It is the prerequisite for everything: understanding LLM capabilities and failure modes, fine-tuning, prompt engineering, RAG, agents, and system design.
This module covers the Transformer architecture with the depth required to build, modify, and debug Transformer-based systems.
The Core Mechanism: Self-Attention
The Transformer's fundamental operation is self-attention: each token attends to every other token in the sequence, producing a context-weighted representation.
For a sequence of token embeddings packed into matrix :
Where , , are linear projections. The scaling prevents dot products from growing too large and pushing softmax into saturation.
pythonimport torch import torch.nn as nn import torch.nn.functional as F class MultiHeadSelfAttention(nn.Module): def __init__(self, d_model: int, n_heads: int, dropout: float = 0.1): super().__init__() assert d_model % n_heads == 0 self.d_model = d_model self.n_heads = n_heads self.d_k = d_model // n_heads self.q_proj = nn.Linear(d_model, d_model) self.k_proj = nn.Linear(d_model, d_model) self.v_proj = nn.Linear(d_model, d_model) self.out_proj = nn.Linear(d_model, d_model) self.dropout = nn.Dropout(dropout) def forward(self, x: torch.Tensor, mask: torch.Tensor = None): B, T, C = x.shape Q = self.q_proj(x).view(B, T, self.n_heads, self.d_k).transpose(1, 2) K = self.k_proj(x).view(B, T, self.n_heads, self.d_k).transpose(1, 2) V = self.v_proj(x).view(B, T, self.n_heads, self.d_k).transpose(1, 2) scores = (Q @ K.transpose(-2, -1)) / (self.d_k ** 0.5) if mask is not None: scores = scores.masked_fill(mask == 0, float('-inf')) attn = self.dropout(F.softmax(scores, dim=-1)) out = (attn @ V).transpose(1, 2).contiguous().view(B, T, C) return self.out_proj(out)
Multiple heads allow the model to attend to different aspects of the input simultaneously - one head might attend to syntactic relationships, another to coreference, another to semantic similarity.
The Full Transformer Block
Each Transformer layer wraps attention with residual connections, layer normalization, and a position-wise feed-forward network:
pythonclass TransformerBlock(nn.Module): def __init__(self, d_model: int, n_heads: int, d_ff: int, dropout: float = 0.1): super().__init__() self.attn = MultiHeadSelfAttention(d_model, n_heads, dropout) self.ff = nn.Sequential( nn.Linear(d_model, d_ff), nn.GELU(), nn.Dropout(dropout), nn.Linear(d_ff, d_model), nn.Dropout(dropout), ) self.norm1 = nn.LayerNorm(d_model) self.norm2 = nn.LayerNorm(d_model) def forward(self, x: torch.Tensor, mask: torch.Tensor = None): x = x + self.attn(self.norm1(x), mask) # Pre-norm (modern standard) x = x + self.ff(self.norm2(x)) return x
Pre-norm (normalize before the sub-layer) vs. post-norm (normalize after): pre-norm produces more stable training gradients and is standard in all modern models (GPT, LLaMA, Mistral).
Positional Encoding
Self-attention is permutation-invariant - it has no notion of token order. Positional encoding adds position information to the token embeddings before the first layer.
Sinusoidal encoding (original Transformer): fixed patterns based on sine/cosine waves at different frequencies. Generalizes to longer sequences than seen during training.
Rotary Position Embedding (RoPE): encodes position in the rotation of Q and K vectors rather than additive offsets. Used in LLaMA, Mistral, GPT-NeoX. Enables better length generalization and relative position sensitivity.
python# Learned positional embeddings (GPT-2 style) - simple and effective class GPTEmbeddings(nn.Module): def __init__(self, vocab_size: int, d_model: int, max_len: int = 2048, dropout: float = 0.1): super().__init__() self.token_emb = nn.Embedding(vocab_size, d_model) self.pos_emb = nn.Embedding(max_len, d_model) self.dropout = nn.Dropout(dropout) def forward(self, input_ids: torch.Tensor): T = input_ids.shape[1] positions = torch.arange(T, device=input_ids.device).unsqueeze(0) return self.dropout(self.token_emb(input_ids) + self.pos_emb(positions))
Using HuggingFace Transformers in Production
In practice, you use pretrained Transformer models rather than implementing your own. HuggingFace Transformers is the standard library:
pythonfrom transformers import AutoTokenizer, AutoModelForSequenceClassification import torch model_name = "distilbert-base-uncased" tokenizer = AutoTokenizer.from_pretrained(model_name) model = AutoModelForSequenceClassification.from_pretrained(model_name, num_labels=2) texts = ["This product is amazing!", "The worst experience I have ever had."] inputs = tokenizer(texts, padding=True, truncation=True, max_length=512, return_tensors="pt") with torch.no_grad(): outputs = model(**inputs) logits = outputs.logits predictions = logits.argmax(dim=-1) print(predictions) # tensor([1, 0]) → positive, negative
Key AutoModel variants to know:
AutoModelForSequenceClassification- text classification, sentimentAutoModelForTokenClassification- NER, POS taggingAutoModelForQuestionAnswering- extractive QAAutoModelForCausalLM- text generation (GPT-style)AutoModelForSeq2SeqLM- summarization, translation (T5-style)
Tokenization: What Actually Happens to Your Text
pythontokenizer = AutoTokenizer.from_pretrained("gpt2") text = "The quick brown fox" tokens = tokenizer.encode(text) print(tokens) # [464, 2068, 7586, 21831] print(tokenizer.decode(tokens)) # "The quick brown fox" # BPE (Byte-Pair Encoding): "un##familiar" splits into ["un", "familiar"] # Rare words become multi-token sequences - "brewYourAgent" might be 4+ tokens # This affects context length accounting and cost estimation
Understanding tokenization matters for: counting context window usage, understanding why multi-lingual text is longer, debugging truncation bugs, and knowing why some code/math prompts consume unexpectedly many tokens.
Attention Patterns: What They Mean
Attention weights reveal what the model is "looking at" when computing each token's representation. Visualizable with:
pythonfrom transformers import AutoModel import matplotlib.pyplot as plt outputs = model(**inputs, output_attentions=True) attn = outputs.attentions # tuple of (batch, heads, seq, seq) per layer # Plot layer 6, head 0 plt.imshow(attn[6][0, 0].detach().numpy(), cmap='viridis')
Common patterns: heads attending to previous tokens (causal LM), to [CLS] token (classification), to syntactically related words (NLP), to diagonal (position-local context).
Common Mistakes and Bad Instincts
Ignoring the attention_mask in batched inference. When padding sequences to the same length, you must pass attention_mask to tell the model which tokens are real vs. padding. Without it, the model attends to padding tokens, producing corrupted representations. HuggingFace tokenizers return this mask by default - do not drop it.
Using BERT for text generation. BERT is an encoder-only model trained with masked language modeling. It is excellent for classification, NER, and embeddings - not for generating text. GPT-style causal LMs (GPT-2, LLaMA) are the right choice for generation tasks.
Truncating prompts without thinking about what you cut. When input exceeds max_length, HuggingFace truncates from the end by default. For tasks where the answer is at the end of the document (e.g., summarization), this discards the most relevant content. Use truncation_side='left' or implement sliding window chunking.
Where to Go Next
- llm-product-engineering: apply Transformer understanding to build reliable LLM-powered product features
- retrieval-systems-vector-databases-and-rag: use Transformer encoders to produce embeddings for retrieval
- fine-tuning-adaptation-and-when-not-to-fine-tune: fine-tune pretrained Transformers using LoRA and PEFT
Module 15 of 34 · Software Engineer to ML/AI Engineer
Stay in the loop
Get new ML/AI lessons in your inbox.
No account needed. We will send curriculum updates, launch notes, and practical learning resources.
Related Posts
More postsFine-Tuning and Post-Training: LoRA, SFT, DPO, and Reasoning RL
What actually happens after pretraining, and when you should do any of it yourself. Parameter-efficient fine-tuning with LoRA, supervised fine-tuning data, preference optimization, and the reinforcement learning recipe behind reasoning models, with a decision framework and a project you can run on one GPU.
LLM Context Windows: What They Mean for System Design
Context window size shapes every architectural decision in LLM applications. This post covers how to reason about context allocation, the limits that still matter even with large windows, and the patterns that scale.
Common ML Architectures Reference: CNN, RNN, Transformer, MoE
A concise technical reference for the neural network architectures that power modern ML - what each one does, how it works, when to use it, and what to watch out for.