Attention Is All You Need: Deconstructing the Transformer Architecture
Attention Is All You Need: Deconstructing the Transformer Architecture
In the history of Deep Learning, few papers have triggered as seismic a shift as "Attention Is All You Need" (Vaswani et al., 2017). Before this paper, sequence-to-sequence tasks (like translation) were dominated by Recurrent Neural Networks (RNNs) and Long Short-Term Memory (LSTM) networks.
The Transformer changed everything by proposing a radical idea: What if we completely dispensed with recurrence and convolutions?
In this post, we will dive deep into the intuition, the mathematics, and a PyTorch implementation of the Transformer.
๐ The Core Intuition: Why Abandon RNNs?
For years, the industry relied on RNNs because they process data sequentially, mirroring how humans read. However, this created two massive bottlenecks:
- Sequential Dependency: You cannot compute the 10th word until you've computed the 9th. This makes training on modern GPUs (which love parallelism) incredibly inefficient.
- Vanishing Gradients: Even with LSTMs, remembering a word from the beginning of a long paragraph by the time you reach the end is difficult.
The Transformer's Solution: A global Attention mechanism. Instead of processing words one by one, the Transformer looks at the entire sequence simultaneously. It calculates a "weight" for every other word in the sentence to determine which ones are most relevant to the current token, regardless of their distance.
๐๏ธ The Architecture Overview
The Transformer follows an Encoder-Decoder structure. The Encoder digests the source sequence into a rich representation, and the Decoder generates the target sequence auto-regressively.
High-Level Workflow
๐งฎ The Mathematical Engine
1. Scaled Dot-Product Attention
The heart of the model is the attention mechanism. It uses three vectors: Query (Q), Key (K), and Value (V). Think of it like a database search: the Query is what you're looking for, the Key is the index, and the Value is the actual content.
$$\text{Attention}(Q, K, V) = \text{softmax}\left(\frac{QK^T}{\sqrt{d_k}}\right)V$$
- Why $\sqrt{d_k}$? As the dimensionality $d_k$ grows, the dot product can grow very large, pushing the softmax function into regions where gradients are extremely small. Scaling prevents this.
2. Multi-Head Attention (MHA)
Instead of performing attention once, the Transformer does it $h$ times in parallel. This allows the model to attend to different types of information (e.g., one head focuses on the subject-verb relationship, another on adjectives).
$$\text{MultiHead}(Q, K, V) = \text{Concat}(\text{head}_1, \dots, \text{head}_h)W^O$$ $$\text{where } \text{head}_i = \text{Attention}(QW_i^Q, KW_i^K, VW_i^V)$$
3. Positional Encoding
Since there is no recurrence, the model has no idea where a word is in a sentence. To fix this, we add a Positional Encoding vector to the input embedding using sine and cosine functions of different frequencies.
๐ป Implementation in PyTorch
Below is a production-style implementation of the Transformer components.
import torch
import torch.nn as nn
import math
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=5000):
super().__init__()
pe = torch.zeros(max_len, d_model)
position = torch.arange(0, max_len, dtype=torch.float).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2).float() * (-math.log(10000.0) / d_model))
pe[:, 0::2] = torch.sin(position * div_term)
pe[:, 1::2] = torch.cos(position * div_term)
self.register_buffer('pe', pe.unsqueeze(0))
def forward(self, x):
return x + self.pe[:, :x.size(1)]
class MultiHeadAttention(nn.Module):
def __init__(self, d_model, num_heads):
super().__init__()
assert d_model % num_heads == 0
self.d_k = d_model // num_heads
self.num_heads = num_heads
self.W_q = nn.Linear(d_model, d_model)
self.W_k = nn.Linear(d_model, d_model)
self.W_v = nn.Linear(d_model, d_model)
self.W_o = nn.Linear(d_model, d_model)
def forward(self, q, k, v, mask=None):
batch_size = q.size(0)
# Linear projections & split into heads
Q = self.W_q(q).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
K = self.W_k(k).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
V = self.W_v(v).view(batch_size, -1, self.num_heads, self.d_k).transpose(1, 2)
# Scaled Dot-Product Attention
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(self.d_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, -1e9)
attn_weights = torch.softmax(scores, dim=-1)
context = torch.matmul(attn_weights, V)
# Concatenate and project
context = context.transpose(1, 2).contiguous().view(batch_size, -1, self.num_heads * self.d_k)
return self.W_o(context)
class Transformer(nn.Module):
def __init__(self, src_vocab_size, tgt_vocab_size, d_model=512, num_layers=6, num_heads=8, d_ff=2048):
super().__init__()
self.encoder_emb = nn.Embedding(src_vocab_size, d_model)
self.decoder_emb = nn.Embedding(tgt_vocab_size, d_model)
self.pos_enc = PositionalEncoding(d_model)
# Simplified stack representation
self.encoder_layers = nn.ModuleList([EncoderLayer(d_model, num_heads, d_ff) for _ in range(num_layers)])
self.decoder_layers = nn.ModuleList([DecoderLayer(d_model, num_heads, d_ff) for _ in range(num_layers)])
self.fc_out = nn.Linear(d_model, tgt_vocab_size)
def forward(self, src, tgt):
# ... (Masking logic and layer iteration as per implementation)
pass
๐ Summary & Key Takeaways
The Transformer architecture represents a paradigm shift in NLP. By replacing recurrence with attention, it achieved:
- Massive Parallelization: Training times dropped significantly compared to LSTMs.
- Better Long-Range Dependencies: The model can link words at opposite ends of a document with a single operation.
- Foundation for LLMs: This architecture is the direct ancestor of BERT, GPT-3, and GPT-4.
| Feature | RNNs/LSTMs | Transformers |
|---|---|---|
| Processing | Sequential | Parallel |
| Memory | Hidden State (Bottleneck) | Global Attention |
| Complexity | $O(n)$ sequential steps | $O(1)$ sequential steps |
| Context | Struggles with long sequences | Handles long-range dependencies well |
Next Steps: If you want to experiment further, try implementing a "Decoder-only" version of this modelโyou'll essentially be building the foundation of a GPT-style language model!