2272 words
11 minutes
Unpacking Transformers: The Foundation of LLM Zero to Hero

Unpacking Transformers: The Foundation of LLM Zero to Hero#

Transformers have revolutionized natural language processing (NLP) and broader artificial intelligence tasks. They are the driving force behind advanced Large Language Models (LLMs). In this post, we will unpack the concepts behind Transformers, starting from the basics and moving to advanced topics to help you navigate from zero to hero. By the end of this blog, you should have a professional-level understanding—and a practical roadmap—on how to implement and expand on Transformers in your own projects.

Table of Contents#

  1. Introduction to Transformers and Their Roots
  2. The Core Concepts of Transformers
    1. Attention Mechanism
    2. Positional Encoding
    3. Multi-Head Attention
    4. Residual Connections, Layer Normalization, and Feed-Forward Layers
  3. Transformer Architecture: Encoder and Decoder
  4. Walkthrough: Building a Simple Transformer
    1. Prerequisites and Basic Setup
    2. Defining the Model
    3. Implementing Multi-Head Self-Attention
    4. Putting It All Together
  5. Training Transformers for Sequence-to-Sequence Tasks
    1. Data Preparation
    2. Hyperparameters and Optimization
    3. Evaluation Metrics
  6. Advanced Topics and Innovations
    1. BERT, GPT, and T5
    2. Pre-training and Fine-tuning
    3. Scaling Laws and Transformer Variations
    4. Combining Transformers with Other Architectures
  7. Real-World Applications
  8. Conclusion and Next Steps

Introduction to Transformers and Their Roots#

When we talk about Transformers, we’re referring to the deep learning architecture introduced in the seminal paper “Attention Is All You Need” by Vaswani et al. This architecture has drastically changed the field of NLP, replacing or enhancing many models based on Recurrent Neural Networks (RNNs) and Convolutional Neural Networks (CNNs).

  1. Parallelization: Unlike RNNs, the Transformer architecture does not rely on sequential processing of input tokens. This results in higher computational efficiency and significantly faster training times, especially on large datasets.
  2. Long-Range Dependencies: Transformers utilize attention mechanisms that help the model focus on different parts of the input sequence. This is in contrast to RNNs that tend to forget older tokens over longer sequences.
  3. State-of-the-Art Results: Since their introduction, Transformers have consistently led to breakthroughs in NLP tasks such as machine translation, text generation, question answering, summarization, and much more.

Whether you are aiming to build a new chatbot, a machine translation tool, or a text summarization service, understanding Transformers is your key to unlocking the power behind state-of-the-art language models.


The Core Concepts of Transformers#

Before diving into code and advanced topics, let’s break down the essential pieces of the Transformer architecture.

Attention Mechanism#

The concept of “attention” was introduced to let models focus on the most relevant parts of an input sequence when making a prediction. In a typical sequence-to-sequence task (like machine translation), the model attempts to align segments of the output with segments of the input.

An attention mechanism calculates a weighted sum of “value” vectors, where the weights derive from how well a “query” vector aligns with a series of “key” vectors:

  1. Queries (Q): Represents the input that queries information from the entire sequence.
  2. Keys (K): Acts like an address or a label to help match queries to relevant information.
  3. Values (V): Contains the actual information.

The attention formula can be summarized as:

Attention(Q, K, V) = softmax( (QKᵀ) / √d_k ) × V

Where d_k is the dimension of the key vectors. The division by √d_k stabilizes gradients by mitigating large dot product values when dimensions are large.

Positional Encoding#

Since Transformers do not process tokens in a strictly sequential manner like RNNs, they need a way to incorporate positional information. This is where positional encoding comes in. Each token in the sequence receives a positional embedding, which describes its location within the sequence.

A common approach uses sinusoidal functions:

PE(pos, 2i) = sin(pos / 10000^(2i / d_model))
PE(pos, 2i+1) = cos(pos / 10000^(2i / d_model))

Here:

  • pos is the position in the sequence (0, 1, 2, …),
  • i is the dimension index,
  • d_model is the embedding dimension.

This allows the model to learn relative position relationships between different tokens without losing the parallelization benefit.

Multi-Head Attention#

Instead of computing attention once, the Transformer splits the dimension of the embeddings into multiple heads. Each head performs an attention operation independently, and their outputs are concatenated, then linearly transformed. This approach allows the model to focus on different parts of the sequence from multiple perspectives.

Formally, multi-head attention can be written as:

MultiHead(Q, K, V) = Concat( head₁, head₂, …, headₕ ) × Wᴼ

where each headₖ is computed as:

headₖ = Attention(QWₖ^Q, KWₖ^K, VWₖ^V)

Residual Connections, Layer Normalization, and Feed-Forward Layers#

Each sub-layer in the Transformer (e.g., multi-head self-attention, feed-forward) is wrapped with residual connections and followed by layer normalization:

  1. Residual Connections: Add the original input to the sub-layer’s output, which helps preserve gradients and mitigate the vanishing or exploding gradient problem.
  2. Layer Normalization: Normalizes the summed output, smoothing optimization.
  3. Position-wise Feed-Forward Layer: A fully connected feed-forward network applied to each attention output vector, typically featuring a non-linear activation like ReLU or GELU.

Transformer Architecture: Encoder and Decoder#

The classic Transformer design consists of two main parts:

  1. Encoder:

    • Processes the entire input sequence and reveals contextual representations.
    • Each encoder layer has a multi-head self-attention sub-layer and a feed-forward sub-layer.
  2. Decoder:

    • Takes the encoder’s output and the shifted target output (usually for tasks like machine translation, where we generate a new sequence).
    • Has a masked multi-head self-attention sub-layer to ensure the autoregressive property.
    • Has another multi-head attention sub-layer that attends over the encoder outputs.
    • Followed by a feed-forward sub-layer.

In simpler tasks, you can sometimes omit the decoder portion or modify it, as in the case of BERT (only the encoder is used) or GPT (only the decoder is used).


Walkthrough: Building a Simple Transformer#

Let’s run through a simplified version of how to build a Transformer in code (Python with PyTorch as an example). This will help solidify the concepts.

Prerequisites and Basic Setup#

We will assume you have:

  • Python 3.7+
  • PyTorch >= 1.10
  • NumPy

Install the core dependencies (if you haven’t already):

Terminal window
pip install torch numpy

Defining the Model#

Let’s start by setting up a skeleton Transformer class. We’ll go step-by-step, focusing first on the building blocks.

import torch
import torch.nn as nn
import math
class Transformer(nn.Module):
def __init__(self, d_model, n_heads, num_encoder_layers, num_decoder_layers, vocab_size, max_seq_len=512):
super(Transformer, self).__init__()
self.d_model = d_model
self.embed = nn.Embedding(vocab_size, d_model)
self.pos_encoding = PositionalEncoding(d_model, max_seq_len)
encoder_layer = nn.TransformerEncoderLayer(d_model, n_heads)
decoder_layer = nn.TransformerDecoderLayer(d_model, n_heads)
self.encoder = nn.TransformerEncoder(encoder_layer, num_layers=num_encoder_layers)
self.decoder = nn.TransformerDecoder(decoder_layer, num_layers=num_decoder_layers)
self.fc_out = nn.Linear(d_model, vocab_size)
def forward(self, src, tgt):
# src: [batch_size, src_seq_len]
# tgt: [batch_size, tgt_seq_len]
# Embedding + positional encoding
src_emb = self.pos_encoding(self.embed(src) * math.sqrt(self.d_model))
tgt_emb = self.pos_encoding(self.embed(tgt) * math.sqrt(self.d_model))
# PyTorch transformer expects shape: [src_seq_len, batch_size, d_model]
src_emb = src_emb.transpose(0, 1)
tgt_emb = tgt_emb.transpose(0, 1)
memory = self.encoder(src_emb)
output = self.decoder(tgt_emb, memory)
# Convert back to token predictions
output = self.fc_out(output)
# [tgt_seq_len, batch_size, vocab_size] => we can transpose if needed
return output.transpose(0, 1)
class PositionalEncoding(nn.Module):
def __init__(self, d_model, max_len=512):
super(PositionalEncoding, self).__init__()
position = torch.arange(0, max_len).unsqueeze(1)
div_term = torch.exp(torch.arange(0, d_model, 2) * -(math.log(10000.0) / d_model))
pe = torch.zeros(max_len, 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):
# x: [batch_size, seq_len, d_model]
seq_len = x.size(1)
x = x + self.pe[:, :seq_len]
return x

Here’s what’s happening in this snippet:

  • We create a simple Transformer class that uses PyTorch’s built-in nn.TransformerEncoder and nn.TransformerDecoder for brevity.
  • The PositionalEncoding class implements sinusoidal positional encodings.
  • We multiply the embedding by math.sqrt(self.d_model) because in the original paper, the transformer’s embeddings are scaled by √d_model to account for the variance in the embeddings.

Implementing Multi-Head Self-Attention#

In practice, PyTorch has a ready-made attention mechanism, but let’s see how you might write a simplified version of the scaled dot-product attention:

def scaled_dot_product_attention(Q, K, V, mask=None):
d_k = K.size(-1)
scores = torch.matmul(Q, K.transpose(-2, -1)) / math.sqrt(d_k)
if mask is not None:
scores = scores.masked_fill(mask == 0, float('-inf'))
attn_weights = torch.softmax(scores, dim=-1)
output = torch.matmul(attn_weights, V)
return output, attn_weights

Now, for multi-head attention:

class MultiHeadAttention(nn.Module):
def __init__(self, d_model, n_heads):
super(MultiHeadAttention, self).__init__()
self.d_model = d_model
self.n_heads = n_heads
assert d_model % n_heads == 0, "d_model must be divisible by n_heads"
self.d_k = d_model // n_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.out = nn.Linear(d_model, d_model)
def forward(self, Q, K, V, mask=None):
# Q, K, V: [batch_size, seq_len, d_model]
batch_size = Q.size(0)
Q = self.W_q(Q).view(batch_size, -1, self.n_heads, self.d_k)
K = self.W_k(K).view(batch_size, -1, self.n_heads, self.d_k)
V = self.W_v(V).view(batch_size, -1, self.n_heads, self.d_k)
# transpose for attention calculation: [batch_size, n_heads, seq_len, d_k]
Q = Q.transpose(1, 2)
K = K.transpose(1, 2)
V = V.transpose(1, 2)
# scaled dot-product attention
attn_output, attn_weights = scaled_dot_product_attention(Q, K, V, mask=mask)
# concat heads
attn_output = attn_output.transpose(1, 2).contiguous().view(batch_size, -1, self.d_model)
# final linear layer
output = self.out(attn_output) # [batch_size, seq_len, d_model]
return output, attn_weights

The above code should give you a sense for how attention is computed under the hood.

Putting It All Together#

In real-world scenarios, you’ll likely rely on pre-built modules (as with PyTorch’s nn.Transformer). However, looking under the hood helps clarify how these components interact. When training, you’ll need to:

  1. Tokenize and numericalize your text.
  2. Convert them to tensors and feed them into the model.
  3. Apply masking (for example, to mask future tokens during training for a language modeling task).
  4. Compute the loss (often cross-entropy for token classification).
  5. Backpropagate and optimize.

Training Transformers for Sequence-to-Sequence Tasks#

Let’s consider how to train a basic English-to-French translation model using a Transformer. Even though you may rely on existing frameworks, it’s beneficial to do a quick rundown of the workflow.

Data Preparation#

For sequence-to-sequence tasks, you typically need parallel data: pairs of sentences in source and target languages. Assume you have a dataset of English-French sentence pairs:

  1. Tokenization: Use a tokenizer (like Byte Pair Encoding - BPE, or a standard library such as Hugging Face’s tokenizers).
  2. Convert tokens to indices: Build a vocabulary or use a pre-built vocabulary.
  3. Create training pairs: src_sequences, tgt_sequences.
  4. Add special tokens: Usually, <pad>, <sos>, <eos>.

Store these in a PyTorch Dataset and then wrap it with a DataLoader.

Hyperparameters and Optimization#

Key hyperparameters:

  • d_model (embedding dimension): A typical starting point is 512.
  • n_heads: 8 or 16 are common choices.
  • number of layers (encoder/decoder): 6 each in the original paper, but can vary.
  • learning rate: Often scheduled, e.g., using an Adam optimizer with “warmup steps.”

Example of an Adam optimizer with warmup scheduling:

import torch.optim as optim
model = Transformer(d_model=512, n_heads=8, num_encoder_layers=6, num_decoder_layers=6, vocab_size=10000)
optimizer = optim.Adam(model.parameters(), lr=1e-4, betas=(0.9, 0.98), eps=1e-9)
criterion = nn.CrossEntropyLoss(ignore_index=0) # ignoring <pad> token
def train_batch(src_batch, tgt_batch):
model.train()
optimizer.zero_grad()
# We assume tgt_batch has <sos> + ... + <eos>
# Typically, you might split the target so the model predicts the next token
# from the partial. For example, input to decoder is [<sos>, w1, w2, w3]
# and we compare output with [w1, w2, w3, <eos>].
output = model(src_batch, tgt_batch[:, :-1]) # all but the last token
# output has shape [batch_size, tgt_seq_len-1, vocab_size]
loss = criterion(output.reshape(-1, output.size(-1)), tgt_batch[:, 1:].reshape(-1))
loss.backward()
optimizer.step()
return loss.item()

Evaluation Metrics#

For machine translation, common metrics include:

  • BLEU (Bilingual Evaluation Understudy)
  • ROUGE (Recall-Oriented Understudy for Gisting Evaluation)
  • METEOR
  • ChrF++ (especially for morphologically-rich languages)

You compute these metrics by comparing your model’s predictions with that of a reference translation across multiple test samples.


Advanced Topics and Innovations#

Since the original Transformer was introduced, countless variations and improvements have emerged. Let’s explore a few:

BERT, GPT, and T5#

  • BERT (Bidirectional Encoder Representations from Transformers)

    • Uses only the encoder component and is pre-trained on tasks like masked language modeling (MLM) and next sentence prediction.
    • Excelled at tasks requiring a deep understanding of context (e.g., question answering, classification).
  • GPT (Generative Pre-trained Transformer)

    • Based on the decoder portion, uses autoregressive language modeling.
    • GPT-2 and GPT-3 significantly scaled up parameters, establishing new benchmarks in generating coherent, contextually relevant text.
  • T5 (Text-to-Text Transfer Transformer)

    • Treats every NLP task (classification, translation, summarization, etc.) as a text-to-text problem.
    • Uses an encoder-decoder architecture, pretrained on a massive “Fill-In-The-Blank” objective.

Pre-training and Fine-tuning#

Modern practice often revolves around:

  1. Pre-training on large corpora with a generic task (e.g., language modeling, masked language modeling).
  2. Fine-tuning on a specific dataset with a smaller, specialized objective.

This approach leverages learned language structures and dramatically reduces the need for large domain-specific datasets when building specialized models.

Scaling Laws and Transformer Variations#

  • Scaling Laws: As you increase model size, dataset size, and compute, performance typically grows in a predictable way. Larger Transformers (e.g., GPT-3, GPT-4) are prime examples.
  • Transformer Variants:
    • Longformer, BigBird: Adapted for very long sequences by introducing sparse attention.
    • Reformer: Reduces complexity using locality-sensitive hashing for attention.
    • Performers: Uses random feature maps for more efficient attention.

Combining Transformers with Other Architectures#

Transformers can be augmented or combined with other architectures for specialized tasks:

  • ConvBERT mixes the strengths of CNN-like local feature extraction with Transformer context.
  • Efficient Transformers: Weighted kernel-based methods and recurrence-based methods can be combined with self-attention for further memory or computational gains.

Real-World Applications#

Transformers have become the go-to architecture for tasks such as:

  1. Machine Translation: From small-scale personal translation apps to large enterprise-level solutions, Transformers provide high-quality translations without domain-specific heuristics.
  2. Text Summarization: Models like BART (a variant that uses both encoder and decoder) excel at creating concise, coherent summaries.
  3. Chatbots and Conversational AI: GPT-based models power sophisticated discussion and reasoning in dialogue systems.
  4. Question Answering: BERT-like models, fine-tuned on QA datasets, can often match or surpass human-level performance on benchmark tasks.
  5. Information Retrieval: Transformers can embed queries and documents in a semantic vector space, enabling improved ranking and relevance for search engines.
  6. Medical and Legal Applications: Domain-adapted Transformers (fine-tuned on specialized vocabularies) can extract information from complex texts (electronic health records, legal documents) with remarkable accuracy.

Conclusion and Next Steps#

Transformers are the foundational architecture behind today’s leading language models. Their flexibility, computational efficiency, and ability to learn complex linguistic patterns have pushed the boundaries of what machines can accomplish with text data. Whether you are just getting started or expanding on existing solutions, understanding how Transformers work at a conceptual and implementation level will serve you well.

Here’s a brief roadmap for further exploration:

  1. Experiment with code: Use frameworks like PyTorch or TensorFlow with pre-built Transformer modules.
  2. Study advanced topics: Dive deeper into attention variants, memory-efficient Transformers, and extended context handling for extremely large sequences.
  3. Contribute to open-source: Many libraries (Hugging Face’s Transformers, Fairseq, etc.) welcome community contributions.
  4. Keep an eye on research: The field moves fast, with new architectures (e.g., Swin Transformers for vision tasks, Transformers for protein folding in bioinformatics).
  5. Fine-tune on your domain: Take a pre-trained model and tailor it to your own dataset for best results.

Transformers are here to stay and continue evolving. They’ve already transformed NLP, and they’re quickly moving into other fields like computer vision (ViT), audio processing, and beyond. As you move from zero to hero in LLM development, mastering Transformers is your essential stepping stone.

Happy transforming!

Unpacking Transformers: The Foundation of LLM Zero to Hero
https://closeaiblog.vercel.app/posts/llm/4/
Author
CloseAI
Published at
2024-02-19
License
CC BY-NC-SA 4.0